From d936c5147aba69a300761286a332028a85533953 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 17 Apr 2023 15:54:58 -0300 Subject: [PATCH 001/468] Update webpack-cli to 4.10.0 The webpack v4 to v5 migration guide [1] says to "Upgrade webpack-cli to the latest available version". Presumably it means "the latest version compatible with webpack 4", which is this one [2]. Part of #1184. [1] https://webpack.js.org/migrate/5/#upgrade-webpack-4-and-its-pluginsloaders [2] https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md#500-2022-11-17 --- package-lock.json | 606 ++++++++++++++-------------------------------- 1 file changed, 182 insertions(+), 424 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d9c52a51f..4ab213dfee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,6 +116,15 @@ "node": ">=4" } }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -757,10 +766,20 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, "node_modules/@webpack-cli/info": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.1.0.tgz", - "integrity": "sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", "dev": true, "dependencies": { "envinfo": "^7.7.3" @@ -770,9 +789,9 @@ } }, "node_modules/@webpack-cli/serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.1.0.tgz", - "integrity": "sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", "dev": true, "peerDependencies": { "webpack-cli": "4.x.x" @@ -987,15 +1006,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-back": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", - "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", @@ -2012,6 +2022,20 @@ "node": ">= 0.10" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -2066,9 +2090,9 @@ "dev": true }, "node_modules/colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "node_modules/colors": { @@ -2092,35 +2116,6 @@ "node": ">= 0.8" } }, - "node_modules/command-line-usage": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.1.tgz", - "integrity": "sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==", - "dev": true, - "dependencies": { - "array-back": "^4.0.1", - "chalk": "^2.4.2", - "table-layout": "^1.0.1", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -2668,15 +2663,6 @@ "node": ">=0.12" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-for-each": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-3.0.0.tgz", @@ -2990,9 +2976,9 @@ } }, "node_modules/envinfo": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", - "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -3309,29 +3295,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -3672,6 +3635,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", @@ -5119,15 +5091,6 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5534,15 +5497,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-typed-array": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", @@ -5746,15 +5700,6 @@ "node": ">=0.10.0" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6052,12 +5997,6 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6152,15 +6091,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -6710,18 +6640,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/null-loader": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", @@ -6905,21 +6823,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", @@ -7671,15 +7574,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -8136,6 +8030,18 @@ "sha.js": "bin.js" } }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8231,12 +8137,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8834,15 +8734,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -8882,21 +8773,6 @@ "node": ">=6.0.0" } }, - "node_modules/table-layout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", - "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", - "dev": true, - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -9419,15 +9295,6 @@ "node": ">=4.2.0" } }, - "node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -9912,24 +9779,23 @@ } }, "node_modules/webpack-cli": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.2.0.tgz", - "integrity": "sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==", - "dev": true, - "dependencies": { - "@webpack-cli/info": "^1.1.0", - "@webpack-cli/serve": "^1.1.0", - "colorette": "^1.2.1", - "command-line-usage": "^6.1.0", - "commander": "^6.2.0", - "enquirer": "^2.3.6", - "execa": "^4.1.0", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", - "leven": "^3.1.0", "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", - "webpack-merge": "^4.2.2" + "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" @@ -9937,17 +9803,15 @@ "engines": { "node": ">=10.13.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, "peerDependencies": { "webpack": "4.x.x || 5.x.x" }, "peerDependenciesMeta": { - "@webpack-cli/generate-loader": { - "optional": true - }, - "@webpack-cli/generate-plugin": { - "optional": true - }, - "@webpack-cli/init": { + "@webpack-cli/generators": { "optional": true }, "@webpack-cli/migrate": { @@ -9962,12 +9826,12 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 10" } }, "node_modules/webpack-cli/node_modules/interpret": { @@ -9980,12 +9844,16 @@ } }, "node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", "dev": true, "dependencies": { - "lodash": "^4.17.15" + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/webpack-sources": { @@ -10116,6 +9984,12 @@ "node": ">=4" } }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -10125,19 +9999,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrapjs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", - "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", - "dev": true, - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -10436,6 +10297,12 @@ } } }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, "@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -10936,19 +10803,26 @@ "@xtuc/long": "4.2.2" } }, + "@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "requires": {} + }, "@webpack-cli/info": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.1.0.tgz", - "integrity": "sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.1.0.tgz", - "integrity": "sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", "dev": true, "requires": {} }, @@ -11112,12 +10986,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-back": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", - "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", - "dev": true - }, "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", @@ -11980,6 +11848,17 @@ "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", "dev": true }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -12031,9 +11910,9 @@ "dev": true }, "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "colors": { @@ -12051,31 +11930,6 @@ "delayed-stream": "~1.0.0" } }, - "command-line-usage": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.1.tgz", - "integrity": "sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==", - "dev": true, - "requires": { - "array-back": "^4.0.1", - "chalk": "^2.4.2", - "table-layout": "^1.0.1", - "typical": "^5.2.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -12500,12 +12354,6 @@ "type-detect": "^4.0.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-for-each": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-3.0.0.tgz", @@ -12765,9 +12613,9 @@ } }, "envinfo": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", - "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, "errno": { @@ -13006,23 +12854,6 @@ "safe-buffer": "^5.1.1" } }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -13313,6 +13144,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, "fastq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", @@ -14427,12 +14264,6 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -14728,12 +14559,6 @@ "is-unc-path": "^1.0.0" } }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, "is-typed-array": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", @@ -14895,12 +14720,6 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -15133,12 +14952,6 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -15211,12 +15024,6 @@ "mime-db": "1.52.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -15640,15 +15447,6 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, "null-loader": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", @@ -15783,15 +15581,6 @@ "wrappy": "1" } }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, "open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", @@ -16362,12 +16151,6 @@ } } }, - "reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -16725,6 +16508,15 @@ "safe-buffer": "^5.0.1" } }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16798,12 +16590,6 @@ "object-inspect": "^1.9.0" } }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -17302,12 +17088,6 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -17335,18 +17115,6 @@ "string-width": "^3.0.0" } }, - "table-layout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", - "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", - "dev": true, - "requires": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - } - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -17754,12 +17522,6 @@ "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", "dev": true }, - "typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -18168,30 +17930,29 @@ } }, "webpack-cli": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.2.0.tgz", - "integrity": "sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==", - "dev": true, - "requires": { - "@webpack-cli/info": "^1.1.0", - "@webpack-cli/serve": "^1.1.0", - "colorette": "^1.2.1", - "command-line-usage": "^6.1.0", - "commander": "^6.2.0", - "enquirer": "^2.3.6", - "execa": "^4.1.0", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", - "leven": "^3.1.0", "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", - "webpack-merge": "^4.2.2" + "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, "interpret": { @@ -18203,12 +17964,13 @@ } }, "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", "dev": true, "requires": { - "lodash": "^4.17.15" + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" } }, "webpack-sources": { @@ -18294,22 +18056,18 @@ } } }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrapjs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", - "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", - "dev": true, - "requires": { - "reduce-flatten": "^2.0.0", - "typical": "^5.0.0" - } - }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", From c62cc6ab0053e2cedd04c30393b627d0c2a9ef46 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 17 Apr 2023 16:10:53 -0300 Subject: [PATCH 002/468] Update tsconfig-paths-webpack-plugin to 4.0.1 The webpack v4 to v5 migration guide [1] says to upgrade plugins to the latest version which supports webpack 4. 4.0.1 is the latest version of this plugin, and it appears to support both webpack 4 and 5. Part of #1184. [1] https://webpack.js.org/migrate/5/#upgrade-webpack-4-and-its-pluginsloaders --- package-lock.json | 78 +++++++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ab213dfee..245f2b3e8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "shelljs": "~0.8", "source-map-explorer": "^2.5.2", "ts-loader": "^8.2.0", - "tsconfig-paths-webpack-plugin": "^3.5.2", + "tsconfig-paths-webpack-plugin": "^4.0.1", "tslib": "^2.3.1", "typedoc": "^0.23.8", "typescript": "^4.6.4", @@ -311,12 +311,6 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, "node_modules/@types/keyv": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", @@ -8728,7 +8722,7 @@ "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "engines": { "node": ">=4" @@ -9114,26 +9108,31 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/tsconfig-paths-webpack-plugin": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", - "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^3.9.0" + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" } }, "node_modules/tsconfig-paths-webpack-plugin/node_modules/enhanced-resolve": { @@ -9158,6 +9157,18 @@ "node": ">=6" } }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -10446,12 +10457,6 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, "@types/keyv": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", @@ -17085,7 +17090,7 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true }, "strip-json-comments": { @@ -17378,26 +17383,33 @@ } }, "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + } } }, "tsconfig-paths-webpack-plugin": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", - "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", "dev": true, "requires": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^3.9.0" + "tsconfig-paths": "^4.1.2" }, "dependencies": { "enhanced-resolve": { diff --git a/package.json b/package.json index 63104ceaed..715b9f6e31 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "shelljs": "~0.8", "source-map-explorer": "^2.5.2", "ts-loader": "^8.2.0", - "tsconfig-paths-webpack-plugin": "^3.5.2", + "tsconfig-paths-webpack-plugin": "^4.0.1", "tslib": "^2.3.1", "typedoc": "^0.23.8", "typescript": "^4.6.4", From 4b496c57419602b5e308c36dd3ecddc78e1d5271 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 17 Apr 2023 16:17:37 -0300 Subject: [PATCH 003/468] Update ts-loader to 8.4.0 The webpack v4 to v5 migration guide [1] says to upgrade loaders to the latest version which supports webpack 4, which in the case of this loader is this version [2]. Part of #1184. [1] https://webpack.js.org/migrate/5/#upgrade-webpack-4-and-its-pluginsloaders [2] https://github.com/TypeStrong/ts-loader/blob/main/CHANGELOG.md#v900 --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 245f2b3e8b..6c711a5d9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9004,9 +9004,9 @@ } }, "node_modules/ts-loader": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.2.0.tgz", - "integrity": "sha512-ebXBFrNyMSmbWgjnb3WBloUBK+VSx1xckaXsMXxlZRDqce/OPdYBVN5efB0W3V0defq0Gcy4YuzvPGqRgjj85A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", + "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -17308,9 +17308,9 @@ "dev": true }, "ts-loader": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.2.0.tgz", - "integrity": "sha512-ebXBFrNyMSmbWgjnb3WBloUBK+VSx1xckaXsMXxlZRDqce/OPdYBVN5efB0W3V0defq0Gcy4YuzvPGqRgjj85A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", + "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", "dev": true, "requires": { "chalk": "^4.1.0", From 6db88b01348cf4c069caa7d47ecfe2d3dd7c5687 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 17 Apr 2023 16:19:28 -0300 Subject: [PATCH 004/468] Remove null-loader dependency It seems like we stopped using this in 2d792c3. --- package-lock.json | 105 ---------------------------------------------- package.json | 1 - 2 files changed, 106 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c711a5d9e..4eca188090 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,6 @@ "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", "mocha": "^8.1.3", - "null-loader": "^4.0.1", "playwright": "^1.10.0", "prettier": "^2.5.1", "requirejs": "~2.1", @@ -6634,70 +6633,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/null-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/null-loader/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/null-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/null-loader/node_modules/schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -15452,46 +15387,6 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" }, - "null-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", - "dev": true, - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", diff --git a/package.json b/package.json index 715b9f6e31..ed019c0718 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", "mocha": "^8.1.3", - "null-loader": "^4.0.1", "playwright": "^1.10.0", "prettier": "^2.5.1", "requirejs": "~2.1", From 62bd4d3d7a8de28f9ed2b7bd58a2f101c70e3311 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 17 Apr 2023 16:29:17 -0300 Subject: [PATCH 005/468] Update grunt-webpack to 5.0.0 Part of our preparations for upgrading to webpack 5. 5.0.0 is the latest version of this package, and its README states that it supports both webpack 4 and 5. Part of #1184. --- package-lock.json | 18 +++++++++--------- package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4eca188090..dd37aba1cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "grunt-closure-tools": "^1.0.0", "grunt-contrib-concat": "~0.5", "grunt-shell": "~1.1", - "grunt-webpack": "^4.0.2", + "grunt-webpack": "^5.0.0", "hexy": "~0.2", "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", @@ -4731,19 +4731,19 @@ } }, "node_modules/grunt-webpack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-4.0.2.tgz", - "integrity": "sha512-rrqb9SRlY69jEJuCglelB7IvGrI7lRpdfH2GXpFlIOGPRTTtlSxYMU4Fjg8FHaC6ilnMbW5jd55Ff1lR5OibCA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-5.0.0.tgz", + "integrity": "sha512-C7emzVIGJhZ5V5ZYjylr9sDD9PE9Dh/55NaSzP2P2dhif+m/1gHbjDZQ7PDzguyuzwBz4Qc8voQHR06FSp4CKg==", "dev": true, "dependencies": { "deep-for-each": "^3.0.0", "lodash": "^4.17.19" }, "engines": { - "node": ">=10.13.0" + "node": ">=12.13.0" }, "peerDependencies": { - "webpack": "^4.0.0" + "webpack": "^4.0.0 || ^5.0.0" } }, "node_modules/grunt/node_modules/glob": { @@ -14002,9 +14002,9 @@ } }, "grunt-webpack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-4.0.2.tgz", - "integrity": "sha512-rrqb9SRlY69jEJuCglelB7IvGrI7lRpdfH2GXpFlIOGPRTTtlSxYMU4Fjg8FHaC6ilnMbW5jd55Ff1lR5OibCA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-5.0.0.tgz", + "integrity": "sha512-C7emzVIGJhZ5V5ZYjylr9sDD9PE9Dh/55NaSzP2P2dhif+m/1gHbjDZQ7PDzguyuzwBz4Qc8voQHR06FSp4CKg==", "dev": true, "requires": { "deep-for-each": "^3.0.0", diff --git a/package.json b/package.json index ed019c0718..803e13816d 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "grunt-closure-tools": "^1.0.0", "grunt-contrib-concat": "~0.5", "grunt-shell": "~1.1", - "grunt-webpack": "^4.0.2", + "grunt-webpack": "^5.0.0", "hexy": "~0.2", "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", From c82895243579a5a1dcb0fc08b4adeca4b60f3820 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 17 Apr 2023 16:31:47 -0300 Subject: [PATCH 006/468] Upgrade to webpack version 5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran the following: npm install webpack@latest \ webpack-cli@latest \ copy-webpack-plugin@latest \ tsconfig-paths-webpack-plugin@latest \ ts-loader@latest Then, performed the following changes to get the build green: - Configured webpack to emit ES5 code (`target` configuration) As described in [1], webpack 4 emitted ES5 code, but webpack 5 emits ES6 code by default. However, as documented in CONTRIBUTING.md, and as expected by `npm run check-closure-compiler`, we aim to use ES5 in the code we ship. So, configure webpack to emit ES5 code. - Removed config of Node-like polyfills (`node` configuration removed or replaced with `resolve.fallback`) As described in [2], webpack no longer automatically applies polyfills for Node.js APIs when targeting frontend environments. If you wish to apply polyfills, you need to opt-in though the the new `resolve.fallback` config. This means that we can remove the config telling it not to polyfill `Buffer`. As for the current behaviour of stubbing the Crypto module with an empty object, we achieve this with the `resolve.fallback.crypto: false` syntax which, whilst not properly documented in [3] at time of writing, is instructed by [4] ("If you are using something like node.fs: 'empty' replace it with resolve.fallback.fs: false") and described in an error message in the webpack code [5]. As Owen said in #1184 "As part of this work we will need to manually test that the lib still works in react-native and web workers, since these aren't covered by automated tests." I’ve tested this in a couple of small example apps [6] and [7], which just check that they’re able to import ably-js and publish / receive a message. [1] https://webpack.js.org/blog/2020-10-10-webpack-5-release/#improved-code-generation [2] https://webpack.js.org/blog/2020-10-10-webpack-5-release/#automatic-nodejs-polyfills-removed [3] https://webpack.js.org/configuration/resolve/#resolvefallback [4] https://webpack.js.org/migrate/5/#clean-up-configuration [5] https://github.com/webpack/webpack/blob/770a5a9cae8e2eddd5ca015efd06847e37480f45/lib/ModuleNotFoundError.js#L70-L72 [6] https://github.com/ably-labs/ably-js-react-native-example, commit 05a0177 [7] https://github.com/ably-labs/ably-js-web-worker-example, commit 62621db --- package-lock.json | 7822 ++++++++------------------------------------- package.json | 8 +- webpack.config.js | 29 +- 3 files changed, 1316 insertions(+), 6543 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd37aba1cd..abd99c597c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1075.0", "chai": "^4.2.0", - "copy-webpack-plugin": "^6.4.1", + "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", "crypto-js": "ably-forks/crypto-js#crypto-lite", "eslint": "^7.13.0", @@ -49,13 +49,13 @@ "requirejs": "~2.1", "shelljs": "~0.8", "source-map-explorer": "^2.5.2", - "ts-loader": "^8.2.0", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^4.0.1", "tslib": "^2.3.1", "typedoc": "^0.23.8", "typescript": "^4.6.4", - "webpack": "^4.44.2", - "webpack-cli": "^4.2.0" + "webpack": "^5.79.0", + "webpack-cli": "^5.0.1" }, "engines": { "node": ">=5.10.x" @@ -159,6 +159,64 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -194,66 +252,6 @@ "node": ">= 8" } }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@sindresorhus/is": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz", @@ -299,6 +297,32 @@ "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", "dev": true }, + "node_modules/@types/eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", @@ -585,209 +609,188 @@ "dev": true }, "node_modules/@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", "dev": true, "dependencies": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, - "dependencies": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", "dev": true }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0" + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", + "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" } }, "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", "dev": true, - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -847,19 +850,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -876,15 +866,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, "peerDependencies": { - "ajv": ">=5.0.0" + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -933,36 +953,6 @@ "node": ">=4" } }, - "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -972,33 +962,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", @@ -1032,67 +995,6 @@ "node": ">=8" } }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "node_modules/assert/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "dependencies": { - "inherits": "2.0.1" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1102,15 +1004,6 @@ "node": "*" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -1127,13 +1020,6 @@ "dev": true, "license": "MIT" }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true, - "optional": true - }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -1145,18 +1031,6 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -1240,141 +1114,32 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, + "node_modules/base64-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.2.tgz", + "integrity": "sha1-R0IRyV5s8qVH20YeT2d4tR0I+mU=", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dev": true, "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.2.tgz", - "integrity": "sha1-R0IRyV5s8qVH20YeT2d4tR0I+mU=", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8", @@ -1415,136 +1180,38 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, "node_modules/browser-stdout": { "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/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "node_modules/btoa": { @@ -1571,15 +1238,9 @@ } }, "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "node_modules/buffer/node_modules/base64-js": { @@ -1602,12 +1263,6 @@ } ] }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1617,84 +1272,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -1751,6 +1328,26 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001480", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", + "integrity": "sha512-q7cpoPPvZYgtyC4VaBSN0Bt+PJ4c4EYRf0DrduInOz2SkFpHD5p3LnvEpqBp7UnJn+8x1Ogl1s38saUxe+ihQQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, "node_modules/chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -1847,60 +1444,6 @@ "node": "*" } }, - "node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", - "dev": true, - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/chokidar/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "optional": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, "node_modules/chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -1919,52 +1462,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -2054,19 +1551,6 @@ "readable-stream": "^2.3.5" } }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2124,51 +1608,12 @@ "node": ">= 12.0.0" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2220,249 +1665,89 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/copy-webpack-plugin": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz", - "integrity": "sha512-MXyPCjdPVx5iiWyl40Va3JGh27bKzOTNY3NjUTrosD2q7dR/cLD0013uqJ3BpFbUjyONINjb6qI7nDIJujrMbA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", "dev": true, "dependencies": { - "cacache": "^15.0.5", - "fast-glob": "^3.2.4", - "find-cache-dir": "^3.3.1", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "webpack-sources": "^1.4.3" + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/cacache": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.1.0.tgz", - "integrity": "sha512-mfx0C+mCfWjD1PnwQ9yaOrwG1ou9FkKnx0SvzUHWdFt7r7GaRtzT+9M8HAvLu62zIHtnpQ/1m93nWNDCckJGXQ==", - "dev": true, - "dependencies": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/copy-webpack-plugin/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/copy-webpack-plugin/node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/copy-webpack-plugin/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" + "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=10" + "node": ">=10.13.0" } }, - "node_modules/copy-webpack-plugin/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", + "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/copy-webpack-plugin/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/copy-webpack-plugin/node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, "engines": { - "node": ">=10" + "node": ">= 4" } }, - "node_modules/copy-webpack-plugin/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "randombytes": "^2.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, "engines": { - "node": ">= 10.13.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/copy-webpack-plugin/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/copy-webpack-plugin/node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/core-util-is": { @@ -2483,49 +1768,6 @@ "node": ">=0.10.0" } }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2540,28 +1782,6 @@ "node": ">= 8" } }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, "node_modules/crypto-js": { "version": "4.0.0", "resolved": "git+https://git@github.com/ably-forks/crypto-js.git#07e09b48fd8f850f71c10c9d9307ca4e6ac622a0", @@ -2569,12 +1789,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, "node_modules/dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -2610,15 +1824,6 @@ "node": ">=0.10.0" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2679,57 +1884,6 @@ "node": ">=10" } }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2748,16 +1902,6 @@ "node": ">= 0.8" } }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -2786,23 +1930,6 @@ "node": ">=0.3.1" } }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2827,34 +1954,12 @@ "node": ">=6.0.0" } }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2876,25 +1981,10 @@ "node": ">=0.10.0" } }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "node_modules/electron-to-chromium": { + "version": "1.4.366", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.366.tgz", + "integrity": "sha512-XjC4pyf1no8kJe24nUfyexpWwiGRbZWXU/KbprSEvXcTXUlr3Zr5vK3lQt2to0ttpMhAc3iENccwPSKbnEW2Fg==", "dev": true }, "node_modules/emoji-regex": { @@ -2903,15 +1993,6 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2930,30 +2011,16 @@ } }, "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enhanced-resolve/node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dev": true, "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" + "node": ">=10.13.0" } }, "node_modules/enquirer": { @@ -2980,17 +2047,11 @@ "node": ">=4" } }, - "node_modules/errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } + "node_modules/es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true }, "node_modules/escalade": { "version": "3.1.1", @@ -3270,24 +2331,14 @@ "dev": true }, "node_modules/events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "engines": { "node": ">=0.8.x" } }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -3297,63 +2348,6 @@ "node": ">= 0.8.0" } }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "node_modules/expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -3429,112 +2423,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3542,20 +2430,19 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-glob/node_modules/braces": { @@ -3646,12 +2533,6 @@ "reusify": "^1.0.4" } }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, "node_modules/file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -3664,13 +2545,6 @@ "node": ">=4" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -3701,33 +2575,6 @@ "node": ">=10" } }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -3761,93 +2608,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3946,16 +2706,6 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4009,18 +2759,6 @@ "node": ">= 0.6" } }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -4030,65 +2768,12 @@ "node": ">= 0.6" } }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4147,15 +2832,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/getobject": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.0.tgz", @@ -4192,6 +2868,12 @@ "node": ">= 6" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "node_modules/glob/node_modules/minimatch": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", @@ -4918,83 +3600,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5013,17 +3618,6 @@ "hexy": "bin/hexy_cmd.js" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -5078,12 +3672,6 @@ "node": ">=10.19.0" } }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5116,12 +3704,6 @@ } ] }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, "node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -5169,21 +3751,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5234,30 +3801,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -5274,25 +3817,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -5306,9 +3830,9 @@ } }, "node_modules/is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -5317,53 +3841,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -5379,15 +3856,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5433,30 +3901,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -5530,15 +3974,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -5584,6 +4019,44 @@ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/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/jest-worker/node_modules/supports-color": { + "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": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -5626,10 +4099,10 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, "node_modules/json-schema-traverse": { @@ -5644,18 +4117,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/jsonc-parser": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", @@ -5812,26 +4273,12 @@ } }, "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, "engines": { - "node": ">=4.0.0" + "node": ">=6.11.5" } }, "node_modules/locate-path": { @@ -5887,28 +4334,6 @@ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "dev": true }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -5930,18 +4355,6 @@ "node": ">=0.10.0" } }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/marked": { "version": "4.0.17", "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz", @@ -5954,17 +4367,6 @@ "node": ">= 12" } }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -5974,22 +4376,18 @@ "node": ">= 0.6" } }, - "node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6008,49 +4406,6 @@ "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -6092,18 +4447,6 @@ "node": ">=4" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -6122,113 +4465,6 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "node_modules/minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -6483,20 +4719,6 @@ "node": ">=8.0" } }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6521,28 +4743,6 @@ "node": "^10 || ^12 || >=13.7" } }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6564,41 +4764,10 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "dev": true }, "node_modules/nopt": { @@ -6633,44 +4802,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -6680,18 +4811,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", @@ -6797,12 +4916,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -6869,21 +4982,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -6893,23 +4991,6 @@ "node": ">=6" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6922,19 +5003,6 @@ "node": ">=6" } }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, "node_modules/parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -6967,28 +5035,6 @@ "node": ">= 0.8" } }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true, - "optional": true - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7067,21 +5113,11 @@ "node": "*" } }, - "node_modules/pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "node_modules/picomatch": { "version": "2.2.3", @@ -7095,15 +5131,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -7196,15 +5223,6 @@ "node": ">=14" } }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7226,15 +5244,6 @@ "node": ">=10.13.0" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7250,12 +5259,6 @@ "node": ">=0.4.0" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -7269,32 +5272,6 @@ "node": ">= 0.10" } }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -7304,27 +5281,6 @@ "once": "^1.3.1" } }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -7359,15 +5315,6 @@ "node": ">=0.4.x" } }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7408,16 +5355,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7463,21 +5400,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -7503,19 +5425,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -7534,24 +5443,6 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/replace-ext": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", @@ -7570,6 +5461,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -7642,13 +5542,6 @@ "node": ">=4" } }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, "node_modules/responselike": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", @@ -7708,16 +5601,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7741,15 +5624,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7792,19 +5666,58 @@ "dev": true }, "node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz", + "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==", "dev": true, "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 4" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" } }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -7907,58 +5820,12 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -8089,158 +5956,6 @@ "node": ">=6" } }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -8432,23 +6147,10 @@ "node": ">=10" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -8464,12 +6166,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", @@ -8492,58 +6188,12 @@ "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", "dev": true }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "node_modules/ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dev": true, - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -8553,45 +6203,6 @@ "node": ">= 0.8" } }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -8687,6 +6298,18 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -8703,52 +6326,14 @@ } }, "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, "engines": { "node": ">=6" } }, - "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/tar/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/task-closure-tools": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/task-closure-tools/-/task-closure-tools-0.1.10.tgz", @@ -8772,158 +6357,102 @@ } }, "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "version": "5.16.9", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", + "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, "node_modules/terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", "dev": true, "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" }, "engines": { - "node": ">= 6.9.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" + "webpack": "^5.1.0" }, - "engines": { - "node": ">=0.6.0" + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" + "node": ">= 10.13.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" + "randombytes": "^2.1.0" } }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "node_modules/terser/node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.4.0" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "node_modules/to-utf8": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", @@ -8939,23 +6468,22 @@ } }, "node_modules/ts-loader": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", - "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", "dev": true, "dependencies": { "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", + "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" }, "peerDependencies": { "typescript": "*", - "webpack": "*" + "webpack": "^5.0.0" } }, "node_modules/ts-loader/node_modules/braces": { @@ -8991,32 +6519,6 @@ "node": ">=0.12.0" } }, - "node_modules/ts-loader/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ts-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/ts-loader/node_modules/micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -9070,28 +6572,6 @@ "node": ">=10.13.0" } }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/enhanced-resolve": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", - "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/tsconfig-paths/node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9131,12 +6611,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -9180,12 +6654,6 @@ "node": ">= 0.6" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "node_modules/typedoc": { "version": "0.23.8", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.8.tgz", @@ -9263,39 +6731,6 @@ "node": "*" } }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -9305,63 +6740,34 @@ "node": ">= 0.8" } }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "escalade": "^3.1.1", + "picocolors": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" + "bin": { + "update-browserslist-db": "cli.js" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4", - "yarn": "*" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, "node_modules/uri-js": { @@ -9373,59 +6779,12 @@ "punycode": "^2.1.0" } }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -9497,12 +6856,6 @@ "source-map": "^0.5.1" } }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, "node_modules/vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", @@ -9516,200 +6869,54 @@ "dev": true }, "node_modules/watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "optionalDependencies": { - "chokidar": "^3.4.1", - "watchpack-chokidar2": "^2.0.1" - } - }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, - "node_modules/watchpack/node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "optional": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/watchpack/node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/watchpack/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/watchpack/node_modules/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, - "optional": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "node_modules/watchpack/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/watchpack/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/watchpack/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, - "optional": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/watchpack/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/watchpack/node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "optional": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/watchpack/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, - "optional": true, "dependencies": { - "is-number": "^7.0.0" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">=8.0" + "node": ">=10.13.0" } }, "node_modules/webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", + "version": "5.79.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.79.0.tgz", + "integrity": "sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" }, "engines": { - "node": ">=6.11.5" + "node": ">=10.13.0" }, "funding": { "type": "opencollective", @@ -9718,51 +6925,46 @@ "peerDependenciesMeta": { "webpack-cli": { "optional": true - }, - "webpack-command": { - "optional": true } } }, "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^9.4.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -9772,21 +6974,50 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "engines": { - "node": ">= 10" + "node": "^12.20.0 || >=14" } }, "node_modules/webpack-cli/node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-cli/node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-cli/node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, - "engines": { - "node": ">= 0.10" + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/webpack-merge": { @@ -9803,28 +7034,18 @@ } }, "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/webpack-sources/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, "node_modules/webpack/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -9833,17 +7054,31 @@ "node": ">=0.4.0" } }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "node_modules/webpack/node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", "dev": true, "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=4.0.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/which": { @@ -9945,15 +7180,6 @@ "node": ">=0.10.0" } }, - "node_modules/worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "dependencies": { - "errno": "~0.1.7" - } - }, "node_modules/workerpool": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", @@ -10039,15 +7265,6 @@ "node": ">=4.0" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -10278,6 +7495,55 @@ "strip-json-comments": "^3.1.1" } }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -10304,47 +7570,6 @@ "fastq": "^1.6.0" } }, - "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "@sindresorhus/is": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz", @@ -10381,6 +7606,32 @@ "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", "dev": true }, + "@types/eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, "@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", @@ -10569,200 +7820,169 @@ "dev": true }, "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", "dev": true }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", "dev": true, "requires": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true - }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0" + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" } }, "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" } }, "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" } }, "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" } }, "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", + "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" } }, "@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "requires": {} }, "@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true, "requires": {} }, @@ -10807,16 +8027,6 @@ "dev": true, "requires": {} }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -10829,12 +8039,34 @@ "uri-js": "^4.2.2" } }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "requires": {} + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } }, "ajv-keywords": { "version": "3.5.2", @@ -10870,35 +8102,6 @@ "color-convert": "^1.9.0" } }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -10908,24 +8111,6 @@ "sprintf-js": "~1.0.2" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", @@ -10950,77 +8135,12 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -11033,13 +8153,6 @@ "dev": true, "from": "async@ably-forks/async#requirejs" }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true, - "optional": true - }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -11051,12 +8164,6 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -11105,126 +8212,36 @@ "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", "dev": true, "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - } - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" + "punycode": "1.3.2", + "querystring": "0.2.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" } } } }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "base64-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.2.tgz", "integrity": "sha1-R0IRyV5s8qVH20YeT2d4tR0I+mU=" }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", - "dev": true - }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -11281,131 +8298,22 @@ "concat-map": "0.0.1" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, "browser-stdout": { "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 }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "dev": true, "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" } }, "btoa": { @@ -11434,21 +8342,9 @@ } }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "bytes": { @@ -11457,77 +8353,6 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, "cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -11569,6 +8394,12 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001480", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", + "integrity": "sha512-q7cpoPPvZYgtyC4VaBSN0Bt+PJ4c4EYRf0DrduInOz2SkFpHD5p3LnvEpqBp7UnJn+8x1Ogl1s38saUxe+ihQQ==", + "dev": true + }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -11640,58 +8471,6 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - } - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -11709,45 +8488,6 @@ } } }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -11824,16 +8564,6 @@ "readable-stream": "^2.3.5" } }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -11882,48 +8612,12 @@ "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", "dev": true }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -11952,192 +8646,78 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-webpack-plugin": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz", - "integrity": "sha512-MXyPCjdPVx5iiWyl40Va3JGh27bKzOTNY3NjUTrosD2q7dR/cLD0013uqJ3BpFbUjyONINjb6qI7nDIJujrMbA==", - "dev": true, - "requires": { - "cacache": "^15.0.5", - "fast-glob": "^3.2.4", - "find-cache-dir": "^3.3.1", - "glob-parent": "^5.1.1", - "globby": "^11.0.1", - "loader-utils": "^2.0.0", - "normalize-path": "^3.0.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "cacache": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.1.0.tgz", - "integrity": "sha512-mfx0C+mCfWjD1PnwQ9yaOrwG1ou9FkKnx0SvzUHWdFt7r7GaRtzT+9M8HAvLu62zIHtnpQ/1m93nWNDCckJGXQ==", - "dev": true, - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true + } + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "yallist": "^4.0.0" + "is-glob": "^4.0.3" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "globby": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", + "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", "dev": true, "requires": { - "semver": "^6.0.0" + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "randombytes": "^2.1.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true - }, - "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } } } }, @@ -12156,51 +8736,6 @@ "vary": "^1" } }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -12212,37 +8747,12 @@ "which": "^2.0.1" } }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, "crypto-js": { "version": "git+https://git@github.com/ably-forks/crypto-js.git#07e09b48fd8f850f71c10c9d9307ca4e6ac622a0", "integrity": "sha512-3RE9Ztinx+QLpEDwcsPTVGMXIIqtS/r+YyD1E7BGXE+CRBcCwkyHWVyl1Aperg5QBOUkg2dtiC8p18a41IKltQ==", "dev": true, "from": "crypto-js@ably-forks/crypto-js#crypto-lite" }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -12264,12 +8774,6 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true - }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -12314,47 +8818,6 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -12367,16 +8830,6 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -12395,25 +8848,6 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -12432,30 +8866,12 @@ "esutils": "^2.0.2" } }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -12471,28 +8887,11 @@ "jake": "^10.8.5" } }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } + "electron-to-chromium": { + "version": "1.4.366", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.366.tgz", + "integrity": "sha512-XjC4pyf1no8kJe24nUfyexpWwiGRbZWXU/KbprSEvXcTXUlr3Zr5vK3lQt2to0ttpMhAc3iENccwPSKbnEW2Fg==", + "dev": true }, "emoji-regex": { "version": "7.0.3", @@ -12500,12 +8899,6 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -12521,26 +8914,13 @@ } }, "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - } + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" } }, "enquirer": { @@ -12558,14 +8938,11 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } + "es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true }, "escalade": { "version": "3.1.1", @@ -12779,77 +9156,17 @@ "dev": true }, "events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -12885,128 +9202,42 @@ "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "ms": "2.0.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -13014,17 +9245,16 @@ "dev": true }, "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" }, "dependencies": { "braces": { @@ -13099,12 +9329,6 @@ "reusify": "^1.0.4" } }, - "figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -13114,13 +9338,6 @@ "flat-cache": "^2.0.1" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -13150,29 +9367,6 @@ } } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -13205,71 +9399,6 @@ } } }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } - } - }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -13346,16 +9475,6 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -13397,69 +9516,18 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -13503,12 +9571,6 @@ "pump": "^3.0.0" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, "getobject": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.0.tgz", @@ -13547,6 +9609,12 @@ "is-glob": "^4.0.1" } }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -14067,72 +10135,6 @@ "has-symbols": "^1.0.2" } }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -14145,17 +10147,6 @@ "integrity": "sha512-ciq6hFsSG/Bpt2DmrZJtv+56zpPdnq+NQ4ijEFrveKN0ZG1mhl/LdT1NQZ9se6ty1fACcI4d4vYqC9v8EYpH2A==", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -14198,12 +10189,6 @@ "resolve-alpn": "^1.0.0" } }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -14219,12 +10204,6 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -14257,18 +10236,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -14313,26 +10280,6 @@ "is-windows": "^1.0.1" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -14343,22 +10290,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -14366,65 +10297,20 @@ "dev": true }, "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", "dev": true, "requires": { "has": "^1.0.3" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -14448,31 +10334,11 @@ }, "is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" } }, "is-plain-obj": { @@ -14527,12 +10393,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -14571,6 +10431,34 @@ } } }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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 + }, + "supports-color": { + "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" + } + } + } + }, "jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -14604,10 +10492,10 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, "json-schema-traverse": { @@ -14622,15 +10510,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, "jsonc-parser": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", @@ -14754,22 +10633,11 @@ } }, "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, - "loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -14811,24 +10679,6 @@ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "dev": true }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -14844,54 +10694,30 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, "marked": { "version": "4.0.17", "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz", "integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==", "dev": true }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -14904,45 +10730,6 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -14969,18 +10756,6 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -14996,91 +10771,6 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -15259,20 +10949,6 @@ } } }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15291,25 +10967,6 @@ "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", "dev": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -15328,44 +10985,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true }, "nopt": { "version": "3.0.6", @@ -15387,52 +11011,12 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", @@ -15516,12 +11100,6 @@ "word-wrap": "^1.2.3" } }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -15567,38 +11145,12 @@ "p-limit": "^3.0.2" } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -15608,19 +11160,6 @@ "callsites": "^3.0.0" } }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -15644,25 +11183,6 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true, - "optional": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -15720,18 +11240,11 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, - "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "picomatch": { "version": "2.2.3", @@ -15739,12 +11252,6 @@ "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", "dev": true }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -15808,12 +11315,6 @@ "integrity": "sha512-w/hc/Ld0RM4pmsNeE6aL/fPNWw8BWit2tg+TfqJ3+p59c6s3B6C8mXvXrIPmfQEobkcFDc+4KirNzOQ+uBSP1Q==", "dev": true }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15826,12 +11327,6 @@ "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -15844,12 +11339,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -15860,34 +11349,6 @@ "ipaddr.js": "1.9.1" } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -15897,29 +11358,6 @@ "once": "^1.3.1" } }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -15941,12 +11379,6 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -15959,21 +11391,11 @@ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, "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" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { - "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, @@ -16018,18 +11440,6 @@ } } }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, "rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -16051,16 +11461,6 @@ } } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -16073,18 +11473,6 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, "replace-ext": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", @@ -16097,6 +11485,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -16153,12 +11547,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "responselike": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", @@ -16204,16 +11592,6 @@ } } }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -16223,15 +11601,6 @@ "queue-microtask": "^1.2.2" } }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "^1.1.1" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -16260,14 +11629,44 @@ "dev": true }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz", + "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "semver": { @@ -16363,51 +11762,12 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -16507,134 +11867,6 @@ "is-fullwidth-code-point": "^2.0.0" } }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -16779,23 +12011,10 @@ } } }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -16810,12 +12029,6 @@ } } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", @@ -16838,96 +12051,18 @@ "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", "dev": true }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -17003,6 +12138,12 @@ "has-flag": "^3.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -17016,39 +12157,11 @@ } }, "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, "task-closure-tools": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/task-closure-tools/-/task-closure-tools-0.1.10.tgz", @@ -17066,130 +12179,65 @@ } }, "terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "version": "5.16.9", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", + "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", "dev": true, "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map-support": "~0.5.20" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true } } }, "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", "dev": true, "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" }, "dependencies": { - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", "dev": true, "requires": { - "randombytes": "^2.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "randombytes": "^2.1.0" } } } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true }, "to-utf8": { "version": "0.0.1", @@ -17203,14 +12251,13 @@ "dev": true }, "ts-loader": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", - "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", "dev": true, "requires": { "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", + "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4" }, @@ -17239,23 +12286,6 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -17305,24 +12335,6 @@ "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", "tsconfig-paths": "^4.1.2" - }, - "dependencies": { - "enhanced-resolve": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", - "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - } } }, "tslib": { @@ -17348,12 +12360,6 @@ } } }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -17385,12 +12391,6 @@ "mime-types": "~2.1.24" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typedoc": { "version": "0.23.8", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.8.tgz", @@ -17445,89 +12445,22 @@ "util-deprecate": "^1.0.2" } }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } + "escalade": "^3.1.1", + "picocolors": "^1.0.0" } }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "optional": true - }, "uri-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", @@ -17537,53 +12470,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -17646,12 +12532,6 @@ "source-map": "^0.5.1" } }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, "vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", @@ -17665,208 +12545,125 @@ "dev": true }, "watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "dev": true, - "requires": { - "chokidar": "^3.4.1", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.1" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "optional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true, - "optional": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": 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, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, - "optional": true, "requires": { - "chokidar": "^2.1.8" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" } }, "webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", + "version": "5.79.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.79.0.tgz", + "integrity": "sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" }, "dependencies": { "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } }, "webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^9.4.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true }, "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } } } }, @@ -17881,22 +12678,10 @@ } }, "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true }, "which": { "version": "2.0.2", @@ -17975,15 +12760,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "requires": { - "errno": "~0.1.7" - } - }, "workerpool": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", @@ -18056,12 +12832,6 @@ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", "dev": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", diff --git a/package.json b/package.json index 803e13816d..e43bef3b8c 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1075.0", "chai": "^4.2.0", - "copy-webpack-plugin": "^6.4.1", + "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", "crypto-js": "ably-forks/crypto-js#crypto-lite", "eslint": "^7.13.0", @@ -65,13 +65,13 @@ "requirejs": "~2.1", "shelljs": "~0.8", "source-map-explorer": "^2.5.2", - "ts-loader": "^8.2.0", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^4.0.1", "tslib": "^2.3.1", "typedoc": "^0.23.8", "typescript": "^4.6.4", - "webpack": "^4.44.2", - "webpack-cli": "^4.2.0" + "webpack": "^5.79.0", + "webpack-cli": "^5.0.1" }, "engines": { "node": ">=5.10.x" diff --git a/webpack.config.js b/webpack.config.js index 54b49e6109..3b53bda271 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,7 +27,7 @@ const baseConfig = { { test: /\.ts$/, loader: 'ts-loader' }, ], }, - target: 'web', + target: ['web', 'es5'], externals: { request: false, ws: false, @@ -54,7 +54,7 @@ const nodeConfig = { ...baseConfig.output, filename: 'ably-node.js', }, - target: 'node', + target: ['node', 'es5'], externals: { got: true, ws: true, @@ -73,9 +73,11 @@ const browserConfig = { entry: { index: platformPath('web'), }, - node: { - crypto: 'empty', - Buffer: false, + resolve: { + ...baseConfig.resolve, + fallback: { + crypto: false, + }, }, externals: { 'crypto-js': true, @@ -95,9 +97,11 @@ const nativeScriptConfig = { entry: { index: platformPath('nativescript'), }, - node: { - crypto: 'empty', - Buffer: false, + resolve: { + ...baseConfig.resolve, + fallback: { + crypto: false, + }, }, externals: { request: false, @@ -122,10 +126,9 @@ const reactNativeConfig = { resolve: { extensions: ['.js', '.ts'], plugins: [new TsconfigPathsPlugin()], - }, - node: { - crypto: 'empty', - Buffer: false, + fallback: { + crypto: false, + }, }, externals: { request: false, @@ -153,7 +156,7 @@ const browserMinConfig = { }; const webworkerConfig = { - target: 'webworker', + target: ['webworker', 'es5'], ...browserConfig, entry: { index: platformPath('web', 'index-webworker.ts'), From 161905684c25fa68f81cc6139c1cd0a2406e6a15 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 24 Apr 2023 14:33:51 -0300 Subject: [PATCH 007/468] Remove withoutjsonp.js This has been unused since cbfcca2. --- src/platform/web/lib/transport/withoutjsonp.js | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/platform/web/lib/transport/withoutjsonp.js diff --git a/src/platform/web/lib/transport/withoutjsonp.js b/src/platform/web/lib/transport/withoutjsonp.js deleted file mode 100644 index 983f0cf0f4..0000000000 --- a/src/platform/web/lib/transport/withoutjsonp.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * This file exists for React Native and Nativescript in order to exclude the unsupported JSONP transport from these platforms. - */ -import XHRPollingTransport from './xhrpollingtransport'; -import XHRStreamingTransport from './xhrstreamingtransport'; - -export default [XHRPollingTransport, XHRStreamingTransport]; From 355144118d7be58feb57a984c6dbfcd8a6282c58 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 24 Apr 2023 14:21:22 -0300 Subject: [PATCH 008/468] Remove support for JSONP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As #1198 says, "Internal queries show that no one is using JSONP and due to this, we have decided to remove it from the library in the next major version update." Note that in both the web and nodejs platforms, Defaults.baseTransportOrder and Defaults.transportPreferenceOrder now have identical contents. I considered combining them into a single property, but I couldn’t think of a way of expressing the two separate concepts that I think these properties are trying to express — that is, "transport most likely to be supported" and "transport we’d most like to use". So I’ve kept them separate. Resolves #1198. --- README.md | 2 +- ably.d.ts | 4 +- src/common/constants/TransportNames.ts | 1 - src/common/lib/transport/comettransport.ts | 18 +- src/common/lib/transport/connectionmanager.ts | 8 +- src/common/lib/util/utils.ts | 1 - src/common/types/IDefaults.d.ts | 1 - src/common/types/IPlatform.d.ts | 1 - src/platform/nativescript/platform.js | 1 - src/platform/nodejs/lib/util/defaults.ts | 4 +- src/platform/react-native/platform.ts | 1 - src/platform/web/lib/transport/index.js | 3 +- .../web/lib/transport/jsonptransport.ts | 248 ------------------ src/platform/web/lib/util/defaults.ts | 26 +- src/platform/web/lib/util/http.ts | 63 ----- src/platform/web/platform.ts | 1 - test/browser/http.test.js | 7 +- test/browser/simple.test.js | 21 -- test/common/modules/shared_helper.js | 9 +- test/common/modules/testapp_manager.js | 30 --- test/realtime/auth.test.js | 98 +++---- test/realtime/sync.test.js | 157 ++++++----- 22 files changed, 145 insertions(+), 560 deletions(-) delete mode 100644 src/platform/web/lib/transport/jsonptransport.ts diff --git a/README.md b/README.md index d0dc77dc23..b5338ad775 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ We regression-test the library against a selection of those (which will change o However, we aim to be compatible with a much wider set of platforms and browsers than we can possibly test on. That means we'll happily support (and investigate reported problems with) any reasonably-widely-used browser. So if you find any compatibility issues, please do [raise an issue](https://github.com/ably/ably-js/issues) in this repository or [contact Ably customer support](https://support.ably.com) for advice. -Ably-js has fallback mechanisms in order to be able to support older browsers; specifically it supports comet-based connections for browsers that do not support websockets, and this includes JSONP for browsers that do not support cross-origin XHR. Each of these fallback transport mechanisms is supported and tested on all the browsers we test against, even when those browsers do not themselves require those fallbacks. These mean that the library should be compatible with nearly any browser on most platforms. +Ably-js has fallback mechanisms in order to be able to support older browsers; specifically it supports comet-based connections for browsers that do not support websockets. Each of these fallback transport mechanisms is supported and tested on all the browsers we test against, even when those browsers do not themselves require those fallbacks. These mean that the library should be compatible with nearly any browser on most platforms. Known browser incompatibilities will be documented as an issue in this repository using the ["compatibility" label](https://github.com/ably/ably-js/issues?q=is%3Aissue+is%3Aopen+label%3A%22compatibility%22). For complete API documentation, see the [Ably documentation](https://www.ably.com/docs). diff --git a/ably.d.ts b/ably.d.ts index 1627a9b540..7064df6546 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -293,7 +293,7 @@ declare namespace Types { /** * A type which specifies the valid transport names. [See here](https://faqs.ably.com/which-transports-are-supported) for more information. */ - type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'jsonp' | 'comet'; + type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'comet'; /** * Contains the details of a {@link ChannelBase} or {@link RealtimeChannelBase} object such as its ID and {@link ChannelStatus}. @@ -525,7 +525,7 @@ declare namespace Types { transportParams?: { [k: string]: string | number }; /** - * An array of transports to use, in descending order of preference. In the browser environment the available transports are: `web_socket`, `xhr`, and `jsonp`. + * An array of transports to use, in descending order of preference. In the browser environment the available transports are: `web_socket` and `xhr`. */ transports?: Transport[]; diff --git a/src/common/constants/TransportNames.ts b/src/common/constants/TransportNames.ts index c3e0ada287..9cda74c614 100644 --- a/src/common/constants/TransportNames.ts +++ b/src/common/constants/TransportNames.ts @@ -3,7 +3,6 @@ enum TransportNames { Comet = 'comet', XhrStreaming = 'xhr_streaming', XhrPolling = 'xhr_polling', - JsonP = 'jsonp', } export default TransportNames; diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index 65d5345f17..f702a995ec 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -367,12 +367,18 @@ abstract class CometTransport extends Transport { return responseData; } - /* For comet, we could do the auth update by aborting the current recv and - * starting a new one with the new token, that'd be sufficient for realtime. - * Problem is JSONP - you can't cancel truly abort a recv once started. So - * we need to send an AUTH for jsonp. In which case it's simpler to keep all - * comet transports the same and do it for all of them. So we send the AUTH - * instead, and don't need to abort the recv */ + /* Historical comment, back from when we supported JSONP: + * + * > For comet, we could do the auth update by aborting the current recv and + * > starting a new one with the new token, that'd be sufficient for realtime. + * > Problem is JSONP - you can't cancel truly abort a recv once started. So + * > we need to send an AUTH for jsonp. In which case it's simpler to keep all + * > comet transports the same and do it for all of them. So we send the AUTH + * > instead, and don't need to abort the recv + * + * Now that we’ve dropped JSONP support, we may be able to revisit the above; + * see https://github.com/ably/ably-js/issues/1214. + */ onAuthUpdated = (tokenDetails: API.Types.TokenDetails): void => { this.authParams = { access_token: tokenDetails.token }; }; diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index c97708b978..1300e59040 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -300,9 +300,7 @@ class ConnectionManager extends EventEmitter { ConnectionManager.supportedTransports ); /* baseTransports selects the leftmost transport in the Defaults.baseTransportOrder list - * that's both requested and supported. Normally this will be xhr_polling; - * if xhr isn't supported it will be jsonp. If the user has forced a - * transport, it'll just be that one. */ + * that's both requested and supported. */ this.baseTransport = Utils.intersect(Defaults.baseTransportOrder, this.transports)[0]; this.upgradeTransports = Utils.intersect(this.transports, Defaults.upgradeTransports); this.transportPreference = null; @@ -1497,9 +1495,7 @@ class ConnectionManager extends EventEmitter { * needed (will only be in the case where the preference is xhrs and the * browser supports ws). * - base: we try to connect with the best transport that we think will - * never fail for this browser (usually this is xhr_polling; for very old - * browsers will be jsonp, for node will be comet). If it doesn't work, we - * try fallback hosts. + * never fail for this platform. If it doesn't work, we try fallback hosts. * - upgrade: given a connected transport, we see if there are any better * ones, and if so, try to upgrade to them. * diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 253dc7e4fc..28b8366274 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -341,7 +341,6 @@ export function allSame(arr: Array>, prop: string): bool const contentTypes = { json: 'application/json', - jsonp: 'application/javascript', xml: 'application/xml', html: 'text/html', msgpack: 'application/x-msgpack', diff --git a/src/common/types/IDefaults.d.ts b/src/common/types/IDefaults.d.ts index 7c4db18aaf..3fb342cbc8 100644 --- a/src/common/types/IDefaults.d.ts +++ b/src/common/types/IDefaults.d.ts @@ -3,7 +3,6 @@ import { RestAgentOptions } from './ClientOptions'; export default interface IDefaults { connectivityCheckUrl: string; - jsonpInternetUpUrl?: string; defaultTransports: TransportNames[]; baseTransportOrder: TransportNames[]; transportPreferenceOrder: TransportNames[]; diff --git a/src/common/types/IPlatform.d.ts b/src/common/types/IPlatform.d.ts index 45e04a84fb..61ac5c2152 100644 --- a/src/common/types/IPlatform.d.ts +++ b/src/common/types/IPlatform.d.ts @@ -38,7 +38,6 @@ export interface IPlatform { noUpgrade?: boolean | string; fetchSupported?: boolean; xhrSupported?: boolean; - jsonpSupported?: boolean; allowComet?: boolean; streamingSupported?: boolean; ArrayBuffer?: typeof ArrayBuffer | false; diff --git a/src/platform/nativescript/platform.js b/src/platform/nativescript/platform.js index 3d6dbe2818..f857bddfda 100644 --- a/src/platform/nativescript/platform.js +++ b/src/platform/nativescript/platform.js @@ -26,7 +26,6 @@ var Platform = { WebSocket: WebSocket, xhrSupported: XMLHttpRequest, allowComet: true, - jsonpSupported: false, streamingSupported: false, useProtocolHeartbeats: true, createHmac: null, diff --git a/src/platform/nodejs/lib/util/defaults.ts b/src/platform/nodejs/lib/util/defaults.ts index 5ec0f8139b..adaf8a5083 100644 --- a/src/platform/nodejs/lib/util/defaults.ts +++ b/src/platform/nodejs/lib/util/defaults.ts @@ -4,9 +4,7 @@ import TransportNames from '../../../../common/constants/TransportNames'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', /* Note: order matters here: the base transport is the leftmost one in the - * intersection of baseTransportOrder and the transports clientOption that's supported. - * (For node this is the same as the transportPreferenceOrder, but for - * browsers it's different*/ + * intersection of baseTransportOrder and the transports clientOption that's supported. */ defaultTransports: [TransportNames.WebSocket], baseTransportOrder: [TransportNames.Comet, TransportNames.WebSocket], transportPreferenceOrder: [TransportNames.Comet, TransportNames.WebSocket], diff --git a/src/platform/react-native/platform.ts b/src/platform/react-native/platform.ts index 07b9279e19..58053eeaf2 100644 --- a/src/platform/react-native/platform.ts +++ b/src/platform/react-native/platform.ts @@ -10,7 +10,6 @@ const Platform: IPlatform = { WebSocket: WebSocket, xhrSupported: true, allowComet: true, - jsonpSupported: false, streamingSupported: true, useProtocolHeartbeats: true, createHmac: null, diff --git a/src/platform/web/lib/transport/index.js b/src/platform/web/lib/transport/index.js index 2e53447e06..8fd7afb26a 100644 --- a/src/platform/web/lib/transport/index.js +++ b/src/platform/web/lib/transport/index.js @@ -1,5 +1,4 @@ -import JSONPTransport from './jsonptransport'; import XHRPollingTransport from './xhrpollingtransport'; import XHRStreamingTransport from './xhrstreamingtransport'; -export default [JSONPTransport, XHRPollingTransport, XHRStreamingTransport]; +export default [XHRPollingTransport, XHRStreamingTransport]; diff --git a/src/platform/web/lib/transport/jsonptransport.ts b/src/platform/web/lib/transport/jsonptransport.ts deleted file mode 100644 index 2ffc69c73a..0000000000 --- a/src/platform/web/lib/transport/jsonptransport.ts +++ /dev/null @@ -1,248 +0,0 @@ -import * as Utils from 'common/lib/util/utils'; -import CometTransport from 'common/lib/transport/comettransport'; -import Platform from 'common/platform'; -import EventEmitter from 'common/lib/util/eventemitter'; -import ErrorInfo from 'common/lib/types/errorinfo'; -import Defaults from 'common/lib/util/defaults'; -import Logger from 'common/lib/util/logger'; -import Auth from 'common/lib/client/auth'; -import HttpMethods from 'common/constants/HttpMethods'; -import { RequestParams } from 'common/types/http'; -import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; -import XHRStates from 'common/constants/XHRStates'; - -// Workaround for salesforce lightning locker compatibility -let globalObject = Utils.getGlobalObject() as unknown as { - _ablyjs_jsonp: Record; - JSONPTransport: typeof JSONPTransport; -}; - -const noop = function () {}; -/* Can't just use window.Ably, as that won't exist if using the commonjs version. */ -const _: Record = (globalObject._ablyjs_jsonp = {}); - -/* express strips out parantheses from the callback! - * Kludge to still alow its responses to work, while not keeping the - * function form for normal use and not cluttering window.Ably - * https://github.com/expressjs/express/blob/5b4d4b4ab1324743534fbcd4709f4e75bb4b4e9d/lib/response.js#L305 - */ -_._ = function (id: string) { - return _['_' + id] || noop; -}; -let idCounter = 1; -const shortName = 'jsonp'; - -export function createRequest( - uri: string, - headers: Record | null, - params?: RequestParams, - body?: unknown, - requestMode?: number, - timeouts?: Record | null, - method?: HttpMethods -) { - /* JSONP requests are used either with the context being a realtime - * transport, or with timeouts passed in (for when used by a rest client), - * or completely standalone. Use the appropriate timeouts in each case */ - timeouts = timeouts || Defaults.TIMEOUTS; - return new Request( - undefined, - uri, - headers, - Utils.copy(params), - body, - requestMode as number, - timeouts as Record, - method - ); -} - -class JSONPTransport extends CometTransport { - shortName = shortName; - - constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { - super(connectionManager, auth, params); - params.stream = false; - } - - static isAvailable() { - return Platform.Config.jsonpSupported && Platform.Config.allowComet; - } - - toString() { - return 'JSONPTransport; uri=' + this.baseUri + '; isConnected=' + this.isConnected; - } - - createRequest( - uri: string, - headers: Record | null, - params?: Record, - body?: unknown, - requestMode?: number, - timeouts?: Record, - method?: HttpMethods - ) { - /* JSONP requests are used either with the context being a realtime - * transport, or with timeouts passed in (for when used by a rest client), - * or completely standalone. Use the appropriate timeouts in each case */ - timeouts = this?.timeouts || timeouts || Defaults.TIMEOUTS; - return createRequest(uri, headers, params, body, requestMode, timeouts, method); - } -} - -export class Request extends EventEmitter { - id: string | number; - uri: string; - params: Record; - body: unknown; - requestMode: number; - timeouts: Record; - requestComplete: boolean; - method?: HttpMethods; - script?: HTMLScriptElement; - timer?: number | NodeJS.Timeout | null; - - constructor( - id: string | number | undefined, - uri: string, - headers: Record | null, - params: Record | null, - body: unknown | null, - requestMode: number, - timeouts: Record, - method?: HttpMethods - ) { - super(); - if (id === undefined) id = idCounter++; - this.id = id; - this.uri = uri; - this.params = params || {}; - this.params.rnd = Utils.cheapRandStr(); - if (headers) { - /* JSONP doesn't allow headers. Cherry-pick a couple to turn into qs params */ - if (headers['X-Ably-Version']) this.params.v = headers['X-Ably-Version']; - if (headers['X-Ably-Lib']) this.params.lib = headers['X-Ably-Lib']; - } - this.body = body; - this.method = method; - this.requestMode = requestMode; - this.timeouts = timeouts; - this.requestComplete = false; - } - - exec() { - const id = this.id, - body = this.body, - method = this.method, - uri = this.uri, - params = this.params; - - params.callback = '_ablyjs_jsonp._(' + id + ')'; - - params.envelope = 'jsonp'; - if (body) { - params.body = body as string; - } - if (method && method !== 'get') { - params.method = method; - } - - const script = (this.script = document.createElement('script')); - const src = uri + Utils.toQueryString(params); - script.src = src; - if (script.src.split('/').slice(-1)[0] !== src.split('/').slice(-1)[0]) { - /* The src has been truncated. Can't abort, but can at least emit an - * error so the user knows what's gone wrong. (Can't compare strings - * directly as src may have a port, script.src won't) */ - Logger.logAction( - Logger.LOG_ERROR, - 'JSONP Request.exec()', - 'Warning: the browser appears to have truncated the script URI. This will likely result in the request failing due to an unparseable body param' - ); - } - script.async = true; - script.type = 'text/javascript'; - script.charset = 'UTF-8'; - script.onerror = (err: string | Event) => { - this.complete(new ErrorInfo('JSONP script error (event: ' + Platform.Config.inspect(err) + ')', null, 400)); - }; - - type JSONPResponse = { - statusCode?: number; - response: { - error?: ErrorInfo; - }; - headers?: Record; - }; - - _['_' + id] = (message: JSONPResponse) => { - if (message.statusCode) { - /* Handle as enveloped jsonp, as all jsonp transport uses should be */ - const response = message.response; - if (message.statusCode == 204) { - this.complete(null, null, null, message.statusCode); - } else if (!response) { - this.complete(new ErrorInfo('Invalid server response: no envelope detected', null, 500)); - } else if (message.statusCode < 400 || Utils.isArray(response)) { - /* If response is an array, it's an array of protocol messages -- even if - * it contains an error action (hence the nonsuccess statuscode), we can - * consider the request to have succeeded, just pass it on to - * onProtocolMessage to decide what to do */ - this.complete(null, response, message.headers, message.statusCode); - } else { - const err = response.error || new ErrorInfo('Error response received from server', null, message.statusCode); - this.complete(err); - } - } else { - /* Handle as non-enveloped -- as will be eg from a customer's authUrl server */ - this.complete(null, message); - } - }; - - const timeout = - this.requestMode == XHRStates.REQ_SEND ? this.timeouts.httpRequestTimeout : this.timeouts.recvTimeout; - this.timer = setTimeout(this.abort.bind(this), timeout); - let head = document.getElementsByTagName('head')[0]; - (head as HTMLHeadElement).insertBefore(script, (head as HTMLHeadElement).firstChild); - } - - complete(err?: ErrorInfo | null, body?: unknown, headers?: Record | null, statusCode?: number) { - headers = headers || {}; - if (!this.requestComplete) { - this.requestComplete = true; - let contentType; - if (body) { - contentType = typeof body == 'string' ? 'text/plain' : 'application/json'; - headers['content-type'] = contentType; - this.emit('data', body); - } - - this.emit('complete', err, body, headers, /* unpacked: */ true, statusCode); - this.dispose(); - } - } - - abort() { - this.dispose(); - } - - dispose() { - const timer = this.timer; - if (timer) { - clearTimeout(timer as NodeJS.Timeout); - this.timer = null; - } - const script = this.script as HTMLScriptElement; - if (script.parentNode) script.parentNode.removeChild(script); - delete _[this.id]; - this.emit('disposed'); - } -} - -export default function (connectionManager: typeof ConnectionManager): typeof JSONPTransport { - globalObject.JSONPTransport = JSONPTransport; - if (JSONPTransport.isAvailable()) { - connectionManager.supportedTransports[shortName] = JSONPTransport; - } - return JSONPTransport; -} diff --git a/src/platform/web/lib/util/defaults.ts b/src/platform/web/lib/util/defaults.ts index d5c26535b0..7366ec75a4 100644 --- a/src/platform/web/lib/util/defaults.ts +++ b/src/platform/web/lib/util/defaults.ts @@ -3,30 +3,12 @@ import TransportNames from 'common/constants/TransportNames'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', - jsonpInternetUpUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up-0-9.js', /* Order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's - * supported. This is not quite the same as the preference order -- e.g. - * xhr_polling is preferred to jsonp, but for browsers that support it we want - * the base transport to be xhr_polling, not jsonp */ - defaultTransports: [ - TransportNames.XhrPolling, - TransportNames.XhrStreaming, - TransportNames.JsonP, - TransportNames.WebSocket, - ], - baseTransportOrder: [ - TransportNames.XhrPolling, - TransportNames.XhrStreaming, - TransportNames.JsonP, - TransportNames.WebSocket, - ], - transportPreferenceOrder: [ - TransportNames.JsonP, - TransportNames.XhrPolling, - TransportNames.XhrStreaming, - TransportNames.WebSocket, - ], + * supported. */ + defaultTransports: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], + baseTransportOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], + transportPreferenceOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], upgradeTransports: [TransportNames.XhrStreaming, TransportNames.WebSocket], }; diff --git a/src/platform/web/lib/util/http.ts b/src/platform/web/lib/util/http.ts index 987a530654..d6a7537327 100644 --- a/src/platform/web/lib/util/http.ts +++ b/src/platform/web/lib/util/http.ts @@ -10,7 +10,6 @@ import XHRRequest from '../transport/xhrrequest'; import XHRStates from 'common/constants/XHRStates'; import Logger from 'common/lib/util/logger'; import { StandardCallback } from 'common/types/utils'; -import { createRequest, Request } from '../transport/jsonptransport'; import fetchRequest from '../transport/fetchrequest'; import { NormalisedClientOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; @@ -115,68 +114,6 @@ const Http: typeof IHttp = class { ); }; } - } else if (Platform.Config.jsonpSupported) { - this.Request = function ( - method: HttpMethods, - rest: Rest | null, - uri: string, - headers: Record | null, - params: RequestParams, - body: unknown, - callback: RequestCallback - ) { - const req = createRequest( - uri, - headers, - params, - body, - XHRStates.REQ_SEND, - rest && rest.options.timeouts, - method - ); - req.once('complete', callback); - Platform.Config.nextTick(function () { - req.exec(); - }); - return req; - }; - - if (this.options.disableConnectivityCheck) { - this.checkConnectivity = function (callback: (err: null, connectivity: true) => void) { - callback(null, true); - }; - } else { - this.checkConnectivity = function (callback: (err: ErrorInfo | null, connectivity?: boolean) => void) { - const upUrl = Defaults.jsonpInternetUpUrl; - - if (this.checksInProgress) { - this.checksInProgress.push(callback); - return; - } - this.checksInProgress = [callback]; - Logger.logAction(Logger.LOG_MICRO, '(JSONP)Http.checkConnectivity()', 'Sending; ' + upUrl); - - const req = new Request( - 'isTheInternetUp', - upUrl as string, - null, - null, - null, - XHRStates.REQ_SEND, - Defaults.TIMEOUTS - ); - req.once('complete', (err: Error, response: string) => { - const result = !err && response; - Logger.logAction(Logger.LOG_MICRO, '(JSONP)Http.checkConnectivity()', 'Result: ' + result); - for (let i = 0; i < (this.checksInProgress as Array>).length; i++) - (this.checksInProgress as Array>)[i](null, result); - this.checksInProgress = null; - }); - Platform.Config.nextTick(function () { - req.exec(); - }); - }; - } } else if (Platform.Config.fetchSupported) { this.supportsAuthHeaders = true; this.Request = fetchRequest; diff --git a/src/platform/web/platform.ts b/src/platform/web/platform.ts index fb87694716..307bf3ae44 100644 --- a/src/platform/web/platform.ts +++ b/src/platform/web/platform.ts @@ -34,7 +34,6 @@ const Platform: IPlatform = { WebSocket: globalObject.WebSocket, fetchSupported: !!globalObject.fetch, xhrSupported: globalObject.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest(), - jsonpSupported: typeof document !== 'undefined', allowComet: allowComet(), streamingSupported: true, useProtocolHeartbeats: true, diff --git a/test/browser/http.test.js b/test/browser/http.test.js index c9822b3918..dfb93c9032 100644 --- a/test/browser/http.test.js +++ b/test/browser/http.test.js @@ -6,12 +6,10 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { describe('rest/http/fetch', function () { this.timeout(60 * 1000); - let initialXhrSupported, initialJsonpSupported; + let initialXhrSupported; before(function (done) { initialXhrSupported = Ably.Rest.Platform.Config.xhrSupported; - initialJsonpSupported = Ably.Rest.Platform.Config.jsonpSupported; Ably.Rest.Platform.Config.xhrSupported = false; - Ably.Rest.Platform.Config.jsonpSupported = false; helper.setupApp(function () { rest = helper.AblyRest(); done(); @@ -20,11 +18,10 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { after((done) => { Ably.Rest.Platform.Config.xhrSupported = initialXhrSupported; - Ably.Rest.Platform.Config.jsonpSupported = initialJsonpSupported; done(); }); - it('Should use fetch when XHR and JSONP are not supported', function (done) { + it('Should use fetch when XHR is not supported', function (done) { let oldFetch = window.fetch; window.fetch = () => { done(); diff --git a/test/browser/simple.test.js b/test/browser/simple.test.js index bf71b2cc63..2709ddb432 100644 --- a/test/browser/simple.test.js +++ b/test/browser/simple.test.js @@ -200,27 +200,6 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); } - var jsonpTransport = 'jsonp'; - if (isTransportAvailable(jsonpTransport)) { - it('jsonpbase0', function (done) { - connectionWithTransport(done, jsonpTransport); - }); - - /* - * Publish and subscribe, json transport - */ - it('jsonppublish0', function (done) { - publishWithTransport(done, jsonpTransport); - }); - - /* - * Check heartbeat - */ - it('jsonpheartbeat0', function (done) { - heartbeatWithTransport(done, jsonpTransport); - }); - } - it('auto_transport_base0', function (done) { connectionWithTransport(done); }); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index e226dc5f07..14442575cf 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -12,14 +12,7 @@ define([ var utils = clientModule.Ably.Realtime.Utils; var platform = clientModule.Ably.Realtime.Platform; clientModule.Ably.Realtime.ConnectionManager.initTransports(); - var supportedTransports = utils.keysArray(clientModule.Ably.Realtime.ConnectionManager.supportedTransports), - /* Don't include jsonp in availableTransports if xhr works. Why? Because - * you can't abort requests. So recv's stick around for 90s till realtime - * ends them. So in a test, the browsers max-connections-per-host limit - * fills up quickly, which messes up other comet transports too */ - availableTransports = utils.arrIn(supportedTransports, 'xhr_polling') - ? utils.arrWithoutValue(supportedTransports, 'jsonp') - : supportedTransports, + var availableTransports = utils.keysArray(clientModule.Ably.Realtime.ConnectionManager.supportedTransports), bestTransport = availableTransports[0], /* IANA reserved; requests to it will hang forever */ unroutableHost = '10.255.255.1', diff --git a/test/common/modules/testapp_manager.js b/test/common/modules/testapp_manager.js index b1da1db152..a6cc42253d 100644 --- a/test/common/modules/testapp_manager.js +++ b/test/common/modules/testapp_manager.js @@ -58,10 +58,6 @@ define(['globals', 'base64', 'utf8', 'ably'], function (ablyGlobals, Base64, UTF } } - function schemeMatchesCurrent(scheme) { - return scheme === window.location.protocol.slice(0, -1); - } - function httpReqFunction() { if (isNativescript) { return function (options, callback) { @@ -90,32 +86,6 @@ define(['globals', 'base64', 'utf8', 'ably'], function (ablyGlobals, Base64, UTF uri = options.scheme + '://' + options.host + ':' + options.port + options.path; - if (xhr.isXDR && !schemeMatchesCurrent(options.scheme)) { - /* Can't use XDR for cross-scheme. For some requests could just force - * the same scheme and be done with it, but not for authenticated - * requests to ably, can't use basic auth for non-tls endpoints. - * Luckily ably can handle jsonp, so just use the ably Http method, - * which will use the jsonp transport. Can't just do this all the time - * as the local express webserver serves files statically, so can't do - * jsonp. */ - if (options.method === 'DELETE') { - /* Ignore DELETEs -- can't be done with jsonp at the moment, and - * simulation apps self-delete after a while */ - callback(); - } else { - new ably.Rest.Platform.Http().doUri( - options.method, - null, - uri, - options.headers, - options.body, - options.paramsIfNoHeaders || {}, - callback - ); - } - return; - } - xhr.open(options.method, uri); if (options.headers && !xhr.isXDR) { for (var h in options.headers) if (h !== 'Content-Length') xhr.setRequestHeader(h, options.headers[h]); diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 0e76918547..7ec9e70a43 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -459,28 +459,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var realtime = helper.AblyRealtime(realtimeOptions); realtime.connection.on(function (stateChange) { if (stateChange.previous !== 'initialized') { - if (helper.bestTransport === 'jsonp') { - try { - // auth endpoints don't envelope, so we assume the 'least harmful' option, which is a disconnection with concomitant retry - expect(stateChange.current).to.equal('disconnected', 'Check connection goes to the expected state'); - // jsonp doesn't let you examine the statuscode - expect(stateChange.reason.statusCode).to.equal(401, 'Check correct cause error code'); - } catch (err) { - done(err); - } - } else { - try { - expect(stateChange.current).to.equal( - expectFailure ? 'failed' : 'disconnected', - 'Check connection goes to the expected state' - ); - expect(stateChange.reason.statusCode).to.equal( - expectFailure ? 403 : 401, - 'Check correct cause error code' - ); - } catch (err) { - done(err); - } + try { + expect(stateChange.current).to.equal( + expectFailure ? 'failed' : 'disconnected', + 'Check connection goes to the expected state' + ); + expect(stateChange.reason.statusCode).to.equal( + expectFailure ? 403 : 401, + 'Check correct cause error code' + ); + } catch (err) { + done(err); } try { expect(stateChange.reason.code).to.equal(80019, 'Check correct error code'); @@ -615,46 +604,43 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ) ); - /* auth endpoints don't envelope, so this won't work with jsonp */ - if (helper.bestTransport !== 'jsonp') { - it('authUrl_403_previously_active', function (done) { - var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken(null, null, function (err, tokenDetails) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } + it('authUrl_403_previously_active', function (done) { + var realtime, + rest = helper.AblyRest(); + rest.auth.requestToken(null, null, function (err, tokenDetails) { + if (err) { + closeAndFinish(done, realtime, err); + return; + } - var authPath = echoServer + '/?type=json&body=' + encodeURIComponent(JSON.stringify(tokenDetails)); + var authPath = echoServer + '/?type=json&body=' + encodeURIComponent(JSON.stringify(tokenDetails)); - realtime = helper.AblyRealtime({ authUrl: authPath }); + realtime = helper.AblyRealtime({ authUrl: authPath }); - realtime.connection.on('connected', function () { - /* replace the authUrl and reauth */ - realtime.auth.authorize( - null, - { authUrl: echoServer + '/respondwith?status=403' }, - function (err, tokenDetails) { - try { - expect(err && err.statusCode).to.equal(403, 'Check err statusCode'); - expect(err && err.code).to.equal(40300, 'Check err code'); - expect(realtime.connection.state).to.equal('failed', 'Check connection goes to the failed state'); - expect(realtime.connection.errorReason && realtime.connection.errorReason.statusCode).to.equal( - 403, - 'Check correct cause error code' - ); - expect(realtime.connection.errorReason.code).to.equal(80019, 'Check correct connection error code'); - closeAndFinish(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } + realtime.connection.on('connected', function () { + /* replace the authUrl and reauth */ + realtime.auth.authorize( + null, + { authUrl: echoServer + '/respondwith?status=403' }, + function (err, tokenDetails) { + try { + expect(err && err.statusCode).to.equal(403, 'Check err statusCode'); + expect(err && err.code).to.equal(40300, 'Check err code'); + expect(realtime.connection.state).to.equal('failed', 'Check connection goes to the failed state'); + expect(realtime.connection.errorReason && realtime.connection.errorReason.statusCode).to.equal( + 403, + 'Check correct cause error code' + ); + expect(realtime.connection.errorReason.code).to.equal(80019, 'Check correct connection error code'); + closeAndFinish(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); } - ); - }); + } + ); }); }); - } + }); /* * Check state change reason is propogated during a disconnect diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index 5469e2fe42..21cb13faee 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -537,91 +537,88 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - /* JSONP can't cope with entering 110 people in one go. */ - if (helper.bestTransport !== 'jsonp') { - /* - * Do a 110-member sync, so split into two sync messages. Inject a normal - * presence enter between the syncs. Check everything was entered correctly - */ - it('presence_sync_interruptus', function (done) { - var channelName = 'presence_sync_interruptus'; - var interrupterClientId = 'dark_horse'; - var enterer = helper.AblyRealtime(); - var syncer = helper.AblyRealtime(); - var entererChannel = enterer.channels.get(channelName); - var syncerChannel = syncer.channels.get(channelName); - - function waitForBothConnect(cb) { - async.parallel( - [ - function (connectCb) { - enterer.connection.on('connected', connectCb); - }, - function (connectCb) { - syncer.connection.on('connected', connectCb); - }, - ], - function () { - cb(); - } - ); - } - - async.series( + /* + * Do a 110-member sync, so split into two sync messages. Inject a normal + * presence enter between the syncs. Check everything was entered correctly + */ + it('presence_sync_interruptus', function (done) { + var channelName = 'presence_sync_interruptus'; + var interrupterClientId = 'dark_horse'; + var enterer = helper.AblyRealtime(); + var syncer = helper.AblyRealtime(); + var entererChannel = enterer.channels.get(channelName); + var syncerChannel = syncer.channels.get(channelName); + + function waitForBothConnect(cb) { + async.parallel( [ - waitForBothConnect, - function (cb) { - entererChannel.attach(cb); - }, - function (cb) { - async.times( - 110, - function (i, presCb) { - entererChannel.presence.enterClient(i.toString(), null, presCb); - }, - cb - ); + function (connectCb) { + enterer.connection.on('connected', connectCb); }, - function (cb) { - var originalOnMessage = syncerChannel.onMessage; - syncerChannel.onMessage = function (message) { - originalOnMessage.apply(this, arguments); - /* Inject an additional presence message after the first sync */ - if (message.action === 16) { - syncerChannel.onMessage = originalOnMessage; - syncerChannel.onMessage({ - action: 14, - id: 'messageid:0', - connectionId: 'connid', - timestamp: 2000000000000, - presence: [ - { - clientId: interrupterClientId, - action: 'enter', - }, - ], - }); - } - }; - syncerChannel.attach(cb); - }, - function (cb) { - syncerChannel.presence.get(function (err, presenceSet) { - try { - expect(presenceSet && presenceSet.length).to.equal(111, 'Check everyone’s in presence set'); - } catch (err) { - cb(err); - return; - } - cb(err); - }); + function (connectCb) { + syncer.connection.on('connected', connectCb); }, ], - function (err) { - closeAndFinish(done, [enterer, syncer], err); + function () { + cb(); } ); - }); - } + } + + async.series( + [ + waitForBothConnect, + function (cb) { + entererChannel.attach(cb); + }, + function (cb) { + async.times( + 110, + function (i, presCb) { + entererChannel.presence.enterClient(i.toString(), null, presCb); + }, + cb + ); + }, + function (cb) { + var originalOnMessage = syncerChannel.onMessage; + syncerChannel.onMessage = function (message) { + originalOnMessage.apply(this, arguments); + /* Inject an additional presence message after the first sync */ + if (message.action === 16) { + syncerChannel.onMessage = originalOnMessage; + syncerChannel.onMessage({ + action: 14, + id: 'messageid:0', + connectionId: 'connid', + timestamp: 2000000000000, + presence: [ + { + clientId: interrupterClientId, + action: 'enter', + }, + ], + }); + } + }; + syncerChannel.attach(cb); + }, + function (cb) { + syncerChannel.presence.get(function (err, presenceSet) { + try { + expect(presenceSet && presenceSet.length).to.equal(111, 'Check everyone’s in presence set'); + } catch (err) { + cb(err); + return; + } + cb(err); + }); + }, + ], + function (err) { + closeAndFinish(done, [enterer, syncer], err); + } + ); + }); }); }); From eaee6f6ff20f5d38ae2305edf2a7061b799249e3 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 26 Apr 2023 11:39:45 -0300 Subject: [PATCH 009/468] Conform to spec for logging configuration Replace the ClientOptions.log property with separate logLevel and logHandler properties, to conform with TO3b and TO3c. Resolves #642. --- ably.d.ts | 28 +++++++++------------------- src/common/lib/client/rest.ts | 4 +--- test/common/globals/environment.js | 16 +++++++--------- test/realtime/auth.test.js | 2 +- test/realtime/connection.test.js | 2 +- test/realtime/presence.test.js | 6 +++--- test/rest/fallbacks.test.js | 2 +- 7 files changed, 23 insertions(+), 37 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 1627a9b540..e1cbce86de 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -392,9 +392,16 @@ declare namespace Types { environment?: string; /** - * Parameters to control the log output of the library, such as the log handler and log level. + * Controls the verbosity of the logs output from the library. Valid values are: 0 (no logs), 1 (errors only), 2 (errors plus connection and channel state changes), 3 (high-level debug output), and 4 (full debug output). + */ + logLevel?: number; + + /** + * Controls the log output of the library. This is a function to handle each line of log output. If you do not set this value, then `console.log` will be used. + * + * @param msg - The log message emitted by the library. */ - log?: LogInfo; + logHandler?: (msg: string) => void; /** * Enables a non-default Ably port to be specified. For development environments only. The default value is 80. @@ -1102,23 +1109,6 @@ declare namespace Types { untilAttach?: boolean; } - /** - * Settings which control the log output of the library. - */ - interface LogInfo { - /** - * Controls the verbosity of the logs output from the library. Valid values are: 0 (no logs), 1 (errors only), 2 (errors plus connection and channel state changes), 3 (high-level debug output), and 4 (full debug output). - */ - level?: number; - - /** - * Controls the log output of the library. This is a function to handle each line of log output. If you do not set this value, then `console.log` will be used. - * - * @param msg - The log message emitted by the library. - */ - handler?: (msg: string) => void; - } - /** * Contains state change information emitted by {@link ChannelBase} and {@link RealtimeChannelBase} objects. */ diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index b0ec25e3e7..f93b264145 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -40,9 +40,7 @@ class Rest { } const optionsObj = Defaults.objectifyOptions(options); - if (optionsObj.log) { - Logger.setLog(optionsObj.log.level, optionsObj.log.handler); - } + Logger.setLog(optionsObj.logLevel, optionsObj.logHandler); Logger.logAction(Logger.LOG_MICRO, 'Rest()', 'initialized with clientOptions ' + Platform.Config.inspect(options)); const normalOptions = (this.options = Defaults.normaliseOptions(optionsObj)); diff --git a/test/common/globals/environment.js b/test/common/globals/environment.js index a3b94d4438..20ecff27fd 100644 --- a/test/common/globals/environment.js +++ b/test/common/globals/environment.js @@ -41,15 +41,13 @@ define(function (require) { port: port, tlsPort: tlsPort, tls: tls, - log: { - level: logLevel, - handler: function (msg) { - var time = new Date(); - console.log( - time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds() + '.' + time.getMilliseconds(), - msg - ); - }, + logLevel: logLevel, + logHandler: function (msg) { + var time = new Date(); + console.log( + time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds() + '.' + time.getMilliseconds(), + msg + ); }, }); }); diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 0e76918547..c8b36c6ea6 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -1337,7 +1337,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Check that only the last authorize matters */ it('multiple_concurrent_authorize', function (done) { var realtime = helper.AblyRealtime({ - log: { level: 4 }, + logLevel: 4, useTokenAuth: true, defaultTokenParams: { capability: { wrong: ['*'] } }, }); diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index ca16073eeb..e1c0754e33 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -65,7 +65,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionAttributes', function (done) { var realtime; try { - realtime = helper.AblyRealtime({ log: { level: 4 } }); + realtime = helper.AblyRealtime({ logLevel: 4 }); realtime.connection.on('connected', function () { try { const recoveryContext = JSON.parse(realtime.connection.recoveryKey); diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 359ca588a4..05b9412102 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1910,9 +1910,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and only members that changed between ATTACHED states should result in * presence events */ it('suspended_preserves_presence', function (done) { - var mainRealtime = helper.AblyRealtime({ clientId: 'main', log: { level: 4 } }), - continuousRealtime = helper.AblyRealtime({ clientId: 'continuous', log: { level: 4 } }), - leavesRealtime = helper.AblyRealtime({ clientId: 'leaves', log: { level: 4 } }), + var mainRealtime = helper.AblyRealtime({ clientId: 'main', logLevel: 4 }), + continuousRealtime = helper.AblyRealtime({ clientId: 'continuous', logLevel: 4 }), + leavesRealtime = helper.AblyRealtime({ clientId: 'leaves', logLevel: 4 }), channelName = 'suspended_preserves_presence', mainChannel = mainRealtime.channels.get(channelName); diff --git a/test/rest/fallbacks.test.js b/test/rest/fallbacks.test.js index 90800227b3..65e024e759 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -25,7 +25,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { restHost: helper.unroutableHost, fallbackHosts: [goodHost], httpRequestTimeout: 3000, - log: { level: 4 }, + logLevel: 4, }); var validUntil; async.series( From 440d89089bbf7c7ee9fb498e62c7ba53691a3d67 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 10:31:34 -0300 Subject: [PATCH 010/468] Remove deprecated fromEncoded* type declarations Part of #1197. --- ably.d.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 2b02bab223..d51d650bd9 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1503,16 +1503,6 @@ declare namespace Types { }, callback: recoverConnectionCompletionCallback ) => void; - /** - * @ignore - * @deprecated No longer used by this library - kept here since it used to be part of our public API. Will be removed in next major version release. - */ - type fromEncoded = (JsonObject: any, channelOptions?: ChannelOptions) => T; - /** - * @ignore - * @deprecated No longer used by this library - kept here since it used to be part of our public API. Will be removed in next major version release. - */ - type fromEncodedArray = (JsonArray: any[], channelOptions?: ChannelOptions) => T[]; // Internal Classes From 359f9a79ac1b31bc544b2b04bed206c439dea541 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 13:25:14 -0300 Subject: [PATCH 011/468] Remove ably-commonjs*.js files These files are identical to ably.js and ably.noencryption.js. We only maintained them in v1 for backwards compatibility. Resolves #1200. --- README.md | 2 +- package.json | 2 +- test/support/browser_file_list.js | 2 -- webpack.config.js | 19 ------------------- 4 files changed, 2 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d5c4d3c7ed..da62f12780 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ For usage, jump to [Using the Realtime API](#using-the-realtime-api) or [Using t WebPack will search your `node_modules` folder by default, so if you include `ably` in your `package.json` file, when running Webpack the following will allow you to `require('ably')` (or if using typescript or ES6 modules, `import * as Ably from 'ably';`). If your webpack target is set to 'browser', this will automatically use the browser commonjs distribution. -If that doesn't work for some reason (e.g. you are using a custom webpack target), you can reference the `ably-commonjs.js` static file directly: `require('ably/build/ably-commonjs.js');` (or `import * as Ably from 'ably/build/ably-commonjs.js'` for typescript / ES6 modules). +If that doesn't work for some reason (e.g. you are using a custom webpack target), you can reference the `ably.js` static file directly: `require('ably/build/ably.js');` (or `import * as Ably from 'ably/build/ably.js'` for typescript / ES6 modules). ### TypeScript diff --git a/package.json b/package.json index 57fb284881..0a8dfbf4c2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "./build/ably-node.js": "./build/ably-reactnative.js" }, "browser": { - "./build/ably-node.js": "./build/ably-commonjs.js" + "./build/ably-node.js": "./build/ably.js" }, "files": [ "build/**", diff --git a/test/support/browser_file_list.js b/test/support/browser_file_list.js index 26ec6f053e..0dbf2cd18b 100644 --- a/test/support/browser_file_list.js +++ b/test/support/browser_file_list.js @@ -1,7 +1,5 @@ window.__testFiles__ = { base: '../' }; window.__testFiles__.files = { - 'build/ably-commonjs.js': true, - 'build/ably-commonjs.noencryption.js': true, 'build/ably-nativescript.js': true, 'build/ably-node.js': true, 'build/ably-reactnative.js': true, diff --git a/webpack.config.js b/webpack.config.js index 3b53bda271..085d573cbf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -207,23 +207,6 @@ const noEncryptionMinConfig = { devtool: 'source-map', }; -// We are using UMD in ably.js now so there is no need to build separately for CommonJS. These files are still being distributed to avoid breaking changes but should no longer be used. -const commonJsConfig = { - ...browserConfig, - output: { - ...baseConfig.output, - filename: 'ably-commonjs.js', - }, -}; - -const commonJsNoEncryptionConfig = { - ...noEncryptionConfig, - output: { - ...baseConfig.output, - filename: 'ably-commonjs.noencryption.js', - }, -}; - module.exports = { node: nodeConfig, browser: browserConfig, @@ -233,6 +216,4 @@ module.exports = { reactNative: reactNativeConfig, noEncryption: noEncryptionConfig, noEncryptionMin: noEncryptionMinConfig, - commonJs: commonJsConfig, - commonJsNoEncryption: commonJsNoEncryptionConfig, }; From 5330f75feb3c01aff64faa7c66dbfca10c03c924 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 26 Apr 2023 15:41:32 -0300 Subject: [PATCH 012/468] Remove DeprecatedClientOptions.host Part of #1197. --- src/common/lib/util/defaults.ts | 4 ---- src/common/types/ClientOptions.ts | 1 - test/rest/defaults.test.js | 6 ++---- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 01310b6f64..5caf45f2ef 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -183,10 +183,6 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions export function normaliseOptions(options: DeprecatedClientOptions): NormalisedClientOptions { /* Deprecated options */ - if (options.host) { - Logger.deprecated('host', 'restHost'); - options.restHost = options.host; - } if (options.wsHost) { Logger.deprecated('wsHost', 'realtimeHost'); options.realtimeHost = options.wsHost; diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index 9e4235e983..066bf03863 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -15,7 +15,6 @@ export default interface ClientOptions extends API.Types.ClientOptions { export type DeprecatedClientOptions = Modify< ClientOptions, { - host?: string; wsHost?: string; queueEvents?: boolean; promises?: boolean; diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index 1abbcbba67..2e536f91ae 100644 --- a/test/rest/defaults.test.js +++ b/test/rest/defaults.test.js @@ -163,10 +163,9 @@ define(['ably', 'chai'], function (Ably, chai) { }); /* will emit a warning */ - it('Init with deprecated host and wsHost options', function () { - var normalisedOptions = Defaults.normaliseOptions({ host: 'test.org', wsHost: 'ws.test.org' }); + it('Init with deprecated wsHost option', function () { + var normalisedOptions = Defaults.normaliseOptions({ wsHost: 'ws.test.org' }); - expect(normalisedOptions.restHost).to.equal('test.org'); expect(normalisedOptions.realtimeHost).to.equal('ws.test.org'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); @@ -175,7 +174,6 @@ define(['ably', 'chai'], function (Ably, chai) { expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]); expect(Defaults.getHost(normalisedOptions, 'test.org', false)).to.deep.equal('test.org'); - expect(Defaults.getHost(normalisedOptions, 'test.org', true)).to.deep.equal('ws.test.org'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); }); From 616d2f24ec4281f87379d16702cff5119082850b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 26 Apr 2023 15:41:32 -0300 Subject: [PATCH 013/468] Remove DeprecatedClientOptions.wsHost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After removing the testing of wsHost’s effect, everything in the test that this commit removes is tested in the subsequent test. Part of #1197. --- src/common/lib/util/defaults.ts | 4 ---- src/common/types/ClientOptions.ts | 1 - test/rest/defaults.test.js | 16 ---------------- 3 files changed, 21 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 5caf45f2ef..348b54d252 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -183,10 +183,6 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions export function normaliseOptions(options: DeprecatedClientOptions): NormalisedClientOptions { /* Deprecated options */ - if (options.wsHost) { - Logger.deprecated('wsHost', 'realtimeHost'); - options.realtimeHost = options.wsHost; - } if (options.queueEvents) { Logger.deprecated('queueEvents', 'queueMessages'); options.queueMessages = options.queueEvents; diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index 066bf03863..332c0844a5 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -15,7 +15,6 @@ export default interface ClientOptions extends API.Types.ClientOptions { export type DeprecatedClientOptions = Modify< ClientOptions, { - wsHost?: string; queueEvents?: boolean; promises?: boolean; headers?: Record; diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index 2e536f91ae..d0e6dd5902 100644 --- a/test/rest/defaults.test.js +++ b/test/rest/defaults.test.js @@ -162,22 +162,6 @@ define(['ably', 'chai'], function (Ably, chai) { }, "Check fallbackHostsUseDefault and tlsPort can't both be set").to.throw; }); - /* will emit a warning */ - it('Init with deprecated wsHost option', function () { - var normalisedOptions = Defaults.normaliseOptions({ wsHost: 'ws.test.org' }); - - expect(normalisedOptions.realtimeHost).to.equal('ws.test.org'); - expect(normalisedOptions.port).to.equal(80); - expect(normalisedOptions.tlsPort).to.equal(443); - expect(normalisedOptions.fallbackHosts).to.equal(undefined); - expect(normalisedOptions.tls).to.equal(true); - - expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]); - expect(Defaults.getHost(normalisedOptions, 'test.org', false)).to.deep.equal('test.org'); - - expect(Defaults.getPort(normalisedOptions)).to.equal(443); - }); - it('Init with no endpoint-related options and given default environment', function () { Defaults.ENVIRONMENT = 'sandbox'; var normalisedOptions = Defaults.normaliseOptions({}); From 757ecb5b81476ed63c0e0e5b232dedb82312f753 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 26 Apr 2023 15:55:47 -0300 Subject: [PATCH 014/468] Remove DeprecatedClientOptions.queueEvents Part of #1197. --- src/common/lib/util/defaults.ts | 6 ------ src/common/types/ClientOptions.ts | 1 - 2 files changed, 7 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 348b54d252..bcece6532d 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -182,12 +182,6 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions } export function normaliseOptions(options: DeprecatedClientOptions): NormalisedClientOptions { - /* Deprecated options */ - if (options.queueEvents) { - Logger.deprecated('queueEvents', 'queueMessages'); - options.queueMessages = options.queueEvents; - } - if (options.fallbackHostsUseDefault) { /* fallbackHostsUseDefault and fallbackHosts are mutually exclusive as per TO3k7 */ if (options.fallbackHosts) { diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index 332c0844a5..1136be6e56 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -15,7 +15,6 @@ export default interface ClientOptions extends API.Types.ClientOptions { export type DeprecatedClientOptions = Modify< ClientOptions, { - queueEvents?: boolean; promises?: boolean; headers?: Record; maxMessageSize?: number; From d7aaf347d26991b6139bce8260e32c3735c9165c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 26 Apr 2023 16:58:13 -0300 Subject: [PATCH 015/468] Remove DeprecatedClientOptions.headers Part of #1197. --- src/common/lib/client/channel.ts | 4 ++-- src/common/lib/client/presence.ts | 4 ++-- src/common/lib/client/push.ts | 20 ++++++++++---------- src/common/lib/client/rest.ts | 6 ++---- src/common/lib/util/defaults.ts | 3 ++- src/common/types/ClientOptions.ts | 2 +- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index 6350ef011d..c6eb9f541a 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -94,7 +94,7 @@ class Channel extends EventEmitter { envelope = this.rest.http.supportsLinkHeaders ? undefined : format, headers = Utils.defaultGetHeaders(rest.options, format); - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); const options = this.channelOptions; new PaginatedResource(rest, this.basePath + '/messages', headers, envelope, function ( @@ -150,7 +150,7 @@ class Channel extends EventEmitter { idempotentRestPublishing = rest.options.idempotentRestPublishing, headers = Utils.defaultPostHeaders(rest.options, format); - if (options.headers) Utils.mixin(headers, options.headers); + Utils.mixin(headers, options.headers); if (idempotentRestPublishing && allEmptyIds(messages)) { const msgIdBase = Utils.randomString(MSG_ID_ENTROPY_BYTES); diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index d17a68bc85..bd410b8b19 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -39,7 +39,7 @@ class Presence extends EventEmitter { envelope = this.channel.rest.http.supportsLinkHeaders ? undefined : format, headers = Utils.defaultGetHeaders(rest.options, format); - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); const options = this.channel.channelOptions; new PaginatedResource(rest, this.basePath, headers, envelope, function ( @@ -81,7 +81,7 @@ class Presence extends EventEmitter { envelope = this.channel.rest.http.supportsLinkHeaders ? undefined : format, headers = Utils.defaultGetHeaders(rest.options, format); - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); const options = this.channel.channelOptions; new PaginatedResource(rest, this.basePath + '/history', headers, envelope, function ( diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 0b1a9c4fa7..fcaaa88d42 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -44,7 +44,7 @@ class Admin { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); @@ -74,7 +74,7 @@ class DeviceRegistrations { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); @@ -124,7 +124,7 @@ class DeviceRegistrations { return; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); Resource.get( rest, @@ -159,7 +159,7 @@ class DeviceRegistrations { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); new PaginatedResource(rest, '/push/deviceRegistrations', headers, envelope, function ( body: any, @@ -195,7 +195,7 @@ class DeviceRegistrations { return; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); @@ -221,7 +221,7 @@ class DeviceRegistrations { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); @@ -250,7 +250,7 @@ class ChannelSubscriptions { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); @@ -284,7 +284,7 @@ class ChannelSubscriptions { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); new PaginatedResource(rest, '/push/channelSubscriptions', headers, envelope, function ( body: any, @@ -307,7 +307,7 @@ class ChannelSubscriptions { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); @@ -330,7 +330,7 @@ class ChannelSubscriptions { callback = noop; } - if (rest.options.headers) Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, rest.options.headers); if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index f93b264145..515799604b 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -102,7 +102,7 @@ class Rest { format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.http.supportsLinkHeaders ? undefined : format; - if (this.options.headers) Utils.mixin(headers, this.options.headers); + Utils.mixin(headers, this.options.headers); new PaginatedResource(this, '/stats', headers, envelope, function ( body: unknown, @@ -195,9 +195,7 @@ class Rest { if (typeof body !== 'string') { body = encoder(body); } - if (this.options.headers) { - Utils.mixin(headers, this.options.headers); - } + Utils.mixin(headers, this.options.headers); if (customHeaders) { Utils.mixin(headers, customHeaders); } diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index bcece6532d..c9902579ab 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -267,8 +267,8 @@ export function normaliseOptions(options: DeprecatedClientOptions): NormalisedCl options.useBinaryProtocol = Platform.Config.preferBinary; } + const headers: Record = {}; if (options.clientId) { - const headers = (options.headers = options.headers || {}); headers['X-Ably-ClientId'] = Platform.BufferUtils.base64Encode(Platform.BufferUtils.utf8Encode(options.clientId)); } @@ -308,6 +308,7 @@ export function normaliseOptions(options: DeprecatedClientOptions): NormalisedCl timeouts, connectivityCheckParams, connectivityCheckUrl, + headers, }; } diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index 1136be6e56..cc06cd86d7 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -16,7 +16,6 @@ export type DeprecatedClientOptions = Modify< ClientOptions, { promises?: boolean; - headers?: Record; maxMessageSize?: number; } >; @@ -31,5 +30,6 @@ export type NormalisedClientOptions = Modify< timeouts: Record; maxMessageSize: number; connectivityCheckParams: Record | null; + headers: Record; } >; From fe3c9ce49df62ffa357dec0843a3f3325ca7c65e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 26 Apr 2023 17:28:03 -0300 Subject: [PATCH 016/468] Move remaining DeprecatedClientOptions into new type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are properties which have been removed from the public API of the SDK, but which internal and test code still use for modifying the behaviour of the SDK via its public constructors. In the case where users were specifying these properties when using v1 of the library, we want these properties to cease to have any effect upon upgrading to v2. Hence we move them into a new `internal:` namespace. I hope that it’s safe to assume that users are not going to pass a set of options that for some reason has a property named `internal`. Part of #1197. --- promises.js | 6 +++++- src/common/lib/client/realtime.ts | 6 +++--- src/common/lib/client/rest.ts | 6 +++--- src/common/lib/util/defaults.ts | 13 +++++++------ src/common/types/ClientOptions.ts | 14 ++++++++++---- test/realtime/connection.test.js | 2 +- test/realtime/event_emitter.test.js | 4 ++-- test/realtime/message.test.js | 2 +- test/realtime/presence.test.js | 2 +- test/rest/auth.test.js | 2 +- test/rest/history.test.js | 2 +- test/rest/message.test.js | 4 ++-- test/rest/presence.test.js | 2 +- test/rest/push.test.js | 6 +++--- test/rest/request.test.js | 2 +- test/rest/stats.test.js | 2 +- test/rest/status.test.js | 2 +- test/rest/time.test.js | 2 +- 18 files changed, 45 insertions(+), 34 deletions(-) diff --git a/promises.js b/promises.js index de63d9d0c3..c40986d4a4 100644 --- a/promises.js +++ b/promises.js @@ -3,7 +3,11 @@ function promisifyOptions(options) { if (typeof options == 'string') { options = options.indexOf(':') == -1 ? { token: options } : { key: options }; } - options.promises = true; + if (options.internal === undefined) { + options.internal = { promises: true } + } else { + options.internal.promises = true + } return options; } diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/realtime.ts index 9387ccfcf5..55c83184ec 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/realtime.ts @@ -8,7 +8,7 @@ import Defaults from '../util/defaults'; import ErrorInfo from '../types/errorinfo'; import ProtocolMessage from '../types/protocolmessage'; import { ChannelOptions } from '../../types/channel'; -import ClientOptions, { DeprecatedClientOptions } from '../../types/ClientOptions'; +import ClientOptions, { InternalClientOptions } from '../../types/ClientOptions'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; import Platform from 'common/platform'; @@ -36,9 +36,9 @@ class Realtime extends Rest { this.connection.close(); } - static Promise = function (options: DeprecatedClientOptions): Realtime { + static Promise = function (options: InternalClientOptions): Realtime { options = Defaults.objectifyOptions(options); - options.promises = true; + options.internal = { ...options.internal, promises: true }; return new Realtime(options); }; diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 515799604b..fa9735e5ab 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -11,7 +11,7 @@ import HttpMethods from '../../constants/HttpMethods'; import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; import { ErrnoException, IHttp, RequestParams } from '../../types/http'; -import ClientOptions, { DeprecatedClientOptions, NormalisedClientOptions } from '../../types/ClientOptions'; +import ClientOptions, { InternalClientOptions, NormalisedClientOptions } from '../../types/ClientOptions'; import Platform from '../../platform'; import Message from '../types/message'; @@ -228,9 +228,9 @@ class Rest { Logger.setLog(logOptions.level, logOptions.handler); } - static Promise = function (options: DeprecatedClientOptions): Rest { + static Promise = function (options: InternalClientOptions): Rest { options = Defaults.objectifyOptions(options); - options.promises = true; + options.internal = { ...options.internal, promises: true }; return new Rest(options); }; diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index c9902579ab..56d410cc1d 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -3,7 +3,7 @@ import * as Utils from './utils'; import Logger from './logger'; import ErrorInfo from 'common/lib/types/errorinfo'; import { version } from '../../../../package.json'; -import ClientOptions, { DeprecatedClientOptions, NormalisedClientOptions } from 'common/types/ClientOptions'; +import ClientOptions, { InternalClientOptions, NormalisedClientOptions } from 'common/types/ClientOptions'; import IDefaults from '../../types/IDefaults'; let agent = 'ably-js/' + version; @@ -41,7 +41,7 @@ type CompleteDefaults = IDefaults & { checkHost(host: string): void; getRealtimeHost(options: ClientOptions, production: boolean, environment: string): string; objectifyOptions(options: ClientOptions | string): ClientOptions; - normaliseOptions(options: DeprecatedClientOptions): NormalisedClientOptions; + normaliseOptions(options: InternalClientOptions): NormalisedClientOptions; }; const Defaults = { @@ -181,7 +181,7 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions return options; } -export function normaliseOptions(options: DeprecatedClientOptions): NormalisedClientOptions { +export function normaliseOptions(options: InternalClientOptions): NormalisedClientOptions { if (options.fallbackHostsUseDefault) { /* fallbackHostsUseDefault and fallbackHosts are mutually exclusive as per TO3k7 */ if (options.fallbackHosts) { @@ -276,13 +276,13 @@ export function normaliseOptions(options: DeprecatedClientOptions): NormalisedCl options.idempotentRestPublishing = true; } - if (options.promises && !Platform.Config.Promise) { + if (options.internal?.promises && !Platform.Config.Promise) { Logger.logAction( Logger.LOG_ERROR, 'Defaults.normaliseOptions', '{promises: true} was specified, but no Promise constructor found; disabling promises' ); - options.promises = false; + options.internal.promises = false; } let connectivityCheckParams = null; @@ -304,11 +304,12 @@ export function normaliseOptions(options: DeprecatedClientOptions): NormalisedCl : Platform.Config.preferBinary, realtimeHost, restHost, - maxMessageSize: options.maxMessageSize || Defaults.maxMessageSize, + maxMessageSize: options.internal?.maxMessageSize || Defaults.maxMessageSize, timeouts, connectivityCheckParams, connectivityCheckUrl, headers, + promises: options.internal?.promises ?? false, }; } diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index cc06cd86d7..ab279cfd65 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -12,16 +12,21 @@ export default interface ClientOptions extends API.Types.ClientOptions { agents?: string[]; } -export type DeprecatedClientOptions = Modify< +/** + * Properties which internal and test code wish to be able to pass to the public constructor of `Rest` and `Realtime` in order to modify the behaviour of those classes. + */ +export type InternalClientOptions = Modify< ClientOptions, { - promises?: boolean; - maxMessageSize?: number; + internal?: { + promises?: boolean; + maxMessageSize?: number; + }; } >; export type NormalisedClientOptions = Modify< - DeprecatedClientOptions, + InternalClientOptions, { realtimeHost: string; restHost: string; @@ -31,5 +36,6 @@ export type NormalisedClientOptions = Modify< maxMessageSize: number; connectivityCheckParams: Record | null; headers: Record; + promises: boolean; } >; diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index e1c0754e33..260e6fdae7 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -286,7 +286,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { describe('connection_promise', function () { it('ping', function (done) { - var client = helper.AblyRealtime({ promises: true }); + var client = helper.AblyRealtime({ internal: { promises: true } }); client.connection .once('connected') diff --git a/test/realtime/event_emitter.test.js b/test/realtime/event_emitter.test.js index eeb2d91851..4575594f21 100644 --- a/test/realtime/event_emitter.test.js +++ b/test/realtime/event_emitter.test.js @@ -442,7 +442,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { if (typeof Promise !== 'undefined') { describe('event_emitter_promise', function () { it('whenState', function (done) { - var realtime = helper.AblyRealtime({ promises: true }); + var realtime = helper.AblyRealtime({ internal: { promises: true } }); var eventEmitter = realtime.connection; eventEmitter @@ -456,7 +456,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('once', function (done) { - var realtime = helper.AblyRealtime({ promises: true }); + var realtime = helper.AblyRealtime({ internal: { promises: true } }); var eventEmitter = realtime.connection; eventEmitter diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index 3fe47724dc..ce9d955f47 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -1141,7 +1141,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { it('publishpromise', function (done) { - var realtime = helper.AblyRealtime({ promises: true }); + var realtime = helper.AblyRealtime({ internal: { promises: true } }); var channel = realtime.channels.get('publishpromise'); var publishPromise = realtime.connection diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 05b9412102..10525c783c 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -2089,7 +2089,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { describe('presence_promise', function () { - var options = { clientId: testClientId, promises: true }; + var options = { clientId: testClientId, internal: { promises: true } }; it('enter_get', function (done) { var client = helper.AblyRealtime(options); diff --git a/test/rest/auth.test.js b/test/rest/auth.test.js index 72b6a1799b..691f481a94 100644 --- a/test/rest/auth.test.js +++ b/test/rest/auth.test.js @@ -694,7 +694,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as if (typeof Promise !== 'undefined') { it('Promise based auth', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); var promise1 = rest.auth.requestToken(); var promise2 = rest.auth.requestToken({ ttl: 200 }); diff --git a/test/rest/history.test.js b/test/rest/history.test.js index f686c0db71..249c2be72c 100644 --- a/test/rest/history.test.js +++ b/test/rest/history.test.js @@ -446,7 +446,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { if (typeof Promise !== 'undefined') { it('historyPromise', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); var testchannel = rest.channels.get('persisted:history_promise'); testchannel diff --git a/test/rest/message.test.js b/test/rest/message.test.js index 125a91e981..546f678c97 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -160,7 +160,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* TO3l8; CD2C; RSL1i */ it('Should error when publishing message larger than maxMessageSize', function (done) { /* No connectionDetails mechanism for REST, so just pass the override into the constructor */ - var realtime = helper.AblyRest({ maxMessageSize: 64 }), + var realtime = helper.AblyRest({ internal: { maxMessageSize: 64 } }), channel = realtime.channels.get('maxMessageSize'); channel.publish('foo', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', function (err) { @@ -292,7 +292,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== undefined) { it('Rest publish promise', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); var channel = rest.channels.get('publishpromise'); channel diff --git a/test/rest/presence.test.js b/test/rest/presence.test.js index c7c6da664d..4d36d63665 100644 --- a/test/rest/presence.test.js +++ b/test/rest/presence.test.js @@ -148,7 +148,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { it('presencePromise', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); var channel = rest.channels.get('some_channel'); channel.presence diff --git a/test/rest/push.test.js b/test/rest/push.test.js index 6b782e4dfe..ca75676c6b 100644 --- a/test/rest/push.test.js +++ b/test/rest/push.test.js @@ -139,7 +139,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { it('Publish promise', function (done) { - var realtime = helper.AblyRealtime({ promises: true }); + var realtime = helper.AblyRealtime({ internal: { promises: true } }); var channelName = 'pushenabled:publish_promise'; var channel = realtime.channels.get(channelName); channel.attach(function (err) { @@ -368,7 +368,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== undefined) { it('deviceRegistrations promise', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); /* save */ rest.push.admin.deviceRegistrations @@ -566,7 +566,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { it('channelSubscriptions promise', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); var channelId = 'pushenabled:channelsubscriptions_promise'; var subscription = { clientId: 'testClient', channel: channelId }; diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 573468e023..718c25aaf7 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -240,7 +240,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { if (typeof Promise !== 'undefined') { it('request_promise', function (done) { - var client = helper.AblyRest({ promises: true }); + var client = helper.AblyRest({ internal: { promises: true } }); client .request('get', '/time', null, null, null) diff --git a/test/rest/stats.test.js b/test/rest/stats.test.js index 840acfe70f..b9a3c04a26 100644 --- a/test/rest/stats.test.js +++ b/test/rest/stats.test.js @@ -602,7 +602,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { if (typeof Promise !== 'undefined') { it('stats_promise', function (done) { - var client = helper.AblyRest({ promises: true }); + var client = helper.AblyRest({ internal: { promises: true } }); client .stats() diff --git a/test/rest/status.test.js b/test/rest/status.test.js index 3a0a2bbd8a..91cc27b6e0 100644 --- a/test/rest/status.test.js +++ b/test/rest/status.test.js @@ -42,7 +42,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { if (typeof Promise !== 'undefined') { it('statusPromise', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); var channel = rest.channels.get('statusPromise'); channel .status() diff --git a/test/rest/time.test.js b/test/rest/time.test.js index 839584378e..f05025a1f0 100644 --- a/test/rest/time.test.js +++ b/test/rest/time.test.js @@ -38,7 +38,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { if (typeof Promise !== 'undefined') { it('timePromise', function (done) { - var rest = helper.AblyRest({ promises: true }); + var rest = helper.AblyRest({ internal: { promises: true } }); rest .time() .then(function () { From adfe5992891e7b140696317bf254edc615e05024 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 10:40:10 -0300 Subject: [PATCH 017/468] Remove deprecated ClientOptions.fallbackHostsUseDefault Part of #1197. --- ably.d.ts | 5 ---- src/common/lib/util/defaults.ts | 29 ------------------- test/rest/defaults.test.js | 49 --------------------------------- 3 files changed, 83 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index d51d650bd9..7f4b8677c6 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -438,11 +438,6 @@ declare namespace Types { */ fallbackHosts?: string[]; - /** - * @deprecated This property is deprecated and will be removed in a future version. Enables default fallback hosts to be used. - */ - fallbackHostsUseDefault?: boolean; - /** * Set of configurable options to set on the HTTP(S) agent used for REST requests. * diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 56d410cc1d..ffcec077c0 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -182,35 +182,6 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions } export function normaliseOptions(options: InternalClientOptions): NormalisedClientOptions { - if (options.fallbackHostsUseDefault) { - /* fallbackHostsUseDefault and fallbackHosts are mutually exclusive as per TO3k7 */ - if (options.fallbackHosts) { - const msg = 'fallbackHosts and fallbackHostsUseDefault cannot both be set'; - Logger.logAction(Logger.LOG_ERROR, 'Defaults.normaliseOptions', msg); - throw new ErrorInfo(msg, 40000, 400); - } - - /* default fallbacks can't be used with custom ports */ - if (options.port || options.tlsPort) { - const msg = 'fallbackHostsUseDefault cannot be set when port or tlsPort are set'; - Logger.logAction(Logger.LOG_ERROR, 'Defaults.normaliseOptions', msg); - throw new ErrorInfo(msg, 40000, 400); - } - - /* emit an appropriate deprecation warning */ - if (options.environment) { - Logger.deprecatedWithMsg( - 'fallbackHostsUseDefault', - 'There is no longer a need to set this when the environment option is also set since the library will now generate the correct fallback hosts using the environment option.' - ); - } else { - Logger.deprecated('fallbackHostsUseDefault', 'fallbackHosts: Ably.Defaults.FALLBACK_HOSTS'); - } - - /* use the default fallback hosts as requested */ - options.fallbackHosts = Defaults.FALLBACK_HOSTS; - } - /* options.recover as a boolean is deprecated, and therefore is not part of the public typing */ if ((options.recover as any) === true) { Logger.deprecated('{recover: true}', '{recover: function(lastConnectionDetails, cb) { cb(true); }}'); diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index d0e6dd5902..40be2e7213 100644 --- a/test/rest/defaults.test.js +++ b/test/rest/defaults.test.js @@ -61,27 +61,6 @@ define(['ably', 'chai'], function (Ably, chai) { expect(Defaults.getPort(normalisedOptions)).to.equal(443); }); - /* will emit a deprecation warning */ - it('Init with given environment and default fallbacks', function () { - var normalisedOptions = Defaults.normaliseOptions({ environment: 'sandbox', fallbackHostsUseDefault: true }); - - expect(normalisedOptions.restHost).to.equal('sandbox-rest.ably.io'); - expect(normalisedOptions.realtimeHost).to.equal('sandbox-realtime.ably.io'); - expect(normalisedOptions.port).to.equal(80); - expect(normalisedOptions.tlsPort).to.equal(443); - expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.FALLBACK_HOSTS.sort()); - expect(normalisedOptions.tls).to.equal(true); - - expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(4); - expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); - expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', false)).to.deep.equal('sandbox-rest.ably.io'); - expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', true)).to.deep.equal( - 'sandbox-realtime.ably.io' - ); - - expect(Defaults.getPort(normalisedOptions)).to.equal(443); - }); - it('Init with local environment and non-default ports', function () { var normalisedOptions = Defaults.normaliseOptions({ environment: 'local', port: 8080, tlsPort: 8081 }); @@ -134,34 +113,6 @@ define(['ably', 'chai'], function (Ably, chai) { expect(Defaults.getPort(normalisedOptions)).to.equal(443); }); - /* init with given restHost and realtimeHost, using the default fallback hosts */ - it('Init with given restHost and realtimeHost, using the default fallback hosts', function () { - var normalisedOptions = Defaults.normaliseOptions({ - restHost: 'test.org', - realtimeHost: 'ws.test.org', - fallbackHostsUseDefault: true, - }); - - expect(normalisedOptions.restHost).to.equal('test.org'); - expect(normalisedOptions.realtimeHost).to.equal('ws.test.org'); - expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.FALLBACK_HOSTS.sort()); - }); - - it('Throws an error when initiated with fallbackHosts and fallbackHostsUseDefault', function () { - expect(function () { - Defaults.normaliseOptions({ fallbackHosts: ['a.example.com', 'b.example.com'], fallbackHostsUseDefault: true }); - }, "Check fallbackHosts and fallbackHostsUseDefault can't both be set").to.throw(); - }); - - it('Throws an error with initiated with fallbackHostsUseDefault and port or tlsPort set', function () { - expect(function () { - Defaults.normaliseOptions({ fallbackHostsUseDefault: true, port: 8080 }); - }, "Check fallbackHostsUseDefault and port can't both be set").to.throw; - expect(function () { - Defaults.normaliseOptions({ fallbackHostsUseDefault: true, tlsPort: 8081 }); - }, "Check fallbackHostsUseDefault and tlsPort can't both be set").to.throw; - }); - it('Init with no endpoint-related options and given default environment', function () { Defaults.ENVIRONMENT = 'sandbox'; var normalisedOptions = Defaults.normaliseOptions({}); From 399c896445b1505adcdf462f5ff7798dc04b7439 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 10:47:34 -0300 Subject: [PATCH 018/468] Remove deprecated ability to pass ClientOptions.recover as a boolean Part of #1197. --- src/common/lib/util/defaults.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index ffcec077c0..512a286d4b 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -182,14 +182,6 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions } export function normaliseOptions(options: InternalClientOptions): NormalisedClientOptions { - /* options.recover as a boolean is deprecated, and therefore is not part of the public typing */ - if ((options.recover as any) === true) { - Logger.deprecated('{recover: true}', '{recover: function(lastConnectionDetails, cb) { cb(true); }}'); - options.recover = function (lastConnectionDetails: unknown, cb: (shouldRecover: boolean) => void) { - cb(true); - }; - } - if (typeof options.recover === 'function' && options.closeOnUnload === true) { Logger.logAction( Logger.LOG_ERROR, From b8ad229524d433c401c1a3a4cd19b452913068ed Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 11:09:16 -0300 Subject: [PATCH 019/468] Remove deprecated ability to pass "xhr" in ClientOptions.transports Part of #1197. --- src/common/lib/util/defaults.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 512a286d4b..9ad236fc97 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -197,12 +197,6 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie options.closeOnUnload = !options.recover; } - if (options.transports && Utils.arrIn(options.transports, 'xhr')) { - Logger.deprecated('transports: ["xhr"]', 'transports: ["xhr_streaming"]'); - Utils.arrDeleteValue(options.transports, 'xhr'); - options.transports.push('xhr_streaming'); - } - if (!('queueMessages' in options)) options.queueMessages = true; /* infer hosts and fallbacks based on the configured environment */ From 742a981dcac464e417f6e80c9c6674e4c66ec181 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 11:13:44 -0300 Subject: [PATCH 020/468] Remove deprecated RealtimePresence.on/off methods Part of #1197. --- src/common/lib/client/realtimepresence.ts | 12 --------- test/realtime/presence.test.js | 33 ----------------------- 2 files changed, 45 deletions(-) diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 43f19f2b4c..1d25be4035 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -507,18 +507,6 @@ class RealtimePresence extends Presence { }); } - /* Deprecated */ - on(...args: unknown[]): void { - Logger.deprecated('presence.on', 'presence.subscribe'); - this.subscribe(...args); - } - - /* Deprecated */ - off(...args: unknown[]): void { - Logger.deprecated('presence.off', 'presence.unsubscribe'); - this.unsubscribe(...args); - } - subscribe(..._args: unknown[] /* [event], listener, [callback] */): void | Promise { const args = RealtimeChannel.processListenerArgs(_args); const event = args[0]; diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 10525c783c..e9e9a65682 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1118,39 +1118,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - /* - * Check that old deprecated on/off methods still work - */ - it('presenceOn', function (done) { - var channelName = 'enterOn'; - var testData = 'some data'; - var eventListener = function (channel, callback) { - var presenceHandler = function () { - callback(); - }; - channel.presence.on(presenceHandler); - }; - var enterOn = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); - clientRealtime.connection.on('connected', function () { - /* get channel, attach, and enter */ - var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { - if (err) { - cb(err, clientRealtime); - return; - } - clientChannel.presence.enter(testData, function (err) { - cb(err, clientRealtime); - }); - }); - }); - monitorConnection(done, clientRealtime); - }; - - runTestWithEventListener(done, channelName, eventListener, enterOn); - }); - /* * Check that encodable presence messages are encoded correctly */ From 2402dc249d683a5b8d3dd39f89b6be15957cefd4 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 11:20:00 -0300 Subject: [PATCH 021/468] Remove deprecated ability to pass AuthOptions.force property Part of #1197. --- src/common/lib/client/auth.ts | 13 ------------- src/common/lib/util/utils.ts | 9 --------- test/realtime/auth.test.js | 8 -------- 3 files changed, 30 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 4abf0f00d2..b97c32c3f3 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -284,19 +284,6 @@ class Auth { throw new ErrorInfo('Unable to update auth options with incompatible key', 40102, 401); } - if (_authOptions && 'force' in _authOptions) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.authorize', - 'Deprecation warning: specifying {force: true} in authOptions is no longer necessary, authorize() now always gets a new token. Please remove this, as in version 1.0 and later, having a non-null authOptions will overwrite stored library authOptions, which may not be what you want' - ); - /* Emulate the old behaviour: if 'force' was the only member of authOptions, - * set it to null so it doesn't overwrite stored. TODO: remove in version 1.0 */ - if (Utils.isOnlyPropIn(_authOptions, 'force')) { - _authOptions = null; - } - } - this._forceNewToken( tokenParams as API.Types.TokenParams, _authOptions, diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 28b8366274..573d9909a9 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -81,15 +81,6 @@ export function isEmpty(ob: Record | unknown[]): boolean { return true; } -export function isOnlyPropIn(ob: Record, property: string): boolean { - for (const prop in ob) { - if (prop !== property) { - return false; - } - } - return true; -} - /* * Determine whether or not an argument to an overloaded function is * undefined (missing) or null. diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 26e97e1935..bfb6604154 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -972,14 +972,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async 'Check authorize completely replaces stored authOptions with passed in ones' ); - /* TODO remove for lib version 1.0 */ - realtime.auth.authorize(null, { authUrl: 'http://invalid' }); - realtime.auth.authorize(null, { force: true }); - expect(realtime.auth.authOptions.authUrl).to.equal( - 'http://invalid', - 'Check authorize does *not* replace stored authOptions when the only option is "force" in 0.9, for compatibility with 0.8' - ); - closeAndFinish(done, realtime); } catch (err) { closeAndFinish(done, realtime, err); From 2b56a434c6d0a81846a29f2d31677dbac0f794b0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 11:44:05 -0300 Subject: [PATCH 022/468] Remove deprecated ability to pass flags to RealtimeChannel.attach Part of #1197. --- src/common/lib/client/realtimechannel.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 6fd9fd64fd..2f128660ff 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -272,14 +272,7 @@ class RealtimeChannel extends Channel { } } - attach(flags?: API.Types.ChannelMode[] | ErrCallback, callback?: ErrCallback): void | Promise { - let _flags: API.Types.ChannelMode[] | null | undefined; - if (typeof flags === 'function') { - callback = flags; - _flags = null; - } else { - _flags = flags; - } + attach(callback?: ErrCallback): void | Promise { if (!callback) { if (this.realtime.options.promises) { return Utils.promisify(this, 'attach', arguments); @@ -290,12 +283,7 @@ class RealtimeChannel extends Channel { } }; } - if (_flags) { - Logger.deprecated('channel.attach() with flags', 'channel.setOptions() with channelOptions.params'); - /* If flags requested, always do a re-attach. TODO only do this if - * current mode differs from requested mode */ - this._requestedFlags = _flags as API.Types.ChannelMode[]; - } else if (this.state === 'attached') { + if (this.state === 'attached') { callback(); return; } From 5d3a18ac0f5b3927ed61250532a8a1c334ec6a16 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 11:45:45 -0300 Subject: [PATCH 023/468] Remove deprecated Auth.authorise method Part of #1197. --- src/common/lib/client/auth.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index b97c32c3f3..eec2d3d0e9 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -313,11 +313,6 @@ class Auth { ); } - authorise(tokenParams: API.Types.TokenParams | null, authOptions: API.Types.AuthOptions, callback: Function): void { - Logger.deprecated('Auth.authorise', 'Auth.authorize'); - this.authorize(tokenParams, authOptions, callback); - } - /* For internal use, eg by connectionManager - useful when want to call back * as soon as we have the new token, rather than waiting for it to take * effect on the connection as #authorize does */ From 0f2074ec5d366991238f93a4000f62a360887437 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 11:53:16 -0300 Subject: [PATCH 024/468] Remove deprecated forms of calling Crypto.getDefaultParams Remove the ability to call it without a key, or to pass a key without wrapping it in a CipherParamOptions object. Resolves #1197. --- src/platform/nodejs/lib/util/crypto.js | 14 -------------- src/platform/web/lib/util/crypto.js | 14 -------------- 2 files changed, 28 deletions(-) diff --git a/src/platform/nodejs/lib/util/crypto.js b/src/platform/nodejs/lib/util/crypto.js index 964352a76f..857aa43b86 100644 --- a/src/platform/nodejs/lib/util/crypto.js +++ b/src/platform/nodejs/lib/util/crypto.js @@ -126,20 +126,6 @@ var Crypto = (function () { */ Crypto.getDefaultParams = function (params) { var key; - /* Backward compatibility */ - if (typeof params === 'function' || typeof params === 'string') { - Logger.deprecated('Crypto.getDefaultParams(key, callback)', 'Crypto.getDefaultParams({key: key})'); - if (typeof params === 'function') { - Crypto.generateRandomKey(function (key) { - params(null, Crypto.getDefaultParams({ key: key })); - }); - } else if (typeof arguments[1] === 'function') { - arguments[1](null, Crypto.getDefaultParams({ key: params })); - } else { - throw new Error('Invalid arguments for Crypto.getDefaultParams'); - } - return; - } if (!params.key) { throw new Error('Crypto.getDefaultParams: a key is required'); diff --git a/src/platform/web/lib/util/crypto.js b/src/platform/web/lib/util/crypto.js index 36eb61e407..eb956dc6f0 100644 --- a/src/platform/web/lib/util/crypto.js +++ b/src/platform/web/lib/util/crypto.js @@ -158,20 +158,6 @@ var Crypto = (function () { */ Crypto.getDefaultParams = function (params) { var key; - /* Backward compatibility */ - if (typeof params === 'function' || typeof params === 'string') { - Logger.deprecated('Crypto.getDefaultParams(key, callback)', 'Crypto.getDefaultParams({key: key})'); - if (typeof params === 'function') { - Crypto.generateRandomKey(function (key) { - params(null, Crypto.getDefaultParams({ key: key })); - }); - } else if (typeof arguments[1] === 'function') { - arguments[1](null, Crypto.getDefaultParams({ key: params })); - } else { - throw new Error('Invalid arguments for Crypto.getDefaultParams'); - } - return; - } if (!params.key) { throw new Error('Crypto.getDefaultParams: a key is required'); From 23354329c8a7c3c89abd52029e2438e9326b1ddb Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 12:55:34 -0300 Subject: [PATCH 025/468] Remove Logger.deprecated* methods No longer used as of 0f2074e. --- src/common/lib/util/logger.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/common/lib/util/logger.ts b/src/common/lib/util/logger.ts index 6e0ff2af1f..8ce22c8b88 100644 --- a/src/common/lib/util/logger.ts +++ b/src/common/lib/util/logger.ts @@ -104,18 +104,6 @@ class Logger { } }; - static deprecated = function (original: string, replacement: string) { - Logger.deprecatedWithMsg(original, "Please use '" + replacement + "' instead."); - }; - - static deprecatedWithMsg = (funcName: string, msg: string) => { - if (Logger.shouldLog(LogLevels.Error)) { - Logger.logErrorHandler( - "Ably: Deprecation warning - '" + funcName + "' is deprecated and will be removed from a future version. " + msg - ); - } - }; - /* Where a logging operation is expensive, such as serialisation of data, use shouldLog will prevent the object being serialised if the log level will not output the message */ static shouldLog = (level: LogLevels) => { From 55c767c0f22cf3c68f52243d88ae0e12950adc77 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 16:38:13 -0300 Subject: [PATCH 026/468] =?UTF-8?q?Turn=20default*Headers=E2=80=99=20optio?= =?UTF-8?q?nal=20param=20into=20an=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I intend to add further optional params and this will allow us to disambiguate them. --- src/common/lib/client/channel.ts | 6 +++--- src/common/lib/client/presence.ts | 4 ++-- src/common/lib/client/push.ts | 20 +++++++++---------- src/common/lib/client/rest.ts | 4 +++- src/common/lib/util/utils.ts | 32 ++++++++++++++++++++++--------- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index c6eb9f541a..5c64844f03 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -92,7 +92,7 @@ class Channel extends EventEmitter { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); Utils.mixin(headers, rest.options.headers); @@ -148,7 +148,7 @@ class Channel extends EventEmitter { options = rest.options, format = options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, idempotentRestPublishing = rest.options.idempotentRestPublishing, - headers = Utils.defaultPostHeaders(rest.options, format); + headers = Utils.defaultPostHeaders(rest.options, { format }); Utils.mixin(headers, options.headers); @@ -197,7 +197,7 @@ class Channel extends EventEmitter { } const format = this.rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; - const headers = Utils.defaultPostHeaders(this.rest.options, format); + const headers = Utils.defaultPostHeaders(this.rest.options, { format }); Resource.get(this.rest, this.basePath, headers, {}, format, callback || noop); } diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index bd410b8b19..8874bccb85 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -37,7 +37,7 @@ class Presence extends EventEmitter { const rest = this.channel.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.channel.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); Utils.mixin(headers, rest.options.headers); @@ -79,7 +79,7 @@ class Presence extends EventEmitter { const rest = this.channel.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.channel.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); Utils.mixin(headers, rest.options.headers); diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index fcaaa88d42..dca0d72135 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -33,7 +33,7 @@ class Admin { publish(recipient: any, payload: any, callback: ErrCallback) { const rest = this.rest; const format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(rest.options, format), + headers = Utils.defaultPostHeaders(rest.options, { format }), params = {}; const body = Utils.mixin({ recipient: recipient }, payload); @@ -64,7 +64,7 @@ class DeviceRegistrations { const rest = this.rest; const body = DeviceDetails.fromValues(device); const format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(rest.options, format), + headers = Utils.defaultPostHeaders(rest.options, { format }), params = {}; if (typeof callback !== 'function') { @@ -103,7 +103,7 @@ class DeviceRegistrations { get(deviceIdOrDetails: any, callback: StandardCallback) { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, format), + headers = Utils.defaultGetHeaders(rest.options, { format }), deviceId = deviceIdOrDetails.id || deviceIdOrDetails; if (typeof callback !== 'function') { @@ -150,7 +150,7 @@ class DeviceRegistrations { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { if (this.rest.options.promises) { @@ -173,7 +173,7 @@ class DeviceRegistrations { remove(deviceIdOrDetails: any, callback: ErrCallback) { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, format), + headers = Utils.defaultGetHeaders(rest.options, { format }), params = {}, deviceId = deviceIdOrDetails.id || deviceIdOrDetails; @@ -212,7 +212,7 @@ class DeviceRegistrations { removeWhere(params: any, callback: ErrCallback) { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { if (this.rest.options.promises) { @@ -240,7 +240,7 @@ class ChannelSubscriptions { const rest = this.rest; const body = PushChannelSubscription.fromValues(subscription); const format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(rest.options, format), + headers = Utils.defaultPostHeaders(rest.options, { format }), params = {}; if (typeof callback !== 'function') { @@ -275,7 +275,7 @@ class ChannelSubscriptions { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { if (this.rest.options.promises) { @@ -298,7 +298,7 @@ class ChannelSubscriptions { removeWhere(params: any, callback: PaginatedResultCallback) { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { if (this.rest.options.promises) { @@ -321,7 +321,7 @@ class ChannelSubscriptions { const rest = this.rest, format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, format); + headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { if (this.rest.options.promises) { diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index fa9735e5ab..9fe61ffeb8 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -181,7 +181,9 @@ class Rest { params = params || {}; const _method = method.toLowerCase() as HttpMethods; const headers = - _method == 'get' ? Utils.defaultGetHeaders(this.options, format) : Utils.defaultPostHeaders(this.options, format); + _method == 'get' + ? Utils.defaultGetHeaders(this.options, { format }) + : Utils.defaultPostHeaders(this.options, { format }); if (callback === undefined) { if (this.options.promises) { diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 7027854fad..5afb272864 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -337,8 +337,24 @@ const contentTypes = { msgpack: 'application/x-msgpack', }; -export function defaultGetHeaders(options: NormalisedClientOptions, format?: Format): Record { - const accept = contentTypes[format || Format.json]; +export enum Format { + msgpack = 'msgpack', + json = 'json', +} + +export interface HeadersOptions { + format?: Format; +} + +const defaultHeadersOptions: Required = { + format: Format.json, +}; + +export function defaultGetHeaders( + options: NormalisedClientOptions, + { format = defaultHeadersOptions.format }: HeadersOptions = {} +): Record { + const accept = contentTypes[format]; return { accept: accept, 'X-Ably-Version': Defaults.protocolVersion.toString(), @@ -346,9 +362,12 @@ export function defaultGetHeaders(options: NormalisedClientOptions, format?: For }; } -export function defaultPostHeaders(options: NormalisedClientOptions, format?: Format): Record { +export function defaultPostHeaders( + options: NormalisedClientOptions, + { format = defaultHeadersOptions.format }: HeadersOptions = {} +): Record { let contentType; - const accept = (contentType = contentTypes[format || Format.json]); + const accept = (contentType = contentTypes[format]); return { accept: accept, @@ -486,11 +505,6 @@ export function promisify(ob: Record, fnName: string, args: IArg }); } -export enum Format { - msgpack = 'msgpack', - json = 'json', -} - export function decodeBody(body: unknown, format?: Format | null): T { return format == 'msgpack' ? Platform.Config.msgpack.decode(body as Buffer) : JSON.parse(String(body)); } From ba77bfb0d2b59210aec1f920604b2f81096f870c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 27 Apr 2023 15:27:09 -0300 Subject: [PATCH 027/468] Add mandatory version param to Rest.request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As described by RSC19f1 in ably/specification at commit 505238c. The docstrings are taken from ably/sdk-api-reference at commit 1e0776e. The corresponding PR [1] has been approved by Owen but is still awaiting tech writer approval. So we might need to tweak these docstrings later, but I don’t think that needs to block us on this feature. Approach for mocking HTTP request handling copied from http.test.js. Other than the added test, the test changes are minimal (just adding an extra argument to a few calls) but Prettier has decided to do some re-indentation that makes them look larger than they are. Resolves #1225. [1] https://github.com/ably/sdk-api-reference/pull/32 --- ably.d.ts | 8 +++ src/common/lib/client/rest.ts | 7 ++- src/common/lib/util/utils.ts | 16 +++-- test/realtime/encoding.test.js | 70 ++++++++++++---------- test/rest/request.test.js | 105 ++++++++++++++++++++++----------- 5 files changed, 134 insertions(+), 72 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 7f4b8677c6..366079db34 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1636,6 +1636,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. + * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1644,6 +1645,7 @@ declare namespace Types { request( method: string, path: string, + version: number, params?: any, body?: any[] | any, headers?: any, @@ -1699,6 +1701,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. + * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1707,6 +1710,7 @@ declare namespace Types { request( method: string, path: string, + version: number, params?: any, body?: any[] | any, headers?: any @@ -1777,6 +1781,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. + * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1785,6 +1790,7 @@ declare namespace Types { request( method: string, path: string, + version: number, params?: any, body?: any[] | any, headers?: any, @@ -1836,6 +1842,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. + * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1844,6 +1851,7 @@ declare namespace Types { request( method: string, path: string, + version: number, params?: any, body?: any[] | any, headers?: any diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 9fe61ffeb8..2f2051ca81 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -168,6 +168,7 @@ class Rest { request( method: string, path: string, + version: number, params: RequestParams, body: unknown, customHeaders: Record, @@ -182,12 +183,12 @@ class Rest { const _method = method.toLowerCase() as HttpMethods; const headers = _method == 'get' - ? Utils.defaultGetHeaders(this.options, { format }) - : Utils.defaultPostHeaders(this.options, { format }); + ? Utils.defaultGetHeaders(this.options, { format, protocolVersion: version }) + : Utils.defaultPostHeaders(this.options, { format, protocolVersion: version }); if (callback === undefined) { if (this.options.promises) { - return Utils.promisify(this, 'request', [method, path, params, body, customHeaders]) as Promise< + return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< HttpPaginatedResponse >; } diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 5afb272864..eb9327b94d 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -344,27 +344,35 @@ export enum Format { export interface HeadersOptions { format?: Format; + protocolVersion?: number; } const defaultHeadersOptions: Required = { format: Format.json, + protocolVersion: Defaults.protocolVersion, }; export function defaultGetHeaders( options: NormalisedClientOptions, - { format = defaultHeadersOptions.format }: HeadersOptions = {} + { + format = defaultHeadersOptions.format, + protocolVersion = defaultHeadersOptions.protocolVersion, + }: HeadersOptions = {} ): Record { const accept = contentTypes[format]; return { accept: accept, - 'X-Ably-Version': Defaults.protocolVersion.toString(), + 'X-Ably-Version': protocolVersion.toString(), 'Ably-Agent': getAgentString(options), }; } export function defaultPostHeaders( options: NormalisedClientOptions, - { format = defaultHeadersOptions.format }: HeadersOptions = {} + { + format = defaultHeadersOptions.format, + protocolVersion = defaultHeadersOptions.protocolVersion, + }: HeadersOptions = {} ): Record { let contentType; const accept = (contentType = contentTypes[format]); @@ -372,7 +380,7 @@ export function defaultPostHeaders( return { accept: accept, 'content-type': contentType, - 'X-Ably-Version': Defaults.protocolVersion.toString(), + 'X-Ably-Version': protocolVersion.toString(), 'Ably-Agent': getAgentString(options), }; } diff --git a/test/realtime/encoding.test.js b/test/realtime/encoding.test.js index 7c50152acb..aeef0b94f7 100644 --- a/test/realtime/encoding.test.js +++ b/test/realtime/encoding.test.js @@ -7,6 +7,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var displayError = helper.displayError; var encodingFixturesPath = helper.testResourcesPath + 'messages-encoding.json'; var closeAndFinish = helper.closeAndFinish; + var Defaults = Ably.Rest.Platform.Defaults; describe('realtime/encoding', function () { this.timeout(60 * 1000); @@ -98,6 +99,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime.request( 'post', channelPath, + Defaults.protocolVersion, null, { name: name, data: encodingSpec.data, encoding: encodingSpec.encoding }, null, @@ -174,39 +176,47 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async eachOfCb(err); return; } - realtime.request('get', channelPath, null, null, null, function (err, resultPage) { - if (err) { - eachOfCb(err); - return; - } - try { - var msgs = helper.arrFilter(resultPage.items, function (m) { - return m.name === name; - }); - expect(msgs.length).to.equal( - 2, - 'Check expected number of results (one from json rt, one from binary rt)' - ); - expect(msgs[0].encoding == encodingSpec.encoding, 'Check encodings match').to.be.ok; - expect(msgs[1].encoding == encodingSpec.encoding, 'Check encodings match').to.be.ok; - if (msgs[0].encoding === 'json') { - expect(JSON.parse(encodingSpec.data)).to.deep.equal( - JSON.parse(msgs[0].data), - 'Check data matches' - ); - expect(JSON.parse(encodingSpec.data)).to.deep.equal( - JSON.parse(msgs[1].data), - 'Check data matches' + realtime.request( + 'get', + channelPath, + Defaults.protocolVersion, + null, + null, + null, + function (err, resultPage) { + if (err) { + eachOfCb(err); + return; + } + try { + var msgs = helper.arrFilter(resultPage.items, function (m) { + return m.name === name; + }); + expect(msgs.length).to.equal( + 2, + 'Check expected number of results (one from json rt, one from binary rt)' ); - } else { - expect(encodingSpec.data).to.equal(msgs[0].data, 'Check data matches'); - expect(encodingSpec.data).to.equal(msgs[1].data, 'Check data matches'); + expect(msgs[0].encoding == encodingSpec.encoding, 'Check encodings match').to.be.ok; + expect(msgs[1].encoding == encodingSpec.encoding, 'Check encodings match').to.be.ok; + if (msgs[0].encoding === 'json') { + expect(JSON.parse(encodingSpec.data)).to.deep.equal( + JSON.parse(msgs[0].data), + 'Check data matches' + ); + expect(JSON.parse(encodingSpec.data)).to.deep.equal( + JSON.parse(msgs[1].data), + 'Check data matches' + ); + } else { + expect(encodingSpec.data).to.equal(msgs[0].data, 'Check data matches'); + expect(encodingSpec.data).to.equal(msgs[1].data, 'Check data matches'); + } + eachOfCb(); + } catch (err) { + eachOfCb(err); } - eachOfCb(); - } catch (err) { - eachOfCb(err); } - }); + ); } ); }, diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 718c25aaf7..6f931e0c90 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -1,11 +1,12 @@ 'use strict'; -define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { +define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async, chai) { var rest; var expect = chai.expect; var utils = helper.Utils; var echoServerHost = 'echo.ably.io'; var restTestOnJsonMsgpack = helper.restTestOnJsonMsgpack; + var Defaults = Ably.Rest.Platform.Defaults; describe('rest/request', function () { this.timeout(60 * 1000); @@ -21,8 +22,26 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); }); + restTestOnJsonMsgpack('request_version', function (done, rest) { + const version = 150; // arbitrarily chosen + + function testRequestHandler(_, __, ___, headers) { + try { + expect('X-Ably-Version' in headers, 'Verify version header exists').to.be.ok; + expect(headers['X-Ably-Version']).to.equal(version.toString(), 'Verify version number sent in request'); + done(); + } catch (err) { + done(err); + } + } + + rest.http.do = testRequestHandler; + + rest.request('get', '/time' /* arbitrarily chosen */, version, null, null, null, function (err, res) {}); + }); + restTestOnJsonMsgpack('request_time', function (done, rest) { - rest.request('get', '/time', null, null, null, function (err, res) { + rest.request('get', '/time', Defaults.protocolVersion, null, null, null, function (err, res) { try { expect(!err, err && helper.displayError(err)).to.be.ok; expect(res.statusCode).to.equal(200, 'Check statusCode'); @@ -40,27 +59,35 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { /* NB: can't just use /invalid or something as the CORS preflight will * fail. Need something superficially a valid path but where the actual * request fails */ - rest.request('get', '/keys/ablyjs.test/requestToken', null, null, null, function (err, res) { - try { - expect(err).to.equal( - null, - 'Check that we do not get an error response for a failure that returns an actual ably error code' - ); - expect(res.success).to.equal(false, 'Check res.success is false for a failure'); - expect(res.statusCode).to.equal(404, 'Check HPR.statusCode is 404'); - expect(res.errorCode).to.equal(40400, 'Check HPR.errorCode is 40400'); - expect(res.errorMessage, 'Check have an HPR.errorMessage').to.be.ok; - done(); - } catch (err) { - done(err); + rest.request( + 'get', + '/keys/ablyjs.test/requestToken', + Defaults.protocolVersion, + null, + null, + null, + function (err, res) { + try { + expect(err).to.equal( + null, + 'Check that we do not get an error response for a failure that returns an actual ably error code' + ); + expect(res.success).to.equal(false, 'Check res.success is false for a failure'); + expect(res.statusCode).to.equal(404, 'Check HPR.statusCode is 404'); + expect(res.errorCode).to.equal(40400, 'Check HPR.errorCode is 40400'); + expect(res.errorMessage, 'Check have an HPR.errorMessage').to.be.ok; + done(); + } catch (err) { + done(err); + } } - }); + ); }); /* With a network issue, should get an actual err, not an HttpPaginatedResponse with error members */ it('request_network_error', function (done) { rest = helper.AblyRest({ restHost: helper.unroutableAddress }); - rest.request('get', '/time', null, null, null, function (err, res) { + rest.request('get', '/time', Defaults.protocolVersion, null, null, null, function (err, res) { try { expect(err, 'Check get an err').to.be.ok; expect(!res, 'Check do not get a res').to.be.ok; @@ -80,7 +107,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { async.waterfall( [ function (cb) { - rest.request('post', channelPath, null, msgone, null, function (err, res) { + rest.request('post', channelPath, Defaults.protocolVersion, null, msgone, null, function (err, res) { try { expect(!err, err && helper.displayError(err)).to.be.ok; expect(res.statusCode).to.equal(201, 'Check statusCode is 201'); @@ -93,7 +120,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); }, function (cb) { - rest.request('post', channelPath, null, msgtwo, null, function (err, res) { + rest.request('post', channelPath, Defaults.protocolVersion, null, msgtwo, null, function (err, res) { try { expect(!err, err && helper.displayError(err)).to.be.ok; expect(res.statusCode).to.equal(201, 'Check statusCode is 201'); @@ -105,19 +132,27 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); }, function (cb) { - rest.request('get', channelPath, { limit: 1, direction: 'forwards' }, null, null, function (err, res) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(res.statusCode).to.equal(200, 'Check statusCode is 200'); - expect(res.items.length).to.equal(1, 'Check only one msg returned'); - expect(res.items[0].name).to.equal(msgone.name, 'Check name is as expected'); - expect(res.items[0].data).to.equal(msgone.data, 'Check data is as expected'); - expect(res.hasNext, 'Check hasNext is true').to.be.ok; - cb(null, res.next); - } catch (err) { - cb(err); + rest.request( + 'get', + channelPath, + Defaults.protocolVersion, + { limit: 1, direction: 'forwards' }, + null, + null, + function (err, res) { + try { + expect(!err, err && helper.displayError(err)).to.be.ok; + expect(res.statusCode).to.equal(200, 'Check statusCode is 200'); + expect(res.items.length).to.equal(1, 'Check only one msg returned'); + expect(res.items[0].name).to.equal(msgone.name, 'Check name is as expected'); + expect(res.items[0].data).to.equal(msgone.data, 'Check data is as expected'); + expect(res.hasNext, 'Check hasNext is true').to.be.ok; + cb(null, res.next); + } catch (err) { + cb(err); + } } - }); + ); }, function (next, cb) { next(function (err, res) { @@ -162,7 +197,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { restTestOnJsonMsgpack('request_batch_api_success', function (done, rest, name) { var body = { channels: [name + '1', name + '2'], messages: { data: 'foo' } }; - rest.request('POST', '/messages', {}, body, {}, function (err, res) { + rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}, function (err, res) { try { expect(err).to.equal(null, 'Check that we do not get an error response for a success'); expect(res.success).to.equal(true, 'Check res.success is true for a success'); @@ -189,7 +224,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { restTestOnJsonMsgpack.skip('request_batch_api_partial_success', function (done, rest, name) { var body = { channels: [name, '[invalid', ''], messages: { data: 'foo' } }; - rest.request('POST', '/messages', {}, body, {}, function (err, res) { + rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}, function (err, res) { try { expect(err).to.equal(null, 'Check that we do not get an error response for a partial success'); expect(res.success).to.equal(false, 'Check res.success is false for a partial failure'); @@ -223,7 +258,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { utils.arrForEach(['put', 'patch', 'delete'], function (method) { it('check' + method, function (done) { var restEcho = helper.AblyRest({ useBinaryProtocol: false, restHost: echoServerHost, tls: true }); - restEcho.request(method, '/methods', {}, {}, {}, function (err, res) { + restEcho.request(method, '/methods', Defaults.protocolVersion, {}, {}, {}, function (err, res) { if (err) { done(err); } else { @@ -243,7 +278,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var client = helper.AblyRest({ internal: { promises: true } }); client - .request('get', '/time', null, null, null) + .request('get', '/time', Defaults.protocolVersion, null, null, null) .then(function (res) { expect(res.statusCode).to.equal(200, 'Check statusCode'); expect(res.success).to.equal(true, 'Check success'); From 806c2fb10e816fba8ce0d8907a13db9e88907cf9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 11 May 2023 09:23:34 -0300 Subject: [PATCH 028/468] Update docstrings for Rest.request version param The docstrings added in ba77bfb had not been approved by tech writers. These new docstrings have been approved, and come from ably/sdk-api-reference at commit f441edc. --- ably.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 366079db34..5921efa16b 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1636,7 +1636,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. - * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). + * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1701,7 +1701,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. - * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). + * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1781,7 +1781,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. - * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). + * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1842,7 +1842,7 @@ declare namespace Types { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. - * @param version - The major version of the Ably REST API to use. For more information about REST API versioning, see [this section of the REST API reference](https://ably.com/docs/api/rest-api#versioning). + * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. From 5f697953071fcc5abcca17dee50b4275bb20c062 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 8 May 2023 09:13:48 -0300 Subject: [PATCH 029/468] Upgrade TypeScript to 4.9.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I want to use the `satsifies` operator introduced in 4.9 [1]. Ran `npm install typescript@4.9.5 typedoc@latest`, and then upgraded @types/node to ^18.0.0, to fix [2]. The TypeScript upgrade introduced one compilation error, which I’ve fixed here. I’m targeting the v2 integration branch (which uses the latest webpack) since trying to upgrade TypeScript on `main` is giving some webpack errors that don’t seem worth trying to understand. [1] https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator [2] https://github.com/microsoft/TypeScript/issues/51567 --- package-lock.json | 159 +++++++++++++++++++---------------- package.json | 6 +- src/common/lib/util/utils.ts | 2 +- 3 files changed, 92 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index abd99c597c..84e007a788 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "devDependencies": { "@ably/vcdiff-decoder": "1.0.4", "@types/crypto-js": "^4.0.1", - "@types/node": "^15.0.0", + "@types/node": "^18.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^5.14.0", @@ -52,8 +52,8 @@ "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^4.0.1", "tslib": "^2.3.1", - "typedoc": "^0.23.8", - "typescript": "^4.6.4", + "typedoc": "^0.24.7", + "typescript": "^4.9.5", "webpack": "^5.79.0", "webpack-cli": "^5.0.1" }, @@ -343,9 +343,9 @@ } }, "node_modules/@types/node": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.0.tgz", - "integrity": "sha512-YN1d+ae2MCb4U0mMa+Zlb5lWTdpFShbAj5nmte6lel27waMMBfivrm0prC16p/Di3DyTrmerrYUT8/145HXxVw==" + "version": "18.16.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.8.tgz", + "integrity": "sha512-p0iAXcfWCOTCBbsExHIDFCfwsqFwBTgETJveKMT+Ci3LY9YqQCI91F5S+TB20+aRCXpcWfvx5Qr5EccnwCm2NA==" }, "node_modules/@types/request": { "version": "2.48.8", @@ -941,6 +941,12 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", + "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", + "dev": true + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -4118,9 +4124,9 @@ "dev": true }, "node_modules/jsonc-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", - "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, "node_modules/kexec": { @@ -4356,9 +4362,9 @@ } }, "node_modules/marked": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz", - "integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "bin": { "marked": "bin/marked.js" @@ -5909,14 +5915,15 @@ } }, "node_modules/shiki": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", - "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.2.tgz", + "integrity": "sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==", "dev": true, "dependencies": { - "jsonc-parser": "^3.0.0", - "vscode-oniguruma": "^1.6.1", - "vscode-textmate": "5.2.0" + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" } }, "node_modules/side-channel": { @@ -6655,15 +6662,15 @@ } }, "node_modules/typedoc": { - "version": "0.23.8", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.8.tgz", - "integrity": "sha512-NLRTY/7XSrhiowR3xnH/nlfTnHk+dkzhHWAMT8guoZ6RHCQZIu3pJREMCqzdkWVCC5+dr9We7TtNeprR3Qy6Ag==", + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.7.tgz", + "integrity": "sha512-zzfKDFIZADA+XRIp2rMzLe9xZ6pt12yQOhCr7cD7/PBTjhPmMyMvGrkZ2lPNJitg3Hj1SeiYFNzCsSDrlpxpKw==", "dev": true, "dependencies": { "lunr": "^2.3.9", - "marked": "^4.0.16", - "minimatch": "^5.1.0", - "shiki": "^0.10.1" + "marked": "^4.3.0", + "minimatch": "^9.0.0", + "shiki": "^0.14.1" }, "bin": { "typedoc": "bin/typedoc" @@ -6672,7 +6679,7 @@ "node": ">= 14.14" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x" } }, "node_modules/typedoc/node_modules/brace-expansion": { @@ -6685,21 +6692,24 @@ } }, "node_modules/typedoc/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6857,15 +6867,15 @@ } }, "node_modules/vscode-oniguruma": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", - "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", "dev": true }, "node_modules/vscode-textmate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", - "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", "dev": true }, "node_modules/watchpack": { @@ -7652,9 +7662,9 @@ } }, "@types/node": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.0.tgz", - "integrity": "sha512-YN1d+ae2MCb4U0mMa+Zlb5lWTdpFShbAj5nmte6lel27waMMBfivrm0prC16p/Di3DyTrmerrYUT8/145HXxVw==" + "version": "18.16.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.8.tgz", + "integrity": "sha512-p0iAXcfWCOTCBbsExHIDFCfwsqFwBTgETJveKMT+Ci3LY9YqQCI91F5S+TB20+aRCXpcWfvx5Qr5EccnwCm2NA==" }, "@types/request": { "version": "2.48.8", @@ -8093,6 +8103,12 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, + "ansi-sequence-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", + "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -10511,9 +10527,9 @@ "dev": true }, "jsonc-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", - "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, "kexec": { @@ -10695,9 +10711,9 @@ "dev": true }, "marked": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz", - "integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true }, "media-typer": { @@ -11829,14 +11845,15 @@ } }, "shiki": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", - "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.2.tgz", + "integrity": "sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==", "dev": true, "requires": { - "jsonc-parser": "^3.0.0", - "vscode-oniguruma": "^1.6.1", - "vscode-textmate": "5.2.0" + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" } }, "side-channel": { @@ -12392,15 +12409,15 @@ } }, "typedoc": { - "version": "0.23.8", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.8.tgz", - "integrity": "sha512-NLRTY/7XSrhiowR3xnH/nlfTnHk+dkzhHWAMT8guoZ6RHCQZIu3pJREMCqzdkWVCC5+dr9We7TtNeprR3Qy6Ag==", + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.7.tgz", + "integrity": "sha512-zzfKDFIZADA+XRIp2rMzLe9xZ6pt12yQOhCr7cD7/PBTjhPmMyMvGrkZ2lPNJitg3Hj1SeiYFNzCsSDrlpxpKw==", "dev": true, "requires": { "lunr": "^2.3.9", - "marked": "^4.0.16", - "minimatch": "^5.1.0", - "shiki": "^0.10.1" + "marked": "^4.3.0", + "minimatch": "^9.0.0", + "shiki": "^0.14.1" }, "dependencies": { "brace-expansion": { @@ -12413,9 +12430,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -12424,9 +12441,9 @@ } }, "typescript": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", - "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "unc-path-regex": { @@ -12533,15 +12550,15 @@ } }, "vscode-oniguruma": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", - "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", "dev": true }, "vscode-textmate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", - "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", "dev": true }, "watchpack": { diff --git a/package.json b/package.json index efda4f0174..a52de66664 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "@ably/vcdiff-decoder": "1.0.4", "@types/crypto-js": "^4.0.1", - "@types/node": "^15.0.0", + "@types/node": "^18.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", "@typescript-eslint/eslint-plugin": "^5.14.0", @@ -68,8 +68,8 @@ "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^4.0.1", "tslib": "^2.3.1", - "typedoc": "^0.23.8", - "typescript": "^4.6.4", + "typedoc": "^0.24.7", + "typescript": "^4.9.5", "webpack": "^5.79.0", "webpack-cli": "^5.0.1" }, diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index eb9327b94d..96404a9100 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -169,7 +169,7 @@ export function arrIntersect(arr1: Array, arr2: Array): Array { return result; } -export function arrIntersectOb(arr: Array, ob: Record): T[] { +export function arrIntersectOb(arr: Array, ob: Record): string[] { const result = []; for (let i = 0; i < arr.length; i++) { const member = arr[i]; From 0e7ca1385cd4adcdd70759d129a9c0a15dd177aa Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 16 May 2023 15:24:05 -0300 Subject: [PATCH 030/468] Update Prettier to 2.8.8 2.8 introduced support for the TypeScript `satisfies` operator, which I wish to use. I should have done this in 5f69795. --- package-lock.json | 17 ++++++++++------- package.json | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 84e007a788..f15532d020 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "minimist": "^1.2.5", "mocha": "^8.1.3", "playwright": "^1.10.0", - "prettier": "^2.5.1", + "prettier": "^2.8.8", "requirejs": "~2.1", "shelljs": "~0.8", "source-map-explorer": "^2.5.2", @@ -5239,15 +5239,18 @@ } }, "node_modules/prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/process-nextick-args": { @@ -11338,9 +11341,9 @@ "dev": true }, "prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true }, "process-nextick-args": { diff --git a/package.json b/package.json index a52de66664..e97913a2e7 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "minimist": "^1.2.5", "mocha": "^8.1.3", "playwright": "^1.10.0", - "prettier": "^2.5.1", + "prettier": "^2.8.8", "requirejs": "~2.1", "shelljs": "~0.8", "source-map-explorer": "^2.5.2", From e013cc05a715f1b5278350374f0e28cb1356b8cb Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 16 May 2023 17:28:57 -0300 Subject: [PATCH 031/468] Update typescript-eslint to 5.59.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I was getting warnings about the current version of TypeScript not being supported — I should have done this in 5f69795. --- package-lock.json | 387 +++++++++++++++++++++++++++------------------- 1 file changed, 232 insertions(+), 155 deletions(-) diff --git a/package-lock.json b/package-lock.json index f15532d020..14b3a02edb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,6 +138,42 @@ "node": "^14 || ^16 || ^17 || ^18 || ^19" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz", @@ -367,6 +403,12 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, "node_modules/@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", @@ -383,19 +425,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz", - "integrity": "sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", + "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.14.0", - "@typescript-eslint/type-utils": "5.14.0", - "@typescript-eslint/utils": "5.14.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/type-utils": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -425,15 +468,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.14.0.tgz", - "integrity": "sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", + "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.14.0", - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/typescript-estree": "5.14.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "debug": "^4.3.4" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -452,13 +495,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz", - "integrity": "sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", + "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/visitor-keys": "5.14.0" + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -469,13 +512,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz", - "integrity": "sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", + "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "5.14.0", - "debug": "^4.3.2", + "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", "tsutils": "^3.21.0" }, "engines": { @@ -495,9 +539,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.14.0.tgz", - "integrity": "sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", + "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -508,17 +552,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz", - "integrity": "sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", + "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/visitor-keys": "5.14.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -535,17 +579,19 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.14.0.tgz", - "integrity": "sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", + "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", "dev": true, "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.14.0", - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/typescript-estree": "5.14.0", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "semver": "^7.3.7" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -558,32 +604,14 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz", - "integrity": "sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", + "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.14.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.59.6", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -594,12 +622,15 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@ungap/promise-all-settled": { @@ -2951,16 +2982,16 @@ } }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -2971,9 +3002,9 @@ } }, "node_modules/globby/node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -3093,6 +3124,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -4755,6 +4792,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -7490,6 +7533,29 @@ "jsdoc-type-pratt-parser": "~3.1.0" } }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, "@eslint/eslintrc": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz", @@ -7689,6 +7755,12 @@ "@types/node": "*" } }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, "@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", @@ -7705,19 +7777,20 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz", - "integrity": "sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", + "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.14.0", - "@typescript-eslint/type-utils": "5.14.0", - "@typescript-eslint/utils": "5.14.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/type-utils": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "dependencies": { @@ -7730,98 +7803,90 @@ } }, "@typescript-eslint/parser": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.14.0.tgz", - "integrity": "sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", + "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.14.0", - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/typescript-estree": "5.14.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz", - "integrity": "sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", + "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/visitor-keys": "5.14.0" + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6" } }, "@typescript-eslint/type-utils": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz", - "integrity": "sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", + "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", "dev": true, "requires": { - "@typescript-eslint/utils": "5.14.0", - "debug": "^4.3.2", + "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.14.0.tgz", - "integrity": "sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", + "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz", - "integrity": "sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", + "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/visitor-keys": "5.14.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" } }, "@typescript-eslint/utils": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.14.0.tgz", - "integrity": "sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", + "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", "dev": true, "requires": { + "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.14.0", - "@typescript-eslint/types": "5.14.0", - "@typescript-eslint/typescript-estree": "5.14.0", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - } + "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz", - "integrity": "sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", + "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", "dev": true, "requires": { - "@typescript-eslint/types": "5.14.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.59.6", + "eslint-visitor-keys": "^3.3.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true } } @@ -9679,23 +9744,23 @@ } }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "dependencies": { "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true } } @@ -9786,6 +9851,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -10992,6 +11063,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", From 006cf5593abdf651af6e4c2c9cf533de5c849790 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 8 May 2023 10:48:45 -0300 Subject: [PATCH 032/468] Create ICryptoStatic and ICipher interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These describe the interface which is currently exposed by the Crypto and CBCCipher "classes" exposed by the Node and web crypto.js files. I wrote them based on the implementation of those classes as well as looking at how they are used within the codebase and tests. Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/common/platform.ts | 8 +++++++- src/common/types/ICipher.ts | 5 +++++ src/common/types/ICryptoStatic.ts | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/common/types/ICipher.ts create mode 100644 src/common/types/ICryptoStatic.ts diff --git a/src/common/platform.ts b/src/common/platform.ts index ff6ad04d66..64ee893373 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -23,7 +23,13 @@ export default class Platform { can in reality handle. */ static BufferUtils: IBufferUtils; - static Crypto: any; //Not typed + /* + This should be a class whose static methods implement the ICryptoStatic + interface, but (for the same reasons as described in the BufferUtils + comment above) Platform doesn’t currently allow us to express the + generic parameters, hence keeping the type as `any`. + */ + static Crypto: any; static Http: typeof IHttp; static Transports: Array<(connectionManager: typeof ConnectionManager) => Transport>; static Defaults: IDefaults; diff --git a/src/common/types/ICipher.ts b/src/common/types/ICipher.ts new file mode 100644 index 0000000000..88e82a80e6 --- /dev/null +++ b/src/common/types/ICipher.ts @@ -0,0 +1,5 @@ +export default interface ICipher { + algorithm: string; + encrypt: (plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) => void; + decrypt: (ciphertext: InputCiphertext) => OutputPlaintext; +} diff --git a/src/common/types/ICryptoStatic.ts b/src/common/types/ICryptoStatic.ts new file mode 100644 index 0000000000..97dac8fcce --- /dev/null +++ b/src/common/types/ICryptoStatic.ts @@ -0,0 +1,15 @@ +import * as API from '../../../ably'; +import ICipher from './ICipher'; + +export type IGetCipherParams = (API.Types.CipherParams | API.Types.CipherParamOptions) & { iv?: IV }; +export interface IGetCipherReturnValue { + cipher: Cipher; + cipherParams: API.Types.CipherParams; +} + +export default interface ICryptoStatic + extends API.Types.Crypto { + getCipher( + params: IGetCipherParams + ): IGetCipherReturnValue>; +} From 06479c38db6fa3b13476ef957de19c13bceb111a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 May 2023 17:32:04 -0300 Subject: [PATCH 033/468] Add helpers for deriving ICryptoStatic generic type arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I arrived at these types by looking at how we use the `Platform.Crypto` object within the library and test code, plus my understanding of what data types a user is allowed to specify as a message’s `data` (for this latter situation, I wasn’t able to find any conclusive information about what ways a user is allowed to specify binary data, so I assumed that any `Bufferlike` object should be accepted). Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/common/types/cryptoDataTypes.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/common/types/cryptoDataTypes.ts diff --git a/src/common/types/cryptoDataTypes.ts b/src/common/types/cryptoDataTypes.ts new file mode 100644 index 0000000000..aed70f6489 --- /dev/null +++ b/src/common/types/cryptoDataTypes.ts @@ -0,0 +1,25 @@ +/** + Allows us to derive the generic type arguments for a platform’s `ICryptoStatic` implementation, given other properties of the platform. + */ +export namespace CryptoDataTypes { + /** + The type of initialization vector that the platform is expected to be able to use when creating a cipher. + */ + export type IV = BufferUtilsOutput; + + /** + The type of plaintext that the platform is expected to be able to encrypt. + + - `Bufferlike`: The `Bufferlike` of the platform’s `IBufferUtils` implementation. + - `BufferUtilsOutput`: The `Output` of the platform’s `IBufferUtils` implementation. + */ + export type InputPlaintext = Bufferlike | BufferUtilsOutput; + + /** + The type of ciphertext that the platform is expected to be able to decrypt. + + - `MessagePackBinaryType`: The type to which this platform’s MessagePack implementation deserializes elements of the `bin` or `ext` type. + - `BufferUtilsOutput`: The `Output` of the platform’s `IBufferUtils` implementation. + */ + export type InputCiphertext = MessagePackBinaryType | BufferUtilsOutput; +} From d2b61f5aa537c6543590fe81669a0a6aa282b712 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 May 2023 09:52:35 -0300 Subject: [PATCH 034/468] Expose toWordArray property in IBufferUtils interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/common/platform.ts | 3 ++- src/common/types/IBufferUtils.ts | 3 ++- src/platform/nodejs/lib/util/bufferutils.ts | 8 +++++++- src/platform/web/lib/util/bufferutils.ts | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/common/platform.ts b/src/common/platform.ts index 64ee893373..c395ce9927 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -12,6 +12,7 @@ type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; type ToBufferOutput = WebBufferUtils.ToBufferOutput | NodeBufferUtils.ToBufferOutput; type ComparableBuffer = WebBufferUtils.ComparableBuffer | NodeBufferUtils.ComparableBuffer; +type WordArrayLike = WebBufferUtils.WordArrayLike | NodeBufferUtils.WordArrayLike; export default class Platform { static Config: IPlatformConfig; @@ -22,7 +23,7 @@ export default class Platform { BufferUtils object that accepts a broader range of data types than it can in reality handle. */ - static BufferUtils: IBufferUtils; + static BufferUtils: IBufferUtils; /* This should be a class whose static methods implement the ICryptoStatic interface, but (for the same reasons as described in the BufferUtils diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index e98c9bccbd..a8122300aa 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -1,6 +1,6 @@ import { TypedArray } from './IPlatformConfig'; -export default interface IBufferUtils { +export default interface IBufferUtils { base64CharSet: string; hexCharSet: string; isBuffer: (buffer: unknown) => buffer is Bufferlike; @@ -17,4 +17,5 @@ export default interface IBufferUtils number; byteLength: (buffer: Bufferlike) => number; typedArrayToBuffer: (typedArray: TypedArray) => Bufferlike; + toWordArray: (buffer: Bufferlike | number[]) => WordArrayLike; } diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index c972886e40..bd61b932b8 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -5,8 +5,9 @@ export type Bufferlike = Buffer | ArrayBuffer | TypedArray; export type Output = Buffer; export type ToBufferOutput = Buffer; export type ComparableBuffer = Buffer; +export type WordArrayLike = never; -class BufferUtils implements IBufferUtils { +class BufferUtils implements IBufferUtils { base64CharSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; hexCharSet: string = '0123456789abcdef'; @@ -72,6 +73,11 @@ class BufferUtils implements IBufferUtils { +class BufferUtils implements IBufferUtils { base64CharSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; hexCharSet = '0123456789abcdef'; From 61415115bad5161a362618ff1f847be7483f42a6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 11 May 2023 11:34:30 -0300 Subject: [PATCH 035/468] Expose isWordArray method in IBufferUtils interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/common/types/IBufferUtils.ts | 1 + src/platform/nodejs/lib/util/bufferutils.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index a8122300aa..9f1c3728da 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -5,6 +5,7 @@ export default interface IBufferUtils buffer is Bufferlike; isArrayBuffer: (buffer: unknown) => buffer is ArrayBuffer; + isWordArray: (val: unknown) => val is WordArrayLike; // On browser this returns a Uint8Array, on node a Buffer toBuffer: (buffer: Bufferlike) => ToBufferOutput; toArrayBuffer: (buffer: Bufferlike) => ArrayBuffer; diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index bd61b932b8..7a9b35f8c2 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -78,6 +78,11 @@ class BufferUtils implements IBufferUtils Date: Tue, 9 May 2023 10:29:26 -0300 Subject: [PATCH 036/468] =?UTF-8?q?Make=20getRandomValues=E2=80=99s=20call?= =?UTF-8?q?back=20error=20parameter=20non-optional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We always pass an argument anyway. Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/common/types/IPlatformConfig.d.ts | 2 +- src/platform/nodejs/config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index f43c5d45aa..cd78d6bd09 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -31,7 +31,7 @@ export interface IPlatformConfig { stringByteSize: Buffer.byteLength; addEventListener?: typeof window.addEventListener | typeof global.addEventListener | null; Promise: typeof Promise; - getRandomValues?: (arr: TypedArray, callback?: (error?: Error | null) => void) => void; + getRandomValues?: (arr: TypedArray, callback?: (error: Error | null) => void) => void; userAgent?: string | null; inherits?: typeof import('util').inherits; currentUrl?: string; diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 86d0a7e5b7..4a80645c35 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -19,7 +19,7 @@ const Config: IPlatformConfig = { stringByteSize: Buffer.byteLength, inherits: util.inherits, addEventListener: null, - getRandomValues: function (arr: TypedArray, callback?: (err?: Error | null) => void): void { + getRandomValues: function (arr: TypedArray, callback?: (err: Error | null) => void): void { const bytes = crypto.randomBytes(arr.length); arr.set(bytes); if (callback) { From f311c2058166c8d9075b558951e2fc7d77c21b2b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 May 2023 11:48:16 -0300 Subject: [PATCH 037/468] Remove possible boolean result of getRandomWordArray MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/common/types/IPlatformConfig.d.ts | 2 +- src/platform/react-native/config.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index cd78d6bd09..126ac5f73e 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -46,7 +46,7 @@ export interface IPlatformConfig { TextDecoder?: typeof TextDecoder; getRandomWordArray?: ( byteLength: number, - callback: (err: Error, result: boolean | CryptoJS.lib.WordArray) => void + callback: (err: Error | null, result: CryptoJS.lib.WordArray | null) => void ) => void; isWebworker?: boolean; } diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index 4f3c2432aa..1a102406ef 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -34,9 +34,9 @@ const Platform: IPlatformConfig = { TextDecoder: global.TextDecoder, Promise: global.Promise, getRandomWordArray: (function (RNRandomBytes) { - return function (byteLength: number, callback: Function) { - RNRandomBytes.randomBytes(byteLength, function (err: Error, base64String: string) { - callback(err, !err && parseBase64(base64String)); + return function (byteLength: number, callback: (err: Error | null, result: CryptoJS.lib.WordArray | null) => void) { + RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { + callback(err, base64String ? parseBase64(base64String) : null); }); }; // Installing @types/react-native would fix this but conflicts with @types/node From 7b29f33d69438f1c2c828a599ccbb3a78cf16f8b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 4 May 2023 14:21:24 -0300 Subject: [PATCH 038/468] =?UTF-8?q?Make=20Node=20Crypto=E2=80=99s=20isInst?= =?UTF-8?q?CipherParams=20return=20a=20boolean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/platform/nodejs/lib/util/crypto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/nodejs/lib/util/crypto.js b/src/platform/nodejs/lib/util/crypto.js index 1f90ed11b8..c9a057379c 100644 --- a/src/platform/nodejs/lib/util/crypto.js +++ b/src/platform/nodejs/lib/util/crypto.js @@ -113,7 +113,7 @@ var Crypto = (function () { /* In node, can't use instanceof CipherParams due to the vm context problem (see * https://github.com/nwjs/nw.js/wiki/Differences-of-JavaScript-contexts). * So just test for presence of all necessary attributes */ - return params.algorithm && params.key && params.keyLength && params.mode; + return !!(params.algorithm && params.key && params.keyLength && params.mode); } /** From dee05392342a90ec2afd6dd9b2714a5d6f2b5912 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 May 2023 10:22:54 -0300 Subject: [PATCH 039/468] =?UTF-8?q?Pass=20consistent=20number=20of=20argum?= =?UTF-8?q?ents=20to=20web=E2=80=99s=20CBCCipher.prototype.encrypt=20callb?= =?UTF-8?q?ack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To match the signature of the corresponding method in the ICipher interface. Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/platform/web/lib/util/crypto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/lib/util/crypto.js b/src/platform/web/lib/util/crypto.js index fd46786c98..f294ad9b50 100644 --- a/src/platform/web/lib/util/crypto.js +++ b/src/platform/web/lib/util/crypto.js @@ -238,7 +238,7 @@ var CryptoFactory = function (config, bufferUtils) { var then = function () { self.getIv(function (err, iv) { if (err) { - callback(err); + callback(err, null); return; } var cipherOut = self.encryptCipher.process(plaintext.concat(pkcs5Padding[paddedLength - plaintextLength])); @@ -254,7 +254,7 @@ var CryptoFactory = function (config, bufferUtils) { } else { generateRandom(DEFAULT_BLOCKLENGTH, function (err, iv) { if (err) { - callback(err); + callback(err, null); return; } self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: iv }); From a940e496d52d87c275f591eb2f70a5a7fd2ef840 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 May 2023 12:22:14 -0300 Subject: [PATCH 040/468] =?UTF-8?q?Pass=20consistent=20number=20of=20argum?= =?UTF-8?q?ents=20to=20web=E2=80=99s=20CBCCipher.prototype.getIv=20callbac?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/platform/web/lib/util/crypto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/lib/util/crypto.js b/src/platform/web/lib/util/crypto.js index f294ad9b50..e9c3698c50 100644 --- a/src/platform/web/lib/util/crypto.js +++ b/src/platform/web/lib/util/crypto.js @@ -297,7 +297,7 @@ var CryptoFactory = function (config, bufferUtils) { var self = this; generateRandom(DEFAULT_BLOCKLENGTH, function (err, randomBlock) { if (err) { - callback(err); + callback(err, null); return; } callback(null, self.encryptCipher.process(randomBlock)); From 31b4b6f9913a7309228f1749286cbae6cd1b78ea Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 25 May 2023 13:57:04 -0300 Subject: [PATCH 041/468] =?UTF-8?q?Always=20initialize=20web=E2=80=99s=20C?= =?UTF-8?q?BCCipher.iv=20property?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/platform/web/lib/util/crypto.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/web/lib/util/crypto.js b/src/platform/web/lib/util/crypto.js index e9c3698c50..12bc6b4f07 100644 --- a/src/platform/web/lib/util/crypto.js +++ b/src/platform/web/lib/util/crypto.js @@ -222,9 +222,7 @@ var CryptoFactory = function (config, bufferUtils) { this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; this.cjsAlgorithm = params.algorithm.toUpperCase().replace(/-\d+$/, ''); this.key = bufferUtils.toWordArray(params.key); - if (iv) { - this.iv = bufferUtils.toWordArray(iv).clone(); - } + this.iv = iv ? bufferUtils.toWordArray(iv).clone() : null; this.blockLengthWords = blockLengthWords; } From 146b3835252879ec4c5b1b1806eeba19785e2932 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 May 2023 12:00:12 -0300 Subject: [PATCH 042/468] Make CipherParams constructor accept its properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/platform/nodejs/lib/util/crypto.js | 19 +++++++++---------- src/platform/web/lib/util/crypto.js | 19 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/platform/nodejs/lib/util/crypto.js b/src/platform/nodejs/lib/util/crypto.js index c9a057379c..d206191ac6 100644 --- a/src/platform/nodejs/lib/util/crypto.js +++ b/src/platform/nodejs/lib/util/crypto.js @@ -100,11 +100,11 @@ var Crypto = (function () { * Clients may instance a CipherParams directly and populate it, or may * query the implementation to obtain a default system CipherParams. */ - function CipherParams() { - this.algorithm = null; - this.keyLength = null; - this.mode = null; - this.key = null; + function CipherParams(algorithm, keyLength, mode, key) { + this.algorithm = algorithm; + this.keyLength = keyLength; + this.mode = mode; + this.key = key; this.iv = null; } Crypto.CipherParams = CipherParams; @@ -140,11 +140,10 @@ var Crypto = (function () { key = params.key; } - var cipherParams = new CipherParams(); - cipherParams.key = key; - cipherParams.algorithm = params.algorithm || DEFAULT_ALGORITHM; - cipherParams.keyLength = key.length * 8; - cipherParams.mode = params.mode || DEFAULT_MODE; + var algorithm = params.algorithm || DEFAULT_ALGORITHM; + var keyLength = key.length * 8; + var mode = params.mode || DEFAULT_MODE; + var cipherParams = new CipherParams(algorithm, keyLength, mode, key); if (params.keyLength && params.keyLength !== cipherParams.keyLength) { throw new Error( diff --git a/src/platform/web/lib/util/crypto.js b/src/platform/web/lib/util/crypto.js index 12bc6b4f07..0f39a4a539 100644 --- a/src/platform/web/lib/util/crypto.js +++ b/src/platform/web/lib/util/crypto.js @@ -139,11 +139,11 @@ var CryptoFactory = function (config, bufferUtils) { * Crypto.getDefaultParams helper, which will fill in any fields not supplied * with default values and validation the result. */ - function CipherParams() { - this.algorithm = null; - this.keyLength = null; - this.mode = null; - this.key = null; + function CipherParams(algorithm, keyLength, mode, key) { + this.algorithm = algorithm; + this.keyLength = keyLength; + this.mode = mode; + this.key = key; } Crypto.CipherParams = CipherParams; @@ -169,11 +169,10 @@ var CryptoFactory = function (config, bufferUtils) { key = bufferUtils.toWordArray(params.key); // Expect key to be an Array, ArrayBuffer, or WordArray at this point } - var cipherParams = new CipherParams(); - cipherParams.key = key; - cipherParams.algorithm = params.algorithm || DEFAULT_ALGORITHM; - cipherParams.keyLength = key.words.length * (4 * 8); - cipherParams.mode = params.mode || DEFAULT_MODE; + var algorithm = params.algorithm || DEFAULT_ALGORITHM; + var keyLength = key.words.length * (4 * 8); + var mode = params.mode || DEFAULT_MODE; + var cipherParams = new CipherParams(algorithm, keyLength, mode, key); if (params.keyLength && params.keyLength !== cipherParams.keyLength) { throw new Error( From 49aa6e6bbd4884aac8e3d0b59294af1450acfa02 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 17 May 2023 15:09:41 -0300 Subject: [PATCH 043/468] Inject BufferUtils into Node Crypto class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same as we started doing for web in 04a5526. Preparation for converting platforms’ crypto.js files to TypeScript (#1252). It will allow the compiler to type-check the data passed between the crypto code and the BufferUtils object. --- src/platform/nodejs/index.ts | 4 +++- src/platform/nodejs/lib/util/crypto.js | 13 ++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index 96bc51e69d..0b4fc9b66c 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -6,7 +6,7 @@ import Platform from '../../common/platform'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; // @ts-ignore -import Crypto from './lib/util/crypto'; +import CryptoFactory from './lib/util/crypto'; import Http from './lib/util/http'; import Config from './config'; // @ts-ignore @@ -15,6 +15,8 @@ import Logger from '../../common/lib/util/logger'; import { getDefaults } from '../../common/lib/util/defaults'; import PlatformDefaults from './lib/util/defaults'; +const Crypto = CryptoFactory(BufferUtils); + Platform.Crypto = Crypto; Platform.BufferUtils = BufferUtils as typeof Platform.BufferUtils; Platform.Http = Http; diff --git a/src/platform/nodejs/lib/util/crypto.js b/src/platform/nodejs/lib/util/crypto.js index d206191ac6..124f94fbb7 100644 --- a/src/platform/nodejs/lib/util/crypto.js +++ b/src/platform/nodejs/lib/util/crypto.js @@ -1,10 +1,9 @@ 'use strict'; import Logger from '../../../../common/lib/util/logger'; -import Platform from '../../../../common/platform'; import crypto from 'crypto'; import ErrorInfo from '../../../../common/lib/types/errorinfo'; -var Crypto = (function () { +var CryptoFactory = function (bufferUtils) { var DEFAULT_ALGORITHM = 'aes'; var DEFAULT_KEYLENGTH = 256; // bits var DEFAULT_MODE = 'cbc'; @@ -133,8 +132,8 @@ var Crypto = (function () { } if (typeof params.key === 'string') { - key = Platform.BufferUtils.base64Decode(normaliseBase64(params.key)); - } else if (Platform.BufferUtils.isArrayBuffer(params.key)) { + key = bufferUtils.base64Decode(normaliseBase64(params.key)); + } else if (bufferUtils.isArrayBuffer(params.key)) { key = Buffer.from(params.key); } else { key = params.key; @@ -200,7 +199,7 @@ var Crypto = (function () { CBCCipher.prototype.encrypt = function (plaintext, callback) { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - var plaintextBuffer = Platform.BufferUtils.toBuffer(plaintext); + var plaintextBuffer = bufferUtils.toBuffer(plaintext); var plaintextLength = plaintextBuffer.length, paddedLength = getPaddedLength(plaintextLength), iv = this.getIv(); @@ -235,6 +234,6 @@ var Crypto = (function () { }; return Crypto; -})(); +}; -export default Crypto; +export default CryptoFactory; From 53c3a5521022b975719444c66dc4223c83373e7a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 9 May 2023 11:49:02 -0300 Subject: [PATCH 044/468] =?UTF-8?q?Add=20types=20for=20CryptoJS=20function?= =?UTF-8?q?ality=20used=20by=20web=E2=80=99s=20crypto.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for converting platforms’ crypto.js files to TypeScript (#1252). We’ll be removing these types shortly, though, when we stop using the CryptoJS library in #1239. --- src/common/types/crypto-js.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/common/types/crypto-js.d.ts b/src/common/types/crypto-js.d.ts index 8a0c9f9e4b..8ce331501b 100644 --- a/src/common/types/crypto-js.d.ts +++ b/src/common/types/crypto-js.d.ts @@ -1,3 +1,9 @@ +declare module 'crypto-js/build' { + import CryptoJS from 'crypto-js'; + import algo = CryptoJS.algo; + export { algo }; +} + declare module 'crypto-js/build/enc-base64' { import CryptoJS from 'crypto-js'; export const parse: typeof CryptoJS.enc.Base64.parse; From 4a0d6dfde14d51597bcc0e8d85864879459d1bfd Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 16 May 2023 11:26:36 -0300 Subject: [PATCH 045/468] Make Crypto class contiguous in crypto.js files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This just moves things around so that all of the functionality of the Crypto class sits in one place in the file. Preparation for converting platforms’ crypto.js files to TypeScript (#1252). --- src/platform/nodejs/lib/util/crypto.js | 41 +++++++++++++------------- src/platform/web/lib/util/crypto.js | 35 +++++++++++----------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/platform/nodejs/lib/util/crypto.js b/src/platform/nodejs/lib/util/crypto.js index 124f94fbb7..95519963c2 100644 --- a/src/platform/nodejs/lib/util/crypto.js +++ b/src/platform/nodejs/lib/util/crypto.js @@ -70,25 +70,6 @@ var CryptoFactory = function (bufferUtils) { return typeof bufferOrString == 'string' ? Buffer.from(bufferOrString, 'binary') : bufferOrString; } - /** - * Utility classes and interfaces for message payload encryption. - * - * This class supports AES/CBC/PKCS5 with a default keylength of 128 bits - * but supporting other keylengths. Other algorithms and chaining modes are - * not supported directly, but supportable by extending/implementing the base - * classes and interfaces here. - * - * Secure random data for creation of Initialization Vectors (IVs) and keys - * is obtained from the default system SecureRandom. Future extensions of this - * class might make the SecureRandom pluggable or at least seedable with - * client-provided entropy. - * - * Each message payload is encrypted with an IV in CBC mode, and the IV is - * concatenated with the resulting raw ciphertext to construct the "ciphertext" - * data passed to the recipient. - */ - function Crypto() {} - /** * A class encapsulating the client-specifiable parameters for * the cipher. @@ -106,7 +87,6 @@ var CryptoFactory = function (bufferUtils) { this.key = key; this.iv = null; } - Crypto.CipherParams = CipherParams; function isInstCipherParams(params) { /* In node, can't use instanceof CipherParams due to the vm context problem (see @@ -115,6 +95,27 @@ var CryptoFactory = function (bufferUtils) { return !!(params.algorithm && params.key && params.keyLength && params.mode); } + /** + * Utility classes and interfaces for message payload encryption. + * + * This class supports AES/CBC/PKCS5 with a default keylength of 128 bits + * but supporting other keylengths. Other algorithms and chaining modes are + * not supported directly, but supportable by extending/implementing the base + * classes and interfaces here. + * + * Secure random data for creation of Initialization Vectors (IVs) and keys + * is obtained from the default system SecureRandom. Future extensions of this + * class might make the SecureRandom pluggable or at least seedable with + * client-provided entropy. + * + * Each message payload is encrypted with an IV in CBC mode, and the IV is + * concatenated with the resulting raw ciphertext to construct the "ciphertext" + * data passed to the recipient. + */ + function Crypto() {} + + Crypto.CipherParams = CipherParams; + /** * Obtain a complete CipherParams instance from the provided params, filling * in any not provided with default values, calculating a keyLength from diff --git a/src/platform/web/lib/util/crypto.js b/src/platform/web/lib/util/crypto.js index 0f39a4a539..0abaef43f3 100644 --- a/src/platform/web/lib/util/crypto.js +++ b/src/platform/web/lib/util/crypto.js @@ -108,6 +108,24 @@ var CryptoFactory = function (config, bufferUtils) { WordArray.create([0x10101010, 0x10101010, 0x10101010, 0x10101010], 16), ]; + /** + * A class encapsulating the client-specifiable parameters for + * the cipher. + * + * algorithm is the name of the algorithm in the default system provider, + * or the lower-cased version of it; eg "aes" or "AES". + * + * Clients are recommended to not call this directly, but instead to use the + * Crypto.getDefaultParams helper, which will fill in any fields not supplied + * with default values and validation the result. + */ + function CipherParams(algorithm, keyLength, mode, key) { + this.algorithm = algorithm; + this.keyLength = keyLength; + this.mode = mode; + this.key = key; + } + /** * Utility classes and interfaces for message payload encryption. * @@ -128,23 +146,6 @@ var CryptoFactory = function (config, bufferUtils) { */ function Crypto() {} - /** - * A class encapsulating the client-specifiable parameters for - * the cipher. - * - * algorithm is the name of the algorithm in the default system provider, - * or the lower-cased version of it; eg "aes" or "AES". - * - * Clients are recommended to not call this directly, but instead to use the - * Crypto.getDefaultParams helper, which will fill in any fields not supplied - * with default values and validation the result. - */ - function CipherParams(algorithm, keyLength, mode, key) { - this.algorithm = algorithm; - this.keyLength = keyLength; - this.mode = mode; - this.key = key; - } Crypto.CipherParams = CipherParams; /** From 3f5ec6fc7882fdb8dac83c16bb3bf7aa538969c6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 4 May 2023 11:07:32 -0300 Subject: [PATCH 046/468] Convert platform crypto.js files to TypeScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As part of this conversion, I intend to convert Crypto, CipherParams, and CBCCipher to ECMAScript classes, but I’m going to do that in a separate commit so that the conversion to TypeScript is reviewable without being lost in a sea of indentation changes. The underscored interfaces that this commit introduces are temporary and will be removed upon conversion to classes. Resolves #1252. --- .eslintrc.js | 4 + .../nodejs/lib/util/{crypto.js => crypto.ts} | 116 ++++++++++++---- .../web/lib/util/{crypto.js => crypto.ts} | 129 ++++++++++++++---- 3 files changed, 196 insertions(+), 53 deletions(-) rename src/platform/nodejs/lib/util/{crypto.js => crypto.ts} (64%) rename src/platform/web/lib/util/{crypto.js => crypto.ts} (66%) diff --git a/.eslintrc.js b/.eslintrc.js index 87931748ed..dce6ba06c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,10 @@ module.exports = { // see https://github.com/nodesecurity/eslint-plugin-security/issues/21 "security/detect-object-injection": "off", "@typescript-eslint/no-var-requires": "error", + // Use typescript-eslint’s version of the no-redeclare rule, which isn’t triggered by overload signatures. + // TODO remove this once we start using the full @typescript-eslint/recommended ruleset in #958 + 'no-redeclare': 'off', + '@typescript-eslint/no-redeclare': 'error', }, overrides: [ { diff --git a/src/platform/nodejs/lib/util/crypto.js b/src/platform/nodejs/lib/util/crypto.ts similarity index 64% rename from src/platform/nodejs/lib/util/crypto.js rename to src/platform/nodejs/lib/util/crypto.ts index 95519963c2..ad91155a17 100644 --- a/src/platform/nodejs/lib/util/crypto.js +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -2,8 +2,23 @@ import Logger from '../../../../common/lib/util/logger'; import crypto from 'crypto'; import ErrorInfo from '../../../../common/lib/types/errorinfo'; +import * as API from '../../../../../ably'; +import ICryptoStatic, { IGetCipherParams } from '../../../../common/types/ICryptoStatic'; +import ICipher from '../../../../common/types/ICipher'; +import { CryptoDataTypes } from '../../../../common/types/cryptoDataTypes'; +import { Cipher as NodeCipher, CipherKey as NodeCipherKey } from 'crypto'; +import BufferUtils, { Bufferlike, Output as BufferUtilsOutput } from './bufferutils'; -var CryptoFactory = function (bufferUtils) { +// The type to which ably-forks/msgpack-js deserializes elements of the `bin` or `ext` type +type MessagePackBinaryType = Buffer; + +type IV = CryptoDataTypes.IV; +type InputPlaintext = CryptoDataTypes.InputPlaintext; +type OutputCiphertext = Buffer; +type InputCiphertext = CryptoDataTypes.InputCiphertext; +type OutputPlaintext = Buffer; + +var CryptoFactory = function (bufferUtils: typeof BufferUtils) { var DEFAULT_ALGORITHM = 'aes'; var DEFAULT_KEYLENGTH = 256; // bits var DEFAULT_MODE = 'cbc'; @@ -14,7 +29,9 @@ var CryptoFactory = function (bufferUtils) { * @param bytes * @param callback (optional) */ - function generateRandom(bytes, callback) { + function generateRandom(bytes: number): Buffer; + function generateRandom(bytes: number, callback: (err: Error | null, buf: Buffer) => void): void; + function generateRandom(bytes: number, callback?: (err: Error | null, buf: Buffer) => void) { return callback === undefined ? crypto.randomBytes(bytes) : crypto.randomBytes(bytes, callback); } @@ -24,7 +41,7 @@ var CryptoFactory = function (bufferUtils) { * @param plaintextLength * @return */ - function getPaddedLength(plaintextLength) { + function getPaddedLength(plaintextLength: number) { return (plaintextLength + DEFAULT_BLOCKLENGTH) & -DEFAULT_BLOCKLENGTH; } @@ -32,7 +49,7 @@ var CryptoFactory = function (bufferUtils) { * Internal: checks that the cipherParams are a valid combination. Currently * just checks that the calculated keyLength is a valid one for aes-cbc */ - function validateCipherParams(params) { + function validateCipherParams(params: API.Types.CipherParams) { if (params.algorithm === 'aes' && params.mode === 'cbc') { if (params.keyLength === 128 || params.keyLength === 256) { return; @@ -45,7 +62,7 @@ var CryptoFactory = function (bufferUtils) { } } - function normaliseBase64(string) { + function normaliseBase64(string: string) { /* url-safe base64 strings use _ and - instread of / and + */ return string.replace('_', '/').replace('-', '+'); } @@ -53,7 +70,7 @@ var CryptoFactory = function (bufferUtils) { /** * Internal: obtain the pkcs5 padding string for a given padded length; */ - function filledBuffer(length, value) { + function filledBuffer(length: number, value: number) { var result = Buffer.alloc(length); result.fill(value); return result; @@ -66,10 +83,22 @@ var CryptoFactory = function (bufferUtils) { * @param bufferOrString * @returns {Buffer} */ - function toBuffer(bufferOrString) { + function toBuffer(bufferOrString: Buffer | string) { return typeof bufferOrString == 'string' ? Buffer.from(bufferOrString, 'binary') : bufferOrString; } + interface _CipherParams extends API.Types.CipherParams { + algorithm: string; + keyLength: number; + mode: string; + key: NodeCipherKey; + iv: unknown; + } + + interface _CipherParamsConstructor { + new (algorithm: string, keyLength: number, mode: string, key: NodeCipherKey): _CipherParams; + } + /** * A class encapsulating the client-specifiable parameters for * the cipher. @@ -80,21 +109,35 @@ var CryptoFactory = function (bufferUtils) { * Clients may instance a CipherParams directly and populate it, or may * query the implementation to obtain a default system CipherParams. */ - function CipherParams(algorithm, keyLength, mode, key) { + const CipherParams = function ( + this: _CipherParams, + algorithm: string, + keyLength: number, + mode: string, + key: NodeCipherKey + ) { this.algorithm = algorithm; this.keyLength = keyLength; this.mode = mode; this.key = key; this.iv = null; - } + } as unknown as _CipherParamsConstructor; - function isInstCipherParams(params) { + function isInstCipherParams( + params: API.Types.CipherParams | API.Types.CipherParamOptions + ): params is API.Types.CipherParams { /* In node, can't use instanceof CipherParams due to the vm context problem (see * https://github.com/nwjs/nw.js/wiki/Differences-of-JavaScript-contexts). * So just test for presence of all necessary attributes */ return !!(params.algorithm && params.key && params.keyLength && params.mode); } + interface _CryptoStatic + extends ICryptoStatic { + CipherParams: typeof CipherParams; + getDefaultParams(params: API.Types.CipherParamOptions): _CipherParams; + } + /** * Utility classes and interfaces for message payload encryption. * @@ -112,7 +155,7 @@ var CryptoFactory = function (bufferUtils) { * concatenated with the resulting raw ciphertext to construct the "ciphertext" * data passed to the recipient. */ - function Crypto() {} + const Crypto = function () {} as unknown as _CryptoStatic; Crypto.CipherParams = CipherParams; @@ -125,8 +168,8 @@ var CryptoFactory = function (bufferUtils) { * base64-encoded string. May optionally also contain: algorithm (defaults to * AES), mode (defaults to 'cbc') */ - Crypto.getDefaultParams = function (params) { - var key; + Crypto.getDefaultParams = function (params: API.Types.CipherParamOptions) { + var key: NodeCipherKey; if (!params.key) { throw new Error('Crypto.getDefaultParams: a key is required'); @@ -164,7 +207,7 @@ var CryptoFactory = function (bufferUtils) { * @param keyLength (optional) the required keyLength in bits * @param callback (optional) (err, key) */ - Crypto.generateRandomKey = function (keyLength, callback) { + Crypto.generateRandomKey = function (keyLength?: number, callback?: API.Types.StandardCallback) { if (arguments.length == 1 && typeof keyLength == 'function') { callback = keyLength; keyLength = undefined; @@ -182,23 +225,42 @@ var CryptoFactory = function (bufferUtils) { * @param params either a CipherParams instance or some subset of its * fields that includes a key */ - Crypto.getCipher = function (params) { - var cipherParams = isInstCipherParams(params) ? params : Crypto.getDefaultParams(params); + Crypto.getCipher = function (params: IGetCipherParams) { + var cipherParams = isInstCipherParams(params) ? (params as _CipherParams) : Crypto.getDefaultParams(params); var iv = params.iv || generateRandom(DEFAULT_BLOCKLENGTH); - return { cipherParams: cipherParams, cipher: new CBCCipher(cipherParams, iv) }; + return { + cipherParams: cipherParams, + cipher: new CBCCipher(cipherParams, iv), + }; }; - function CBCCipher(params, iv) { - var algorithm = (this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode); - var key = (this.key = params.key); - // eslint-disable-next-line no-redeclare - var iv = (this.iv = iv); - this.encryptCipher = crypto.createCipheriv(algorithm, key, iv); - this.blockLength = iv.length; + interface _CBCCipher extends ICipher { + algorithm: string; + key: NodeCipherKey; + iv: Buffer | null; + encryptCipher: NodeCipher; + blockLength: number; + getIv: () => Buffer; } - CBCCipher.prototype.encrypt = function (plaintext, callback) { + interface _CBCCipherConstructor { + new (params: _CipherParams, iv: Buffer): _CBCCipher; + } + + const CBCCipher = function CBCCipher(this: _CBCCipher, params: _CipherParams, iv: Buffer) { + this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; + this.key = params.key; + this.iv = iv; + this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, this.iv); + this.blockLength = this.iv.length; + } as unknown as _CBCCipherConstructor; + + CBCCipher.prototype.encrypt = function ( + this: _CBCCipher, + plaintext: InputPlaintext, + callback: (error: Error | null, data: OutputCiphertext | null) => void + ) { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); var plaintextBuffer = bufferUtils.toBuffer(plaintext); var plaintextLength = plaintextBuffer.length, @@ -211,7 +273,7 @@ var CryptoFactory = function (bufferUtils) { return callback(null, ciphertext); }; - CBCCipher.prototype.decrypt = function (ciphertext) { + CBCCipher.prototype.decrypt = function (this: _CBCCipher, ciphertext: InputCiphertext) { var blockLength = this.blockLength, decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, blockLength)), plaintext = toBuffer(decryptCipher.update(ciphertext.slice(blockLength))), @@ -220,7 +282,7 @@ var CryptoFactory = function (bufferUtils) { return plaintext; }; - CBCCipher.prototype.getIv = function () { + CBCCipher.prototype.getIv = function (this: _CBCCipher) { if (this.iv) { var iv = this.iv; this.iv = null; diff --git a/src/platform/web/lib/util/crypto.js b/src/platform/web/lib/util/crypto.ts similarity index 66% rename from src/platform/web/lib/util/crypto.js rename to src/platform/web/lib/util/crypto.ts index 0abaef43f3..9506ef538c 100644 --- a/src/platform/web/lib/util/crypto.js +++ b/src/platform/web/lib/util/crypto.ts @@ -3,8 +3,23 @@ import { parse as parseBase64 } from 'crypto-js/build/enc-base64'; import CryptoJS from 'crypto-js/build'; import Logger from '../../../../common/lib/util/logger'; import ErrorInfo from 'common/lib/types/errorinfo'; +import * as API from '../../../../../ably'; +import ICryptoStatic, { IGetCipherParams } from '../../../../common/types/ICryptoStatic'; +import ICipher from '../../../../common/types/ICipher'; +import { CryptoDataTypes } from '../../../../common/types/cryptoDataTypes'; +import BufferUtils, { Bufferlike, Output as BufferUtilsOutput } from './bufferutils'; +import { IPlatformConfig } from 'common/types/IPlatformConfig'; -var CryptoFactory = function (config, bufferUtils) { +// The type to which ./msgpack.ts deserializes elements of the `bin` or `ext` type +type MessagePackBinaryType = ArrayBuffer; + +type IV = CryptoDataTypes.IV; +type InputPlaintext = CryptoDataTypes.InputPlaintext; +type OutputCiphertext = WordArray; +type InputCiphertext = CryptoDataTypes.InputCiphertext; +type OutputPlaintext = WordArray; + +var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof BufferUtils) { var DEFAULT_ALGORITHM = 'aes'; var DEFAULT_KEYLENGTH = 256; // bits var DEFAULT_MODE = 'cbc'; @@ -18,7 +33,7 @@ var CryptoFactory = function (config, bufferUtils) { * @param bytes * @param callback */ - var generateRandom; + var generateRandom: (byteLength: number, callback: (error: Error | null, result: WordArray | null) => void) => void; if (config.getRandomWordArray) { generateRandom = config.getRandomWordArray; } else if (typeof Uint32Array !== 'undefined' && config.getRandomValues) { @@ -26,7 +41,7 @@ var CryptoFactory = function (config, bufferUtils) { generateRandom = function (bytes, callback) { var words = bytes / 4, nativeArray = words == DEFAULT_BLOCKLENGTH_WORDS ? blockRandomArray : new Uint32Array(words); - config.getRandomValues(nativeArray, function (err) { + config.getRandomValues!(nativeArray, function (err) { if (typeof callback !== 'undefined') { callback(err, bufferUtils.toWordArray(nativeArray)); } @@ -59,7 +74,7 @@ var CryptoFactory = function (config, bufferUtils) { * @param plaintextLength * @return */ - function getPaddedLength(plaintextLength) { + function getPaddedLength(plaintextLength: number) { return (plaintextLength + DEFAULT_BLOCKLENGTH) & -DEFAULT_BLOCKLENGTH; } @@ -67,7 +82,7 @@ var CryptoFactory = function (config, bufferUtils) { * Internal: checks that the cipherParams are a valid combination. Currently * just checks that the calculated keyLength is a valid one for aes-cbc */ - function validateCipherParams(params) { + function validateCipherParams(params: API.Types.CipherParams) { if (params.algorithm === 'aes' && params.mode === 'cbc') { if (params.keyLength === 128 || params.keyLength === 256) { return; @@ -80,7 +95,7 @@ var CryptoFactory = function (config, bufferUtils) { } } - function normaliseBase64(string) { + function normaliseBase64(string: string) { /* url-safe base64 strings use _ and - instread of / and + */ return string.replace('_', '/').replace('-', '+'); } @@ -108,6 +123,24 @@ var CryptoFactory = function (config, bufferUtils) { WordArray.create([0x10101010, 0x10101010, 0x10101010, 0x10101010], 16), ]; + function isCipherParams( + params: API.Types.CipherParams | API.Types.CipherParamOptions + ): params is API.Types.CipherParams { + // Although API.Types.CipherParams is an interface, the documentation for its `key` property makes it clear that the only valid way to form one is by using getDefaultParams. The implementation of getDefaultParams returns an instance of CipherParams. + return params instanceof CipherParams; + } + + interface _CipherParams extends API.Types.CipherParams { + algorithm: string; + keyLength: number; + mode: string; + key: WordArray; + } + + interface _CipherParamsConstructor { + new (algorithm: string, keyLength: number, mode: string, key: WordArray): _CipherParams; + } + /** * A class encapsulating the client-specifiable parameters for * the cipher. @@ -119,11 +152,23 @@ var CryptoFactory = function (config, bufferUtils) { * Crypto.getDefaultParams helper, which will fill in any fields not supplied * with default values and validation the result. */ - function CipherParams(algorithm, keyLength, mode, key) { + const CipherParams = function ( + this: _CipherParams, + algorithm: string, + keyLength: number, + mode: string, + key: WordArray + ) { this.algorithm = algorithm; this.keyLength = keyLength; this.mode = mode; this.key = key; + } as unknown as _CipherParamsConstructor; + + interface _CryptoStatic + extends ICryptoStatic { + CipherParams: typeof CipherParams; + getDefaultParams(params: API.Types.CipherParamOptions): _CipherParams; } /** @@ -144,7 +189,7 @@ var CryptoFactory = function (config, bufferUtils) { * concatenated with the resulting raw ciphertext to construct the "ciphertext" * data passed to the recipient. */ - function Crypto() {} + const Crypto = function () {} as unknown as _CryptoStatic; Crypto.CipherParams = CipherParams; @@ -157,8 +202,8 @@ var CryptoFactory = function (config, bufferUtils) { * base64-encoded string. May optionally also contain: algorithm (defaults to * AES), mode (defaults to 'cbc') */ - Crypto.getDefaultParams = function (params) { - var key; + Crypto.getDefaultParams = function (params: API.Types.CipherParamOptions) { + var key: WordArray; if (!params.key) { throw new Error('Crypto.getDefaultParams: a key is required'); @@ -194,7 +239,7 @@ var CryptoFactory = function (config, bufferUtils) { * @param keyLength (optional) the required keyLength in bits * @param callback (optional) (err, key) */ - Crypto.generateRandomKey = function (keyLength, callback) { + Crypto.generateRandomKey = function (keyLength?: number, callback?: API.Types.StandardCallback) { if (arguments.length == 1 && typeof keyLength == 'function') { callback = keyLength; keyLength = undefined; @@ -212,24 +257,51 @@ var CryptoFactory = function (config, bufferUtils) { * @param params either a CipherParams instance or some subset of its * fields that includes a key */ - Crypto.getCipher = function (params) { - var cipherParams = params instanceof CipherParams ? params : Crypto.getDefaultParams(params); + Crypto.getCipher = function (params: IGetCipherParams) { + var cipherParams = isCipherParams(params) ? (params as _CipherParams) : Crypto.getDefaultParams(params); - return { cipherParams: cipherParams, cipher: new CBCCipher(cipherParams, DEFAULT_BLOCKLENGTH_WORDS, params.iv) }; + return { + cipherParams: cipherParams, + cipher: new CBCCipher(cipherParams, DEFAULT_BLOCKLENGTH_WORDS, params.iv ?? null), + }; }; - function CBCCipher(params, blockLengthWords, iv) { + // This is the only way I could think of to get a reference to the Cipher type, which doesn’t seem to be exported by CryptoJS’s type definitions file. + type CryptoJSCipher = ReturnType; + + interface _CBCCipher extends ICipher { + algorithm: string; + // All of the keys in the CryptoJS.algo namespace whose value is a CipherStatic. + cjsAlgorithm: 'AES' | 'DES' | 'TripleDES' | 'RC4' | 'RC4Drop' | 'Rabbit' | 'RabbitLegacy'; + key: WordArray; + iv: WordArray | null; + blockLengthWords: number; + encryptCipher: CryptoJSCipher | null; + getIv: (callback: (error: Error | null, iv: WordArray | null) => void) => void; + } + + interface _CBCCipherConstructor { + new (params: _CipherParams, blockLengthWords: number, iv: IV | null): _CBCCipher; + } + + const CBCCipher = function (this: _CBCCipher, params: _CipherParams, blockLengthWords: number, iv: IV | null) { this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; - this.cjsAlgorithm = params.algorithm.toUpperCase().replace(/-\d+$/, ''); + // We trust that we can handle the algorithm specified by the user — this is the same as the pre-TypeScript behaviour. + this.cjsAlgorithm = params.algorithm.toUpperCase().replace(/-\d+$/, '') as typeof this.cjsAlgorithm; this.key = bufferUtils.toWordArray(params.key); this.iv = iv ? bufferUtils.toWordArray(iv).clone() : null; this.blockLengthWords = blockLengthWords; - } + this.encryptCipher = null; + } as unknown as _CBCCipherConstructor; - CBCCipher.prototype.encrypt = function (plaintext, callback) { + CBCCipher.prototype.encrypt = function ( + this: _CBCCipher, + plaintext: InputPlaintext, + callback: (error: Error | null, data: OutputCiphertext | null) => void + ) { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - plaintext = bufferUtils.toWordArray(plaintext); - var plaintextLength = plaintext.sigBytes, + const plaintextWordArray = bufferUtils.toWordArray(plaintext); + var plaintextLength = plaintextWordArray.sigBytes, paddedLength = getPaddedLength(plaintextLength), self = this; @@ -239,8 +311,10 @@ var CryptoFactory = function (config, bufferUtils) { callback(err, null); return; } - var cipherOut = self.encryptCipher.process(plaintext.concat(pkcs5Padding[paddedLength - plaintextLength])); - var ciphertext = iv.concat(cipherOut); + var cipherOut = self.encryptCipher!.process( + plaintextWordArray.concat(pkcs5Padding[paddedLength - plaintextLength]) + ); + var ciphertext = iv!.concat(cipherOut); callback(null, ciphertext); }); }; @@ -255,7 +329,7 @@ var CryptoFactory = function (config, bufferUtils) { callback(err, null); return; } - self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: iv }); + self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: iv! }); self.iv = iv; then(); }); @@ -265,7 +339,7 @@ var CryptoFactory = function (config, bufferUtils) { } }; - CBCCipher.prototype.decrypt = function (ciphertext) { + CBCCipher.prototype.decrypt = function (this: _CBCCipher, ciphertext: InputCiphertext) { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.decrypt()', ''); ciphertext = bufferUtils.toWordArray(ciphertext); var blockLengthWords = this.blockLengthWords, @@ -281,7 +355,10 @@ var CryptoFactory = function (config, bufferUtils) { return plaintext; }; - CBCCipher.prototype.getIv = function (callback) { + CBCCipher.prototype.getIv = function ( + this: _CBCCipher, + callback: (error: Error | null, iv: WordArray | null) => void + ) { if (this.iv) { var iv = this.iv; this.iv = null; @@ -298,7 +375,7 @@ var CryptoFactory = function (config, bufferUtils) { callback(err, null); return; } - callback(null, self.encryptCipher.process(randomBlock)); + callback(null, self.encryptCipher!.process(randomBlock!)); }); }; From b7977e4f25641b423331864a2151b80806d89728 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 4 May 2023 15:05:20 -0300 Subject: [PATCH 047/468] Convert platform crypto.ts files to use classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s hard to tell what’s happened here due to all of the indentation changes. It’s the following: - remove the temporary underscored interfaces introduced in 3f5ec6f, and integrate their contents into the class constructor and body - convert prototype function properties to methods (removing the `this` argument) - convert Crypto.CipherParams to a property - add `satisfies` operator for compiler to validate that static methods of Crypto classes implement ICryptoStatic (approach suggested in [1]) [1] https://github.com/microsoft/TypeScript/issues/33892 --- src/platform/nodejs/lib/util/crypto.ts | 279 ++++++++++---------- src/platform/web/lib/util/crypto.ts | 338 ++++++++++++------------- 2 files changed, 284 insertions(+), 333 deletions(-) diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index ad91155a17..3eecf1e649 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -87,18 +87,6 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { return typeof bufferOrString == 'string' ? Buffer.from(bufferOrString, 'binary') : bufferOrString; } - interface _CipherParams extends API.Types.CipherParams { - algorithm: string; - keyLength: number; - mode: string; - key: NodeCipherKey; - iv: unknown; - } - - interface _CipherParamsConstructor { - new (algorithm: string, keyLength: number, mode: string, key: NodeCipherKey): _CipherParams; - } - /** * A class encapsulating the client-specifiable parameters for * the cipher. @@ -109,19 +97,21 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { * Clients may instance a CipherParams directly and populate it, or may * query the implementation to obtain a default system CipherParams. */ - const CipherParams = function ( - this: _CipherParams, - algorithm: string, - keyLength: number, - mode: string, - key: NodeCipherKey - ) { - this.algorithm = algorithm; - this.keyLength = keyLength; - this.mode = mode; - this.key = key; - this.iv = null; - } as unknown as _CipherParamsConstructor; + class CipherParams implements API.Types.CipherParams { + algorithm: string; + keyLength: number; + mode: string; + key: NodeCipherKey; + iv: unknown; + + constructor(algorithm: string, keyLength: number, mode: string, key: NodeCipherKey) { + this.algorithm = algorithm; + this.keyLength = keyLength; + this.mode = mode; + this.key = key; + this.iv = null; + } + } function isInstCipherParams( params: API.Types.CipherParams | API.Types.CipherParamOptions @@ -132,12 +122,6 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { return !!(params.algorithm && params.key && params.keyLength && params.mode); } - interface _CryptoStatic - extends ICryptoStatic { - CipherParams: typeof CipherParams; - getDefaultParams(params: API.Types.CipherParamOptions): _CipherParams; - } - /** * Utility classes and interfaces for message payload encryption. * @@ -155,146 +139,139 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { * concatenated with the resulting raw ciphertext to construct the "ciphertext" * data passed to the recipient. */ - const Crypto = function () {} as unknown as _CryptoStatic; - - Crypto.CipherParams = CipherParams; + class Crypto { + static CipherParams = CipherParams; + + /** + * Obtain a complete CipherParams instance from the provided params, filling + * in any not provided with default values, calculating a keyLength from + * the supplied key, and validating the result. + * @param params an object containing at a minimum a `key` key with value the + * key, as either a binary (ArrayBuffer, Array, WordArray) or a + * base64-encoded string. May optionally also contain: algorithm (defaults to + * AES), mode (defaults to 'cbc') + */ + static getDefaultParams(params: API.Types.CipherParamOptions) { + var key: NodeCipherKey; + + if (!params.key) { + throw new Error('Crypto.getDefaultParams: a key is required'); + } - /** - * Obtain a complete CipherParams instance from the provided params, filling - * in any not provided with default values, calculating a keyLength from - * the supplied key, and validating the result. - * @param params an object containing at a minimum a `key` key with value the - * key, as either a binary (ArrayBuffer, Array, WordArray) or a - * base64-encoded string. May optionally also contain: algorithm (defaults to - * AES), mode (defaults to 'cbc') - */ - Crypto.getDefaultParams = function (params: API.Types.CipherParamOptions) { - var key: NodeCipherKey; + if (typeof params.key === 'string') { + key = bufferUtils.base64Decode(normaliseBase64(params.key)); + } else if (bufferUtils.isArrayBuffer(params.key)) { + key = Buffer.from(params.key); + } else { + key = params.key; + } - if (!params.key) { - throw new Error('Crypto.getDefaultParams: a key is required'); - } + var algorithm = params.algorithm || DEFAULT_ALGORITHM; + var keyLength = key.length * 8; + var mode = params.mode || DEFAULT_MODE; + var cipherParams = new CipherParams(algorithm, keyLength, mode, key); + + if (params.keyLength && params.keyLength !== cipherParams.keyLength) { + throw new Error( + 'Crypto.getDefaultParams: a keyLength of ' + + params.keyLength + + ' was specified, but the key actually has length ' + + cipherParams.keyLength + ); + } - if (typeof params.key === 'string') { - key = bufferUtils.base64Decode(normaliseBase64(params.key)); - } else if (bufferUtils.isArrayBuffer(params.key)) { - key = Buffer.from(params.key); - } else { - key = params.key; + validateCipherParams(cipherParams); + return cipherParams; } - var algorithm = params.algorithm || DEFAULT_ALGORITHM; - var keyLength = key.length * 8; - var mode = params.mode || DEFAULT_MODE; - var cipherParams = new CipherParams(algorithm, keyLength, mode, key); + /** + * Generate a random encryption key from the supplied keylength (or the + * default keyLength if none supplied) as a Buffer + * @param keyLength (optional) the required keyLength in bits + * @param callback (optional) (err, key) + */ + static generateRandomKey(keyLength?: number, callback?: API.Types.StandardCallback) { + if (arguments.length == 1 && typeof keyLength == 'function') { + callback = keyLength; + keyLength = undefined; + } - if (params.keyLength && params.keyLength !== cipherParams.keyLength) { - throw new Error( - 'Crypto.getDefaultParams: a keyLength of ' + - params.keyLength + - ' was specified, but the key actually has length ' + - cipherParams.keyLength - ); + generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { + if (callback !== undefined) { + callback(err ? ErrorInfo.fromValues(err) : null, buf); + } + }); } - validateCipherParams(cipherParams); - return cipherParams; - }; - - /** - * Generate a random encryption key from the supplied keylength (or the - * default keyLength if none supplied) as a Buffer - * @param keyLength (optional) the required keyLength in bits - * @param callback (optional) (err, key) - */ - Crypto.generateRandomKey = function (keyLength?: number, callback?: API.Types.StandardCallback) { - if (arguments.length == 1 && typeof keyLength == 'function') { - callback = keyLength; - keyLength = undefined; + /** + * Internal; get a ChannelCipher instance based on the given cipherParams + * @param params either a CipherParams instance or some subset of its + * fields that includes a key + */ + static getCipher(params: IGetCipherParams) { + var cipherParams = isInstCipherParams(params) ? (params as CipherParams) : this.getDefaultParams(params); + + var iv = params.iv || generateRandom(DEFAULT_BLOCKLENGTH); + return { + cipherParams: cipherParams, + cipher: new CBCCipher(cipherParams, iv), + }; } + } - generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { - if (callback !== undefined) { - callback(err ? ErrorInfo.fromValues(err) : null, buf); - } - }); - }; - - /** - * Internal; get a ChannelCipher instance based on the given cipherParams - * @param params either a CipherParams instance or some subset of its - * fields that includes a key - */ - Crypto.getCipher = function (params: IGetCipherParams) { - var cipherParams = isInstCipherParams(params) ? (params as _CipherParams) : Crypto.getDefaultParams(params); - - var iv = params.iv || generateRandom(DEFAULT_BLOCKLENGTH); - return { - cipherParams: cipherParams, - cipher: new CBCCipher(cipherParams, iv), - }; - }; + Crypto satisfies ICryptoStatic; - interface _CBCCipher extends ICipher { + class CBCCipher implements ICipher { algorithm: string; key: NodeCipherKey; iv: Buffer | null; encryptCipher: NodeCipher; blockLength: number; - getIv: () => Buffer; - } - interface _CBCCipherConstructor { - new (params: _CipherParams, iv: Buffer): _CBCCipher; - } + constructor(params: CipherParams, iv: Buffer) { + this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; + this.key = params.key; + this.iv = iv; + this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, this.iv); + this.blockLength = this.iv.length; + } - const CBCCipher = function CBCCipher(this: _CBCCipher, params: _CipherParams, iv: Buffer) { - this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; - this.key = params.key; - this.iv = iv; - this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, this.iv); - this.blockLength = this.iv.length; - } as unknown as _CBCCipherConstructor; - - CBCCipher.prototype.encrypt = function ( - this: _CBCCipher, - plaintext: InputPlaintext, - callback: (error: Error | null, data: OutputCiphertext | null) => void - ) { - Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - var plaintextBuffer = bufferUtils.toBuffer(plaintext); - var plaintextLength = plaintextBuffer.length, - paddedLength = getPaddedLength(plaintextLength), - iv = this.getIv(); - var cipherOut = this.encryptCipher.update( - Buffer.concat([plaintextBuffer, pkcs5Padding[paddedLength - plaintextLength]]) - ); - var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]); - return callback(null, ciphertext); - }; - - CBCCipher.prototype.decrypt = function (this: _CBCCipher, ciphertext: InputCiphertext) { - var blockLength = this.blockLength, - decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, blockLength)), - plaintext = toBuffer(decryptCipher.update(ciphertext.slice(blockLength))), - final = decryptCipher.final(); - if (final && final.length) plaintext = Buffer.concat([plaintext, toBuffer(final)]); - return plaintext; - }; - - CBCCipher.prototype.getIv = function (this: _CBCCipher) { - if (this.iv) { - var iv = this.iv; - this.iv = null; - return iv; + encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) { + Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); + var plaintextBuffer = bufferUtils.toBuffer(plaintext); + var plaintextLength = plaintextBuffer.length, + paddedLength = getPaddedLength(plaintextLength), + iv = this.getIv(); + var cipherOut = this.encryptCipher.update( + Buffer.concat([plaintextBuffer, pkcs5Padding[paddedLength - plaintextLength]]) + ); + var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]); + return callback(null, ciphertext); } - var randomBlock = generateRandom(DEFAULT_BLOCKLENGTH); - /* Since the iv for a new block is the ciphertext of the last, this - * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as - * returning it */ - return toBuffer(this.encryptCipher.update(randomBlock)); - }; + decrypt(ciphertext: InputCiphertext) { + var blockLength = this.blockLength, + decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, blockLength)), + plaintext = toBuffer(decryptCipher.update(ciphertext.slice(blockLength))), + final = decryptCipher.final(); + if (final && final.length) plaintext = Buffer.concat([plaintext, toBuffer(final)]); + return plaintext; + } + + getIv() { + if (this.iv) { + var iv = this.iv; + this.iv = null; + return iv; + } + + var randomBlock = generateRandom(DEFAULT_BLOCKLENGTH); + /* Since the iv for a new block is the ciphertext of the last, this + * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as + * returning it */ + return toBuffer(this.encryptCipher.update(randomBlock)); + } + } return Crypto; }; diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 9506ef538c..8f7fe5526d 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -130,17 +130,6 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe return params instanceof CipherParams; } - interface _CipherParams extends API.Types.CipherParams { - algorithm: string; - keyLength: number; - mode: string; - key: WordArray; - } - - interface _CipherParamsConstructor { - new (algorithm: string, keyLength: number, mode: string, key: WordArray): _CipherParams; - } - /** * A class encapsulating the client-specifiable parameters for * the cipher. @@ -152,23 +141,18 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe * Crypto.getDefaultParams helper, which will fill in any fields not supplied * with default values and validation the result. */ - const CipherParams = function ( - this: _CipherParams, - algorithm: string, - keyLength: number, - mode: string, - key: WordArray - ) { - this.algorithm = algorithm; - this.keyLength = keyLength; - this.mode = mode; - this.key = key; - } as unknown as _CipherParamsConstructor; - - interface _CryptoStatic - extends ICryptoStatic { - CipherParams: typeof CipherParams; - getDefaultParams(params: API.Types.CipherParamOptions): _CipherParams; + class CipherParams implements API.Types.CipherParams { + algorithm: string; + keyLength: number; + mode: string; + key: WordArray; + + constructor(algorithm: string, keyLength: number, mode: string, key: WordArray) { + this.algorithm = algorithm; + this.keyLength = keyLength; + this.mode = mode; + this.key = key; + } } /** @@ -189,87 +173,89 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe * concatenated with the resulting raw ciphertext to construct the "ciphertext" * data passed to the recipient. */ - const Crypto = function () {} as unknown as _CryptoStatic; - - Crypto.CipherParams = CipherParams; + class Crypto { + static CipherParams = CipherParams; + + /** + * Obtain a complete CipherParams instance from the provided params, filling + * in any not provided with default values, calculating a keyLength from + * the supplied key, and validating the result. + * @param params an object containing at a minimum a `key` key with value the + * key, as either a binary (ArrayBuffer, Array, WordArray) or a + * base64-encoded string. May optionally also contain: algorithm (defaults to + * AES), mode (defaults to 'cbc') + */ + static getDefaultParams(params: API.Types.CipherParamOptions) { + var key: WordArray; + + if (!params.key) { + throw new Error('Crypto.getDefaultParams: a key is required'); + } - /** - * Obtain a complete CipherParams instance from the provided params, filling - * in any not provided with default values, calculating a keyLength from - * the supplied key, and validating the result. - * @param params an object containing at a minimum a `key` key with value the - * key, as either a binary (ArrayBuffer, Array, WordArray) or a - * base64-encoded string. May optionally also contain: algorithm (defaults to - * AES), mode (defaults to 'cbc') - */ - Crypto.getDefaultParams = function (params: API.Types.CipherParamOptions) { - var key: WordArray; + if (typeof params.key === 'string') { + key = parseBase64(normaliseBase64(params.key)); + } else { + key = bufferUtils.toWordArray(params.key); // Expect key to be an Array, ArrayBuffer, or WordArray at this point + } - if (!params.key) { - throw new Error('Crypto.getDefaultParams: a key is required'); - } + var algorithm = params.algorithm || DEFAULT_ALGORITHM; + var keyLength = key.words.length * (4 * 8); + var mode = params.mode || DEFAULT_MODE; + var cipherParams = new CipherParams(algorithm, keyLength, mode, key); + + if (params.keyLength && params.keyLength !== cipherParams.keyLength) { + throw new Error( + 'Crypto.getDefaultParams: a keyLength of ' + + params.keyLength + + ' was specified, but the key actually has length ' + + cipherParams.keyLength + ); + } - if (typeof params.key === 'string') { - key = parseBase64(normaliseBase64(params.key)); - } else { - key = bufferUtils.toWordArray(params.key); // Expect key to be an Array, ArrayBuffer, or WordArray at this point + validateCipherParams(cipherParams); + return cipherParams; } - var algorithm = params.algorithm || DEFAULT_ALGORITHM; - var keyLength = key.words.length * (4 * 8); - var mode = params.mode || DEFAULT_MODE; - var cipherParams = new CipherParams(algorithm, keyLength, mode, key); + /** + * Generate a random encryption key from the supplied keylength (or the + * default keyLength if none supplied) as a CryptoJS WordArray + * @param keyLength (optional) the required keyLength in bits + * @param callback (optional) (err, key) + */ + static generateRandomKey(keyLength?: number, callback?: API.Types.StandardCallback) { + if (arguments.length == 1 && typeof keyLength == 'function') { + callback = keyLength; + keyLength = undefined; + } - if (params.keyLength && params.keyLength !== cipherParams.keyLength) { - throw new Error( - 'Crypto.getDefaultParams: a keyLength of ' + - params.keyLength + - ' was specified, but the key actually has length ' + - cipherParams.keyLength - ); + generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { + if (callback !== undefined) { + callback(err ? ErrorInfo.fromValues(err) : null, buf); + } + }); } - validateCipherParams(cipherParams); - return cipherParams; - }; - - /** - * Generate a random encryption key from the supplied keylength (or the - * default keyLength if none supplied) as a CryptoJS WordArray - * @param keyLength (optional) the required keyLength in bits - * @param callback (optional) (err, key) - */ - Crypto.generateRandomKey = function (keyLength?: number, callback?: API.Types.StandardCallback) { - if (arguments.length == 1 && typeof keyLength == 'function') { - callback = keyLength; - keyLength = undefined; + /** + * Internal; get a ChannelCipher instance based on the given cipherParams + * @param params either a CipherParams instance or some subset of its + * fields that includes a key + */ + static getCipher(params: IGetCipherParams) { + var cipherParams = isCipherParams(params) ? (params as CipherParams) : this.getDefaultParams(params); + + return { + cipherParams: cipherParams, + cipher: new CBCCipher(cipherParams, DEFAULT_BLOCKLENGTH_WORDS, params.iv ?? null), + }; } + } - generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { - if (callback !== undefined) { - callback(err ? ErrorInfo.fromValues(err) : null, buf); - } - }); - }; - - /** - * Internal; get a ChannelCipher instance based on the given cipherParams - * @param params either a CipherParams instance or some subset of its - * fields that includes a key - */ - Crypto.getCipher = function (params: IGetCipherParams) { - var cipherParams = isCipherParams(params) ? (params as _CipherParams) : Crypto.getDefaultParams(params); - - return { - cipherParams: cipherParams, - cipher: new CBCCipher(cipherParams, DEFAULT_BLOCKLENGTH_WORDS, params.iv ?? null), - }; - }; + Crypto satisfies ICryptoStatic; // This is the only way I could think of to get a reference to the Cipher type, which doesn’t seem to be exported by CryptoJS’s type definitions file. type CryptoJSCipher = ReturnType; - interface _CBCCipher extends ICipher { + class CBCCipher implements ICipher { algorithm: string; // All of the keys in the CryptoJS.algo namespace whose value is a CipherStatic. cjsAlgorithm: 'AES' | 'DES' | 'TripleDES' | 'RC4' | 'RC4Drop' | 'Rabbit' | 'RabbitLegacy'; @@ -277,107 +263,95 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe iv: WordArray | null; blockLengthWords: number; encryptCipher: CryptoJSCipher | null; - getIv: (callback: (error: Error | null, iv: WordArray | null) => void) => void; - } - interface _CBCCipherConstructor { - new (params: _CipherParams, blockLengthWords: number, iv: IV | null): _CBCCipher; - } + constructor(params: CipherParams, blockLengthWords: number, iv: IV | null) { + this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; + // We trust that we can handle the algorithm specified by the user — this is the same as the pre-TypeScript behaviour. + this.cjsAlgorithm = params.algorithm.toUpperCase().replace(/-\d+$/, '') as typeof this.cjsAlgorithm; + this.key = bufferUtils.toWordArray(params.key); + this.iv = iv ? bufferUtils.toWordArray(iv).clone() : null; + this.blockLengthWords = blockLengthWords; + this.encryptCipher = null; + } - const CBCCipher = function (this: _CBCCipher, params: _CipherParams, blockLengthWords: number, iv: IV | null) { - this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; - // We trust that we can handle the algorithm specified by the user — this is the same as the pre-TypeScript behaviour. - this.cjsAlgorithm = params.algorithm.toUpperCase().replace(/-\d+$/, '') as typeof this.cjsAlgorithm; - this.key = bufferUtils.toWordArray(params.key); - this.iv = iv ? bufferUtils.toWordArray(iv).clone() : null; - this.blockLengthWords = blockLengthWords; - this.encryptCipher = null; - } as unknown as _CBCCipherConstructor; - - CBCCipher.prototype.encrypt = function ( - this: _CBCCipher, - plaintext: InputPlaintext, - callback: (error: Error | null, data: OutputCiphertext | null) => void - ) { - Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - const plaintextWordArray = bufferUtils.toWordArray(plaintext); - var plaintextLength = plaintextWordArray.sigBytes, - paddedLength = getPaddedLength(plaintextLength), - self = this; - - var then = function () { - self.getIv(function (err, iv) { - if (err) { - callback(err, null); - return; - } - var cipherOut = self.encryptCipher!.process( - plaintextWordArray.concat(pkcs5Padding[paddedLength - plaintextLength]) - ); - var ciphertext = iv!.concat(cipherOut); - callback(null, ciphertext); - }); - }; + encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) { + Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); + const plaintextWordArray = bufferUtils.toWordArray(plaintext); + var plaintextLength = plaintextWordArray.sigBytes, + paddedLength = getPaddedLength(plaintextLength), + self = this; - if (!this.encryptCipher) { - if (this.iv) { - this.encryptCipher = CryptoJS.algo[this.cjsAlgorithm].createEncryptor(this.key, { iv: this.iv }); - then(); - } else { - generateRandom(DEFAULT_BLOCKLENGTH, function (err, iv) { + var then = function () { + self.getIv(function (err, iv) { if (err) { callback(err, null); return; } - self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: iv! }); - self.iv = iv; - then(); + var cipherOut = self.encryptCipher!.process( + plaintextWordArray.concat(pkcs5Padding[paddedLength - plaintextLength]) + ); + var ciphertext = iv!.concat(cipherOut); + callback(null, ciphertext); }); + }; + + if (!this.encryptCipher) { + if (this.iv) { + this.encryptCipher = CryptoJS.algo[this.cjsAlgorithm].createEncryptor(this.key, { iv: this.iv }); + then(); + } else { + generateRandom(DEFAULT_BLOCKLENGTH, function (err, iv) { + if (err) { + callback(err, null); + return; + } + self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: iv! }); + self.iv = iv; + then(); + }); + } + } else { + then(); } - } else { - then(); } - }; - - CBCCipher.prototype.decrypt = function (this: _CBCCipher, ciphertext: InputCiphertext) { - Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.decrypt()', ''); - ciphertext = bufferUtils.toWordArray(ciphertext); - var blockLengthWords = this.blockLengthWords, - ciphertextWords = ciphertext.words, - iv = WordArray.create(ciphertextWords.slice(0, blockLengthWords)), - ciphertextBody = WordArray.create(ciphertextWords.slice(blockLengthWords)); - - var decryptCipher = CryptoJS.algo[this.cjsAlgorithm].createDecryptor(this.key, { iv: iv }); - var plaintext = decryptCipher.process(ciphertextBody); - var epilogue = decryptCipher.finalize(); - decryptCipher.reset(); - if (epilogue && epilogue.sigBytes) plaintext.concat(epilogue); - return plaintext; - }; - - CBCCipher.prototype.getIv = function ( - this: _CBCCipher, - callback: (error: Error | null, iv: WordArray | null) => void - ) { - if (this.iv) { - var iv = this.iv; - this.iv = null; - callback(null, iv); - return; + + decrypt(ciphertext: InputCiphertext) { + Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.decrypt()', ''); + ciphertext = bufferUtils.toWordArray(ciphertext); + var blockLengthWords = this.blockLengthWords, + ciphertextWords = ciphertext.words, + iv = WordArray.create(ciphertextWords.slice(0, blockLengthWords)), + ciphertextBody = WordArray.create(ciphertextWords.slice(blockLengthWords)); + + var decryptCipher = CryptoJS.algo[this.cjsAlgorithm].createDecryptor(this.key, { iv: iv }); + var plaintext = decryptCipher.process(ciphertextBody); + var epilogue = decryptCipher.finalize(); + decryptCipher.reset(); + if (epilogue && epilogue.sigBytes) plaintext.concat(epilogue); + return plaintext; } - /* Since the iv for a new block is the ciphertext of the last, this - * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as - * returning it */ - var self = this; - generateRandom(DEFAULT_BLOCKLENGTH, function (err, randomBlock) { - if (err) { - callback(err, null); + getIv(callback: (error: Error | null, iv: WordArray | null) => void) { + if (this.iv) { + var iv = this.iv; + this.iv = null; + callback(null, iv); return; } - callback(null, self.encryptCipher!.process(randomBlock!)); - }); - }; + + /* Since the iv for a new block is the ciphertext of the last, this + * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as + * returning it */ + var self = this; + generateRandom(DEFAULT_BLOCKLENGTH, function (err, randomBlock) { + if (err) { + callback(err, null); + return; + } + callback(null, self.encryptCipher!.process(randomBlock!)); + }); + } + } return Crypto; }; From ab22a5693f5f303ac918685088550346f93380d2 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 25 May 2023 17:13:28 -0300 Subject: [PATCH 048/468] Rename Channels#onChannelMessage As part of #1293 (making ICipher.decrypt asynchronous), we will be making this method async, and another method will wish to wait on its completion. As such, this should no longer be named as if it were only an informative callback. --- src/common/lib/client/realtime.ts | 6 +++--- src/common/lib/transport/connectionmanager.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/realtime.ts index 55c83184ec..e1e6b5eb2a 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/realtime.ts @@ -83,12 +83,12 @@ class Channels extends EventEmitter { } } - onChannelMessage(msg: ProtocolMessage) { + processChannelMessage(msg: ProtocolMessage) { const channelName = msg.channel; if (channelName === undefined) { Logger.logAction( Logger.LOG_ERROR, - 'Channels.onChannelMessage()', + 'Channels.processChannelMessage()', 'received event unspecified channel, action = ' + msg.action ); return; @@ -97,7 +97,7 @@ class Channels extends EventEmitter { if (!channel) { Logger.logAction( Logger.LOG_ERROR, - 'Channels.onChannelMessage()', + 'Channels.processChannelMessage()', 'received event for non-existent channel: ' + channelName ); return; diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index a7c84e035b..14f713886d 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1973,13 +1973,13 @@ class ConnectionManager extends EventEmitter { * before it's become active (while waiting for the old one to become * idle), message can validly arrive on it even though it isn't active */ if (onActiveTransport || onUpgradeTransport) { - this.realtime.channels.onChannelMessage(message); + this.realtime.channels.processChannelMessage(message); } else { // Message came in on a defunct transport. Allow only acks, nacks, & errors for outstanding // messages, no new messages (as sync has been sent on new transport so new messages will // be resent there, or connection has been closed so don't want new messages) if (Utils.arrIndexOf([actions.ACK, actions.NACK, actions.ERROR], message.action) > -1) { - this.realtime.channels.onChannelMessage(message); + this.realtime.channels.processChannelMessage(message); } else { Logger.logAction( Logger.LOG_MICRO, From a407328cfc6a531d10fd750d1e25b7b4b60f00b0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 25 May 2023 17:15:10 -0300 Subject: [PATCH 049/468] Rename RealtimeChannel#onMessage As in ab22a56. --- src/common/lib/client/realtime.ts | 2 +- src/common/lib/client/realtimechannel.ts | 18 +++++--- test/realtime/failure.test.js | 12 ++--- test/realtime/presence.test.js | 6 +-- test/realtime/sync.test.js | 58 ++++++++++++------------ 5 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/realtime.ts index e1e6b5eb2a..c39aa21832 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/realtime.ts @@ -102,7 +102,7 @@ class Channels extends EventEmitter { ); return; } - channel.onMessage(msg); + channel.processMessage(msg); } /* called when a transport becomes connected; reattempt attach/detach diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 70d09e3b1c..0ce7d57cca 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -582,7 +582,7 @@ class RealtimeChannel extends Channel { this.sendMessage(msg, callback); } - onMessage(message: ProtocolMessage): void { + processMessage(message: ProtocolMessage): void { if ( message.action === actions.ATTACHED || message.action === actions.MESSAGE || @@ -661,7 +661,7 @@ class RealtimeChannel extends Channel { if (!presenceMsg.timestamp) presenceMsg.timestamp = timestamp; if (!presenceMsg.id) presenceMsg.id = id + ':' + i; } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.onMessage()', (e as Error).toString()); + Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString()); } } this.presence.setPresence(presence, isSync, syncChannelSerial as any); @@ -672,7 +672,7 @@ class RealtimeChannel extends Channel { if (this.state !== 'attached') { Logger.logAction( Logger.LOG_MAJOR, - 'RealtimeChannel.onMessage()', + 'RealtimeChannel.processMessage()', 'Message "' + message.id + '" skipped as this channel "' + @@ -702,7 +702,7 @@ class RealtimeChannel extends Channel { '" on this channel "' + this.name + '".'; - Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.onMessage()', msg); + Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', msg); this._startDecodeFailureRecovery(new ErrorInfo(msg, 40018, 400)); break; } @@ -713,7 +713,7 @@ class RealtimeChannel extends Channel { Message.decode(msg, this._decodingContext); } catch (e) { /* decrypt failed .. the most likely cause is that we have the wrong key */ - Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.onMessage()', (e as Error).toString()); + Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString()); switch ((e as ErrorInfo).code) { case 40018: /* decode failure */ @@ -753,7 +753,7 @@ class RealtimeChannel extends Channel { default: Logger.logAction( Logger.LOG_ERROR, - 'RealtimeChannel.onMessage()', + 'RealtimeChannel.processMessage()', 'Fatal protocol error: unrecognised action (' + message.action + ')' ); this.connectionManager.abort(ConnectionErrors.unknownChannelErr()); @@ -762,7 +762,11 @@ class RealtimeChannel extends Channel { _startDecodeFailureRecovery(reason: ErrorInfo): void { if (!this._lastPayload.decodeFailureRecoveryInProgress) { - Logger.logAction(Logger.LOG_MAJOR, 'RealtimeChannel.onMessage()', 'Starting decode failure recovery process.'); + Logger.logAction( + Logger.LOG_MAJOR, + 'RealtimeChannel.processMessage()', + 'Starting decode failure recovery process.' + ); this._lastPayload.decodeFailureRecoveryInProgress = true; this._attach(true, reason, () => { this._lastPayload.decodeFailureRecoveryInProgress = false; diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index 03dd02757e..d660d47c1a 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -327,13 +327,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('attach_timeout', function (done) { var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 2000, channelRetryTimeout: 1000 }), channel = realtime.channels.get('failed_attach'), - originalOnMessage = channel.onMessage.bind(channel); + originalProcessMessage = channel.processMessage.bind(channel); - channel.onMessage = function (message) { + channel.processMessage = function (message) { if (message.action === 11) { return; } - originalOnMessage(message); + originalProcessMessage(message); }; realtime.connection.once('connected', function () { @@ -366,17 +366,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async transports: [transport], }), channel = realtime.channels.get('failed_attach'), - originalOnMessage = channel.onMessage.bind(channel), + originalProcessMessage = channel.processMessage.bind(channel), retryCount = 0; var performance = isBrowser ? window.performance : require('perf_hooks').performance; - channel.onMessage = function (message) { + channel.processMessage = function (message) { // Ignore ATTACHED messages if (message.action === 11) { return; } - originalOnMessage(message); + originalProcessMessage(message); }; realtime.connection.on('connected', function () { diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index e9e9a65682..de74a1df6f 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1732,7 +1732,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* Inject an additional member locally */ - channel.onMessage({ + channel.processMessage({ action: 14, id: 'messageid:0', connectionId: 'connid', @@ -1812,7 +1812,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* Inject a member locally */ - channel.onMessage({ + channel.processMessage({ action: 14, id: 'messageid:0', connectionId: 'connid', @@ -1846,7 +1846,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async cb(); }); /* Inject an ATTACHED with RESUMED and HAS_PRESENCE both false */ - channel.onMessage( + channel.processMessage( createPM({ action: 11, channelSerial: channel.properties.attachSerial, diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index 21cb13faee..3cf3279292 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -47,7 +47,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'syncexistingset', channel = realtime.channels.get(channelName); - channel.onMessage( + channel.processMessage( createPM({ action: 11, channel: channelName, @@ -58,7 +58,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.series( [ function (cb) { - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, presence: [ @@ -95,7 +95,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* Trigger another sync. Two has gone without so much as a `leave` message! */ - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, presence: [ @@ -149,7 +149,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_member_arrives_in_middle', channel = realtime.channels.get(channelName); - channel.onMessage( + channel.processMessage( createPM({ action: 11, channel: channelName, @@ -158,7 +158,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); /* First sync */ - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, presence: [ @@ -173,7 +173,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* A second sync, this time in multiple parts, with a presence message in the middle */ - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:cursor', @@ -188,7 +188,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, presence: [ @@ -202,7 +202,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:', @@ -242,7 +242,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_member_arrives_normally_after_came_in_sync', channel = realtime.channels.get(channelName); - channel.onMessage( + channel.processMessage( createPM({ action: 11, channel: channelName, @@ -250,7 +250,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }) ); - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:cursor', @@ -265,7 +265,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, presence: [ @@ -279,7 +279,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:', @@ -319,7 +319,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_member_arrives_normally_before_comes_in_sync', channel = realtime.channels.get(channelName); - channel.onMessage( + channel.processMessage( createPM({ action: 11, channel: channelName, @@ -327,7 +327,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }) ); - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:cursor', @@ -342,7 +342,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, presence: [ @@ -356,7 +356,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.onMessage({ + channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:', @@ -397,7 +397,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_ordering', channel = realtime.channels.get(channelName); - channel.onMessage( + channel.processMessage( createPM({ action: 11, channel: channelName, @@ -405,7 +405,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); /* One enters */ - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, id: 'one_connid:1', @@ -420,7 +420,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* An earlier leave from one (should be ignored) */ - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, connectionId: 'one_connid', @@ -435,7 +435,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* One adds some data in a newer msgSerial */ - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, connectionId: 'one_connid', @@ -451,7 +451,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* Two enters */ - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, connectionId: 'two_connid', @@ -466,7 +466,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* Two updates twice in the same message */ - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, connectionId: 'two_connid', @@ -487,7 +487,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* Three enters */ - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, connectionId: 'three_connid', @@ -503,7 +503,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Synthesized leave for three (with earlier msgSerial, incompatible id, * and later timestamp) */ - channel.onMessage({ + channel.processMessage({ action: 14, channel: channelName, connectionId: 'synthesized', @@ -581,13 +581,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); }, function (cb) { - var originalOnMessage = syncerChannel.onMessage; - syncerChannel.onMessage = function (message) { - originalOnMessage.apply(this, arguments); + var originalProcessMessage = syncerChannel.processMessage; + syncerChannel.processMessage = function (message) { + originalProcessMessage.apply(this, arguments); /* Inject an additional presence message after the first sync */ if (message.action === 16) { - syncerChannel.onMessage = originalOnMessage; - syncerChannel.onMessage({ + syncerChannel.processMessage = originalProcessMessage; + syncerChannel.processMessage({ action: 14, id: 'messageid:0', connectionId: 'connid', From 51cf0b048a42b203a04bfe3d1c1e3cb715fc7ead Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 25 May 2023 17:32:14 -0300 Subject: [PATCH 050/468] Make ConnectionManager#onChannelMessages process messages asynchronously MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is preparation for #1293 (making ICipher.decrypt asynchronous). This will require us to make RealtimeChannel#processMessage asynchronous. Since RealtimeChannel#processMessage reads from and writes to the _decodingContext.baseEncodedPreviousPayload property of the channel, we need to ensure that, once this method becomes asynchronous, we serialise access to this method — that is, we wait for one call to complete before performing the next. To do this, we need to introduce a queue. I decided to put this queue at the level of the ConnectionManager instead of RealtimeChannel. This is because ConnectionManager also has its own logic for deciding whether a message should be processed — specifically, whether it comes from the current transport — and I thought it made sense to evaluate these conditions at the moment we pass the message to the channel. I’m not 100% sure this is the right choice, though, since it means that the synchronisation is now the concern of three components (ConnectionManager, Channels, RealtimeChannel) when it instead could be the concern of just RealtimeChannel. But we can always revisit this. The handling of the case where ConnectionManager#processChannelMessage throws an error is copied from the places where this error was previously handled — namely, WebSocketTransport.onWsData and CometTransport.onData, both of which log an error message without affecting the processing of subsequent messages. (Note also that this marks the first use of `async` or promises internally in the library. We have avoided this until now because we were not guaranteed to be running in browsers with Promise support, but since the library will _only_ be exposing a Promise API as of #1199, which is also scheduled for version 2.0, we’re fine to start doing so now.) --- src/common/lib/transport/connectionmanager.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 14f713886d..0221c47ad2 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -215,6 +215,12 @@ class ConnectionManager extends EventEmitter { suspendTimer?: number | NodeJS.Timeout | null; retryTimer?: number | NodeJS.Timeout | null; disconnectedRetryCount: number = 0; + pendingChannelMessagesState: { + // Whether a message is currently being processed + isProcessing: boolean; + // The messages remaining to be processed (excluding any message currently being processed) + queue: { message: ProtocolMessage; transport: Transport }[]; + } = { isProcessing: false, queue: [] }; constructor(realtime: Realtime, options: ClientOptions) { super(); @@ -1966,6 +1972,34 @@ class ConnectionManager extends EventEmitter { } onChannelMessage(message: ProtocolMessage, transport: Transport): void { + this.pendingChannelMessagesState.queue.push({ message, transport }); + + if (!this.pendingChannelMessagesState.isProcessing) { + this.processNextPendingChannelMessage(); + } + } + + private processNextPendingChannelMessage() { + if (this.pendingChannelMessagesState.queue.length > 0) { + this.pendingChannelMessagesState.isProcessing = true; + + const pendingChannelMessage = this.pendingChannelMessagesState.queue.shift()!; + this.processChannelMessage(pendingChannelMessage.message, pendingChannelMessage.transport) + .catch((err) => { + Logger.logAction( + Logger.LOG_ERROR, + 'ConnectionManager.processNextPendingChannelMessage() received error ', + err + ); + }) + .finally(() => { + this.pendingChannelMessagesState.isProcessing = false; + this.processNextPendingChannelMessage(); + }); + } + } + + private async processChannelMessage(message: ProtocolMessage, transport: Transport) { const onActiveTransport = this.activeProtocol && transport === this.activeProtocol.getTransport(), onUpgradeTransport = Utils.arrIn(this.pendingTransports, transport) && this.state == this.states.synchronizing; From ec8d77e3796f3b3eefee0297cb33da6cbb708967 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 08:50:43 -0300 Subject: [PATCH 051/468] Make Channels#processChannelMessage async Preparation for #1293 (making ICipher.decrypt asynchronous). --- src/common/lib/client/realtime.ts | 3 ++- src/common/lib/transport/connectionmanager.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/realtime.ts index c39aa21832..caba09bbbf 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/realtime.ts @@ -83,7 +83,8 @@ class Channels extends EventEmitter { } } - processChannelMessage(msg: ProtocolMessage) { + // Access to this method is synchronised by ConnectionManager#processChannelMessage. + async processChannelMessage(msg: ProtocolMessage) { const channelName = msg.channel; if (channelName === undefined) { Logger.logAction( diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 0221c47ad2..a3b98c9a53 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -2007,13 +2007,13 @@ class ConnectionManager extends EventEmitter { * before it's become active (while waiting for the old one to become * idle), message can validly arrive on it even though it isn't active */ if (onActiveTransport || onUpgradeTransport) { - this.realtime.channels.processChannelMessage(message); + await this.realtime.channels.processChannelMessage(message); } else { // Message came in on a defunct transport. Allow only acks, nacks, & errors for outstanding // messages, no new messages (as sync has been sent on new transport so new messages will // be resent there, or connection has been closed so don't want new messages) if (Utils.arrIndexOf([actions.ACK, actions.NACK, actions.ERROR], message.action) > -1) { - this.realtime.channels.processChannelMessage(message); + await this.realtime.channels.processChannelMessage(message); } else { Logger.logAction( Logger.LOG_MICRO, From ec8a1b12923b10a05bcd271beb259ca920c7f645 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 11:34:30 -0300 Subject: [PATCH 052/468] Make some tests return a Promise These are the tests that call RealtimeChannel#processMessage at their top level. This is preparation for making that method asynchronous as part of #1293 (making ICipher.decrypt asynchronous). --- test/realtime/sync.test.js | 320 ++++++++++++++++++++----------------- 1 file changed, 176 insertions(+), 144 deletions(-) diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index 3cf3279292..e6f7eb10ae 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -42,7 +42,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * sync in progress, then do one sync, then a second with a slightly * different presence set */ - it('sync_existing_set', function (done) { + it('sync_existing_set', async function () { var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'syncexistingset', channel = realtime.channels.get(channelName); @@ -55,96 +55,102 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }) ); - async.series( - [ - function (cb) { - channel.processMessage({ - action: 16, - channel: channelName, - presence: [ - { - action: 'present', - clientId: 'one', - connectionId: 'one_connid', - id: 'one_connid:0:0', - timestamp: 1e12, - }, - { - action: 'present', - clientId: 'two', - connectionId: 'two_connid', - id: 'two_connid:0:0', - timestamp: 1e12, - }, - ], - }); - cb(); - }, - function (cb) { - channel.presence.get(function (err, results) { - try { - expect(results.length).to.equal(2, 'Check correct number of results'); - expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; - expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check correct members'); - } catch (err) { + await new Promise(function (resolve, reject) { + var done = function (err) { + err ? reject(err) : resolve(); + }; + + async.series( + [ + function (cb) { + channel.processMessage({ + action: 16, + channel: channelName, + presence: [ + { + action: 'present', + clientId: 'one', + connectionId: 'one_connid', + id: 'one_connid:0:0', + timestamp: 1e12, + }, + { + action: 'present', + clientId: 'two', + connectionId: 'two_connid', + id: 'two_connid:0:0', + timestamp: 1e12, + }, + ], + }); + cb(); + }, + function (cb) { + channel.presence.get(function (err, results) { + try { + expect(results.length).to.equal(2, 'Check correct number of results'); + expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; + expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check correct members'); + } catch (err) { + cb(err); + return; + } cb(err); - return; - } - cb(err); - }); - }, - function (cb) { - /* Trigger another sync. Two has gone without so much as a `leave` message! */ - channel.processMessage({ - action: 16, - channel: channelName, - presence: [ - { - action: 'present', - clientId: 'one', - connectionId: 'one_connid', - id: 'one_connid:0:0', - timestamp: 1e12, - }, - { - action: 'present', - clientId: 'three', - connectionId: 'three_connid', - id: 'three_connid:0:0', - timestamp: 1e12, - }, - ], - }); - cb(); - }, - function (cb) { - channel.presence.get(function (err, results) { - try { - expect(results.length).to.equal(2, 'Check correct number of results'); - expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; - expect(extractClientIds(results)).to.deep.equal( - ['one', 'three'], - 'check two has gone and three is there' - ); - } catch (err) { + }); + }, + function (cb) { + /* Trigger another sync. Two has gone without so much as a `leave` message! */ + channel.processMessage({ + action: 16, + channel: channelName, + presence: [ + { + action: 'present', + clientId: 'one', + connectionId: 'one_connid', + id: 'one_connid:0:0', + timestamp: 1e12, + }, + { + action: 'present', + clientId: 'three', + connectionId: 'three_connid', + id: 'three_connid:0:0', + timestamp: 1e12, + }, + ], + }); + cb(); + }, + function (cb) { + channel.presence.get(function (err, results) { + try { + expect(results.length).to.equal(2, 'Check correct number of results'); + expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; + expect(extractClientIds(results)).to.deep.equal( + ['one', 'three'], + 'check two has gone and three is there' + ); + } catch (err) { + cb(err); + return; + } cb(err); - return; - } - cb(err); - }); - }, - ], - function (err) { - closeAndFinish(done, realtime, err); - } - ); + }); + }, + ], + function (err) { + closeAndFinish(done, realtime, err); + } + ); + }); }); /* * Sync with an existing presence set and a presence member added in the * middle of the sync should should discard the former, but not the latter * */ - it('sync_member_arrives_in_middle', function (done) { + it('sync_member_arrives_in_middle', async function () { var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_member_arrives_in_middle', channel = realtime.channels.get(channelName); @@ -217,27 +223,36 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.presence.get(function (err, results) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - try { - expect(results.length).to.equal(3, 'Check correct number of results'); - expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; - expect(extractClientIds(results)).to.deep.equal(['four', 'three', 'two'], 'check expected presence members'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); + await new Promise(function (resolve, reject) { + var done = function (err) { + err ? reject(err) : resolve(); + }; + + channel.presence.get(function (err, results) { + if (err) { + closeAndFinish(done, realtime, err); + return; + } + try { + expect(results.length).to.equal(3, 'Check correct number of results'); + expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; + expect(extractClientIds(results)).to.deep.equal( + ['four', 'three', 'two'], + 'check expected presence members' + ); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }); }); }); /* * Presence message that was in the sync arrives again as a normal message, after it's come in the sync */ - it('sync_member_arrives_normally_after_came_in_sync', function (done) { + it('sync_member_arrives_normally_after_came_in_sync', async function () { var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_member_arrives_normally_after_came_in_sync', channel = realtime.channels.get(channelName); @@ -294,27 +309,33 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.presence.get(function (err, results) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - try { - expect(results.length).to.equal(2, 'Check correct number of results'); - expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; - expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check expected presence members'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); + await new Promise(function (resolve, reject) { + var done = function (err) { + err ? reject(err) : resolve(); + }; + + channel.presence.get(function (err, results) { + if (err) { + closeAndFinish(done, realtime, err); + return; + } + try { + expect(results.length).to.equal(2, 'Check correct number of results'); + expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; + expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check expected presence members'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }); }); }); /* * Presence message that will be in the sync arrives as a normal message, before it comes in the sync */ - it('sync_member_arrives_normally_before_comes_in_sync', function (done) { + it('sync_member_arrives_normally_before_comes_in_sync', async function () { var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_member_arrives_normally_before_comes_in_sync', channel = realtime.channels.get(channelName); @@ -371,20 +392,26 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.presence.get(function (err, results) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - try { - expect(results.length).to.equal(2, 'Check correct number of results'); - expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; - expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check expected presence members'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); + await new Promise(function (resolve, reject) { + var done = function (err) { + err ? reject(err) : resolve(); + }; + + channel.presence.get(function (err, results) { + if (err) { + closeAndFinish(done, realtime, err); + return; + } + try { + expect(results.length).to.equal(2, 'Check correct number of results'); + expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; + expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check expected presence members'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }); }); }); @@ -392,7 +419,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Get several presence messages with various combinations of msgserial, * index, and synthesized leaves, check that the end result is correct */ - it('presence_ordering', function (done) { + it('presence_ordering', async function () { var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_ordering', channel = realtime.channels.get(channelName); @@ -518,22 +545,27 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.presence.get(function (err, results) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - try { - expect(results.length).to.equal(2, 'Check correct number of results'); - expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; - expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check expected presence members'); - expect(extractMember(results, 'one').data).to.equal('onedata', 'check correct data on one'); - expect(extractMember(results, 'two').data).to.equal('twodata', 'check correct data on two'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); + await new Promise(function (resolve, reject) { + var done = function (err) { + err ? reject(err) : resolve(); + }; + channel.presence.get(function (err, results) { + if (err) { + closeAndFinish(done, realtime, err); + return; + } + try { + expect(results.length).to.equal(2, 'Check correct number of results'); + expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; + expect(extractClientIds(results)).to.deep.equal(['one', 'two'], 'check expected presence members'); + expect(extractMember(results, 'one').data).to.equal('onedata', 'check correct data on one'); + expect(extractMember(results, 'two').data).to.equal('twodata', 'check correct data on two'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }); }); }); From 346e5ebd37389651db30e989c8576ed1882f64e1 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 08:53:42 -0300 Subject: [PATCH 053/468] Make RealtimeChannel#processMessage async Preparation for #1293 (making ICipher.decrypt asynchronous). --- src/common/lib/client/realtime.ts | 2 +- src/common/lib/client/realtimechannel.ts | 3 +- test/realtime/failure.test.js | 8 +- test/realtime/presence.test.js | 66 ++++++---- test/realtime/sync.test.js | 146 ++++++++++++----------- 5 files changed, 128 insertions(+), 97 deletions(-) diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/realtime.ts index caba09bbbf..7911c90966 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/realtime.ts @@ -103,7 +103,7 @@ class Channels extends EventEmitter { ); return; } - channel.processMessage(msg); + await channel.processMessage(msg); } /* called when a transport becomes connected; reattempt attach/detach diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 0ce7d57cca..b1c096ad05 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -582,7 +582,8 @@ class RealtimeChannel extends Channel { this.sendMessage(msg, callback); } - processMessage(message: ProtocolMessage): void { + // Access to this method is synchronised by ConnectionManager#processChannelMessage, in order to synchronise access to the state stored in _decodingContext. + async processMessage(message: ProtocolMessage): Promise { if ( message.action === actions.ATTACHED || message.action === actions.MESSAGE || diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index d660d47c1a..142ac9c5f1 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -329,11 +329,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel = realtime.channels.get('failed_attach'), originalProcessMessage = channel.processMessage.bind(channel); - channel.processMessage = function (message) { + channel.processMessage = async function (message) { if (message.action === 11) { return; } - originalProcessMessage(message); + await originalProcessMessage(message); }; realtime.connection.once('connected', function () { @@ -371,12 +371,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var performance = isBrowser ? window.performance : require('perf_hooks').performance; - channel.processMessage = function (message) { + channel.processMessage = async function (message) { // Ignore ATTACHED messages if (message.action === 11) { return; } - originalProcessMessage(message); + await originalProcessMessage(message); }; realtime.connection.on('connected', function () { diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index de74a1df6f..1c90d988d9 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1732,18 +1732,27 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* Inject an additional member locally */ - channel.processMessage({ - action: 14, - id: 'messageid:0', - connectionId: 'connid', - timestamp: utils.now(), - presence: [ - { - clientId: goneClientId, - action: 'enter', - }, - ], - }); + channel + .processMessage({ + action: 14, + id: 'messageid:0', + connectionId: 'connid', + timestamp: utils.now(), + presence: [ + { + clientId: goneClientId, + action: 'enter', + }, + ], + }) + .then(function () { + cb(null); + }) + .catch(function (err) { + cb(err); + }); + }, + function (cb) { channel.presence.get(function (err, members) { try { expect(members && members.length).to.equal(2, 'Check two members present'); @@ -1812,18 +1821,27 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* Inject a member locally */ - channel.processMessage({ - action: 14, - id: 'messageid:0', - connectionId: 'connid', - timestamp: utils.now(), - presence: [ - { - clientId: fakeClientId, - action: 'enter', - }, - ], - }); + channel + .processMessage({ + action: 14, + id: 'messageid:0', + connectionId: 'connid', + timestamp: utils.now(), + presence: [ + { + clientId: fakeClientId, + action: 'enter', + }, + ], + }) + .then(function () { + cb(); + }) + .catch(function () { + cb(err); + }); + }, + function (cb) { channel.presence.get(function (err, members) { try { expect(members && members.length).to.equal(1, 'Check one member present'); diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index e6f7eb10ae..5f62dabe98 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -47,7 +47,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'syncexistingset', channel = realtime.channels.get(channelName); - channel.processMessage( + await channel.processMessage( createPM({ action: 11, channel: channelName, @@ -63,27 +63,33 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.series( [ function (cb) { - channel.processMessage({ - action: 16, - channel: channelName, - presence: [ - { - action: 'present', - clientId: 'one', - connectionId: 'one_connid', - id: 'one_connid:0:0', - timestamp: 1e12, - }, - { - action: 'present', - clientId: 'two', - connectionId: 'two_connid', - id: 'two_connid:0:0', - timestamp: 1e12, - }, - ], - }); - cb(); + channel + .processMessage({ + action: 16, + channel: channelName, + presence: [ + { + action: 'present', + clientId: 'one', + connectionId: 'one_connid', + id: 'one_connid:0:0', + timestamp: 1e12, + }, + { + action: 'present', + clientId: 'two', + connectionId: 'two_connid', + id: 'two_connid:0:0', + timestamp: 1e12, + }, + ], + }) + .then(function () { + cb(); + }) + .catch(function (err) { + cb(err); + }); }, function (cb) { channel.presence.get(function (err, results) { @@ -100,27 +106,33 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { /* Trigger another sync. Two has gone without so much as a `leave` message! */ - channel.processMessage({ - action: 16, - channel: channelName, - presence: [ - { - action: 'present', - clientId: 'one', - connectionId: 'one_connid', - id: 'one_connid:0:0', - timestamp: 1e12, - }, - { - action: 'present', - clientId: 'three', - connectionId: 'three_connid', - id: 'three_connid:0:0', - timestamp: 1e12, - }, - ], - }); - cb(); + channel + .processMessage({ + action: 16, + channel: channelName, + presence: [ + { + action: 'present', + clientId: 'one', + connectionId: 'one_connid', + id: 'one_connid:0:0', + timestamp: 1e12, + }, + { + action: 'present', + clientId: 'three', + connectionId: 'three_connid', + id: 'three_connid:0:0', + timestamp: 1e12, + }, + ], + }) + .then(function () { + cb(); + }) + .catch(function (err) { + cb(err); + }); }, function (cb) { channel.presence.get(function (err, results) { @@ -155,7 +167,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_member_arrives_in_middle', channel = realtime.channels.get(channelName); - channel.processMessage( + await channel.processMessage( createPM({ action: 11, channel: channelName, @@ -164,7 +176,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); /* First sync */ - channel.processMessage({ + await channel.processMessage({ action: 16, channel: channelName, presence: [ @@ -179,7 +191,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* A second sync, this time in multiple parts, with a presence message in the middle */ - channel.processMessage({ + await channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:cursor', @@ -194,7 +206,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, presence: [ @@ -208,7 +220,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.processMessage({ + await channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:', @@ -257,7 +269,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_member_arrives_normally_after_came_in_sync', channel = realtime.channels.get(channelName); - channel.processMessage( + await channel.processMessage( createPM({ action: 11, channel: channelName, @@ -265,7 +277,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }) ); - channel.processMessage({ + await channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:cursor', @@ -280,7 +292,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, presence: [ @@ -294,7 +306,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.processMessage({ + await channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:', @@ -340,7 +352,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_member_arrives_normally_before_comes_in_sync', channel = realtime.channels.get(channelName); - channel.processMessage( + await channel.processMessage( createPM({ action: 11, channel: channelName, @@ -348,7 +360,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }) ); - channel.processMessage({ + await channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:cursor', @@ -363,7 +375,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, presence: [ @@ -377,7 +389,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ], }); - channel.processMessage({ + await channel.processMessage({ action: 16, channel: channelName, channelSerial: 'serial:', @@ -424,7 +436,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'sync_ordering', channel = realtime.channels.get(channelName); - channel.processMessage( + await channel.processMessage( createPM({ action: 11, channel: channelName, @@ -432,7 +444,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); /* One enters */ - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, id: 'one_connid:1', @@ -447,7 +459,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* An earlier leave from one (should be ignored) */ - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, connectionId: 'one_connid', @@ -462,7 +474,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* One adds some data in a newer msgSerial */ - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, connectionId: 'one_connid', @@ -478,7 +490,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* Two enters */ - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, connectionId: 'two_connid', @@ -493,7 +505,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* Two updates twice in the same message */ - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, connectionId: 'two_connid', @@ -514,7 +526,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* Three enters */ - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, connectionId: 'three_connid', @@ -530,7 +542,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Synthesized leave for three (with earlier msgSerial, incompatible id, * and later timestamp) */ - channel.processMessage({ + await channel.processMessage({ action: 14, channel: channelName, connectionId: 'synthesized', @@ -614,12 +626,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { var originalProcessMessage = syncerChannel.processMessage; - syncerChannel.processMessage = function (message) { - originalProcessMessage.apply(this, arguments); + syncerChannel.processMessage = async function (message) { + await originalProcessMessage.apply(this, arguments); /* Inject an additional presence message after the first sync */ if (message.action === 16) { syncerChannel.processMessage = originalProcessMessage; - syncerChannel.processMessage({ + await syncerChannel.processMessage({ action: 14, id: 'messageid:0', connectionId: 'connid', From c19b68893df6fe01521b0b025a654108136b0d24 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 09:34:56 -0300 Subject: [PATCH 054/468] Make Message#fromEncoded/fromEncodedArray async MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for #1293 (making ICipher.decrypt asynchronous). This is an unavoidable public API change. Note that I haven’t bothered exposing a callbacks version of these methods, since as of #1199 — which is also scheduled for version 2.0 — the library will only be exposing a Promise API. --- ably.d.ts | 16 ++++++++-------- src/common/lib/types/message.ts | 12 +++++++----- test/realtime/crypto.test.js | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 5921efa16b..f9d406f097 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2758,17 +2758,17 @@ declare namespace Types { * * @param JsonObject - A `Message`-like deserialized object. * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A `Message` object. + * @returns A promise which will be fulfilled with a `Message` object. */ - static fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Message; + static fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; /** * A static factory method to create an array of `Message` objects from an array of deserialized Message-like object encoded using Ably's wire protocol. * * @param JsonArray - An array of `Message`-like deserialized objects. * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns An array of {@link Message} objects. + * @returns A promise which will be fulfilled with an array of {@link Message} objects. */ - static fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Message[]; + static fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; /** * The client ID of the publisher of this message. */ @@ -2812,17 +2812,17 @@ declare namespace Types { * * @param JsonObject - A `Message`-like deserialized object. * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A `Message` object. + * @returns A promise which will be fulfilled with a `Message` object. */ - fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Message; + fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; /** * A static factory method to create an array of `Message` objects from an array of deserialized Message-like object encoded using Ably's wire protocol. * * @param JsonArray - An array of `Message`-like deserialized objects. * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns An array of {@link Message} objects. + * @returns A promise which will be fulfilled with an array of {@link Message} objects. */ - fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Message[]; + fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; } /** diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 45dcc9a2e0..5f3232bbf8 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -332,7 +332,7 @@ class Message { return result; } - static fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Message { + static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { const msg = Message.fromValues(encoded); const options = normalizeCipherOptions(inputOptions ?? null); /* if decoding fails at any point, catch and return the message decoded to @@ -345,10 +345,12 @@ class Message { return msg; } - static fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Message[] { - return encodedArray.map(function (encoded) { - return Message.fromEncoded(encoded, options); - }); + static async fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Promise { + return Promise.all( + encodedArray.map(function (encoded) { + return Message.fromEncoded(encoded, options); + }) + ); } /* This should be called on encode()d (and encrypt()d) Messages (as it diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 042699be23..2873642dc2 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -56,7 +56,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - loadTestData(testResourcesPath + filename, function (err, testData) { + loadTestData(testResourcesPath + filename, async function (err, testData) { if (err) { done(new Error('Unable to get test assets; err = ' + displayError(err))); return; @@ -71,11 +71,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var item = testData.items[i]; /* read messages from test data and decode (ie remove any base64 encoding). */ - var createTestMessage = function () { - return Message.fromEncoded(item.encoded); + var createTestMessage = async function () { + return await Message.fromEncoded(item.encoded); }; - var encryptedMessage = Message.fromEncoded(item.encrypted); + var encryptedMessage = await Message.fromEncoded(item.encrypted); var runTest = function (testMessage) { /* reset channel cipher, to ensure it uses the given iv */ @@ -84,13 +84,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; // Run the test with the message’s data as-is. - runTest(createTestMessage()); + runTest(await createTestMessage()); if (testPlaintextVariants) { - var testMessage = createTestMessage(); + var testMessage = await createTestMessage(); if (BufferUtils.isBuffer(testMessage.data) && !(testMessage.data instanceof ArrayBuffer)) { // Now, check that we can also handle an ArrayBuffer plaintext. - var testMessageWithArrayBufferData = createTestMessage(); + var testMessageWithArrayBufferData = await createTestMessage(); testMessageWithArrayBufferData.data = BufferUtils.toArrayBuffer(testMessageWithArrayBufferData.data); runTest(testMessageWithArrayBufferData); } @@ -305,7 +305,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - loadTestData(testResourcesPath + 'crypto-data-256.json', function (err, testData) { + loadTestData(testResourcesPath + 'crypto-data-256.json', async function (err, testData) { if (err) { done(err); return; @@ -315,8 +315,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async for (var i = 0; i < testData.items.length; i++) { var item = testData.items[i]; - var testMessage = Message.fromEncoded(item.encoded); - var decryptedMessage = Message.fromEncoded(item.encrypted, { cipher: { key: key, iv: iv } }); + var testMessage = await Message.fromEncoded(item.encoded); + var decryptedMessage = await Message.fromEncoded(item.encrypted, { cipher: { key: key, iv: iv } }); testMessageEquality(done, testMessage, decryptedMessage); } done(); From 0ba216429905ef870b296db702196dc0d2d08091 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 09:34:56 -0300 Subject: [PATCH 055/468] Make PresenceMessage#fromEncoded/fromEncodedArray async As in 14fdfc3. --- ably.d.ts | 8 ++++---- src/common/lib/types/presencemessage.ts | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index f9d406f097..dc29099b54 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2841,14 +2841,14 @@ declare namespace Types { * @param JsonObject - The deserialized `PresenceMessage`-like object to decode and decrypt. * @param channelOptions - A {@link ChannelOptions} object containing the cipher. */ - static fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => PresenceMessage; + static fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; /** * Decodes and decrypts an array of deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. * * @param JsonArray - An array of deserialized `PresenceMessage`-like objects to decode and decrypt. * @param channelOptions - A {@link ChannelOptions} object containing the cipher. */ - static fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => PresenceMessage[]; + static fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; /** * The type of {@link PresenceAction} the `PresenceMessage` is for. */ @@ -2889,14 +2889,14 @@ declare namespace Types { * @param JsonObject - The deserialized `PresenceMessage`-like object to decode and decrypt. * @param channelOptions - A {@link ChannelOptions} object containing the cipher. */ - fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => PresenceMessage; + fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; /** * Decodes and decrypts an array of deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. * * @param JsonArray - An array of deserialized `PresenceMessage`-like objects to decode and decrypt. * @param channelOptions - A {@link ChannelOptions} object containing the cipher. */ - fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => PresenceMessage[]; + fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; } /** diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index b258d7e3a9..ed12a507a3 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -137,7 +137,7 @@ class PresenceMessage { return result; } - static fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): PresenceMessage { + static async fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { const msg = PresenceMessage.fromValues(encoded as PresenceMessage | Record, true); /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ @@ -149,10 +149,15 @@ class PresenceMessage { return msg; } - static fromEncodedArray(encodedArray: unknown[], options?: API.Types.ChannelOptions): PresenceMessage[] { - return encodedArray.map(function (encoded) { - return PresenceMessage.fromEncoded(encoded, options); - }); + static async fromEncodedArray( + encodedArray: unknown[], + options?: API.Types.ChannelOptions + ): Promise { + return Promise.all( + encodedArray.map(function (encoded) { + return PresenceMessage.fromEncoded(encoded, options); + }) + ); } static getMessagesSize = Message.getMessagesSize; From 5ac1f23730ac09a33a014c7affa85b69215aee3e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 10:19:43 -0300 Subject: [PATCH 056/468] =?UTF-8?q?Make=20PaginatedResource=E2=80=99s=20bo?= =?UTF-8?q?dyHandler=20async?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for #1293 (making ICipher.decrypt asynchronous). --- src/common/lib/client/channel.ts | 2 +- src/common/lib/client/paginatedresource.ts | 43 +++++++++++++--------- src/common/lib/client/presence.ts | 4 +- src/common/lib/client/push.ts | 6 +-- src/common/lib/client/rest.ts | 2 +- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index 5c64844f03..a5e156dc24 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -97,7 +97,7 @@ class Channel extends EventEmitter { Utils.mixin(headers, rest.options.headers); const options = this.channelOptions; - new PaginatedResource(rest, this.basePath + '/messages', headers, envelope, function ( + new PaginatedResource(rest, this.basePath + '/messages', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 3cadfaba2b..bf54698b23 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -5,7 +5,7 @@ import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; import { PaginatedResultCallback } from '../../types/utils'; import Rest from './rest'; -export type BodyHandler = (body: unknown, headers: Record, packed?: boolean) => any; +export type BodyHandler = (body: unknown, headers: Record, packed?: boolean) => Promise; function getRelParams(linkUrl: string) { const urlMatch = linkUrl.match(/^\.\/(\w+)\?(.*)$/); @@ -149,25 +149,32 @@ class PaginatedResource { callback?.(err); return; } - let items, linkHeader, relParams; - try { - items = this.bodyHandler(body, headers || {}, unpacked); - } catch (e) { - /* If we got an error, the failure to parse the body is almost certainly - * due to that, so callback with that in preference over the parse error */ - callback?.(err || e); - return; - } - if (headers && (linkHeader = headers['Link'] || headers['link'])) { - relParams = parseRelLinks(linkHeader); - } + const handleBody = async () => { + let items, linkHeader, relParams; - if (this.useHttpPaginatedResponse) { - callback(null, new HttpPaginatedResponse(this, items, headers || {}, statusCode as number, relParams, err)); - } else { - callback(null, new PaginatedResult(this, items, relParams)); - } + try { + items = await this.bodyHandler(body, headers || {}, unpacked); + } catch (e) { + /* If we got an error, the failure to parse the body is almost certainly + * due to that, so throw that in preference over the parse error */ + throw err || e; + } + + if (headers && (linkHeader = headers['Link'] || headers['link'])) { + relParams = parseRelLinks(linkHeader); + } + + if (this.useHttpPaginatedResponse) { + return new HttpPaginatedResponse(this, items, headers || {}, statusCode as number, relParams, err); + } else { + return new PaginatedResult(this, items, relParams); + } + }; + + handleBody() + .then((result) => callback(null, result)) + .catch((err) => callback(err, null)); } } diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index 8874bccb85..3f7b9b3309 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -42,7 +42,7 @@ class Presence extends EventEmitter { Utils.mixin(headers, rest.options.headers); const options = this.channel.channelOptions; - new PaginatedResource(rest, this.basePath, headers, envelope, function ( + new PaginatedResource(rest, this.basePath, headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean @@ -84,7 +84,7 @@ class Presence extends EventEmitter { Utils.mixin(headers, rest.options.headers); const options = this.channel.channelOptions; - new PaginatedResource(rest, this.basePath + '/history', headers, envelope, function ( + new PaginatedResource(rest, this.basePath + '/history', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index dca0d72135..fb694eaded 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -161,7 +161,7 @@ class DeviceRegistrations { Utils.mixin(headers, rest.options.headers); - new PaginatedResource(rest, '/push/deviceRegistrations', headers, envelope, function ( + new PaginatedResource(rest, '/push/deviceRegistrations', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean @@ -286,7 +286,7 @@ class ChannelSubscriptions { Utils.mixin(headers, rest.options.headers); - new PaginatedResource(rest, '/push/channelSubscriptions', headers, envelope, function ( + new PaginatedResource(rest, '/push/channelSubscriptions', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean @@ -334,7 +334,7 @@ class ChannelSubscriptions { if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - new PaginatedResource(rest, '/push/channels', headers, envelope, function ( + new PaginatedResource(rest, '/push/channels', headers, envelope, async function ( body: unknown, headers: Record, unpacked?: boolean diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 6f5182e9b5..5629f8f391 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -207,7 +207,7 @@ class Rest { path, headers, envelope, - function (resbody: unknown, headers: Record, unpacked?: boolean) { + async function (resbody: unknown, headers: Record, unpacked?: boolean) { return Utils.ensureArray(unpacked ? resbody : decoder(resbody as string & Buffer)); }, /* useHttpPaginatedResponse: */ true From 835f6df5e6c15386e1d2e47961f662f3c33b596a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 10:25:18 -0300 Subject: [PATCH 057/468] Make Message.fromResponseBody async Preparation for #1293 (making ICipher.decrypt asynchronous). --- src/common/lib/client/channel.ts | 2 +- src/common/lib/types/message.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index a5e156dc24..8937709d5e 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -102,7 +102,7 @@ class Channel extends EventEmitter { headers: Record, unpacked?: boolean ) { - return Message.fromResponseBody(body, options, unpacked ? undefined : format); + return await Message.fromResponseBody(body, options, unpacked ? undefined : format); }).get(params as Record, callback); } diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 5f3232bbf8..540ae67de5 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -301,11 +301,11 @@ class Message { context.baseEncodedPreviousPayload = lastPayload; } - static fromResponseBody( + static async fromResponseBody( body: Array, options: ChannelOptions | EncodingDecodingContext, format?: Utils.Format - ): Message[] { + ): Promise { if (format) { body = Utils.decodeBody(body, format); } From 1b9bdb7fc7d6b9018b8cb40e15a20c9de3874eac Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 10:30:22 -0300 Subject: [PATCH 058/468] Make PresenceMessage.fromResponseBody async Preparation for #1293 (making ICipher.decrypt asynchronous). --- src/common/lib/client/presence.ts | 4 ++-- src/common/lib/types/presencemessage.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index 3f7b9b3309..77210ade55 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -47,7 +47,7 @@ class Presence extends EventEmitter { headers: Record, unpacked?: boolean ) { - return PresenceMessage.fromResponseBody(body, options as CipherOptions, unpacked ? undefined : format); + return await PresenceMessage.fromResponseBody(body, options as CipherOptions, unpacked ? undefined : format); }).get(params, callback); } @@ -89,7 +89,7 @@ class Presence extends EventEmitter { headers: Record, unpacked?: boolean ) { - return PresenceMessage.fromResponseBody(body, options as CipherOptions, unpacked ? undefined : format); + return await PresenceMessage.fromResponseBody(body, options as CipherOptions, unpacked ? undefined : format); }).get(params, callback); } } diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index ed12a507a3..bd83aa96f8 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -102,11 +102,11 @@ class PresenceMessage { static encode = Message.encode; static decode = Message.decode; - static fromResponseBody( + static async fromResponseBody( body: Record[], options: CipherOptions, format?: Utils.Format - ): PresenceMessage[] { + ): Promise { const messages: PresenceMessage[] = []; if (format) { body = Utils.decodeBody(body, format); From 21bc8012834da7bd83c689c61a0a6fb4a33f6057 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 May 2023 10:39:26 -0300 Subject: [PATCH 059/468] Make Message.decode async Preparation for #1293 (making ICipher.decrypt asynchronous). --- src/common/lib/client/realtimechannel.ts | 4 ++-- src/common/lib/types/message.ts | 8 ++++---- src/common/lib/types/presencemessage.ts | 4 ++-- test/realtime/crypto.test.js | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index b1c096ad05..4a04ee5051 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -657,7 +657,7 @@ class RealtimeChannel extends Channel { for (let i = 0; i < presence.length; i++) { try { presenceMsg = presence[i]; - PresenceMessage.decode(presenceMsg, options); + await PresenceMessage.decode(presenceMsg, options); if (!presenceMsg.connectionId) presenceMsg.connectionId = connectionId; if (!presenceMsg.timestamp) presenceMsg.timestamp = timestamp; if (!presenceMsg.id) presenceMsg.id = id + ':' + i; @@ -711,7 +711,7 @@ class RealtimeChannel extends Channel { for (let i = 0; i < messages.length; i++) { const msg = messages[i]; try { - Message.decode(msg, this._decodingContext); + await Message.decode(msg, this._decodingContext); } catch (e) { /* decrypt failed .. the most likely cause is that we have the wrong key */ Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString()); diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 540ae67de5..7c69766e86 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -197,10 +197,10 @@ class Message { static serialize = Utils.encodeBody; - static decode( + static async decode( message: Message | PresenceMessage, inputContext: CipherOptions | EncodingDecodingContext | ChannelOptions - ): void { + ): Promise { const context = normaliseContext(inputContext); let lastPayload = message.data; @@ -313,7 +313,7 @@ class Message { for (let i = 0; i < body.length; i++) { const msg = (body[i] = Message.fromValues(body[i])); try { - Message.decode(msg, options); + await Message.decode(msg, options); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'Message.fromResponseBody()', (e as Error).toString()); } @@ -338,7 +338,7 @@ class Message { /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ try { - Message.decode(msg, options); + await Message.decode(msg, options); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'Message.fromEncoded()', (e as Error).toString()); } diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index bd83aa96f8..de2abfa256 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -115,7 +115,7 @@ class PresenceMessage { for (let i = 0; i < body.length; i++) { const msg = (messages[i] = PresenceMessage.fromValues(body[i], true)); try { - PresenceMessage.decode(msg, options); + await PresenceMessage.decode(msg, options); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromResponseBody()', (e as Error).toString()); } @@ -142,7 +142,7 @@ class PresenceMessage { /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ try { - PresenceMessage.decode(msg, options ?? {}); + await PresenceMessage.decode(msg, options ?? {}); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromEncoded()', (e as Error).toString()); } diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 2873642dc2..3f1ec0aae1 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -274,9 +274,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async 'decrypt_message_128', 2, false, - function (channelOpts, testMessage, encryptedMessage) { + async function (channelOpts, testMessage, encryptedMessage) { /* decrypt encrypted message; decode() also to handle data that is not string or buffer */ - Message.decode(encryptedMessage, channelOpts); + await Message.decode(encryptedMessage, channelOpts); /* compare */ testMessageEquality(done, testMessage, encryptedMessage); } @@ -290,9 +290,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async 'decrypt_message_256', 2, false, - function (channelOpts, testMessage, encryptedMessage) { + async function (channelOpts, testMessage, encryptedMessage) { /* decrypt encrypted message; decode() also to handle data that is not string or buffer */ - Message.decode(encryptedMessage, channelOpts); + await Message.decode(encryptedMessage, channelOpts); /* compare */ testMessageEquality(done, testMessage, encryptedMessage); } From c9557024dbd076714b158cce39544cbdc7c59b7c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 23 May 2023 11:17:49 -0300 Subject: [PATCH 060/468] Make ICipher.decrypt async In preparation for using the (async-only) SubtleCrypto.decrypt method in #1292. Resolves #1293. --- src/common/lib/types/message.ts | 2 +- src/common/types/ICipher.ts | 2 +- src/platform/nodejs/lib/util/crypto.ts | 2 +- src/platform/web/lib/util/crypto.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 7c69766e86..b2979a0688 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -243,7 +243,7 @@ class Message { if (xformAlgorithm != cipher.algorithm) { throw new Error('Unable to decrypt message with given cipher; incompatible cipher params'); } - data = cipher.decrypt(data); + data = await cipher.decrypt(data); continue; } else { throw new Error('Unable to decrypt message; not an encrypted channel'); diff --git a/src/common/types/ICipher.ts b/src/common/types/ICipher.ts index 88e82a80e6..a7d07c0598 100644 --- a/src/common/types/ICipher.ts +++ b/src/common/types/ICipher.ts @@ -1,5 +1,5 @@ export default interface ICipher { algorithm: string; encrypt: (plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) => void; - decrypt: (ciphertext: InputCiphertext) => OutputPlaintext; + decrypt: (ciphertext: InputCiphertext) => Promise; } diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index 3eecf1e649..ef02b907a8 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -249,7 +249,7 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { return callback(null, ciphertext); } - decrypt(ciphertext: InputCiphertext) { + async decrypt(ciphertext: InputCiphertext): Promise { var blockLength = this.blockLength, decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, blockLength)), plaintext = toBuffer(decryptCipher.update(ciphertext.slice(blockLength))), diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 8f7fe5526d..de71b0537b 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -315,7 +315,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe } } - decrypt(ciphertext: InputCiphertext) { + async decrypt(ciphertext: InputCiphertext): Promise { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.decrypt()', ''); ciphertext = bufferUtils.toWordArray(ciphertext); var blockLengthWords = this.blockLengthWords, From cbb98f182171e365392f8cdbbf32cfb0b3c1d06b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 31 May 2023 10:51:35 -0300 Subject: [PATCH 061/468] =?UTF-8?q?Remove=20IBufferUtils=E2=80=99s=20Compa?= =?UTF-8?q?rableBuffer=20generic=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead, make it so that we can compare any two Bufferlike objects. When I was changing some test code (hence, not TypeScript) that made use of bufferCompare, I found myself having to think more than I would have liked about which type of objects I could pass it. --- src/common/platform.ts | 3 +-- src/common/types/IBufferUtils.ts | 4 ++-- src/platform/nodejs/lib/util/bufferutils.ts | 7 +++---- src/platform/web/lib/util/bufferutils.ts | 5 ++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/common/platform.ts b/src/common/platform.ts index c395ce9927..03973a09c6 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -11,7 +11,6 @@ import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils'; type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; type ToBufferOutput = WebBufferUtils.ToBufferOutput | NodeBufferUtils.ToBufferOutput; -type ComparableBuffer = WebBufferUtils.ComparableBuffer | NodeBufferUtils.ComparableBuffer; type WordArrayLike = WebBufferUtils.WordArrayLike | NodeBufferUtils.WordArrayLike; export default class Platform { @@ -23,7 +22,7 @@ export default class Platform { BufferUtils object that accepts a broader range of data types than it can in reality handle. */ - static BufferUtils: IBufferUtils; + static BufferUtils: IBufferUtils; /* This should be a class whose static methods implement the ICryptoStatic interface, but (for the same reasons as described in the BufferUtils diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index 9f1c3728da..5929294d63 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -1,6 +1,6 @@ import { TypedArray } from './IPlatformConfig'; -export default interface IBufferUtils { +export default interface IBufferUtils { base64CharSet: string; hexCharSet: string; isBuffer: (buffer: unknown) => buffer is Bufferlike; @@ -15,7 +15,7 @@ export default interface IBufferUtils Output; utf8Encode: (string: string) => Output; utf8Decode: (buffer: Bufferlike) => string; - bufferCompare: (buffer1: ComparableBuffer, buffer2: ComparableBuffer) => number; + bufferCompare: (buffer1: Bufferlike, buffer2: Bufferlike) => number; byteLength: (buffer: Bufferlike) => number; typedArrayToBuffer: (typedArray: TypedArray) => Bufferlike; toWordArray: (buffer: Bufferlike | number[]) => WordArrayLike; diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index 7a9b35f8c2..514577ba90 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -4,10 +4,9 @@ import IBufferUtils from 'common/types/IBufferUtils'; export type Bufferlike = Buffer | ArrayBuffer | TypedArray; export type Output = Buffer; export type ToBufferOutput = Buffer; -export type ComparableBuffer = Buffer; export type WordArrayLike = never; -class BufferUtils implements IBufferUtils { +class BufferUtils implements IBufferUtils { base64CharSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; hexCharSet: string = '0123456789abcdef'; @@ -19,10 +18,10 @@ class BufferUtils implements IBufferUtils { +class BufferUtils implements IBufferUtils { base64CharSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; hexCharSet = '0123456789abcdef'; @@ -189,7 +188,7 @@ class BufferUtils implements IBufferUtils Date: Wed, 31 May 2023 13:36:47 -0300 Subject: [PATCH 062/468] Always use `instanceof` for checking if something is an ArrayBuffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gives richer type information to compiler (i.e. whether the object was created with the ArrayBuffer constructor instead of whether it just conforms to the ArrayBuffer interface), which I believe a type guard can't express. I’m doing this because the the compiler seemed to consider a TypedArray to satisfy the ArrayBuffer interface: > const foo: TypedArray = new Uint8Array(); > const bar: ArrayBuffer = foo; // This compiles, weird! The effect of this was that, given an object of TypeScript type `ArrayBuffer | TypedArray`, then, after performing an `is ArrayBuffer` type guard that returned false, the compiler believed the object to be of type `never` (when in fact it could still be a TypedArray). --- src/common/types/IBufferUtils.ts | 1 - src/platform/nodejs/lib/util/bufferutils.ts | 6 +----- src/platform/nodejs/lib/util/crypto.ts | 2 +- src/platform/web/lib/util/bufferutils.ts | 12 ++++-------- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index 5929294d63..390f2408f3 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -4,7 +4,6 @@ export default interface IBufferUtils buffer is Bufferlike; - isArrayBuffer: (buffer: unknown) => buffer is ArrayBuffer; isWordArray: (val: unknown) => val is WordArrayLike; // On browser this returns a Uint8Array, on node a Buffer toBuffer: (buffer: Bufferlike) => ToBufferOutput; diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index 514577ba90..4affdfeb51 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -36,14 +36,10 @@ class BufferUtils implements IBufferUtils Date: Wed, 31 May 2023 13:59:34 -0300 Subject: [PATCH 063/468] Replace use of TypedArray with ArrayBufferView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a more broad definition that means that our web Bufferlike type agrees with the web standards’ list of binary data types [1]. [1] https://www.w3.org/TR/WebIDL-1/#common-BufferSource --- src/common/lib/types/message.ts | 2 +- src/common/types/IBufferUtils.ts | 4 +--- src/common/types/IPlatformConfig.d.ts | 13 +------------ src/platform/nodejs/config.ts | 13 +++++++++---- src/platform/nodejs/lib/util/bufferutils.ts | 14 ++++++++------ src/platform/web/config.ts | 4 ++-- src/platform/web/lib/util/bufferutils.ts | 17 ++++++++--------- 7 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index b2979a0688..d77a1cb313 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -275,7 +275,7 @@ class Message { deltaBase = Platform.BufferUtils.toBuffer(deltaBase as Buffer); data = Platform.BufferUtils.toBuffer(data); - data = Platform.BufferUtils.typedArrayToBuffer(context.plugins.vcdiff.decode(data, deltaBase)); + data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBase)); lastPayload = data; } catch (e) { throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400); diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index 390f2408f3..72d50a6d00 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -1,5 +1,3 @@ -import { TypedArray } from './IPlatformConfig'; - export default interface IBufferUtils { base64CharSet: string; hexCharSet: string; @@ -16,6 +14,6 @@ export default interface IBufferUtils string; bufferCompare: (buffer1: Bufferlike, buffer2: Bufferlike) => number; byteLength: (buffer: Bufferlike) => number; - typedArrayToBuffer: (typedArray: TypedArray) => Bufferlike; + arrayBufferViewToBuffer: (arrayBufferView: ArrayBufferView) => Bufferlike; toWordArray: (buffer: Bufferlike | number[]) => WordArrayLike; } diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 126ac5f73e..c9303b68fa 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -1,14 +1,3 @@ -export type TypedArray = - | Int8Array - | Uint8Array - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array; - interface MsgPack { encode(value: any, sparse?: boolean): Buffer | ArrayBuffer | undefined; decode(buffer: Buffer): any; @@ -31,7 +20,7 @@ export interface IPlatformConfig { stringByteSize: Buffer.byteLength; addEventListener?: typeof window.addEventListener | typeof global.addEventListener | null; Promise: typeof Promise; - getRandomValues?: (arr: TypedArray, callback?: (error: Error | null) => void) => void; + getRandomValues?: (arr: ArrayBufferView, callback?: (error: Error | null) => void) => void; userAgent?: string | null; inherits?: typeof import('util').inherits; currentUrl?: string; diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 4a80645c35..4cf1f90142 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -1,4 +1,4 @@ -import { TypedArray, IPlatformConfig } from '../../common/types/IPlatformConfig'; +import { IPlatformConfig } from '../../common/types/IPlatformConfig'; import crypto from 'crypto'; import WebSocket from 'ws'; import util from 'util'; @@ -19,9 +19,14 @@ const Config: IPlatformConfig = { stringByteSize: Buffer.byteLength, inherits: util.inherits, addEventListener: null, - getRandomValues: function (arr: TypedArray, callback?: (err: Error | null) => void): void { - const bytes = crypto.randomBytes(arr.length); - arr.set(bytes); + getRandomValues: function (arr: ArrayBufferView, callback?: (err: Error | null) => void): void { + const bytes = crypto.randomBytes(arr.byteLength); + const dataView = new DataView(arr.buffer, arr.byteOffset, arr.byteLength); + + for (let i = 0; i < bytes.length; i++) { + dataView.setUint8(i, bytes[i]); + } + if (callback) { callback(null); } diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index 4affdfeb51..4613fd45fa 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -1,7 +1,6 @@ -import { TypedArray } from 'common/types/IPlatformConfig'; import IBufferUtils from 'common/types/IBufferUtils'; -export type Bufferlike = Buffer | ArrayBuffer | TypedArray; +export type Bufferlike = Buffer | ArrayBuffer | ArrayBufferView; export type Output = Buffer; export type ToBufferOutput = Buffer; export type WordArrayLike = never; @@ -51,11 +50,14 @@ class BufferUtils implements IBufferUtils void) { + return function (arr: ArrayBufferView, callback?: (error: Error | null) => void) { crypto.getRandomValues(arr); if (callback) { callback(null); diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index a5374861c2..23d51d33e9 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -3,14 +3,13 @@ import { parse as parseUtf8, stringify as stringifyUtf8 } from 'crypto-js/build/ import { parse as parseBase64, stringify as stringifyBase64 } from 'crypto-js/build/enc-base64'; import WordArray from 'crypto-js/build/lib-typedarrays'; import Platform from 'common/platform'; -import { TypedArray } from 'common/types/IPlatformConfig'; import IBufferUtils from 'common/types/IBufferUtils'; /* Most BufferUtils methods that return a binary object return an ArrayBuffer * if supported, else a CryptoJS WordArray. The exception is toBuffer, which * returns a Uint8Array (and won't work on browsers too old to support it) */ -export type Bufferlike = WordArray | ArrayBuffer | TypedArray; +export type Bufferlike = WordArray | BufferSource; export type Output = Bufferlike; export type ToBufferOutput = Uint8Array; export type WordArrayLike = WordArray; @@ -23,7 +22,7 @@ class BufferUtils implements IBufferUtils Date: Wed, 31 May 2023 14:24:16 -0300 Subject: [PATCH 064/468] Replace isArrayBufferView with direct call to ArrayBuffer.isView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There’s no need for this indirection. --- src/platform/web/lib/util/bufferutils.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index 23d51d33e9..4627f0754b 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -22,10 +22,6 @@ class BufferUtils implements IBufferUtils Date: Mon, 22 May 2023 16:31:00 -0300 Subject: [PATCH 065/468] Remove WordArray from web Bufferlike type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #1300 (removing CryptoJS functionality from web’s BufferUtils). --- src/common/lib/types/message.ts | 4 +- src/common/types/IBufferUtils.ts | 2 +- src/common/types/crypto-js.d.ts | 6 -- src/platform/nodejs/lib/util/bufferutils.ts | 2 +- src/platform/web/lib/util/bufferutils.ts | 67 ++++++++++++--------- src/platform/web/lib/util/crypto.ts | 6 +- test/rest/bufferutils.test.js | 19 +----- 7 files changed, 46 insertions(+), 60 deletions(-) diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index d77a1cb313..052df0b178 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -269,9 +269,7 @@ class Message { deltaBase = Platform.BufferUtils.utf8Encode(deltaBase); } - /* vcdiff expects Uint8Arrays, can't copy with ArrayBuffers. (also, if we - * don't have a TextDecoder, deltaBase might be a WordArray here, so need - * to process it into a buffer anyway) */ + // vcdiff expects Uint8Arrays, can't copy with ArrayBuffers. deltaBase = Platform.BufferUtils.toBuffer(deltaBase as Buffer); data = Platform.BufferUtils.toBuffer(data); diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index 72d50a6d00..3c16265750 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -5,7 +5,7 @@ export default interface IBufferUtils val is WordArrayLike; // On browser this returns a Uint8Array, on node a Buffer toBuffer: (buffer: Bufferlike) => ToBufferOutput; - toArrayBuffer: (buffer: Bufferlike) => ArrayBuffer; + toArrayBuffer: (buffer: Bufferlike | WordArrayLike) => ArrayBuffer; base64Encode: (buffer: Bufferlike) => string; base64Decode: (string: string) => Output; hexEncode: (buffer: Bufferlike) => string; diff --git a/src/common/types/crypto-js.d.ts b/src/common/types/crypto-js.d.ts index 8ce331501b..71690a5d68 100644 --- a/src/common/types/crypto-js.d.ts +++ b/src/common/types/crypto-js.d.ts @@ -10,12 +10,6 @@ declare module 'crypto-js/build/enc-base64' { export const stringify: typeof CryptoJS.enc.Base64.stringify; } -declare module 'crypto-js/build/enc-hex' { - import CryptoJS from 'crypto-js'; - export const parse: typeof CryptoJS.enc.Hex.parse; - export const stringify: typeof CryptoJS.enc.Hex.stringify; -} - declare module 'crypto-js/build/enc-utf8' { import CryptoJS from 'crypto-js'; export const parse: typeof CryptoJS.enc.Utf8.parse; diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index 4613fd45fa..f3cacc5d4a 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -41,7 +41,7 @@ class BufferUtils implements IBufferUtils accum + byte.toString(16).padStart(2, '0'), ''); } - hexDecode(string: string) { - var wordArray = parseHex(string); - return ArrayBuffer ? this.toArrayBuffer(wordArray) : wordArray; + hexDecode(hexEncodedBytes: string) { + if (hexEncodedBytes.length % 2 !== 0) { + throw new Error("Can't create a byte array from a hex string of odd length"); + } + + const uint8Array = new Uint8Array(hexEncodedBytes.length / 2); + + for (let i = 0; i < uint8Array.length; i++) { + uint8Array[i] = parseInt(hexEncodedBytes.slice(2 * i, 2 * (i + 1)), 16); + } + + return uint8Array.buffer.slice(uint8Array.byteOffset, uint8Array.byteOffset + uint8Array.byteLength); } utf8Encode(string: string) { if (Platform.Config.TextEncoder) { return new Platform.Config.TextEncoder().encode(string).buffer; + } else { + throw new Error('Expected TextEncoder to be configured'); } - return parseUtf8(string); } /* For utf8 decoding we apply slightly stricter input validation than to @@ -170,13 +179,13 @@ class BufferUtils implements IBufferUtils { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.decrypt()', ''); - ciphertext = bufferUtils.toWordArray(ciphertext); + const ciphertextWordArray = bufferUtils.toWordArray(ciphertext); var blockLengthWords = this.blockLengthWords, - ciphertextWords = ciphertext.words, + ciphertextWords = ciphertextWordArray.words, iv = WordArray.create(ciphertextWords.slice(0, blockLengthWords)), ciphertextBody = WordArray.create(ciphertextWords.slice(blockLengthWords)); diff --git a/test/rest/bufferutils.test.js b/test/rest/bufferutils.test.js index 86b56da784..56ffb08a4b 100644 --- a/test/rest/bufferutils.test.js +++ b/test/rest/bufferutils.test.js @@ -6,9 +6,6 @@ define(['ably', 'chai'], function (Ably, chai) { var testString = 'test'; var testBase64 = 'dGVzdA=='; var testHex = '74657374'; - function isWordArray(ob) { - return ob !== null && ob !== undefined && ob.sigBytes !== undefined; - } describe('rest/bufferutils', function () { it('Basic encoding and decoding', function () { @@ -31,7 +28,7 @@ define(['ably', 'chai'], function (Ably, chai) { /* In node it's idiomatic for most methods dealing with binary data to * return Buffers. In the browser it's more idiomatic to return - * ArrayBuffers (or in browser too old to support ArrayBuffer, wordarrays). */ + * ArrayBuffers */ it('BufferUtils return correct types', function () { if (typeof Buffer !== 'undefined') { /* node */ @@ -40,23 +37,13 @@ define(['ably', 'chai'], function (Ably, chai) { expect(BufferUtils.base64Decode(testBase64).constructor).to.equal(Buffer); expect(BufferUtils.toBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(Buffer); expect(BufferUtils.toArrayBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(ArrayBuffer); - } else if (typeof ArrayBuffer !== 'undefined') { + } else { /* modern browsers */ - if (typeof TextDecoder !== 'undefined') { - expect(BufferUtils.utf8Encode(testString).constructor).to.equal(ArrayBuffer); - } else { - expect(isWordArray(BufferUtils.utf8Encode(testString))).to.be.ok; - } + expect(BufferUtils.utf8Encode(testString).constructor).to.equal(ArrayBuffer); expect(BufferUtils.hexDecode(testHex).constructor).to.equal(ArrayBuffer); expect(BufferUtils.base64Decode(testBase64).constructor).to.equal(ArrayBuffer); expect(BufferUtils.toBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(Uint8Array); expect(BufferUtils.toArrayBuffer(BufferUtils.utf8Encode(testString)).constructor).to.equal(ArrayBuffer); - } else { - /* legacy browsers */ - expect(isWordArray(BufferUtils.utf8Encode(testString))).to.be.ok; - expect(isWordArray(BufferUtils.hexDecode(testHex))).to.be.ok; - expect(isWordArray(BufferUtils.base64Decode(testBase64))).to.be.ok; - expect(isWordArray(BufferUtils.toWordArray(BufferUtils.utf8Encode(testString)))).to.be.ok; } }); }); From 79668b129d310a7f4c92c4c3ba9665a3fdf33f4e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 May 2023 16:05:36 -0300 Subject: [PATCH 066/468] Rename IBufferUtils.bufferCompare to areBuffersEqual MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And make it return just a boolean instead of a number — we aren’t making use of the ordering information it provides, which I don’t really understand and don’t want to spend time trying to reproduce when I reimplement the web version of this method. --- src/common/types/IBufferUtils.ts | 2 +- src/platform/nodejs/lib/util/bufferutils.ts | 7 +++---- src/platform/web/lib/util/bufferutils.ts | 13 +++++-------- test/realtime/crypto.test.js | 18 +++++++++--------- test/rest/bufferutils.test.js | 8 ++++---- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index 3c16265750..e211f11f81 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -12,7 +12,7 @@ export default interface IBufferUtils Output; utf8Encode: (string: string) => Output; utf8Decode: (buffer: Bufferlike) => string; - bufferCompare: (buffer1: Bufferlike, buffer2: Bufferlike) => number; + areBuffersEqual: (buffer1: Bufferlike, buffer2: Bufferlike) => boolean; byteLength: (buffer: Bufferlike) => number; arrayBufferViewToBuffer: (arrayBufferView: ArrayBufferView) => Bufferlike; toWordArray: (buffer: Bufferlike | number[]) => WordArrayLike; diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index f3cacc5d4a..143dcb2215 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -17,10 +17,9 @@ class BufferUtils implements IBufferUtils Date: Mon, 22 May 2023 17:17:46 -0300 Subject: [PATCH 067/468] =?UTF-8?q?Don=E2=80=99t=20use=20CryptoJS=20in=20w?= =?UTF-8?q?eb=20implementation=20of=20BufferUtils.areBuffersEqual?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now, web’s BufferUtils only uses the CryptoJS library to provide the functionality needed by our web Crypto class for: - checking if something is a WordArray - converting something to a WordArray - converting a WordArray to an ArrayBuffer We will remove this remaining CryptoJS code after implementing #1299 (removing the use of CryptoJS in our web Crypto class). Resolves #1300. --- src/platform/web/lib/util/bufferutils.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index 211f894e05..e5ff2413e6 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -190,16 +190,16 @@ class BufferUtils implements IBufferUtils Date: Wed, 31 May 2023 11:12:52 -0300 Subject: [PATCH 068/468] Remove erroneous mention of WordArray in Node crypto.ts --- src/platform/nodejs/lib/util/crypto.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index a33206ff08..bfc85928a0 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -147,9 +147,9 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { * in any not provided with default values, calculating a keyLength from * the supplied key, and validating the result. * @param params an object containing at a minimum a `key` key with value the - * key, as either a binary (ArrayBuffer, Array, WordArray) or a - * base64-encoded string. May optionally also contain: algorithm (defaults to - * AES), mode (defaults to 'cbc') + * key, as either a binary or a base64-encoded string. + * May optionally also contain: algorithm (defaults to AES), + * mode (defaults to 'cbc') */ static getDefaultParams(params: API.Types.CipherParamOptions) { var key: NodeCipherKey; From b8881cf85ad848bb1ae0792f005147c7ffe47213 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 May 2023 16:54:20 -0300 Subject: [PATCH 069/468] Rename misnamed test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s only a WordArray on web, not in Node. --- test/realtime/crypto.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index be7a4b3e34..7643d9dc45 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -149,7 +149,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - it('getDefaultParams_wordArray_key', function (done) { + it('getDefaultParams_withResultOfGenerateRandomKey', function (done) { Crypto.generateRandomKey(function (err, key) { if (err) { done(err); From 50af8e7b31527f19c11ee7a2aa674490cb3b7f2e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 31 May 2023 12:40:46 -0300 Subject: [PATCH 070/468] Fix comment in test This test is executed on all platforms, but WordArray is only used on web. --- test/realtime/crypto.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 7643d9dc45..08fc1b118b 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -342,7 +342,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* Mainly testing that we're correctly encoding the direct output from - * CryptoJS (a wordArray) into the msgpack binary type */ + * the platform's ICipher implementation into the msgpack binary type */ expect(BufferUtils.areBuffersEqual(msgpackFromEncoded, msgpackFromEncrypted)).to.equal( true, 'verify msgpack encodings of newly-encrypted and preencrypted messages identical using areBuffersEqual' @@ -376,7 +376,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* Mainly testing that we're correctly encoding the direct output from - * CryptoJS (a wordArray) into the msgpack binary type */ + * the platform's ICipher implementation into the msgpack binary type */ expect(BufferUtils.areBuffersEqual(msgpackFromEncoded, msgpackFromEncrypted)).to.equal( true, 'verify msgpack encodings of newly-encrypted and preencrypted messages identical using areBuffersEqual' From fd833ab3d9af2786c4e5f1d13f2758416f6a596f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 23 May 2023 08:40:04 -0300 Subject: [PATCH 071/468] Convert React Native platform config to a factory function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m going to want to inject a BufferUtils instance in an upcoming commit. --- src/platform/react-native/config.ts | 91 +++++++++++++++-------------- src/platform/react-native/index.ts | 4 +- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index 1a102406ef..a7f43d4cfb 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -2,47 +2,50 @@ import msgpack from '../web/lib/util/msgpack'; import { parse as parseBase64 } from 'crypto-js/build/enc-base64'; import { IPlatformConfig } from '../../common/types/IPlatformConfig'; -const Platform: IPlatformConfig = { - agent: 'reactnative', - logTimestamps: true, - noUpgrade: false, - binaryType: 'arraybuffer', - WebSocket: WebSocket, - xhrSupported: true, - allowComet: true, - streamingSupported: true, - useProtocolHeartbeats: true, - createHmac: null, - msgpack: msgpack, - supportsBinary: !!(typeof TextDecoder !== 'undefined' && TextDecoder), - preferBinary: false, - ArrayBuffer: typeof ArrayBuffer !== 'undefined' && ArrayBuffer, - atob: global.atob, - nextTick: function (f: Function) { - setTimeout(f, 0); - }, - addEventListener: null, - inspect: JSON.stringify, - stringByteSize: function (str: string) { - /* str.length will be an underestimate for non-ascii strings. But if we're - * in a browser too old to support TextDecoder, not much we can do. Better - * to underestimate, so if we do go over-size, the server will reject the - * message */ - return (typeof TextDecoder !== 'undefined' && new TextEncoder().encode(str).length) || str.length; - }, - TextEncoder: global.TextEncoder, - TextDecoder: global.TextDecoder, - Promise: global.Promise, - getRandomWordArray: (function (RNRandomBytes) { - return function (byteLength: number, callback: (err: Error | null, result: CryptoJS.lib.WordArray | null) => void) { - RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { - callback(err, base64String ? parseBase64(base64String) : null); - }); - }; - // Installing @types/react-native would fix this but conflicts with @types/node - // See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/15960 - // eslint-disable-next-line @typescript-eslint/no-var-requires - })(require('react-native').NativeModules.RNRandomBytes), -}; - -export default Platform; +export default function (): IPlatformConfig { + return { + agent: 'reactnative', + logTimestamps: true, + noUpgrade: false, + binaryType: 'arraybuffer', + WebSocket: WebSocket, + xhrSupported: true, + allowComet: true, + streamingSupported: true, + useProtocolHeartbeats: true, + createHmac: null, + msgpack: msgpack, + supportsBinary: !!(typeof TextDecoder !== 'undefined' && TextDecoder), + preferBinary: false, + ArrayBuffer: typeof ArrayBuffer !== 'undefined' && ArrayBuffer, + atob: global.atob, + nextTick: function (f: Function) { + setTimeout(f, 0); + }, + addEventListener: null, + inspect: JSON.stringify, + stringByteSize: function (str: string) { + /* str.length will be an underestimate for non-ascii strings. But if we're + * in a browser too old to support TextDecoder, not much we can do. Better + * to underestimate, so if we do go over-size, the server will reject the + * message */ + return (typeof TextDecoder !== 'undefined' && new TextEncoder().encode(str).length) || str.length; + }, + TextEncoder: global.TextEncoder, + TextDecoder: global.TextDecoder, + Promise: global.Promise, + getRandomWordArray: (function (RNRandomBytes) { + return function ( + byteLength: number, + callback: (err: Error | null, result: CryptoJS.lib.WordArray | null) => void + ) { + RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { + callback(err, base64String ? parseBase64(base64String) : null); + }); + }; + // Installing @types/react-native would fix this but conflicts with @types/node + // See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/15960 + // eslint-disable-next-line @typescript-eslint/no-var-requires + })(require('react-native').NativeModules.RNRandomBytes), + }; +} diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index e658834ec7..cc7515854e 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -8,7 +8,7 @@ import BufferUtils from '../web/lib/util/bufferutils'; // @ts-ignore import CryptoFactory from '../web/lib/util/crypto'; import Http from '../web/lib/util/http'; -import Config from './config'; +import configFactory from './config'; // @ts-ignore import Transports from '../web/lib/transport'; import Logger from '../../common/lib/util/logger'; @@ -17,6 +17,8 @@ import WebStorage from '../web/lib/util/webstorage'; import PlatformDefaults from '../web/lib/util/defaults'; import msgpack from '../web/lib/util/msgpack'; +const Config = configFactory(); + const Crypto = CryptoFactory(Config, BufferUtils); Platform.Crypto = Crypto; From 7832b44d24b17cdccd67b6f68e6b4dbca008dfa6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 22 May 2023 17:32:28 -0300 Subject: [PATCH 072/468] Change getRandomWordArray to getRandomArrayBuffer Preparation for #1292 (using Web Crypto API for encrypting and decrypting). --- src/common/types/IPlatformConfig.d.ts | 4 ++-- src/platform/react-native/config.ts | 13 +++++-------- src/platform/react-native/index.ts | 2 +- src/platform/web/lib/util/crypto.ts | 8 ++++++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index c9303b68fa..35c9f2c1a8 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -33,9 +33,9 @@ export interface IPlatformConfig { atob?: typeof atob | null; TextEncoder?: typeof TextEncoder; TextDecoder?: typeof TextDecoder; - getRandomWordArray?: ( + getRandomArrayBuffer?: ( byteLength: number, - callback: (err: Error | null, result: CryptoJS.lib.WordArray | null) => void + callback: (err: Error | null, result: ArrayBuffer | null) => void ) => void; isWebworker?: boolean; } diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index a7f43d4cfb..cc203ed9d5 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -1,8 +1,8 @@ import msgpack from '../web/lib/util/msgpack'; -import { parse as parseBase64 } from 'crypto-js/build/enc-base64'; import { IPlatformConfig } from '../../common/types/IPlatformConfig'; +import BufferUtils from '../web/lib/util/bufferutils'; -export default function (): IPlatformConfig { +export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { return { agent: 'reactnative', logTimestamps: true, @@ -34,13 +34,10 @@ export default function (): IPlatformConfig { TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder, Promise: global.Promise, - getRandomWordArray: (function (RNRandomBytes) { - return function ( - byteLength: number, - callback: (err: Error | null, result: CryptoJS.lib.WordArray | null) => void - ) { + getRandomArrayBuffer: (function (RNRandomBytes) { + return function (byteLength: number, callback: (err: Error | null, result: ArrayBuffer | null) => void) { RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { - callback(err, base64String ? parseBase64(base64String) : null); + callback(err, base64String ? bufferUtils.toArrayBuffer(bufferUtils.base64Decode(base64String)) : null); }); }; // Installing @types/react-native would fix this but conflicts with @types/node diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index cc7515854e..3084040580 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -17,7 +17,7 @@ import WebStorage from '../web/lib/util/webstorage'; import PlatformDefaults from '../web/lib/util/defaults'; import msgpack from '../web/lib/util/msgpack'; -const Config = configFactory(); +const Config = configFactory(BufferUtils); const Crypto = CryptoFactory(Config, BufferUtils); diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 9bda3039f4..309d969f14 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -34,8 +34,12 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe * @param callback */ var generateRandom: (byteLength: number, callback: (error: Error | null, result: WordArray | null) => void) => void; - if (config.getRandomWordArray) { - generateRandom = config.getRandomWordArray; + if (config.getRandomArrayBuffer) { + generateRandom = (byteLength, callback) => { + config.getRandomArrayBuffer!(byteLength, (error, result) => { + callback(error, result ? bufferUtils.toWordArray(result) : null); + }); + }; } else if (typeof Uint32Array !== 'undefined' && config.getRandomValues) { var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); generateRandom = function (bytes, callback) { From e14f1bf7b8996c74f91f5c0486a1d734b0c4cbe6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 22 May 2023 17:39:31 -0300 Subject: [PATCH 073/468] Make CipherParams.key an ArrayBuffer on web instead of a WordArray Preparation for #1292 (using Web Crypto API for encrypting and decrypting). --- ably.d.ts | 4 +-- src/platform/web/lib/util/crypto.ts | 44 +++++++++++++---------------- test/realtime/crypto.test.js | 6 ++-- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index dc29099b54..db54ac201a 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2904,11 +2904,11 @@ declare namespace Types { */ type CipherKeyParam = ArrayBuffer | Uint8Array | string; // if string must be base64-encoded /** - * Typed differently depending on platform. (`WordArray` in browser, `Buffer` in node) + * Typed differently depending on platform. (`ArrayBuffer` in browser, `Buffer` in node) * * @internal */ - type CipherKey = unknown; // WordArray on browsers, Buffer on node, using unknown as + type CipherKey = unknown; // ArrayBuffer on browsers, Buffer on node, using unknown as // user should not be interacting with it - output of getDefaultParams should be used opaquely /** diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 309d969f14..df72d446d2 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -1,5 +1,4 @@ import WordArray from 'crypto-js/build/lib-typedarrays'; -import { parse as parseBase64 } from 'crypto-js/build/enc-base64'; import CryptoJS from 'crypto-js/build'; import Logger from '../../../../common/lib/util/logger'; import ErrorInfo from 'common/lib/types/errorinfo'; @@ -26,20 +25,15 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe var DEFAULT_BLOCKLENGTH = 16; // bytes var DEFAULT_BLOCKLENGTH_WORDS = 4; // 32-bit words var UINT32_SUP = 0x100000000; - var INT32_SUP = 0x80000000; /** * Internal: generate an array of secure random words corresponding to the given length of bytes * @param bytes * @param callback */ - var generateRandom: (byteLength: number, callback: (error: Error | null, result: WordArray | null) => void) => void; + var generateRandom: (byteLength: number, callback: (error: Error | null, result: ArrayBuffer | null) => void) => void; if (config.getRandomArrayBuffer) { - generateRandom = (byteLength, callback) => { - config.getRandomArrayBuffer!(byteLength, (error, result) => { - callback(error, result ? bufferUtils.toWordArray(result) : null); - }); - }; + generateRandom = config.getRandomArrayBuffer; } else if (typeof Uint32Array !== 'undefined' && config.getRandomValues) { var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); generateRandom = function (bytes, callback) { @@ -47,7 +41,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe nativeArray = words == DEFAULT_BLOCKLENGTH_WORDS ? blockRandomArray : new Uint32Array(words); config.getRandomValues!(nativeArray, function (err) { if (typeof callback !== 'undefined') { - callback(err, bufferUtils.toWordArray(nativeArray)); + callback(err, bufferUtils.toArrayBuffer(nativeArray)); } }); }; @@ -59,16 +53,12 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe 'Warning: the browser you are using does not support secure cryptographically secure randomness generation; falling back to insecure Math.random()' ); var words = bytes / 4, - array = new Array(words); + array = new Uint32Array(words); for (var i = 0; i < words; i++) { - /* cryptojs wordarrays use signed ints. When WordArray.create is fed a - * Uint32Array unsigned are converted to signed automatically, but when - * fed a normal array they aren't, so need to do so ourselves by - * subtracting INT32_SUP */ - array[i] = Math.floor(Math.random() * UINT32_SUP) - INT32_SUP; + array[i] = Math.floor(Math.random() * UINT32_SUP); } - callback(null, WordArray.create(array)); + callback(null, bufferUtils.toArrayBuffer(array)); }; } @@ -149,9 +139,9 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe algorithm: string; keyLength: number; mode: string; - key: WordArray; + key: ArrayBuffer; - constructor(algorithm: string, keyLength: number, mode: string, key: WordArray) { + constructor(algorithm: string, keyLength: number, mode: string, key: ArrayBuffer) { this.algorithm = algorithm; this.keyLength = keyLength; this.mode = mode; @@ -190,20 +180,22 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe * AES), mode (defaults to 'cbc') */ static getDefaultParams(params: API.Types.CipherParamOptions) { - var key: WordArray; + var key: ArrayBuffer; if (!params.key) { throw new Error('Crypto.getDefaultParams: a key is required'); } if (typeof params.key === 'string') { - key = parseBase64(normaliseBase64(params.key)); + key = bufferUtils.toArrayBuffer(bufferUtils.base64Decode(normaliseBase64(params.key))); + } else if (params.key instanceof ArrayBuffer) { + key = params.key; } else { - key = bufferUtils.toWordArray(params.key); // Expect key to be an Array, ArrayBuffer, or WordArray at this point + key = bufferUtils.toArrayBuffer(params.key); } var algorithm = params.algorithm || DEFAULT_ALGORITHM; - var keyLength = key.words.length * (4 * 8); + var keyLength = key.byteLength * 8; var mode = params.mode || DEFAULT_MODE; var cipherParams = new CipherParams(algorithm, keyLength, mode, key); @@ -309,8 +301,9 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe callback(err, null); return; } - self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: iv! }); - self.iv = iv; + const ivWordArray = bufferUtils.toWordArray(iv!); + self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: ivWordArray }); + self.iv = ivWordArray; then(); }); } @@ -352,7 +345,8 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe callback(err, null); return; } - callback(null, self.encryptCipher!.process(randomBlock!)); + const randomBlockWordArray = bufferUtils.toWordArray(randomBlock!); + callback(null, self.encryptCipher!.process(randomBlockWordArray)); }); } } diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 08fc1b118b..f54ffc3cd3 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -124,8 +124,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } try { - /* .length for a nodejs buffer, .sigbytes for a browser CryptoJS WordArray */ - expect(key.length || key.sigBytes).to.equal(8, 'generated key is the correct length'); + /* .length for a nodejs buffer, .byteLength for a browser ArrayBuffer */ + expect(key.length || key.byteLength).to.equal(8, 'generated key is the correct length'); done(); } catch (err) { done(err); @@ -141,7 +141,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } try { - expect(key.length || key.sigBytes).to.equal(32, 'generated key is the default length'); + expect(key.length || key.byteLength).to.equal(32, 'generated key is the default length'); done(); } catch (err) { done(err); From af675d47257e0315068e06dfdf28d96ad42726b6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 23 May 2023 09:03:23 -0300 Subject: [PATCH 074/468] Change plain/ciphertext output of web Crypto to ArrayBuffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The web crypto code now no longer emits any WordArray objects — it only uses them internally. Preparation for #1292 (using Web Crypto API for encrypting and decrypting). --- src/platform/web/lib/util/crypto.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index df72d446d2..ecaca13991 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -14,9 +14,9 @@ type MessagePackBinaryType = ArrayBuffer; type IV = CryptoDataTypes.IV; type InputPlaintext = CryptoDataTypes.InputPlaintext; -type OutputCiphertext = WordArray; +type OutputCiphertext = ArrayBuffer; type InputCiphertext = CryptoDataTypes.InputCiphertext; -type OutputPlaintext = WordArray; +type OutputPlaintext = ArrayBuffer; var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof BufferUtils) { var DEFAULT_ALGORITHM = 'aes'; @@ -287,7 +287,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe plaintextWordArray.concat(pkcs5Padding[paddedLength - plaintextLength]) ); var ciphertext = iv!.concat(cipherOut); - callback(null, ciphertext); + callback(null, bufferUtils.toArrayBuffer(ciphertext)); }); }; @@ -325,7 +325,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe var epilogue = decryptCipher.finalize(); decryptCipher.reset(); if (epilogue && epilogue.sigBytes) plaintext.concat(epilogue); - return plaintext; + return bufferUtils.toArrayBuffer(plaintext); } getIv(callback: (error: Error | null, iv: WordArray | null) => void) { From dd848f3fcca2745028d44f638486b7ea4cd26af8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 24 May 2023 15:38:06 -0300 Subject: [PATCH 075/468] Remove blockLengthWords property of web CBCCipher All of the rest of the crypto code is built around a non-configurable block length, so this property is just misleading. --- src/platform/web/lib/util/crypto.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index ecaca13991..49e44f6031 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -241,7 +241,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe return { cipherParams: cipherParams, - cipher: new CBCCipher(cipherParams, DEFAULT_BLOCKLENGTH_WORDS, params.iv ?? null), + cipher: new CBCCipher(cipherParams, params.iv ?? null), }; } } @@ -257,16 +257,14 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe cjsAlgorithm: 'AES' | 'DES' | 'TripleDES' | 'RC4' | 'RC4Drop' | 'Rabbit' | 'RabbitLegacy'; key: WordArray; iv: WordArray | null; - blockLengthWords: number; encryptCipher: CryptoJSCipher | null; - constructor(params: CipherParams, blockLengthWords: number, iv: IV | null) { + constructor(params: CipherParams, iv: IV | null) { this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; // We trust that we can handle the algorithm specified by the user — this is the same as the pre-TypeScript behaviour. this.cjsAlgorithm = params.algorithm.toUpperCase().replace(/-\d+$/, '') as typeof this.cjsAlgorithm; this.key = bufferUtils.isWordArray(params.key) ? params.key : bufferUtils.toWordArray(params.key); this.iv = iv ? bufferUtils.toWordArray(iv).clone() : null; - this.blockLengthWords = blockLengthWords; this.encryptCipher = null; } @@ -315,10 +313,9 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe async decrypt(ciphertext: InputCiphertext): Promise { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.decrypt()', ''); const ciphertextWordArray = bufferUtils.toWordArray(ciphertext); - var blockLengthWords = this.blockLengthWords, - ciphertextWords = ciphertextWordArray.words, - iv = WordArray.create(ciphertextWords.slice(0, blockLengthWords)), - ciphertextBody = WordArray.create(ciphertextWords.slice(blockLengthWords)); + var ciphertextWords = ciphertextWordArray.words, + iv = WordArray.create(ciphertextWords.slice(0, DEFAULT_BLOCKLENGTH_WORDS)), + ciphertextBody = WordArray.create(ciphertextWords.slice(DEFAULT_BLOCKLENGTH_WORDS)); var decryptCipher = CryptoJS.algo[this.cjsAlgorithm].createDecryptor(this.key, { iv: iv }); var plaintext = decryptCipher.process(ciphertextBody); From fcdb9f35ebeaf47883493be04d2ff3d0f985a4fc Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 23 May 2023 15:41:19 -0300 Subject: [PATCH 076/468] Use Web Crypto for encrypting and decrypting on web MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes our use of the CryptoJS library for performing encryption and decryption operations, instead using the browser’s built-in crypto APIs. We’re doing this as part of our work to remove the CryptoJS library (#1239) to reduce the size of our SDK. Resolves #1292. --- ably.d.ts | 2 +- src/platform/web/lib/util/crypto.ts | 174 ++++++++++++---------------- 2 files changed, 72 insertions(+), 104 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index db54ac201a..092e044be3 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -975,7 +975,7 @@ declare namespace Types { */ interface ChannelOptions { /** - * Requests encryption for this channel when not null, and specifies encryption-related parameters (such as algorithm, chaining mode, key length and key). See [an example](https://ably.com/docs/realtime/encryption#getting-started). + * Requests encryption for this channel when not null, and specifies encryption-related parameters (such as algorithm, chaining mode, key length and key). See [an example](https://ably.com/docs/realtime/encryption#getting-started). When running in a browser, encryption is only available when the current environment is a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). */ cipher?: CipherParamOptions | CipherParams; /** diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 49e44f6031..92d6bb5c00 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -1,5 +1,3 @@ -import WordArray from 'crypto-js/build/lib-typedarrays'; -import CryptoJS from 'crypto-js/build'; import Logger from '../../../../common/lib/util/logger'; import ErrorInfo from 'common/lib/types/errorinfo'; import * as API from '../../../../../ably'; @@ -27,7 +25,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe var UINT32_SUP = 0x100000000; /** - * Internal: generate an array of secure random words corresponding to the given length of bytes + * Internal: generate an array of secure random data corresponding to the given length of bytes * @param bytes * @param callback */ @@ -62,16 +60,6 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe }; } - /** - * Internal: calculate the padded length of a given plaintext - * using PKCS5. - * @param plaintextLength - * @return - */ - function getPaddedLength(plaintextLength: number) { - return (plaintextLength + DEFAULT_BLOCKLENGTH) & -DEFAULT_BLOCKLENGTH; - } - /** * Internal: checks that the cipherParams are a valid combination. Currently * just checks that the calculated keyLength is a valid one for aes-cbc @@ -94,29 +82,6 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe return string.replace('_', '/').replace('-', '+'); } - /** - * Internal: obtain the pkcs5 padding string for a given padded length; - */ - var pkcs5Padding = [ - WordArray.create([0x10101010, 0x10101010, 0x10101010, 0x10101010], 16), - WordArray.create([0x01000000], 1), - WordArray.create([0x02020000], 2), - WordArray.create([0x03030300], 3), - WordArray.create([0x04040404], 4), - WordArray.create([0x05050505, 0x05000000], 5), - WordArray.create([0x06060606, 0x06060000], 6), - WordArray.create([0x07070707, 0x07070700], 7), - WordArray.create([0x08080808, 0x08080808], 8), - WordArray.create([0x09090909, 0x09090909, 0x09000000], 9), - WordArray.create([0x0a0a0a0a, 0x0a0a0a0a, 0x0a0a0000], 10), - WordArray.create([0x0b0b0b0b, 0x0b0b0b0b, 0x0b0b0b00], 11), - WordArray.create([0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c], 12), - WordArray.create([0x0d0d0d0d, 0x0d0d0d0d, 0x0d0d0d0d, 0x0d000000], 13), - WordArray.create([0x0e0e0e0e, 0x0e0e0e0e, 0x0e0e0e0e, 0x0e0e0000], 14), - WordArray.create([0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f], 15), - WordArray.create([0x10101010, 0x10101010, 0x10101010, 0x10101010], 16), - ]; - function isCipherParams( params: API.Types.CipherParams | API.Types.CipherParamOptions ): params is API.Types.CipherParams { @@ -175,9 +140,9 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe * in any not provided with default values, calculating a keyLength from * the supplied key, and validating the result. * @param params an object containing at a minimum a `key` key with value the - * key, as either a binary (ArrayBuffer, Array, WordArray) or a - * base64-encoded string. May optionally also contain: algorithm (defaults to - * AES), mode (defaults to 'cbc') + * key, as either a binary or a base64-encoded string. + * May optionally also contain: algorithm (defaults to AES), + * mode (defaults to 'cbc') */ static getDefaultParams(params: API.Types.CipherParamOptions) { var key: ArrayBuffer; @@ -214,7 +179,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe /** * Generate a random encryption key from the supplied keylength (or the - * default keyLength if none supplied) as a CryptoJS WordArray + * default keyLength if none supplied) as an ArrayBuffer * @param keyLength (optional) the required keyLength in bits * @param callback (optional) (err, key) */ @@ -248,84 +213,89 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe Crypto satisfies ICryptoStatic; - // This is the only way I could think of to get a reference to the Cipher type, which doesn’t seem to be exported by CryptoJS’s type definitions file. - type CryptoJSCipher = ReturnType; - class CBCCipher implements ICipher { algorithm: string; - // All of the keys in the CryptoJS.algo namespace whose value is a CipherStatic. - cjsAlgorithm: 'AES' | 'DES' | 'TripleDES' | 'RC4' | 'RC4Drop' | 'Rabbit' | 'RabbitLegacy'; - key: WordArray; - iv: WordArray | null; - encryptCipher: CryptoJSCipher | null; + webCryptoAlgorithm: string; + key: ArrayBuffer; + iv: ArrayBuffer | null; constructor(params: CipherParams, iv: IV | null) { + if (!crypto.subtle) { + if (isSecureContext) { + throw new Error( + 'Crypto operations are not possible since the browser’s SubtleCrypto class is unavailable (reason unknown).' + ); + } else { + throw new Error( + 'Crypto operations are is not possible since the current environment is a non-secure context and hence the browser’s SubtleCrypto class is not available.' + ); + } + } + this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; - // We trust that we can handle the algorithm specified by the user — this is the same as the pre-TypeScript behaviour. - this.cjsAlgorithm = params.algorithm.toUpperCase().replace(/-\d+$/, '') as typeof this.cjsAlgorithm; - this.key = bufferUtils.isWordArray(params.key) ? params.key : bufferUtils.toWordArray(params.key); - this.iv = iv ? bufferUtils.toWordArray(iv).clone() : null; - this.encryptCipher = null; + this.webCryptoAlgorithm = params.algorithm + '-' + params.mode; + this.key = bufferUtils.toArrayBuffer(params.key); + this.iv = iv ? bufferUtils.toArrayBuffer(iv) : null; + } + + private concat(buffer1: Bufferlike, buffer2: Bufferlike) { + const output = new ArrayBuffer(buffer1.byteLength + buffer2.byteLength); + const outputView = new DataView(output); + + const buffer1View = new DataView(bufferUtils.toArrayBuffer(buffer1)); + for (let i = 0; i < buffer1View.byteLength; i++) { + outputView.setInt8(i, buffer1View.getInt8(i)); + } + + const buffer2View = new DataView(bufferUtils.toArrayBuffer(buffer2)); + for (let i = 0; i < buffer2View.byteLength; i++) { + outputView.setInt8(buffer1View.byteLength + i, buffer2View.getInt8(i)); + } + + return output; } encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - const plaintextWordArray = bufferUtils.toWordArray(plaintext); - var plaintextLength = plaintextWordArray.sigBytes, - paddedLength = getPaddedLength(plaintextLength), - self = this; - - var then = function () { - self.getIv(function (err, iv) { - if (err) { - callback(err, null); - return; - } - var cipherOut = self.encryptCipher!.process( - plaintextWordArray.concat(pkcs5Padding[paddedLength - plaintextLength]) - ); - var ciphertext = iv!.concat(cipherOut); - callback(null, bufferUtils.toArrayBuffer(ciphertext)); - }); - }; - if (!this.encryptCipher) { - if (this.iv) { - this.encryptCipher = CryptoJS.algo[this.cjsAlgorithm].createEncryptor(this.key, { iv: this.iv }); - then(); - } else { - generateRandom(DEFAULT_BLOCKLENGTH, function (err, iv) { - if (err) { - callback(err, null); - return; + const encryptAsync = async () => { + const iv = await new Promise((resolve: (iv: IV) => void, reject: (error: Error) => void) => { + this.getIv((error, iv) => { + if (error) { + reject(error); + } else { + resolve(iv!); } - const ivWordArray = bufferUtils.toWordArray(iv!); - self.encryptCipher = CryptoJS.algo[self.cjsAlgorithm].createEncryptor(self.key, { iv: ivWordArray }); - self.iv = ivWordArray; - then(); }); - } - } else { - then(); - } + }); + + const cryptoKey = await crypto.subtle.importKey('raw', this.key, this.webCryptoAlgorithm, false, ['encrypt']); + const ciphertext = await crypto.subtle.encrypt({ name: this.webCryptoAlgorithm, iv }, cryptoKey, plaintext); + + return this.concat(iv, ciphertext); + }; + + encryptAsync() + .then((ciphertext) => { + callback(null, ciphertext); + }) + .catch((error) => { + callback(error, null); + }); } async decrypt(ciphertext: InputCiphertext): Promise { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.decrypt()', ''); - const ciphertextWordArray = bufferUtils.toWordArray(ciphertext); - var ciphertextWords = ciphertextWordArray.words, - iv = WordArray.create(ciphertextWords.slice(0, DEFAULT_BLOCKLENGTH_WORDS)), - ciphertextBody = WordArray.create(ciphertextWords.slice(DEFAULT_BLOCKLENGTH_WORDS)); - - var decryptCipher = CryptoJS.algo[this.cjsAlgorithm].createDecryptor(this.key, { iv: iv }); - var plaintext = decryptCipher.process(ciphertextBody); - var epilogue = decryptCipher.finalize(); - decryptCipher.reset(); - if (epilogue && epilogue.sigBytes) plaintext.concat(epilogue); - return bufferUtils.toArrayBuffer(plaintext); + + const ciphertextArrayBuffer = bufferUtils.toArrayBuffer(ciphertext); + const iv = ciphertextArrayBuffer.slice(0, DEFAULT_BLOCKLENGTH); + const ciphertextBody = ciphertextArrayBuffer.slice(DEFAULT_BLOCKLENGTH); + + const cryptoKey = await crypto.subtle.importKey('raw', this.key, this.webCryptoAlgorithm, false, ['decrypt']); + return crypto.subtle.decrypt({ name: this.webCryptoAlgorithm, iv }, cryptoKey, ciphertextBody); } - getIv(callback: (error: Error | null, iv: WordArray | null) => void) { + getIv(callback: (error: Error | null, iv: ArrayBuffer | null) => void) { if (this.iv) { var iv = this.iv; this.iv = null; @@ -336,14 +306,12 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe /* Since the iv for a new block is the ciphertext of the last, this * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as * returning it */ - var self = this; generateRandom(DEFAULT_BLOCKLENGTH, function (err, randomBlock) { if (err) { callback(err, null); return; } - const randomBlockWordArray = bufferUtils.toWordArray(randomBlock!); - callback(null, self.encryptCipher!.process(randomBlockWordArray)); + callback(null, bufferUtils.toArrayBuffer(randomBlock!)); }); } } From b4865bc994b20e2cf30a92f4eb894a26243eb8f9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 1 Jun 2023 12:38:45 -0300 Subject: [PATCH 077/468] Describe the types of key returned by generateRandomKey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My understanding of the intended use case of this method is that it allows a user to generate a key which they can then share with other parties through some side channel in order to establish encrypted communication with those parties. Hence, the output of generateRandomKey should not be an opaque value — the user needs to know how to interact with this value so that they can serialise it. Resolves #1296. --- ably.d.ts | 9 +++------ src/platform/web/lib/util/crypto.ts | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 092e044be3..054d5f5a6f 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -716,7 +716,7 @@ declare namespace Types { /** * The private key used to encrypt and decrypt payloads. You should not set this value directly; rather, you should pass a `key` of type {@link Types.CipherKeyParam} to {@link Crypto.getDefaultParams}. */ - key: CipherKey; + key: unknown; /** * The length of the key in bits; either 128 or 256. */ @@ -2904,12 +2904,9 @@ declare namespace Types { */ type CipherKeyParam = ArrayBuffer | Uint8Array | string; // if string must be base64-encoded /** - * Typed differently depending on platform. (`ArrayBuffer` in browser, `Buffer` in node) - * - * @internal + * The type of the key returned by {@link Crypto.generateRandomKey}. Typed differently depending on platform (`Buffer` in Node.js, `ArrayBuffer` elsewhere). */ - type CipherKey = unknown; // ArrayBuffer on browsers, Buffer on node, using unknown as - // user should not be interacting with it - output of getDefaultParams should be used opaquely + type CipherKey = ArrayBuffer | Buffer; /** * Contains the properties used to generate a {@link CipherParams} object. diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 92d6bb5c00..1598981b51 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -191,7 +191,7 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { if (callback !== undefined) { - callback(err ? ErrorInfo.fromValues(err) : null, buf); + callback(err ? ErrorInfo.fromValues(err) : null, buf ?? undefined); } }); } From 99e4926fa8fcd25efeb94fbc7b35d5ffea47f71d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 1 Jun 2023 16:03:02 -0300 Subject: [PATCH 078/468] =?UTF-8?q?Remove=20platform-specific=20code=20fro?= =?UTF-8?q?m=20auth.ts=E2=80=99s=20toBase64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We already have BufferUtils methods that provide this functionality. --- src/common/lib/client/auth.ts | 7 ++----- src/common/types/crypto-js.d.ts | 6 ------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index b27ba7b81c..8c86522c0f 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -4,7 +4,6 @@ import Multicaster from '../util/multicaster'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; import HmacSHA256 from 'crypto-js/build/hmac-sha256'; import { stringify as stringifyBase64 } from 'crypto-js/build/enc-base64'; -import { parse as parseUtf8 } from 'crypto-js/build/enc-utf8'; import { createHmac } from 'crypto'; import { ErrnoException, RequestCallback, RequestParams } from '../../types/http'; import * as API from '../../../../ably'; @@ -44,10 +43,8 @@ function normaliseAuthcallbackError(err: any) { } let toBase64 = (str: string): string => { - if (Platform.Config.createHmac) { - return Buffer.from(str, 'ascii').toString('base64'); - } - return stringifyBase64(parseUtf8(str)); + const buffer = Platform.BufferUtils.utf8Encode(str); + return Platform.BufferUtils.base64Encode(buffer); }; let hmac = (text: string, key: string): string => { diff --git a/src/common/types/crypto-js.d.ts b/src/common/types/crypto-js.d.ts index 71690a5d68..97cf549c1e 100644 --- a/src/common/types/crypto-js.d.ts +++ b/src/common/types/crypto-js.d.ts @@ -10,12 +10,6 @@ declare module 'crypto-js/build/enc-base64' { export const stringify: typeof CryptoJS.enc.Base64.stringify; } -declare module 'crypto-js/build/enc-utf8' { - import CryptoJS from 'crypto-js'; - export const parse: typeof CryptoJS.enc.Utf8.parse; - export const stringify: typeof CryptoJS.enc.Utf8.stringify; -} - declare module 'crypto-js/build/lib-typedarrays' { import CryptoJS from 'crypto-js'; export default CryptoJS.lib.WordArray; From b110420b45789abe8ff800e6af0f98f6845e8483 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 1 Jun 2023 16:30:16 -0300 Subject: [PATCH 079/468] =?UTF-8?q?Remove=20platform-specific=20code=20fro?= =?UTF-8?q?m=20auth.ts=E2=80=99s=20hmac?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the platform-specific code to the BufferUtils classes (the Crypto classes might seem more appropriate, but we need HMAC functionality to be available even in the noencryption version of the library, which doesn’t have a Crypto class). --- src/common/lib/client/auth.ts | 17 ++++++++--------- src/common/types/IBufferUtils.ts | 1 + src/common/types/IPlatformConfig.d.ts | 3 --- src/common/types/crypto-js.d.ts | 6 ------ src/platform/nativescript/config.js | 1 - src/platform/nodejs/config.ts | 1 - src/platform/nodejs/lib/util/bufferutils.ts | 11 +++++++++++ src/platform/react-native/config.ts | 1 - src/platform/web/config.ts | 1 - src/platform/web/lib/util/bufferutils.ts | 10 ++++++++++ 10 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 8c86522c0f..3775f6c440 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -2,9 +2,6 @@ import Logger from '../util/logger'; import * as Utils from '../util/utils'; import Multicaster from '../util/multicaster'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; -import HmacSHA256 from 'crypto-js/build/hmac-sha256'; -import { stringify as stringifyBase64 } from 'crypto-js/build/enc-base64'; -import { createHmac } from 'crypto'; import { ErrnoException, RequestCallback, RequestParams } from '../../types/http'; import * as API from '../../../../ably'; import { StandardCallback } from '../../types/utils'; @@ -48,12 +45,14 @@ let toBase64 = (str: string): string => { }; let hmac = (text: string, key: string): string => { - if (Platform.Config.createHmac) { - const inst = (Platform.Config.createHmac as typeof createHmac)('SHA256', key); - inst.update(text); - return inst.digest('base64'); - } - return stringifyBase64(HmacSHA256(text, key)); + const bufferUtils = Platform.BufferUtils; + + const textBuffer = bufferUtils.utf8Encode(text); + const keyBuffer = bufferUtils.utf8Encode(key); + + const digest = bufferUtils.hmacSha256(textBuffer, keyBuffer); + + return bufferUtils.base64Encode(digest); }; function c14n(capability?: string | Record>) { diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index e211f11f81..8e88110714 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -16,4 +16,5 @@ export default interface IBufferUtils number; arrayBufferViewToBuffer: (arrayBufferView: ArrayBufferView) => Bufferlike; toWordArray: (buffer: Bufferlike | number[]) => WordArrayLike; + hmacSha256(message: Bufferlike, key: Bufferlike): Output; } diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 35c9f2c1a8..f4766ea2e5 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -9,9 +9,6 @@ export interface IPlatformConfig { binaryType: BinaryType; WebSocket: typeof WebSocket | typeof import('ws'); useProtocolHeartbeats: boolean; - createHmac: - | ((algorithm: string, key: import('crypto').BinaryLike | import('crypto').KeyObject) => import('crypto').Hmac) - | null; msgpack: MsgPack; supportsBinary: boolean; preferBinary: boolean; diff --git a/src/common/types/crypto-js.d.ts b/src/common/types/crypto-js.d.ts index 97cf549c1e..dde4ea6b17 100644 --- a/src/common/types/crypto-js.d.ts +++ b/src/common/types/crypto-js.d.ts @@ -4,12 +4,6 @@ declare module 'crypto-js/build' { export { algo }; } -declare module 'crypto-js/build/enc-base64' { - import CryptoJS from 'crypto-js'; - export const parse: typeof CryptoJS.enc.Base64.parse; - export const stringify: typeof CryptoJS.enc.Base64.stringify; -} - declare module 'crypto-js/build/lib-typedarrays' { import CryptoJS from 'crypto-js'; export default CryptoJS.lib.WordArray; diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index 8295f24f58..a68d0e7468 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -28,7 +28,6 @@ var Config = { allowComet: true, streamingSupported: false, useProtocolHeartbeats: true, - createHmac: null, msgpack: msgpack, supportsBinary: typeof TextDecoder !== 'undefined' && TextDecoder, preferBinary: false, diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 4cf1f90142..2a99da4ad7 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -10,7 +10,6 @@ const Config: IPlatformConfig = { binaryType: 'nodebuffer' as BinaryType, WebSocket, useProtocolHeartbeats: false, - createHmac: crypto.createHmac, msgpack: require('@ably/msgpack-js'), supportsBinary: true, preferBinary: true, diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index 143dcb2215..bded068114 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -1,4 +1,5 @@ import IBufferUtils from 'common/types/IBufferUtils'; +import crypto from 'crypto'; export type Bufferlike = Buffer | ArrayBuffer | ArrayBufferView; export type Output = Buffer; @@ -79,6 +80,16 @@ class BufferUtils implements IBufferUtils Date: Wed, 7 Jun 2023 11:58:44 -0300 Subject: [PATCH 080/468] Add an implementation of HMAC-SHA-256 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re going to use this to replace our current usage of CryptoJS, which we intend to remove in #1239. We considered using the implementation of HMAC from the Web Crypto API, but after weighing the implications of doing so (token signing becoming unavailable in non-secure contexts) against the mildly-increased library size (1.61 kB increase) from adding an HMAC implementation, decided to go with the latter [1]. I picked this implementation because it's a single file that contains precisely the functionality that we need and it claims to be “designed for efficient minification”. It doesn’t come with any tests, but since our SDK has quite a few tests that will fail if the result of token signing is incorrect, I believe we’re OK. The code added here is taken verbatim from the linked gist, and then I’ve added an attribution comment and run Prettier. [1] https://ably-real-time.slack.com/archives/C030C5YLY/p1686152214032779?thread_ts=1686096207.512069&cid=C030C5YLY --- src/platform/web/lib/util/hmac-sha256.js | 234 +++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 src/platform/web/lib/util/hmac-sha256.js diff --git a/src/platform/web/lib/util/hmac-sha256.js b/src/platform/web/lib/util/hmac-sha256.js new file mode 100644 index 0000000000..1b84346206 --- /dev/null +++ b/src/platform/web/lib/util/hmac-sha256.js @@ -0,0 +1,234 @@ +/** + * Copied from https://gist.github.com/stevendesu/2d52f7b5e1f1184af3b667c0b5e054b8 + * + * "A simple, open-source, HMAC-SHA256 implementation in pure JavaScript. Designed for efficient minification." + * + * I asked about licensing, and the author said: + * + * > Feel free to use it however you'd like 😄 As the gist title indicates, + * > this is "a simple open source implementation". Feel free to choose whatever + * > license you find most permissible, but I offer no warranty for the code. + * > It's 100% free to do with as you please. + */ + +// To ensure cross-browser support even without a proper SubtleCrypto +// impelmentation (or without access to the impelmentation, as is the case with +// Chrome loaded over HTTP instead of HTTPS), this library can create SHA-256 +// HMAC signatures using nothing but raw JavaScript + +/* eslint-disable no-magic-numbers, id-length, no-param-reassign, new-cap */ + +// By giving internal functions names that we can mangle, future calls to +// them are reduced to a single byte (minor space savings in minified file) +var uint8Array = Uint8Array; +var uint32Array = Uint32Array; +var pow = Math.pow; + +// Will be initialized below +// Using a Uint32Array instead of a simple array makes the minified code +// a bit bigger (we lose our `unshift()` hack), but comes with huge +// performance gains +var DEFAULT_STATE = new uint32Array(8); +var ROUND_CONSTANTS = []; + +// Reusable object for expanded message +// Using a Uint32Array instead of a simple array makes the minified code +// 7 bytes larger, but comes with huge performance gains +var M = new uint32Array(64); + +// After minification the code to compute the default state and round +// constants is smaller than the output. More importantly, this serves as a +// good educational aide for anyone wondering where the magic numbers come +// from. No magic numbers FTW! +function getFractionalBits(n) { + return ((n - (n | 0)) * pow(2, 32)) | 0; +} + +var n = 2, + nPrime = 0; +while (nPrime < 64) { + // isPrime() was in-lined from its original function form to save + // a few bytes + var isPrime = true; + // Math.sqrt() was replaced with pow(n, 1/2) to save a few bytes + // var sqrtN = pow(n, 1 / 2); + // So technically to determine if a number is prime you only need to + // check numbers up to the square root. However this function only runs + // once and we're only computing the first 64 primes (up to 311), so on + // any modern CPU this whole function runs in a couple milliseconds. + // By going to n / 2 instead of sqrt(n) we net 8 byte savings and no + // scaling performance cost + for (var factor = 2; factor <= n / 2; factor++) { + if (n % factor === 0) { + isPrime = false; + } + } + if (isPrime) { + if (nPrime < 8) { + DEFAULT_STATE[nPrime] = getFractionalBits(pow(n, 1 / 2)); + } + ROUND_CONSTANTS[nPrime] = getFractionalBits(pow(n, 1 / 3)); + + nPrime++; + } + + n++; +} + +// For cross-platform support we need to ensure that all 32-bit words are +// in the same endianness. A UTF-8 TextEncoder will return BigEndian data, +// so upon reading or writing to our ArrayBuffer we'll only swap the bytes +// if our system is LittleEndian (which is about 99% of CPUs) +var LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0]; + +function convertEndian(word) { + if (LittleEndian) { + return ( + // byte 1 -> byte 4 + (word >>> 24) | + // byte 2 -> byte 3 + (((word >>> 16) & 0xff) << 8) | + // byte 3 -> byte 2 + ((word & 0xff00) << 8) | + // byte 4 -> byte 1 + (word << 24) + ); + } else { + return word; + } +} + +function rightRotate(word, bits) { + return (word >>> bits) | (word << (32 - bits)); +} + +function sha256(data) { + // Copy default state + var STATE = DEFAULT_STATE.slice(); + + // Caching this reduces occurrences of ".length" in minified JavaScript + // 3 more byte savings! :D + var legth = data.length; + + // Pad data + var bitLength = legth * 8; + var newBitLength = 512 - ((bitLength + 64) % 512) - 1 + bitLength + 65; + + // "bytes" and "words" are stored BigEndian + var bytes = new uint8Array(newBitLength / 8); + var words = new uint32Array(bytes.buffer); + + bytes.set(data, 0); + // Append a 1 + bytes[legth] = 0b10000000; + // Store length in BigEndian + words[words.length - 1] = convertEndian(bitLength); + + // Loop iterator (avoid two instances of "var") -- saves 2 bytes + var round; + + // Process blocks (512 bits / 64 bytes / 16 words at a time) + for (var block = 0; block < newBitLength / 32; block += 16) { + var workingState = STATE.slice(); + + // Rounds + for (round = 0; round < 64; round++) { + var MRound; + // Expand message + if (round < 16) { + // Convert to platform Endianness for later math + MRound = convertEndian(words[block + round]); + } else { + var gamma0x = M[round - 15]; + var gamma1x = M[round - 2]; + MRound = + M[round - 7] + + M[round - 16] + + (rightRotate(gamma0x, 7) ^ rightRotate(gamma0x, 18) ^ (gamma0x >>> 3)) + + (rightRotate(gamma1x, 17) ^ rightRotate(gamma1x, 19) ^ (gamma1x >>> 10)); + } + + // M array matches platform endianness + M[round] = MRound |= 0; + + // Computation + var t1 = + (rightRotate(workingState[4], 6) ^ rightRotate(workingState[4], 11) ^ rightRotate(workingState[4], 25)) + + ((workingState[4] & workingState[5]) ^ (~workingState[4] & workingState[6])) + + workingState[7] + + MRound + + ROUND_CONSTANTS[round]; + var t2 = + (rightRotate(workingState[0], 2) ^ rightRotate(workingState[0], 13) ^ rightRotate(workingState[0], 22)) + + ((workingState[0] & workingState[1]) ^ (workingState[2] & (workingState[0] ^ workingState[1]))); + for (var i = 7; i > 0; i--) { + workingState[i] = workingState[i - 1]; + } + workingState[0] = (t1 + t2) | 0; + workingState[4] = (workingState[4] + t1) | 0; + } + + // Update state + for (round = 0; round < 8; round++) { + STATE[round] = (STATE[round] + workingState[round]) | 0; + } + } + + // Finally the state needs to be converted to BigEndian for output + // And we want to return a Uint8Array, not a Uint32Array + return new uint8Array( + new uint32Array( + STATE.map(function (val) { + return convertEndian(val); + }) + ).buffer + ); +} + +function hmac(key, data) { + if (key.length > 64) key = sha256(key); + + if (key.length < 64) { + const tmp = new Uint8Array(64); + tmp.set(key, 0); + key = tmp; + } + + // Generate inner and outer keys + var innerKey = new Uint8Array(64); + var outerKey = new Uint8Array(64); + for (var i = 0; i < 64; i++) { + innerKey[i] = 0x36 ^ key[i]; + outerKey[i] = 0x5c ^ key[i]; + } + + // Append the innerKey + var msg = new Uint8Array(data.length + 64); + msg.set(innerKey, 0); + msg.set(data, 64); + + // Has the previous message and append the outerKey + var result = new Uint8Array(64 + 32); + result.set(outerKey, 0); + result.set(sha256(msg), 64); + + // Hash the previous message + return sha256(result); +} + +// Convert a string to a Uint8Array, SHA-256 it, and convert back to string +const encoder = new TextEncoder('utf-8'); + +export function sign(inputKey, inputData) { + const key = typeof inputKey === 'string' ? encoder.encode(inputKey) : inputKey; + const data = typeof inputData === 'string' ? encoder.encode(inputData) : inputData; + return hmac(key, data); +} + +export function hash(str) { + return hex(sha256(encoder.encode(str))); +} + +export function hex(bin) { + return bin.reduce((acc, val) => acc + ('00' + val.toString(16)).substr(-2), ''); +} From 32833d442a8abb11282eea625c119c10a61cf942 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 7 Jun 2023 14:21:32 -0300 Subject: [PATCH 081/468] Convert HMAC implementation to TypeScript --- .../util/{hmac-sha256.js => hmac-sha256.ts} | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename src/platform/web/lib/util/{hmac-sha256.js => hmac-sha256.ts} (94%) diff --git a/src/platform/web/lib/util/hmac-sha256.js b/src/platform/web/lib/util/hmac-sha256.ts similarity index 94% rename from src/platform/web/lib/util/hmac-sha256.js rename to src/platform/web/lib/util/hmac-sha256.ts index 1b84346206..9fba818b06 100644 --- a/src/platform/web/lib/util/hmac-sha256.js +++ b/src/platform/web/lib/util/hmac-sha256.ts @@ -29,7 +29,7 @@ var pow = Math.pow; // a bit bigger (we lose our `unshift()` hack), but comes with huge // performance gains var DEFAULT_STATE = new uint32Array(8); -var ROUND_CONSTANTS = []; +var ROUND_CONSTANTS: number[] = []; // Reusable object for expanded message // Using a Uint32Array instead of a simple array makes the minified code @@ -40,7 +40,7 @@ var M = new uint32Array(64); // constants is smaller than the output. More importantly, this serves as a // good educational aide for anyone wondering where the magic numbers come // from. No magic numbers FTW! -function getFractionalBits(n) { +function getFractionalBits(n: number) { return ((n - (n | 0)) * pow(2, 32)) | 0; } @@ -81,7 +81,7 @@ while (nPrime < 64) { // if our system is LittleEndian (which is about 99% of CPUs) var LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0]; -function convertEndian(word) { +function convertEndian(word: number) { if (LittleEndian) { return ( // byte 1 -> byte 4 @@ -98,11 +98,11 @@ function convertEndian(word) { } } -function rightRotate(word, bits) { +function rightRotate(word: number, bits: number) { return (word >>> bits) | (word << (32 - bits)); } -function sha256(data) { +function sha256(data: Uint8Array) { // Copy default state var STATE = DEFAULT_STATE.slice(); @@ -185,7 +185,7 @@ function sha256(data) { ); } -function hmac(key, data) { +function hmac(key: Uint8Array, data: Uint8Array) { if (key.length > 64) key = sha256(key); if (key.length < 64) { @@ -217,18 +217,18 @@ function hmac(key, data) { } // Convert a string to a Uint8Array, SHA-256 it, and convert back to string -const encoder = new TextEncoder('utf-8'); +const encoder = new TextEncoder(); -export function sign(inputKey, inputData) { +export function sign(inputKey: string | Uint8Array, inputData: string | Uint8Array) { const key = typeof inputKey === 'string' ? encoder.encode(inputKey) : inputKey; const data = typeof inputData === 'string' ? encoder.encode(inputData) : inputData; return hmac(key, data); } -export function hash(str) { +export function hash(str: string) { return hex(sha256(encoder.encode(str))); } -export function hex(bin) { +export function hex(bin: Uint8Array) { return bin.reduce((acc, val) => acc + ('00' + val.toString(16)).substr(-2), ''); } From 90dde04a7bed93e26e2c5139874faab3f097f6e7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 7 Jun 2023 12:08:37 -0300 Subject: [PATCH 082/468] Stop using CryptoJS for token request signing Instead, use the HMAC implementation added in fc94b80. Resolves #1295. --- src/platform/web/lib/util/bufferutils.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index 973356f41f..139a5aa234 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -1,7 +1,7 @@ import WordArray from 'crypto-js/build/lib-typedarrays'; import Platform from 'common/platform'; import IBufferUtils from 'common/types/IBufferUtils'; -import HmacSHA256 from 'crypto-js/build/hmac-sha256'; +import { sign as hmacSha256 } from './hmac-sha256'; /* Most BufferUtils methods that return a binary object return an ArrayBuffer * The exception is toBuffer, which returns a Uint8Array (and won't work on @@ -218,12 +218,7 @@ class BufferUtils implements IBufferUtils Date: Wed, 7 Jun 2023 14:34:44 -0300 Subject: [PATCH 083/468] Remove unused code from HMAC implementation --- src/platform/web/lib/util/bufferutils.ts | 2 +- src/platform/web/lib/util/hmac-sha256.ts | 19 +------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index 139a5aa234..392395fadb 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -1,7 +1,7 @@ import WordArray from 'crypto-js/build/lib-typedarrays'; import Platform from 'common/platform'; import IBufferUtils from 'common/types/IBufferUtils'; -import { sign as hmacSha256 } from './hmac-sha256'; +import { hmac as hmacSha256 } from './hmac-sha256'; /* Most BufferUtils methods that return a binary object return an ArrayBuffer * The exception is toBuffer, which returns a Uint8Array (and won't work on diff --git a/src/platform/web/lib/util/hmac-sha256.ts b/src/platform/web/lib/util/hmac-sha256.ts index 9fba818b06..1000b5172d 100644 --- a/src/platform/web/lib/util/hmac-sha256.ts +++ b/src/platform/web/lib/util/hmac-sha256.ts @@ -185,7 +185,7 @@ function sha256(data: Uint8Array) { ); } -function hmac(key: Uint8Array, data: Uint8Array) { +export function hmac(key: Uint8Array, data: Uint8Array) { if (key.length > 64) key = sha256(key); if (key.length < 64) { @@ -215,20 +215,3 @@ function hmac(key: Uint8Array, data: Uint8Array) { // Hash the previous message return sha256(result); } - -// Convert a string to a Uint8Array, SHA-256 it, and convert back to string -const encoder = new TextEncoder(); - -export function sign(inputKey: string | Uint8Array, inputData: string | Uint8Array) { - const key = typeof inputKey === 'string' ? encoder.encode(inputKey) : inputKey; - const data = typeof inputData === 'string' ? encoder.encode(inputData) : inputData; - return hmac(key, data); -} - -export function hash(str: string) { - return hex(sha256(encoder.encode(str))); -} - -export function hex(bin: Uint8Array) { - return bin.reduce((acc, val) => acc + ('00' + val.toString(16)).substr(-2), ''); -} From 9e50d2ee7128eff4b3eb924313420de4cdeb31b0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 7 Jun 2023 16:03:39 -0300 Subject: [PATCH 084/468] Remove an error that will never be thrown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This message was added in the very first commit of this repo — perhaps it was relevant then, but it certainly no longer is, since the `hmac` function is always present. --- src/common/lib/client/auth.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 3775f6c440..6a0cb45339 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -130,11 +130,6 @@ class Auth { if (useTokenAuth(options)) { /* Token auth */ - if (options.key && !hmac) { - const msg = 'client-side token request signing not supported'; - Logger.logAction(Logger.LOG_ERROR, 'Auth()', msg); - throw new Error(msg); - } if (noWayToRenew(options)) { Logger.logAction( Logger.LOG_ERROR, From e036148c315b1c6a68cb5f0b3172000b0818addf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 6 Jun 2023 17:30:55 -0300 Subject: [PATCH 085/468] =?UTF-8?q?Make=20ErrorInfo.fromValues=E2=80=99s?= =?UTF-8?q?=20signature=20correspond=20to=20its=20expectations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It expects the received object to have certain keys of certain types, and throws an error if not. So, get the compiler’s help in enforcing this. --- src/common/lib/types/devicedetails.ts | 4 ++-- src/common/lib/types/errorinfo.ts | 10 ++++++++-- src/platform/nodejs/lib/util/crypto.ts | 5 ++++- src/platform/web/lib/util/crypto.ts | 5 ++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/common/lib/types/devicedetails.ts b/src/common/lib/types/devicedetails.ts index e7ba9ffde5..59cc80b514 100644 --- a/src/common/lib/types/devicedetails.ts +++ b/src/common/lib/types/devicedetails.ts @@ -1,5 +1,5 @@ import * as Utils from '../util/utils'; -import ErrorInfo from './errorinfo'; +import ErrorInfo, { IConvertibleToErrorInfo } from './errorinfo'; enum DeviceFormFactor { Phone = 'phone', @@ -88,7 +88,7 @@ class DeviceDetails { } static fromValues(values: Record): DeviceDetails { - values.error = values.error && ErrorInfo.fromValues(values.error as Error); + values.error = values.error && ErrorInfo.fromValues(values.error as IConvertibleToErrorInfo); return Object.assign(new DeviceDetails(), values); } diff --git a/src/common/lib/types/errorinfo.ts b/src/common/lib/types/errorinfo.ts index 88bfd27ca7..f7ed5193b7 100644 --- a/src/common/lib/types/errorinfo.ts +++ b/src/common/lib/types/errorinfo.ts @@ -20,6 +20,12 @@ function toString(err: ErrorInfo | PartialErrorInfo) { return result; } +export interface IConvertibleToErrorInfo { + message: string; + code: number; + statusCode: number; +} + export default class ErrorInfo extends Error implements IPartialErrorInfo, API.Types.ErrorInfo { code: number; statusCode: number; @@ -40,8 +46,8 @@ export default class ErrorInfo extends Error implements IPartialErrorInfo, API.T return toString(this); } - static fromValues(values: Record | ErrorInfo | Error): ErrorInfo { - const { message, code, statusCode } = values as ErrorInfo; + static fromValues(values: IConvertibleToErrorInfo): ErrorInfo { + const { message, code, statusCode } = values; if (typeof message !== 'string' || typeof code !== 'number' || typeof statusCode !== 'number') { throw new Error('ErrorInfo.fromValues(): invalid values: ' + Platform.Config.inspect(values)); } diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index bfc85928a0..3a19dc93fe 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -198,7 +198,10 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { if (callback !== undefined) { - callback(err ? ErrorInfo.fromValues(err) : null, buf); + const errorInfo = err + ? new ErrorInfo('Failed to generate random key: ' + err.message, 500, 50000, err) + : null; + callback(errorInfo, buf); } }); } diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 1598981b51..92168b5801 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -191,7 +191,10 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { if (callback !== undefined) { - callback(err ? ErrorInfo.fromValues(err) : null, buf ?? undefined); + const errorInfo = err + ? new ErrorInfo('Failed to generate random key: ' + err.message, 400, 50000, err) + : null; + callback(errorInfo, buf ?? undefined); } }); } From 68d4aaf1f2f335341df75cd13b855dce502b54d8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 24 May 2023 16:50:52 -0300 Subject: [PATCH 086/468] Remove WordArray functionality from BufferUtils No longer needed as of 11a4a39. --- src/common/platform.ts | 3 +-- src/common/types/IBufferUtils.ts | 6 ++--- src/common/types/crypto-js.d.ts | 5 ---- src/platform/nodejs/lib/util/bufferutils.ts | 15 ++--------- src/platform/web/lib/util/bufferutils.ts | 29 ++------------------- 5 files changed, 7 insertions(+), 51 deletions(-) diff --git a/src/common/platform.ts b/src/common/platform.ts index 03973a09c6..41992c3bc5 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -11,7 +11,6 @@ import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils'; type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; type ToBufferOutput = WebBufferUtils.ToBufferOutput | NodeBufferUtils.ToBufferOutput; -type WordArrayLike = WebBufferUtils.WordArrayLike | NodeBufferUtils.WordArrayLike; export default class Platform { static Config: IPlatformConfig; @@ -22,7 +21,7 @@ export default class Platform { BufferUtils object that accepts a broader range of data types than it can in reality handle. */ - static BufferUtils: IBufferUtils; + static BufferUtils: IBufferUtils; /* This should be a class whose static methods implement the ICryptoStatic interface, but (for the same reasons as described in the BufferUtils diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index 8e88110714..efa1b51209 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -1,11 +1,10 @@ -export default interface IBufferUtils { +export default interface IBufferUtils { base64CharSet: string; hexCharSet: string; isBuffer: (buffer: unknown) => buffer is Bufferlike; - isWordArray: (val: unknown) => val is WordArrayLike; // On browser this returns a Uint8Array, on node a Buffer toBuffer: (buffer: Bufferlike) => ToBufferOutput; - toArrayBuffer: (buffer: Bufferlike | WordArrayLike) => ArrayBuffer; + toArrayBuffer: (buffer: Bufferlike) => ArrayBuffer; base64Encode: (buffer: Bufferlike) => string; base64Decode: (string: string) => Output; hexEncode: (buffer: Bufferlike) => string; @@ -15,6 +14,5 @@ export default interface IBufferUtils boolean; byteLength: (buffer: Bufferlike) => number; arrayBufferViewToBuffer: (arrayBufferView: ArrayBufferView) => Bufferlike; - toWordArray: (buffer: Bufferlike | number[]) => WordArrayLike; hmacSha256(message: Bufferlike, key: Bufferlike): Output; } diff --git a/src/common/types/crypto-js.d.ts b/src/common/types/crypto-js.d.ts index dde4ea6b17..db91283ac4 100644 --- a/src/common/types/crypto-js.d.ts +++ b/src/common/types/crypto-js.d.ts @@ -4,11 +4,6 @@ declare module 'crypto-js/build' { export { algo }; } -declare module 'crypto-js/build/lib-typedarrays' { - import CryptoJS from 'crypto-js'; - export default CryptoJS.lib.WordArray; -} - declare module 'crypto-js/build/hmac-sha256' { import CryptoJS from 'crypto-js'; export default CryptoJS.HmacSHA256; diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index bded068114..e1c6fbcb67 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -4,9 +4,8 @@ import crypto from 'crypto'; export type Bufferlike = Buffer | ArrayBuffer | ArrayBufferView; export type Output = Buffer; export type ToBufferOutput = Buffer; -export type WordArrayLike = never; -class BufferUtils implements IBufferUtils { +class BufferUtils implements IBufferUtils { base64CharSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; hexCharSet: string = '0123456789abcdef'; @@ -41,7 +40,7 @@ class BufferUtils implements IBufferUtils { +class BufferUtils implements IBufferUtils { base64CharSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; hexCharSet = '0123456789abcdef'; - isWordArray(ob: unknown): ob is WordArray { - return ob !== null && ob !== undefined && (ob as WordArray).sigBytes !== undefined; - } - // // https://gist.githubusercontent.com/jonleighton/958841/raw/f200e30dfe95212c0165ccf1ae000ca51e9de803/gistfile1.js uint8ViewToBase64(bytes: Uint8Array) { let base64 = ''; @@ -104,32 +98,13 @@ class BufferUtils implements IBufferUtils>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - } - - return uint8View; - } return this.toBuffer(buffer).buffer; } - toWordArray(buffer: Bufferlike | number[]) { - if (ArrayBuffer.isView(buffer)) { - buffer = buffer.buffer; - } - return this.isWordArray(buffer) ? buffer : WordArray.create(buffer as number[]); - } - base64Encode(buffer: Bufferlike) { return this.uint8ViewToBase64(this.toBuffer(buffer)); } From 8fe958d3ba6be4d58469c39972bb7a67de2263f4 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 6 Jun 2023 10:17:10 -0300 Subject: [PATCH 087/468] Remove unused randomHexString function --- src/common/lib/util/utils.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 5ffb9e4297..f1ecd837ca 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -479,21 +479,6 @@ export const randomString = (numBytes: number): string => { return result; }; -export const randomHexString = (numBytes: number): string => { - if (Platform.Config.getRandomValues && typeof Uint8Array !== 'undefined') { - const uIntArr = new Uint8Array(numBytes); - (Platform.Config.getRandomValues as Function)(uIntArr); - return Platform.BufferUtils.hexEncode(uIntArr); - } - const charset = Platform.BufferUtils.hexCharSet; - const length = numBytes * 2; - let result = ''; - for (let i = 0; i < length; i++) { - result += charset[randomPosn(charset)]; - } - return result; -}; - /* Pick n elements at random without replacement from an array */ export function arrChooseN(arr: Array, n: number): Array { const numItems = Math.min(n, arr.length), From a2e3e00e8c979e3ea11537bdaa37c28dc4225c6b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 5 Jun 2023 13:36:14 -0300 Subject: [PATCH 088/468] Remove a comment relating to CryptoJS Preparation for #1239 (removing CryptoJS dependency). --- src/common/lib/util/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index f1ecd837ca..52a5e5f6e0 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -466,9 +466,9 @@ export const randomString = (numBytes: number): string => { (Platform.Config.getRandomValues as Function)(uIntArr); return Platform.BufferUtils.base64Encode(uIntArr); } - /* Old browser; fall back to Math.random. Could just use a - * CryptoJS version of the above, but want this to still work in nocrypto - * versions of the library */ + /* No secure random generator available; fall back to Math.random. + * TODO we should no longer end up in this scenario — and hence should be able to remove this code — given that all supported platforms should now have a random generator — see https://github.com/ably/ably-js/issues/1332 + */ const charset = Platform.BufferUtils.base64CharSet; /* base64 has 33% overhead; round length up */ const length = Math.round((numBytes * 4) / 3); From e15dccd266014c3f655815460bcb2d73c6caa1c1 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 6 Jun 2023 13:52:16 -0300 Subject: [PATCH 089/468] Stop using CryptoJS in testapp_manager.js Preparation for #1239 (removing CryptoJS dependency). --- test/common/globals/named_dependencies.js | 2 -- test/common/modules/testapp_manager.js | 21 +++++---------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/test/common/globals/named_dependencies.js b/test/common/globals/named_dependencies.js index 3228c893c6..4fc3a7407e 100644 --- a/test/common/globals/named_dependencies.js +++ b/test/common/globals/named_dependencies.js @@ -4,8 +4,6 @@ define(function () { // Ably modules ably: { browser: 'build/ably', node: 'build/ably-node' }, 'ably.noencryption': { browser: 'build/ably.noencryption' }, - base64: { browser: 'node_modules/crypto-js/build/enc-base64', node: 'skip' }, - utf8: { browser: 'node_modules/crypto-js/build/enc-utf8', node: 'skip' }, 'vcdiff-decoder': { browser: 'node_modules/@ably/vcdiff-decoder/dist/vcdiff-decoder', node: 'node_modules/@ably/vcdiff-decoder', diff --git a/test/common/modules/testapp_manager.js b/test/common/modules/testapp_manager.js index a6cc42253d..41724490e3 100644 --- a/test/common/modules/testapp_manager.js +++ b/test/common/modules/testapp_manager.js @@ -2,14 +2,13 @@ /* global define, isNativescript, fetch */ /* testapp module is responsible for setting up and tearing down apps in the test environment */ -define(['globals', 'base64', 'utf8', 'ably'], function (ablyGlobals, Base64, UTF8, ably) { +define(['globals', 'ably'], function (ablyGlobals, ably) { var restHost = ablyGlobals.restHost || prefixDomainWithEnvironment('rest.ably.io', ablyGlobals.environment), tlsPort = ablyGlobals.tlsPort; var isBrowser = typeof window === 'object', isNativescript = typeof global === 'object' && global.isNativescript, httpReq = httpReqFunction(), - toBase64 = base64Function(), loadJsonData = loadJsonDataNode, testResourcesPath = 'test/common/ably-common/test-resources/'; @@ -42,20 +41,10 @@ define(['globals', 'base64', 'utf8', 'ably'], function (ablyGlobals, Base64, UTF return null; } - function NSbase64Function(d) { - return Base64.stringify(d); - } - - function base64Function() { - if (isBrowser) { - return function (str) { - return Base64.stringify(UTF8.parse(str)); - }; - } else { - return function (str) { - return Buffer.from(str, 'ascii').toString('base64'); - }; - } + function toBase64(str) { + var bufferUtils = ably.Realtime.Platform.BufferUtils; + var buffer = bufferUtils.utf8Encode(str); + return bufferUtils.base64Encode(buffer); } function httpReqFunction() { From f1bf7480ec2b758a8bce443d07b4415fe167707f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 1 Jun 2023 16:54:27 -0300 Subject: [PATCH 090/468] Remove all usage of the CryptoJS library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re no longer making use of this library (in order to reduce bundle size, given that modern browsers provide all of the necessary crypto functionality). Resolves #1239. --- Gruntfile.js | 1 - package-lock.json | 27 --------------------------- package.json | 2 -- src/common/types/crypto-js.d.ts | 10 ---------- test/support/browser_file_list.js | 3 --- test/web_server | 9 --------- webpack.config.js | 3 --- 7 files changed, 55 deletions(-) delete mode 100644 src/common/types/crypto-js.d.ts diff --git a/Gruntfile.js b/Gruntfile.js index ac233ce0b2..bf5ddfcc45 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,7 +16,6 @@ module.exports = function (grunt) { fragments: 'src/platform/web/fragments', static: 'build', dest: 'build', - crypto_js: 'node_modules/crypto-js/src', tools_compiler: __dirname + '/node_modules/google-closure-compiler/compiler.jar', }; diff --git a/package-lock.json b/package-lock.json index b2f93b69b1..ee1341051c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ }, "devDependencies": { "@ably/vcdiff-decoder": "1.0.4", - "@types/crypto-js": "^4.0.1", "@types/node": "^18.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", @@ -26,7 +25,6 @@ "chai": "^4.2.0", "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", - "crypto-js": "ably-forks/crypto-js#crypto-lite", "eslint": "^7.13.0", "eslint-plugin-jsdoc": "^40.0.0", "eslint-plugin-security": "^1.4.0", @@ -327,12 +325,6 @@ "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", "dev": true }, - "node_modules/@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", - "dev": true - }, "node_modules/@types/eslint": { "version": "8.37.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", @@ -1819,13 +1811,6 @@ "node": ">= 8" } }, - "node_modules/crypto-js": { - "version": "4.0.0", - "resolved": "git+https://git@github.com/ably-forks/crypto-js.git#07e09b48fd8f850f71c10c9d9307ca4e6ac622a0", - "integrity": "sha512-3RE9Ztinx+QLpEDwcsPTVGMXIIqtS/r+YyD1E7BGXE+CRBcCwkyHWVyl1Aperg5QBOUkg2dtiC8p18a41IKltQ==", - "dev": true, - "license": "MIT" - }, "node_modules/dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -7679,12 +7664,6 @@ "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", "dev": true }, - "@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", - "dev": true - }, "@types/eslint": { "version": "8.37.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", @@ -8831,12 +8810,6 @@ "which": "^2.0.1" } }, - "crypto-js": { - "version": "git+https://git@github.com/ably-forks/crypto-js.git#07e09b48fd8f850f71c10c9d9307ca4e6ac622a0", - "integrity": "sha512-3RE9Ztinx+QLpEDwcsPTVGMXIIqtS/r+YyD1E7BGXE+CRBcCwkyHWVyl1Aperg5QBOUkg2dtiC8p18a41IKltQ==", - "dev": true, - "from": "crypto-js@ably-forks/crypto-js#crypto-lite" - }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", diff --git a/package.json b/package.json index 7e54b83243..ce9639b23b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ }, "devDependencies": { "@ably/vcdiff-decoder": "1.0.4", - "@types/crypto-js": "^4.0.1", "@types/node": "^18.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", @@ -42,7 +41,6 @@ "chai": "^4.2.0", "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", - "crypto-js": "ably-forks/crypto-js#crypto-lite", "eslint": "^7.13.0", "eslint-plugin-jsdoc": "^40.0.0", "eslint-plugin-security": "^1.4.0", diff --git a/src/common/types/crypto-js.d.ts b/src/common/types/crypto-js.d.ts deleted file mode 100644 index db91283ac4..0000000000 --- a/src/common/types/crypto-js.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -declare module 'crypto-js/build' { - import CryptoJS from 'crypto-js'; - import algo = CryptoJS.algo; - export { algo }; -} - -declare module 'crypto-js/build/hmac-sha256' { - import CryptoJS from 'crypto-js'; - export default CryptoJS.HmacSHA256; -} diff --git a/test/support/browser_file_list.js b/test/support/browser_file_list.js index 0dbf2cd18b..ad830a97cc 100644 --- a/test/support/browser_file_list.js +++ b/test/support/browser_file_list.js @@ -11,9 +11,6 @@ window.__testFiles__.files = { 'browser/lib/util/base64.js': true, 'node_modules/async/lib/async.js': true, 'node_modules/@ably/vcdiff-decoder/dist/vcdiff-decoder.js': true, - 'node_modules/crypto-js/build/enc-base64.js': true, - 'node_modules/crypto-js/build/enc-utf8.js': true, - 'node_modules/crypto-js/build/core.js': true, 'test/common/globals/environment.js': true, 'test/common/globals/named_dependencies.js': true, 'test/common/modules/client_module.js': true, diff --git a/test/web_server b/test/web_server index 1549b96692..f42134d906 100755 --- a/test/web_server +++ b/test/web_server @@ -35,15 +35,6 @@ server.get('/', function(req, res) { } }); - -/** - * This is a rather hacky workaround since crypto-js base64 depends on node_modules/crypto-js/build/core but will request the file from the root level of the server. - * I'm leaving this here for now in lieu of a more elegant solution as the 'real' solution is to improve the way we use dependencies in these tests. - */ -server.get('/core.js', function (req, res) { - res.redirect('/node_modules/crypto-js/build/core.js'); -}); - server.use('/node_modules', express.static(__dirname + '/../node_modules')); server.use('/test', express.static(__dirname)); server.use('/browser', express.static(__dirname + '/../src/web')); diff --git a/webpack.config.js b/webpack.config.js index 085d573cbf..2c0c981ca7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -79,9 +79,6 @@ const browserConfig = { crypto: false, }, }, - externals: { - 'crypto-js': true, - }, optimization: { minimize: false, }, From 94bb5a14d0bdbb0afea29304831090ab681e6dea Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 22 Jun 2023 11:43:54 -0300 Subject: [PATCH 091/468] Fix the type definition of Crypto.getDefaultParams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The implementations of this method return their result synchronously, as does the feature spec IDL. I’m not sure why the compiler didn’t catch this. Resolves #1350. --- ably.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 15d9d6fe9c..0a85fd2602 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2970,9 +2970,9 @@ declare namespace Types { * Returns a {@link CipherParams} object, using the default values for any fields not supplied by the {@link CipherParamOptions} object. * * @param params - A {@link CipherParamOptions} object. - * @param callback - A function which, upon success, will be called with a {@link CipherParams} object, using the default values for any fields not supplied. Upon failure, the function will be called with information about the error. + * @returns A {@link CipherParams} object, using the default values for any fields not supplied. */ - getDefaultParams(params: CipherParamOptions, callback: Types.StandardCallback): void; + getDefaultParams(params: CipherParamOptions): CipherParams; } /** From 3340ca4add0dd9aee9402a2d76eebf6b196f8aaa Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 11:08:03 +0100 Subject: [PATCH 092/468] test: add test constructors for promise-based clients Co-authored-by: Lawrence Forooghian --- test/common/modules/client_module.js | 10 ++++++++++ test/common/modules/shared_helper.js | 2 ++ 2 files changed, 12 insertions(+) diff --git a/test/common/modules/client_module.js b/test/common/modules/client_module.js index 7f2c990ea0..df26c74cff 100644 --- a/test/common/modules/client_module.js +++ b/test/common/modules/client_module.js @@ -26,13 +26,23 @@ define(['ably', 'globals', 'test/common/modules/testapp_module'], function (Ably return new Ably.Rest(ablyClientOptions(options)); } + function ablyRestPromise(options) { + return new Ably.Rest.Promise(ablyClientOptions(options)); + } + function ablyRealtime(options) { return new Ably.Realtime(ablyClientOptions(options)); } + function ablyRealtimePromise(options) { + return new Ably.Realtime.Promise(ablyClientOptions(options)); + } + return (module.exports = { Ably: Ably, AblyRest: ablyRest, + AblyRestPromise: ablyRestPromise, AblyRealtime: ablyRealtime, + AblyRealtimePromise: ablyRealtimePromise, }); }); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 14442575cf..ef18ae9297 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -213,7 +213,9 @@ define([ Ably: clientModule.Ably, AblyRest: clientModule.AblyRest, + AblyRestPromise: clientModule.AblyRestPromise, AblyRealtime: clientModule.AblyRealtime, + AblyRealtimePromise: clientModule.AblyRealtimePromise, Utils: utils, loadTestData: testAppManager.loadJsonData, From 1ca52a34e6581c4644fd221cf97360b51d5a0207 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 4 May 2023 22:46:40 +0100 Subject: [PATCH 093/468] test: add restTestOnJsonMsgpackAsync Co-authored-by: Lawrence Forooghian --- test/common/modules/shared_helper.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index ef18ae9297..d9a6b7216e 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -162,6 +162,20 @@ define([ restTestOnJsonMsgpack(name, testFn, true); }; + function restTestOnJsonMsgpackAsync(name, testFn, skip) { + var itFn = skip ? it.skip : it; + itFn(name + ' with binary protocol', async function () { + await testFn(new clientModule.AblyRestPromise({ useBinaryProtocol: true }), name + '_binary'); + }); + itFn(name + ' with text protocol', async function () { + await testFn(new clientModule.AblyRestPromise({ useBinaryProtocol: false }), name + '_text'); + }); + } + + restTestOnJsonMsgpackAsync.skip = function (name, testFn) { + restTestOnJsonMsgpackAsync(name, testFn, true); + }; + function clearTransportPreference() { if (isBrowser && window.localStorage) { window.localStorage.removeItem('ably-transport-preference'); @@ -228,6 +242,7 @@ define([ becomeSuspended: becomeSuspended, testOnAllTransports: testOnAllTransports, restTestOnJsonMsgpack: restTestOnJsonMsgpack, + restTestOnJsonMsgpackAsync: restTestOnJsonMsgpackAsync, availableTransports: availableTransports, bestTransport: bestTransport, clearTransportPreference: clearTransportPreference, From 76a1e6342dba55e50312ab2e1dd1be1e4c9ee9e8 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 15:59:19 +0100 Subject: [PATCH 094/468] test: convert rest auth tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/auth.test.js | 800 ++++++++++++----------------------------- 1 file changed, 235 insertions(+), 565 deletions(-) diff --git a/test/rest/auth.test.js b/test/rest/auth.test.js index c2ff774f02..e1a9825be1 100644 --- a/test/rest/auth.test.js +++ b/test/rest/auth.test.js @@ -10,437 +10,243 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as describe('rest/auth', function () { this.timeout(60 * 1000); - var getServerTime = function (callback) { - rest.time(function (err, time) { - if (err) { - callback(err); - } - callback(null, time); - }); - }; - before(function (done) { helper.setupApp(function () { - rest = helper.AblyRest({ queryTime: true }); - getServerTime(function (err, time) { - if (err) { + rest = helper.AblyRestPromise({ queryTime: true }); + rest + .time() + .then(function (time) { + currentTime = time; + expect(true, 'Obtained time').to.be.ok; + done(); + }) + .catch(function (err) { done(err); - return; - } - currentTime = time; - expect(true, 'Obtained time').to.be.ok; - done(); - }); + }); }); }); - it('Base token generation case', function (done) { - rest.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(tokenDetails.expires).to.equal(60 * 60 * 1000 + tokenDetails.issued, 'Verify default expiry period'); - expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + it('Base token generation case', async function () { + var tokenDetails = await rest.auth.requestToken(); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(tokenDetails.expires).to.equal(60 * 60 * 1000 + tokenDetails.issued, 'Verify default expiry period'); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); }); - it('Base token generation with options', function (done) { - rest.auth.requestToken(null, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + it('Base token generation with options', async function () { + var tokenDetails = await rest.auth.requestToken(null); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); }); - it('Generate token and init library with it', function (done) { - rest.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - helper.AblyRest({ token: tokenDetails.token }); - done(); - } catch (err) { - done(err); - } - }); + it('Generate token and init library with it', async function () { + var tokenDetails = await rest.auth.requestToken(); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + helper.AblyRestPromise({ token: tokenDetails.token }); }); - it('Token generation with explicit timestamp', function (done) { - getServerTime(function (err, serverTime) { - if (err) { - done(err); - return; - } - - rest.auth.requestToken({ timestamp: serverTime }, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token).to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); - }); + it('Token generation with explicit timestamp', async function () { + var serverTime = await rest.time(); + var tokenDetails = await rest.auth.requestToken({ timestamp: serverTime }); + expect(tokenDetails.token).to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); }); - it('Token generation with invalid timestamp', function (done) { + it('Token generation with invalid timestamp', async function () { var badTime = utils.now() - 30 * 60 * 1000; - rest.auth.requestToken({ timestamp: badTime }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(401, 'Verify token request rejected with bad timestamp'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Invalid timestamp, expected rejection')); - }); + try { + var tokenDetails = await rest.auth.requestToken({ timestamp: badTime }); + } catch (err) { + expect(err.statusCode).to.equal(401, 'Verify token request rejected with bad timestamp'); + return; + } + throw new Error('Invalid timestamp, expected rejection'); }); - it('Token generation with system timestamp', function (done) { - rest.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + it('Token generation with system timestamp', async function () { + var tokenDetails = await rest.auth.requestToken(); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); }); - it('Token generation with duplicate nonce', function (done) { - getServerTime(function (err, serverTime) { - if (err) { - done(err); - return; - } - rest.auth.requestToken({ timestamp: serverTime, nonce: '1234567890123456' }, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - rest.auth.requestToken({ timestamp: serverTime, nonce: '1234567890123456' }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(401, 'Verify request rejected with duplicated nonce'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Invalid nonce, expected rejection')); - }); - }); - }); + it('Token generation with duplicate nonce', async function () { + var serverTime = await rest.time(); + await rest.auth.requestToken({ timestamp: serverTime, nonce: '1234567890123456' }); + try { + await rest.auth.requestToken({ timestamp: serverTime, nonce: '1234567890123456' }); + } catch (err) { + expect(err.statusCode).to.equal(401, 'Verify request rejected with duplicated nonce'); + return; + } + throw new Error('Invalid nonce, expected rejection'); }); - it('Token generation with clientId', function (done) { + it('Token generation with clientId', async function () { var testClientId = 'test client id'; - rest.auth.requestToken({ clientId: testClientId }, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(tokenDetails.clientId).to.equal(testClientId, 'Verify client id'); - expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ clientId: testClientId }); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(tokenDetails.clientId).to.equal(testClientId, 'Verify client id'); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal({ '*': ['*'] }, 'Verify token capability'); }); - it('Token generation with empty string clientId should error', function (done) { - rest.auth.requestToken({ clientId: '' }, function (err, tokenDetails) { - if (err) { - expect(err.code).to.equal(40012); - done(); - return; - } - done(new Error('Expected token generation to error with empty string clientId')); - }); + it('Token generation with empty string clientId should error', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ clientId: '' }); + } catch (err) { + expect(err.code).to.equal(40012); + return; + } + throw new Error('Expected token generation to error with empty string clientId'); }); - it('Token generation with capability that subsets key capability', function (done) { + it('Token generation with capability that subsets key capability', async function () { var testCapability = { onlythischannel: ['subscribe'] }; - rest.auth.requestToken({ capability: testCapability }, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); }); - it('Token generation with specified key', function (done) { + it('Token generation with specified key', async function () { var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; var testCapability = JSON.parse(helper.getTestApp().keys[1].capability); - rest.auth.requestToken(null, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(tokenDetails.keyName).to.equal(helper.getTestApp().keys[1].keyName, 'Verify token key'); - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken(null, testKeyOpts); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(tokenDetails.keyName).to.equal(helper.getTestApp().keys[1].keyName, 'Verify token key'); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); }); - it('Token generation with explicit auth', function (done) { - rest.auth.getAuthHeaders(function (err, authHeaders) { - if (err) { - done(err); - return; - } - rest.auth.authOptions.requestHeaders = authHeaders; - rest.auth.requestToken(function (err, tokenDetails) { - delete rest.auth.authOptions.requestHeaders; + it('Token generation with explicit auth', async function () { + var authHeaders = await new Promise((resolve, reject) => { + rest.auth.getAuthHeaders(function (err, authHeaders) { if (err) { - done(err); + reject(err); return; } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(tokenDetails.keyName).to.equal(helper.getTestApp().keys[0].keyName, 'Verify token key'); - done(); - } catch (err) { - done(err); - } + resolve(authHeaders); }); }); + rest.auth.authOptions.requestHeaders = authHeaders; + var tokenDetails = await rest.auth.requestToken(); + delete rest.auth.authOptions.requestHeaders; + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(tokenDetails.keyName).to.equal(helper.getTestApp().keys[0].keyName, 'Verify token key'); }); - it('Token generation with explicit auth, different key', function (done) { - rest.auth.getAuthHeaders(function (err, authHeaders) { - var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; - var testCapability = JSON.parse(helper.getTestApp().keys[1].capability); - rest.auth.requestToken(null, testKeyOpts, function (err, tokenDetails) { + it('Token generation with explicit auth, different key', async function () { + var authHeaders = await new Promise((resolve, reject) => { + rest.auth.getAuthHeaders(function (err, authHeaders) { if (err) { - done(err); + reject(err); return; } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; - expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; - expect(tokenDetails.keyName).to.equal(helper.getTestApp().keys[1].keyName, 'Verify token key'); - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); - done(); - } catch (e) { - done(e); - } + resolve(authHeaders); }); }); + var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; + var testCapability = JSON.parse(helper.getTestApp().keys[1].capability); + var tokenDetails = await rest.auth.requestToken(null, testKeyOpts); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.issued && tokenDetails.issued >= currentTime, 'Verify token issued').to.be.ok; + expect(tokenDetails.expires && tokenDetails.expires > tokenDetails.issued, 'Verify token expires').to.be.ok; + expect(tokenDetails.keyName).to.equal(helper.getTestApp().keys[1].keyName, 'Verify token key'); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); }); - it('Token generation with invalid mac', function (done) { - rest.auth.requestToken({ mac: '12345' }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(401, 'Verify request rejected with bad mac'); - done(); - } catch (e) { - done(e); - } - return; - } - done(new Error('Invalid mac, expected rejection')); - }); + it('Token generation with invalid mac', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ mac: '12345' }); + } catch (err) { + expect(err.statusCode).to.equal(401, 'Verify request rejected with bad mac'); + return; + } + throw new Error('Invalid mac, expected rejection'); }); - it('Token generation with defaultTokenParams set and no tokenParams passed in', function (done) { - var rest1 = helper.AblyRest({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); - rest1.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.token, 'Verify token value').to.be.ok; - expect(tokenDetails.clientId).to.equal('foo', 'Verify client id from defaultTokenParams used'); - expect(tokenDetails.expires - tokenDetails.issued).to.equal(123, 'Verify ttl from defaultTokenParams used'); - done(); - } catch (err) { - done(err); - } - }); + it('Token generation with defaultTokenParams set and no tokenParams passed in', async function () { + var rest1 = helper.AblyRestPromise({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); + var tokenDetails = await rest1.auth.requestToken(); + expect(tokenDetails.token, 'Verify token value').to.be.ok; + expect(tokenDetails.clientId).to.equal('foo', 'Verify client id from defaultTokenParams used'); + expect(tokenDetails.expires - tokenDetails.issued).to.equal(123, 'Verify ttl from defaultTokenParams used'); }); - it('Token generation: if tokenParams passed in, defaultTokenParams should be ignored altogether, not merged', function (done) { - var rest1 = helper.AblyRest({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); - rest1.auth.requestToken({ clientId: 'bar' }, null, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.clientId).to.equal( - 'bar', - 'Verify clientId passed in is used, not the one from defaultTokenParams' - ); - expect(tokenDetails.expires - tokenDetails.issued).to.equal( - 60 * 60 * 1000, - 'Verify ttl from defaultTokenParams ignored completely, even though not overridden' - ); - done(); - } catch (err) { - done(err); - } - }); + it('Token generation: if tokenParams passed in, defaultTokenParams should be ignored altogether, not merged', async function () { + var rest1 = helper.AblyRestPromise({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); + var tokenDetails = await rest1.auth.requestToken({ clientId: 'bar' }, null); + expect(tokenDetails.clientId).to.equal( + 'bar', + 'Verify clientId passed in is used, not the one from defaultTokenParams' + ); + expect(tokenDetails.expires - tokenDetails.issued).to.equal( + 60 * 60 * 1000, + 'Verify ttl from defaultTokenParams ignored completely, even though not overridden' + ); }); /* * authorize with different args */ - it('Authorize with different args', function (done) { - async.parallel( - [ - function (cb) { - rest.auth.authorize(null, null, function (err, tokenDetails) { - expect(tokenDetails.token, 'Check token obtained').to.be.ok; - cb(err); - }); - }, - function (cb) { - rest.auth.authorize(null, function (err, tokenDetails) { - expect(tokenDetails.token, 'Check token obtained').to.be.ok; - cb(err); - }); - }, - function (cb) { - rest.auth.authorize(function (err, tokenDetails) { - expect(tokenDetails.token, 'Check token obtained').to.be.ok; - cb(err); - }); - }, - ], - function (err) { - if (err) { - done(err); - } - done(); - } - ); - }); + it('Authorize with different args', async function () { + var results = await Promise.all([ + rest.auth.authorize(), + rest.auth.authorize(null), + rest.auth.authorize(null, null), + ]); - it('Specify non-default ttl', function (done) { - rest.auth.requestToken({ ttl: 100 * 1000 }, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(tokenDetails.expires).to.equal(100 * 1000 + tokenDetails.issued, 'Verify non-default expiry period'); - done(); - } catch (err) { - done(err); - } + results.forEach((tokenDetails) => { + expect(tokenDetails.token, 'Check token obtained').to.be.ok; }); }); - it('Should error with excessive ttl', function (done) { - rest.auth.requestToken({ ttl: 365 * 24 * 60 * 60 * 1000 }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(400, 'Verify request rejected with excessive expiry'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Excessive expiry, expected rejection')); - }); + it('Specify non-default ttl', async function () { + var tokenDetails = await rest.auth.requestToken({ ttl: 100 * 1000 }); + expect(tokenDetails.expires).to.equal(100 * 1000 + tokenDetails.issued, 'Verify non-default expiry period'); }); - it('Should error with negative ttl', function (done) { - rest.auth.requestToken({ ttl: -1 }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(400, 'Verify request rejected with negative expiry'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Negative expiry, expected rejection')); - }); + it('Should error with excessive ttl', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ ttl: 365 * 24 * 60 * 60 * 1000 }); + } catch (err) { + expect(err.statusCode).to.equal(400, 'Verify request rejected with excessive expiry'); + return; + } + throw new Error('Excessive expiry, expected rejection'); }); - it('Should error with invalid ttl', function (done) { - rest.auth.requestToken({ ttl: 'notanumber' }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(400, 'Verify request rejected with invalid expiry'); - done(); - } catch (e) { - done(e); - } - return; - } - done(new Error('Invalid expiry, expected rejection')); - }); + it('Should error with negative ttl', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ ttl: -1 }); + } catch (err) { + expect(err.statusCode).to.equal(400, 'Verify request rejected with negative expiry'); + return; + } + throw new Error('Negative expiry, expected rejection'); + }); + + it('Should error with invalid ttl', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ ttl: 'notanumber' }); + } catch (err) { + expect(err.statusCode).to.equal(400, 'Verify request rejected with invalid expiry'); + return; + } + throw new Error('Invalid expiry, expected rejection'); }); /* @@ -448,91 +254,28 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as * and the token request includes all the fields it should include, but * doesn't include ttl or capability by default */ - it('createTokenRequest without authOptions', function (done) { - rest.auth.createTokenRequest(null, null, function (err, tokenRequest) { - if (err) { - done(err); - return; - } - try { - expect('mac' in tokenRequest, 'check tokenRequest contains a mac').to.be.ok; - expect('nonce' in tokenRequest, 'check tokenRequest contains a nonce').to.be.ok; - expect('timestamp' in tokenRequest, 'check tokenRequest contains a timestamp').to.be.ok; - expect(!('ttl' in tokenRequest), 'check tokenRequest does not contains a ttl by default').to.be.ok; - expect( - !('capability' in tokenRequest), - 'check tokenRequest does not contains capabilities by default' - ).to.be.ok; - expect(tokenRequest.keyName).to.equal(helper.getTestApp().keys[0].keyName); - done(); - } catch (err) { - done(err); - } - }); - }); - - it('createTokenRequest without authOptions, callback as 2nd param', function (done) { - rest.auth.createTokenRequest(null, function (err, tokenRequest) { - if (err) { - done(err); - return; - } - try { - expect(tokenRequest.keyName).to.equal(helper.getTestApp().keys[0].keyName); - done(); - } catch (err) { - done(err); - } - }); + it('createTokenRequest without authOptions', async function () { + var tokenRequest = await rest.auth.createTokenRequest(null, null); + expect('mac' in tokenRequest, 'check tokenRequest contains a mac').to.be.ok; + expect('nonce' in tokenRequest, 'check tokenRequest contains a nonce').to.be.ok; + expect('timestamp' in tokenRequest, 'check tokenRequest contains a timestamp').to.be.ok; + expect(!('ttl' in tokenRequest), 'check tokenRequest does not contains a ttl by default').to.be.ok; + expect(!('capability' in tokenRequest), 'check tokenRequest does not contains capabilities by default').to.be.ok; + expect(tokenRequest.keyName).to.equal(helper.getTestApp().keys[0].keyName); }); - it('createTokenRequest without authOptions or tokenParams, callback as 1st param', function (done) { - rest.auth.createTokenRequest(function (err, tokenRequest) { - if (err) { - done(err); - return; - } - try { - expect(tokenRequest.keyName).to.equal(helper.getTestApp().keys[0].keyName); - done(); - } catch (err) { - done(err); - } - }); - }); - - it('createTokenRequest uses the key it was initialized with if authOptions does not have a "key" key', function (done) { - rest.auth.createTokenRequest(function (err, tokenRequest) { - if (err) { - done(err); - return; - } - try { - expect(tokenRequest.keyName).to.equal(helper.getTestApp().keys[0].keyName); - done(); - } catch (err) { - done(err); - } - }); + it('createTokenRequest uses the key it was initialized with if authOptions does not have a "key" key', async function () { + var tokenRequest = await rest.auth.createTokenRequest(); + expect(tokenRequest.keyName).to.equal(helper.getTestApp().keys[0].keyName); }); - it('createTokenRequest should serialise capability object as JSON', function (done) { + it('createTokenRequest should serialise capability object as JSON', async function () { var capability = { '*': ['*'] }; - rest.auth.createTokenRequest({ capability: capability }, null, function (err, tokenRequest) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenRequest.capability)).to.deep.equal( - capability, - 'Verify createTokenRequest has JSON-stringified capability' - ); - done(); - } catch (err) { - done(err); - } - }); + var tokenRequest = await rest.auth.createTokenRequest({ capability: capability }, null); + expect(JSON.parse(tokenRequest.capability)).to.deep.equal( + capability, + 'Verify createTokenRequest has JSON-stringified capability' + ); }); /** @@ -541,27 +284,16 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as * @param {object} params The authParams to be tested */ function testJWTAuthParams(description, params) { - it(description, function (done) { + it(description, async function () { var currentKey = helper.getTestApp().keys[0]; var keys = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; var authParams = utils.mixin(keys, params); var authUrl = echoServer + '/createJWT' + utils.toQueryString(authParams); - var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); + var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); - restJWTRequester.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } - var restClient = helper.AblyRest({ token: tokenDetails.token }); - restClient.stats(function (err, stats) { - if (err) { - done(err); - return; - } - done(); - }); - }); + var tokenDetails = await restJWTRequester.auth.requestToken(); + var restClient = helper.AblyRestPromise({ token: tokenDetails.token }); + await restClient.stats(); }); } @@ -580,147 +312,85 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); } - it('JWT request with invalid key', function (done) { + it('JWT request with invalid key', async function () { var keys = { keyName: 'invalid.invalid', keySecret: 'invalidinvalid' }; var authUrl = echoServer + '/createJWT' + utils.toQueryString(keys); - var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); - - restJWTRequester.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } - var restClient = helper.AblyRest({ token: tokenDetails.token }); - restClient.stats(function (err, stats) { - try { - expect(err.code).to.equal(40400, 'Verify token is invalid because app id does not exist'); - expect(err.statusCode).to.equal(404, 'Verify token is invalid because app id does not exist'); - done(); - return; - } catch (err) { - done(err); - } - }); - }); + var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); + + var tokenDetails = await restJWTRequester.auth.requestToken(); + var restClient = helper.AblyRestPromise({ token: tokenDetails.token }); + try { + var stats = await restClient.stats(); + } catch (err) { + expect(err.code).to.equal(40400, 'Verify token is invalid because app id does not exist'); + expect(err.statusCode).to.equal(404, 'Verify token is invalid because app id does not exist'); + return; + } + throw new Error('Expected restClient.stats() to throw token error'); }); /* * RSA8g */ - it('Rest JWT with authCallback', function (done) { + it('Rest JWT with authCallback', async function () { var currentKey = helper.getTestApp().keys[0]; var keys = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; var authUrl = echoServer + '/createJWT' + utils.toQueryString(keys); - var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); + var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); var authCallback = function (tokenParams, callback) { - restJWTRequester.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } + restJWTRequester.auth.requestToken().then(function (tokenDetails) { callback(null, tokenDetails.token); }); }; - var restClient = helper.AblyRest({ authCallback: authCallback }); - restClient.stats(function (err, stats) { - if (err) { - done(err); - return; - } - try { - expect(err).to.equal(null, 'Verify that the error is null'); - done(); - } catch (err) { - done(err); - } - }); + var restClient = helper.AblyRestPromise({ authCallback: authCallback }); + var stats = await restClient.stats(); }); /* * RSA8g */ - it('Rest JWT with authCallback and invalid keys', function (done) { + it('Rest JWT with authCallback and invalid keys', async function () { var keys = { keyName: 'invalid.invalid', keySecret: 'invalidinvalid' }; var authUrl = echoServer + '/createJWT' + utils.toQueryString(keys); - var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); + var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); var authCallback = function (tokenParams, callback) { - restJWTRequester.auth.requestToken(function (err, tokenDetails) { - if (err) { - done(err); - return; - } + restJWTRequester.auth.requestToken().then(function (tokenDetails) { callback(null, tokenDetails.token); }); }; - var restClient = helper.AblyRest({ authCallback: authCallback }); - restClient.stats(function (err, stats) { - try { - expect(err.code).to.equal(40400, 'Verify code is 40400'); - expect(err.statusCode).to.equal(404, 'Verify token is invalid because app id does not exist'); - done(); - } catch (err) { - done(err); - } - }); + var restClient = helper.AblyRestPromise({ authCallback: authCallback }); + try { + await restClient.stats(); + } catch (err) { + expect(err.code).to.equal(40400, 'Verify code is 40400'); + expect(err.statusCode).to.equal(404, 'Verify token is invalid because app id does not exist'); + return; + } + throw new Error('Expected restClient.stats() to throw token error'); }); - it('authCallback is only invoked once on concurrent auth', function (done) { + it('authCallback is only invoked once on concurrent auth', async function () { var authCallbackInvocations = 0; function authCallback(tokenParams, callback) { authCallbackInvocations++; - rest.auth.createTokenRequest(tokenParams, callback); + rest.auth.createTokenRequest(tokenParams).then(function (tokenRequest) { + callback(null, tokenRequest); + }); } /* Example client-side using the token */ - var restClient = helper.AblyRest({ authCallback: authCallback }); + var restClient = helper.AblyRestPromise({ authCallback: authCallback }); var channel = restClient.channels.get('auth_concurrent'); - async.parallel([channel.history.bind(channel), channel.history.bind(channel)], function (err) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(authCallbackInvocations).to.equal( - 1, - 'Check authCallback only invoked once -- was: ' + authCallbackInvocations - ); - done(); - } catch (err) { - done(err); - } - }); + await Promise.all([channel.history(), channel.history()]); + expect(authCallbackInvocations).to.equal( + 1, + 'Check authCallback only invoked once -- was: ' + authCallbackInvocations + ); }); - - if (typeof Promise !== 'undefined') { - it('Promise based auth', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); - - var promise1 = rest.auth.requestToken(); - var promise2 = rest.auth.requestToken({ ttl: 200 }); - var promise3 = rest.auth.requestToken({ ttl: 200 }, { key: helper.getTestApp().keys[1].keyStr }); - var promise4 = rest.auth.createTokenRequest(); - var promise5 = rest.auth.createTokenRequest({ ttl: 200 }); - var promise6 = rest.auth.requestToken({ ttl: 200 }, { key: 'bad' })['catch'](function (err) { - expect(true, 'Token attempt with bad key was rejected').to.be.ok; - }); - - Promise.all([promise1, promise2, promise3, promise4, promise5, promise6]) - .then(function (results) { - try { - for (var i = 0; i < 5; i++) { - expect(results[i].token || results[i].nonce).to.be.ok; - } - done(); - } catch (err) { - done(err); - } - }) - ['catch'](function (err) { - done(err); - }); - }); - } }); }); From af38ec87800dc02e6a15a3c584d7297ddfc8138c Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 16:39:21 +0100 Subject: [PATCH 095/468] test: convert rest capability tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/capability.test.js | 274 ++++++++++------------------------- 1 file changed, 78 insertions(+), 196 deletions(-) diff --git a/test/rest/capability.test.js b/test/rest/capability.test.js index 60e4172e73..0c88b94bfc 100644 --- a/test/rest/capability.test.js +++ b/test/rest/capability.test.js @@ -19,266 +19,148 @@ define(['shared_helper', 'chai'], function (helper, chai) { this.timeout(60 * 1000); before(function (done) { - helper.setupApp(function (err) { - if (err) { - done(err); - return; - } - - rest = helper.AblyRest(); + helper.setupApp(function () { + rest = helper.AblyRestPromise({ queryTime: true }); testApp = helper.getTestApp(); - rest.time(function (err, time) { - if (err) { + rest + .time() + .then(function (time) { + currentTime = time; + expect(true, 'Obtained time').to.be.ok; + done(); + }) + .catch(function (err) { done(err); - return; - } - currentTime = time; - done(); - }); + }); }); }); - it('Blanket intersection with specified key', function (done) { + it('Blanket intersection with specified key', async function () { var testKeyOpts = { key: testApp.keys[1].keyStr }; var testCapability = JSON.parse(testApp.keys[1].capability); - rest.auth.requestToken(null, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); - done(); - } catch (e) { - done(e); - } - }); + var tokenDetails = await rest.auth.requestToken(null, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); }); - it('Equal intersection with specified key', function (done) { + it('Equal intersection with specified key', async function () { var testKeyOpts = { key: testApp.keys[1].keyStr }; var testCapability = JSON.parse(testApp.keys[1].capability); - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(testCapability, 'Verify token capability'); }); - it('Empty ops intersection', function (done) { + it('Empty ops intersection', async function () { var testKeyOpts = { key: testApp.keys[1].keyStr }; var testCapability = { 'canpublish:test': ['subscribe'] }; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(401, 'Verify request rejected with insufficient capability'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Invalid capability, expected rejection')); - }); + try { + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + } catch (err) { + expect(err.statusCode).to.equal(401, 'Verify request rejected with insufficient capability'); + return; + } + expect.fail('Invalid capability, expected rejection'); }); - it('Empty paths intersection', function (done) { + it('Empty paths intersection', async function () { var testKeyOpts = { key: testApp.keys[2].keyStr }; var testCapability = { channelx: ['publish'] }; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(401, 'Verify request rejected with insufficient capability'); - done(); - } catch (err) { - done(err); - } - return; - } - done('Invalid capability, expected rejection'); - }); + try { + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + } catch (err) { + expect(err.statusCode).to.equal(401, 'Verify request rejected with insufficient capability'); + return; + } + expect.fail('Invalid capability, expected rejection'); }); - it('Ops intersection non-empty', function (done) { + it('Ops intersection non-empty', async function () { var testKeyOpts = { key: testApp.keys[2].keyStr }; var testCapability = { channel2: ['presence', 'subscribe'] }; var expectedIntersection = { channel2: ['subscribe'] }; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); }); - it('Paths intersection non-empty', function (done) { + it('Paths intersection non-empty', async function () { var testKeyOpts = { key: testApp.keys[2].keyStr }; var testCapability = { channel2: ['presence', 'subscribe'], channelx: ['presence', 'subscribe'], }; var expectedIntersection = { channel2: ['subscribe'] }; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); }); - it('Wildcard token with publish and subscribe key', function (done) { + it('Wildcard token with publish and subscribe key', async function () { var testKeyOpts = { key: testApp.keys[2].keyStr }; var testCapability = { channel2: ['*'] }; var expectedIntersection = { channel2: ['publish', 'subscribe'] }; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); }); - it('Publish and subscribe token with wildcard key', function (done) { + it('Publish and subscribe token with wildcard key', async function () { var testKeyOpts = { key: testApp.keys[2].keyStr }; var testCapability = { channel6: ['publish', 'subscribe'] }; var expectedIntersection = { channel6: ['publish', 'subscribe'] }; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); }); - it('Resources wildcard matching 1', function (done) { + it('Resources wildcard matching 1', async function () { var testKeyOpts = { key: testApp.keys[3].keyStr }; var testCapability = { cansubscribe: ['subscribe'] }; var expectedIntersection = testCapability; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); }); - it('Resources wildcard matching 2', function (done) { + it('Resources wildcard matching 2', async function () { var testKeyOpts = { key: testApp.keys[1].keyStr }; var testCapability = { 'canpublish:check': ['publish'] }; var expectedIntersection = testCapability; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); }); - it('Resources wildcard matching 3', function (done) { + it('Resources wildcard matching 3', async function () { var testKeyOpts = { key: testApp.keys[3].keyStr }; var testCapability = { 'cansubscribe:*': ['subscribe'] }; var expectedIntersection = testCapability; - rest.auth.requestToken({ capability: testCapability }, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } - try { - expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); - done(); - } catch (err) { - done(err); - } - }); + var tokenDetails = await rest.auth.requestToken({ capability: testCapability }, testKeyOpts); + expect(JSON.parse(tokenDetails.capability)).to.deep.equal(expectedIntersection, 'Verify token capability'); }); /* Invalid capabilities */ - it('Invalid capabilities 1', function (done) { - rest.auth.requestToken({ capability: invalid0 }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(400, 'Verify request rejected with bad capability'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Invalid capability, expected rejection')); - }); + it('Invalid capabilities 1', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ capability: invalid0 }); + } catch (err) { + expect(err.statusCode).to.equal(400, 'Verify request rejected with bad capability'); + return; + } + expect.fail('Invalid capability, expected rejection'); }); - it('Invalid capabilities 2', function (done) { - rest.auth.requestToken({ capability: invalid1 }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(400, 'Verify request rejected with bad capability'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Invalid capability, expected rejection')); - }); + it('Invalid capabilities 2', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ capability: invalid1 }); + } catch (err) { + expect(err.statusCode).to.equal(400, 'Verify request rejected with bad capability'); + return; + } + expect.fail('Invalid capability, expected rejection'); }); - it('Invalid capabilities 3', function (done) { - rest.auth.requestToken({ capability: invalid2 }, function (err, tokenDetails) { - if (err) { - try { - expect(err.statusCode).to.equal(400, 'Verify request rejected with bad capability'); - done(); - } catch (err) { - done(err); - } - return; - } - done(new Error('Invalid capability, expected rejection')); - }); + it('Invalid capabilities 3', async function () { + try { + var tokenDetails = await rest.auth.requestToken({ capability: invalid2 }); + } catch (err) { + expect(err.statusCode).to.equal(400, 'Verify request rejected with bad capability'); + return; + } + expect.fail('Invalid capability, expected rejection'); }); }); }); From a1fb8ddfdec16cc934ac7d770d8d95e54dee1532 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 16:59:08 +0100 Subject: [PATCH 096/468] test: convert rest fallbacks tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/fallbacks.test.js | 82 +++++++++++-------------------------- 1 file changed, 25 insertions(+), 57 deletions(-) diff --git a/test/rest/fallbacks.test.js b/test/rest/fallbacks.test.js index 65e024e759..df716ecede 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -14,75 +14,43 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { done(err); return; } - goodHost = helper.AblyRest().options.restHost; + goodHost = helper.AblyRestPromise().options.restHost; done(); }); }); /* RSC15f */ - it('Store working fallback', function (done) { - var rest = helper.AblyRest({ + it('Store working fallback', async function () { + var rest = helper.AblyRestPromise({ restHost: helper.unroutableHost, fallbackHosts: [goodHost], httpRequestTimeout: 3000, logLevel: 4, }); var validUntil; - async.series( - [ - function (cb) { - rest.time(function (err, serverTime) { - if (err) { - return cb(err); - } - expect(serverTime, 'Check serverTime returned').to.be.ok; - var currentFallback = rest._currentFallback; - expect(currentFallback, 'Check current fallback stored').to.be.ok; - expect(currentFallback && currentFallback.host).to.equal(goodHost, 'Check good host set'); - validUntil = currentFallback.validUntil; - cb(); - }); - }, - /* now try again, check that this time it uses the remembered good endpoint straight away */ - function (cb) { - rest.time(function (err, serverTime) { - if (err) { - return cb(err); - } - expect(serverTime, 'Check serverTime returned').to.be.ok; - var currentFallback = rest._currentFallback; - expect(currentFallback.validUntil).to.equal( - validUntil, - 'Check validUntil is the same (implying currentFallback has not been re-set)' - ); - cb(); - }); - }, - /* set the validUntil to the past and check that the stored fallback is forgotten */ - function (cb) { - var now = utils.now(); - rest._currentFallback.validUntil = now - 1000; - rest.time(function (err, serverTime) { - if (err) { - return cb(err); - } - expect(serverTime, 'Check serverTime returned').to.be.ok; - var currentFallback = rest._currentFallback; - expect(currentFallback, 'Check current fallback re-stored').to.be.ok; - expect(currentFallback && currentFallback.host).to.equal(goodHost, 'Check good host set again'); - expect(currentFallback.validUntil > now, 'Check validUntil has been re-set').to.be.ok; - cb(); - }); - }, - ], - function (err) { - if (err) { - done(err); - return; - } - done(); - } + var serverTime = await rest.time(); + expect(serverTime, 'Check serverTime returned').to.be.ok; + var currentFallback = rest._currentFallback; + expect(currentFallback, 'Check current fallback stored').to.be.ok; + expect(currentFallback && currentFallback.host).to.equal(goodHost, 'Check good host set'); + validUntil = currentFallback.validUntil; + /* now try again, check that this time it uses the remembered good endpoint straight away */ + var serverTime = await rest.time(); + expect(serverTime, 'Check serverTime returned').to.be.ok; + var currentFallback = rest._currentFallback; + expect(currentFallback.validUntil).to.equal( + validUntil, + 'Check validUntil is the same (implying currentFallback has not been re-set)' ); + /* set the validUntil to the past and check that the stored fallback is forgotten */ + var now = utils.now(); + rest._currentFallback.validUntil = now - 1000; + var serverTime = await rest.time(); + expect(serverTime, 'Check serverTime returned').to.be.ok; + var currentFallback = rest._currentFallback; + expect(currentFallback, 'Check current fallback re-stored').to.be.ok; + expect(currentFallback && currentFallback.host).to.equal(goodHost, 'Check good host set again'); + expect(currentFallback.validUntil > now, 'Check validUntil has been re-set').to.be.ok; }); }); }); From 6d0d329b0e2d312b25a6b7c974ae5e901af7d918 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 18:00:40 +0100 Subject: [PATCH 097/468] test: convert rest history tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/history.test.js | 543 ++++++++++++-------------------------- 1 file changed, 168 insertions(+), 375 deletions(-) diff --git a/test/rest/history.test.js b/test/rest/history.test.js index 249c2be72c..8946788e64 100644 --- a/test/rest/history.test.js +++ b/test/rest/history.test.js @@ -4,7 +4,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var rest; var expect = chai.expect; var exports = {}; - var restTestOnJsonMsgpack = helper.restTestOnJsonMsgpack; + var restTestOnJsonMsgpackAsync = helper.restTestOnJsonMsgpackAsync; var utils = helper.Utils; var testMessages = [ { name: 'event0', data: 'some data' }, @@ -15,433 +15,226 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { { name: 'event5', data: { one: 1, two: 2, three: 3 } }, { name: 'event6', data: { foo: 'bar' } }, ]; + var reversedMessages = testMessages.map((_, i) => testMessages[testMessages.length - 1 - i]); describe('rest/history', function () { this.timeout(60 * 1000); before(function (done) { helper.setupApp(function () { - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); done(); }); }); - restTestOnJsonMsgpack('history_simple', function (done, rest, channelName) { + restTestOnJsonMsgpackAsync('history_simple', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); /* first, send a number of events to this channel */ - - var publishTasks = utils.arrMap(testMessages, function (event) { - return function (publishCb) { - testchannel.publish(event.name, event.data, publishCb); - }; - }); - - publishTasks.push(function (waitCb) { - setTimeout(function () { - waitCb(null); - }, 1000); + await Promise.all([ + new Promise((resolve) => setTimeout(resolve, 1000)), + ...testMessages.map((event) => testchannel.publish(event.name, event.data)), + ]); + + /* so now the messages are there; try querying the timeline */ + var resultPage = await testchannel.history(); + /* verify all messages are received */ + var messages = resultPage.items; + expect(messages.length).to.equal(testMessages.length, 'Verify correct number of messages found'); + + /* verify message ids are unique */ + var ids = {}; + utils.arrForEach(messages, function (msg) { + ids[msg.id] = msg; }); - try { - async.parallel(publishTasks, function (err) { - if (err) { - done(err); - return; - } - - /* so now the messages are there; try querying the timeline */ - testchannel.history(function (err, resultPage) { - if (err) { - done(err); - return; - } - /* verify all messages are received */ - var messages = resultPage.items; - expect(messages.length).to.equal(testMessages.length, 'Verify correct number of messages found'); - - /* verify message ids are unique */ - var ids = {}; - utils.arrForEach(messages, function (msg) { - ids[msg.id] = msg; - }); - expect(utils.keysArray(ids).length).to.equal( - testMessages.length, - 'Verify correct number of distinct message ids found' - ); - done(); - }); - }); - } catch (err) { - done(err); - } + expect(utils.keysArray(ids).length).to.equal( + testMessages.length, + 'Verify correct number of distinct message ids found' + ); }); - restTestOnJsonMsgpack('history_multiple', function (done, rest, channelName) { + restTestOnJsonMsgpackAsync('history_multiple', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); /* first, send a number of events to this channel */ - var publishTasks = [ - function (publishCb) { - testchannel.publish(testMessages, publishCb); - }, - ]; - - publishTasks.push(function (waitCb) { - setTimeout(function () { - waitCb(null); - }, 1000); + await Promise.all([new Promise((resolve) => setTimeout(resolve, 1000)), testchannel.publish(testMessages)]); + + /* so now the messages are there; try querying the timeline */ + var resultPage = await testchannel.history(); + /* verify all messages are received */ + var messages = resultPage.items; + expect(messages.length).to.equal(testMessages.length, 'Verify correct number of messages found'); + + /* verify message ids are unique */ + var ids = {}; + utils.arrForEach(messages, function (msg) { + ids[msg.id] = msg; }); - try { - async.parallel(publishTasks, function (err) { - if (err) { - done(err); - return; - } - - /* so now the messages are there; try querying the timeline */ - testchannel.history(function (err, resultPage) { - if (err) { - done(err); - return; - } - /* verify all messages are received */ - var messages = resultPage.items; - expect(messages.length).to.equal(testMessages.length, 'Verify correct number of messages found'); - - /* verify message ids are unique */ - var ids = {}; - utils.arrForEach(messages, function (msg) { - ids[msg.id] = msg; - }); - expect(utils.keysArray(ids).length).to.equal( - testMessages.length, - 'Verify correct number of distinct message ids found' - ); - done(); - }); - }); - } catch (err) { - done(err); - } + expect(utils.keysArray(ids).length).to.equal( + testMessages.length, + 'Verify correct number of distinct message ids found' + ); }); - restTestOnJsonMsgpack('history_simple_paginated_b', function (done, rest, channelName) { + restTestOnJsonMsgpackAsync('history_simple_paginated_b', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); /* first, send a number of events to this channel */ - var publishTasks = utils.arrMap(testMessages, function (event) { - return function (publishCb) { - testchannel.publish(event.name, event.data, publishCb); - }; - }); + for (var message of testMessages) { + await testchannel.publish(message.name, message.data); + } - publishTasks.push(function (waitCb) { - setTimeout(function () { - waitCb(null); - }, 1000); - }); - try { - async.series(publishTasks, function (err) { - if (err) { - done(err); - return; - } - - /* so now the messages are there; try querying the timeline to get messages one at a time */ - var ids = {}, - totalMessagesExpected = testMessages.length, - nextPage = function (cb) { - testchannel.history({ limit: 1, direction: 'backwards' }, cb); - }; - - testMessages.reverse(); - async.mapSeries( - testMessages, - function (expectedMessage, cb) { - nextPage(function (err, resultPage) { - if (err) { - cb(err); - return; - } - /* verify expected number of messages in this page */ - expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); - var resultMessage = resultPage.items[0]; - ids[resultMessage.id] = resultMessage; - - /* verify expected message */ - expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); - expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); - - if (--totalMessagesExpected > 0) { - expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; - expect(!resultPage.isLast(), 'Verify not last page').to.be.ok; - nextPage = resultPage.next; - } - cb(); - }); - }, - function (err) { - if (err) { - done(err); - return; - } - /* verify message ids are unique */ - expect(utils.keysArray(ids).length).to.equal( - testMessages.length, - 'Verify correct number of distinct message ids found' - ); - done(); - } - ); - }); - } catch (err) { - done(err); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + /* so now the messages are there; try querying the timeline to get messages one at a time */ + var ids = {}, + totalMessagesExpected = testMessages.length; + + var resultPage = await testchannel.history({ limit: 1, direction: 'backwards' }); + for (var expectedMessage of reversedMessages) { + /* verify expected number of messages in this page */ + expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); + var resultMessage = resultPage.items[0]; + ids[resultMessage.id] = resultMessage; + + /* verify expected message */ + expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); + expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); + + if (--totalMessagesExpected > 0) { + expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; + expect(!resultPage.isLast(), 'Verify not last page').to.be.ok; + resultPage = await resultPage.next(); + } } + /* verify message ids are unique */ + expect(utils.keysArray(ids).length).to.equal( + testMessages.length, + 'Verify correct number of distinct message ids found' + ); }); - it('history_simple_paginated_f', function (done) { + it('history_simple_paginated_f', async function () { var testchannel = rest.channels.get('persisted:history_simple_paginated_f'); /* first, send a number of events to this channel */ - var publishTasks = utils.arrMap(testMessages, function (event) { - return function (publishCb) { - testchannel.publish(event.name, event.data, publishCb); - }; - }); + for (var message of testMessages) { + await testchannel.publish(message.name, message.data); + } - publishTasks.push(function (waitCb) { - setTimeout(function () { - waitCb(null); - }, 1000); + await new Promise(function (resolve) { + setTimeout(resolve, 1000); }); - try { - async.series(publishTasks, function (err) { - if (err) { - done(err); - return; - } - - /* so now the messages are there; try querying the timeline to get messages one at a time */ - var ids = {}, - totalMessagesExpected = testMessages.length, - nextPage = function (cb) { - testchannel.history({ limit: 1, direction: 'forwards' }, cb); - }; - - async.mapSeries( - testMessages, - function (expectedMessage, cb) { - nextPage(function (err, resultPage) { - if (err) { - cb(err); - return; - } - /* verify expected number of messages in this page */ - expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); - var resultMessage = resultPage.items[0]; - ids[resultMessage.id] = resultMessage; - - /* verify expected message */ - expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); - expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); - - if (--totalMessagesExpected > 0) { - expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; - nextPage = resultPage.next; - } - cb(); - }); - }, - function (err) { - if (err) { - done(err); - return; - } - /* verify message ids are unique */ - expect(utils.keysArray(ids).length).to.equal( - testMessages.length, - 'Verify correct number of distinct message ids found' - ); - done(); - } - ); - }); - } catch (err) { - done(err); + + var ids = {}, + totalMessagesExpected = testMessages.length; + + var resultPage = await testchannel.history({ limit: 1, direction: 'forwards' }); + for (var expectedMessage of testMessages) { + /* verify expected number of messages in this page */ + expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); + var resultMessage = resultPage.items[0]; + ids[resultMessage.id] = resultMessage; + + /* verify expected message */ + expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); + expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); + + if (--totalMessagesExpected > 0) { + expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; + resultPage = await resultPage.next(); + } } + + /* verify message ids are unique */ + expect(utils.keysArray(ids).length).to.equal( + testMessages.length, + 'Verify correct number of distinct message ids found' + ); }); - it('history_multiple_paginated_b', function (done) { + it('history_multiple_paginated_b', async function () { var testchannel = rest.channels.get('persisted:history_multiple_paginated_b'); /* first, send a number of events to this channel */ - var publishTasks = [ - function (publishCb) { - testchannel.publish(testMessages, publishCb); - }, - ]; - - publishTasks.push(function (waitCb) { - setTimeout(function () { - waitCb(null); - }, 1000); + for (var message of testMessages) { + await testchannel.publish(message.name, message.data); + } + + await new Promise(function (resolve) { + setTimeout(resolve, 1000); }); - try { - async.series(publishTasks, function (err) { - if (err) { - done(err); - return; - } - - /* so now the messages are there; try querying the timeline to get messages one at a time */ - var ids = {}, - totalMessagesExpected = testMessages.length, - nextPage = function (cb) { - testchannel.history({ limit: 1, direction: 'backwards' }, cb); - }; - - testMessages.reverse(); - async.mapSeries( - testMessages, - function (expectedMessage, cb) { - nextPage(function (err, resultPage) { - if (err) { - cb(err); - return; - } - /* verify expected number of messages in this page */ - expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); - var resultMessage = resultPage.items[0]; - ids[resultMessage.id] = resultMessage; - - /* verify expected message */ - expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); - expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); - - if (--totalMessagesExpected > 0) { - expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; - nextPage = resultPage.next; - } - cb(); - }); - }, - function (err) { - if (err) { - done(err); - return; - } - /* verify message ids are unique */ - expect(utils.keysArray(ids).length).to.equal( - testMessages.length, - 'Verify correct number of distinct message ids found' - ); - done(); - } - ); - }); - } catch (err) { - done(err); + + /* so now the messages are there; try querying the timeline to get messages one at a time */ + var ids = {}, + totalMessagesExpected = testMessages.length; + + var resultPage = await testchannel.history({ limit: 1, direction: 'backwards' }); + for (var expectedMessage of reversedMessages) { + /* verify expected number of messages in this page */ + expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); + var resultMessage = resultPage.items[0]; + ids[resultMessage.id] = resultMessage; + + /* verify expected message */ + expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); + expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); + + if (--totalMessagesExpected > 0) { + expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; + resultPage = await resultPage.next(); + } } }); - it('history_multiple_paginated_f', function (done) { + it('history_multiple_paginated_f', async function () { var testchannel = rest.channels.get('persisted:history_multiple_paginated_f'); /* first, send a number of events to this channel */ - var publishTasks = [ - function (publishCb) { - testchannel.publish(testMessages, publishCb); - }, - ]; - - publishTasks.push(function (waitCb) { - setTimeout(function () { - waitCb(null); - }, 1000); + await testchannel.publish(testMessages); + + await new Promise(function (resolve) { + setTimeout(resolve, 1000); }); - try { - async.series(publishTasks, function (err) { - if (err) { - done(err); - return; - } - - /* so now the messages are there; try querying the timeline to get messages one at a time */ - var ids = {}, - totalMessagesExpected = testMessages.length, - nextPage = function (cb) { - testchannel.history({ limit: 1, direction: 'forwards' }, cb); - }; - - async.mapSeries( - testMessages, - function (expectedMessage, cb) { - nextPage(function (err, resultPage) { - if (err) { - cb(err); - return; - } - /* verify expected number of messages in this page */ - expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); - var resultMessage = resultPage.items[0]; - ids[resultMessage.id] = resultMessage; - - /* verify expected message */ - expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); - expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); - - if (--totalMessagesExpected > 0) { - expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; - nextPage = resultPage.next; - } - cb(); - }); - }, - function (err) { - if (err) { - done(err); - return; - } - /* verify message ids are unique */ - expect(utils.keysArray(ids).length).to.equal( - testMessages.length, - 'Verify correct number of distinct message ids found' - ); - done(); - } - ); - }); - } catch (err) { - done(err); + + /* so now the messages are there; try querying the timeline to get messages one at a time */ + var ids = {}, + totalMessagesExpected = testMessages.length; + + var resultPage = await testchannel.history({ limit: 1, direction: 'forwards' }); + for (var expectedMessage of testMessages) { + /* verify expected number of messages in this page */ + expect(resultPage.items.length).to.equal(1, 'Verify a single message received'); + var resultMessage = resultPage.items[0]; + ids[resultMessage.id] = resultMessage; + + /* verify expected message */ + expect(expectedMessage.name).to.equal(resultMessage.name, 'Verify expected name value present'); + expect(expectedMessage.data).to.deep.equal(resultMessage.data, 'Verify expected data value present'); + + if (--totalMessagesExpected > 0) { + expect(resultPage.hasNext(), 'Verify next link is present').to.be.ok; + var resultPage = await resultPage.next(); + } } + + /* verify message ids are unique */ + expect(utils.keysArray(ids).length).to.equal( + testMessages.length, + 'Verify correct number of distinct message ids found' + ); }); - restTestOnJsonMsgpack('history_encoding_errors', function (done, rest, channelName) { + restTestOnJsonMsgpackAsync('history_encoding_errors', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); var badMessage = { name: 'jsonUtf8string', encoding: 'json/utf-8', data: '{"foo":"bar"}' }; - try { - testchannel.publish(badMessage, function (err) { - if (err) { - done(err); - return; - } - setTimeout(function () { - testchannel.history(function (err, resultPage) { - if (err) { - done(err); - return; - } - /* verify all messages are received */ - var message = resultPage.items[0]; - expect(message.data).to.equal(badMessage.data, 'Verify data preserved'); - expect(message.encoding).to.equal(badMessage.encoding, 'Verify encoding preserved'); - done(); - }); - }, 1000); - }); - } catch (err) { - done(err); - } + testchannel.publish(badMessage); + await new Promise((resolve) => setTimeout(resolve, 1000)); + var resultPage = await testchannel.history(); + /* verify all messages are received */ + var message = resultPage.items[0]; + expect(message.data).to.equal(badMessage.data, 'Verify data preserved'); + expect(message.encoding).to.equal(badMessage.encoding, 'Verify encoding preserved'); }); if (typeof Promise !== 'undefined') { From b07d0325cb2188ba4fe5b10e3ec82261c2684e78 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 18:00:59 +0100 Subject: [PATCH 098/468] test: remove legacy rest history promise test Co-authored-by: Lawrence Forooghian --- test/rest/history.test.js | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/test/rest/history.test.js b/test/rest/history.test.js index 8946788e64..a4d7420ee5 100644 --- a/test/rest/history.test.js +++ b/test/rest/history.test.js @@ -236,41 +236,5 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { expect(message.data).to.equal(badMessage.data, 'Verify data preserved'); expect(message.encoding).to.equal(badMessage.encoding, 'Verify encoding preserved'); }); - - if (typeof Promise !== 'undefined') { - it('historyPromise', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); - var testchannel = rest.channels.get('persisted:history_promise'); - - testchannel - .publish('one', null) - .then(function () { - return testchannel.publish('two', null); - }) - .then(function () { - return testchannel.history({ limit: 1, direction: 'forwards' }); - }) - .then(function (resultPage) { - expect(resultPage.items.length).to.equal(1); - expect(resultPage.items[0].name).to.equal('one'); - return resultPage.first(); - }) - .then(function (resultPage) { - expect(resultPage.items[0].name).to.equal('one'); - return resultPage.current(); - }) - .then(function (resultPage) { - expect(resultPage.items[0].name).to.equal('one'); - return resultPage.next(); - }) - .then(function (resultPage) { - expect(resultPage.items[0].name).to.equal('two'); - done(); - }) - ['catch'](function (err) { - done(err); - }); - }); - } }); }); From 24d299e17041ff0885220ff7e53979f8f1d0b322 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 18:14:21 +0100 Subject: [PATCH 099/468] test: convert rest http tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/http.test.js | 62 ++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/test/rest/http.test.js b/test/rest/http.test.js index 21502fed71..ff363d3b19 100644 --- a/test/rest/http.test.js +++ b/test/rest/http.test.js @@ -9,7 +9,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { this.timeout(60 * 1000); before(function (done) { helper.setupApp(function () { - rest = helper.AblyRest({ + rest = helper.AblyRestPromise({ agents: { 'custom-agent': '0.1.2', }, @@ -21,46 +21,44 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { /** * RSC7a */ - it('Should send X-Ably-Version and Ably-Agent headers in get/post requests', function (done) { - //Intercept Http.do with test - function testRequestHandler(_, __, ___, headers) { - try { - expect('X-Ably-Version' in headers, 'Verify version header exists').to.be.ok; - expect('Ably-Agent' in headers, 'Verify agent header exists').to.be.ok; + it('Should send X-Ably-Version and Ably-Agent headers in get/post requests', async function () { + var originalDo = rest.http.do; - // This test should not directly validate version against Defaults.version, as - // ultimately the version header has been derived from that value. - expect(headers['X-Ably-Version']).to.equal('2', 'Verify current version number'); - expect(headers['Ably-Agent'].indexOf('ably-js/' + Defaults.version) > -1, 'Verify agent').to.be.ok; - expect(headers['Ably-Agent'].indexOf('custom-agent/0.1.2') > -1, 'Verify custom agent').to.be.ok; + // Intercept Http.do with test + function testRequestHandler(method, rest, path, headers, body, params, callback) { + expect('X-Ably-Version' in headers, 'Verify version header exists').to.be.ok; + expect('Ably-Agent' in headers, 'Verify agent header exists').to.be.ok; - // We don't test on NativeScript so a check for that platform is excluded here - if (typeof document !== 'undefined') { - // browser - expect(headers['Ably-Agent'].indexOf('browser') > -1, 'Verify agent').to.be.ok; - } else if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { - // reactnative - expect(headers['Ably-Agent'].indexOf('reactnative') > -1, 'Verify agent').to.be.ok; - } else { - // node - expect(headers['Ably-Agent'].indexOf('nodejs') > -1, 'Verify agent').to.be.ok; - } - } catch (err) { - done(err); + // This test should not directly validate version against Defaults.version, as + // ultimately the version header has been derived from that value. + expect(headers['X-Ably-Version']).to.equal('2', 'Verify current version number'); + expect(headers['Ably-Agent'].indexOf('ably-js/' + Defaults.version) > -1, 'Verify agent').to.be.ok; + expect(headers['Ably-Agent'].indexOf('custom-agent/0.1.2') > -1, 'Verify custom agent').to.be.ok; + + // We don't test on NativeScript so a check for that platform is excluded here + if (typeof document !== 'undefined') { + // browser + expect(headers['Ably-Agent'].indexOf('browser') > -1, 'Verify agent').to.be.ok; + } else if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { + // reactnative + expect(headers['Ably-Agent'].indexOf('reactnative') > -1, 'Verify agent').to.be.ok; + } else { + // node + expect(headers['Ably-Agent'].indexOf('nodejs') > -1, 'Verify agent').to.be.ok; } + + originalDo.call(rest.http, method, rest, path, headers, body, params, callback); } rest.http.do = testRequestHandler; // Call all methods that use rest http calls - rest.auth.requestToken(); - rest.time(); - rest.stats(); + await rest.auth.requestToken(); + await rest.time(); + await rest.stats(); var channel = rest.channels.get('http_test_channel'); - channel.publish('test', 'Testing http headers'); - channel.presence.get(); - - done(); + await channel.publish('test', 'Testing http headers'); + await channel.presence.get(); }); }); }); From d40ea8f962e787bcbce0396276ea395ab8ef1012 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 18:16:34 +0100 Subject: [PATCH 100/468] test: convert rest constructor tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/init.test.js | 48 +++++++++++++----------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/test/rest/init.test.js b/test/rest/init.test.js index 7d83f277e0..11f9b5a11d 100644 --- a/test/rest/init.test.js +++ b/test/rest/init.test.js @@ -18,59 +18,48 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('Init with key string', function () { var keyStr = helper.getTestApp().keys[0].keyStr; - var rest = new helper.Ably.Rest(keyStr); + var rest = new helper.Ably.Rest.Promise(keyStr); expect(rest.options.key).to.equal(keyStr); }); - it('Init with token string', function (done) { - try { - /* first generate a token ... */ - var rest = helper.AblyRest(); - var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; + it('Init with token string', async function () { + /* first generate a token ... */ + var rest = helper.AblyRestPromise(); + var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; - rest.auth.requestToken(null, testKeyOpts, function (err, tokenDetails) { - if (err) { - done(err); - return; - } + var tokenDetails = await rest.auth.requestToken(null, testKeyOpts); + var tokenStr = tokenDetails.token, + rest = new helper.Ably.Rest.Promise(tokenStr); - var tokenStr = tokenDetails.token, - rest = new helper.Ably.Rest(tokenStr); - - expect(rest.options.token).to.equal(tokenStr); - done(); - }); - } catch (err) { - done(err); - } + expect(rest.options.token).to.equal(tokenStr); }); it('Init with tls: false', function () { - var rest = helper.AblyRest({ tls: false, port: 123, tlsPort: 456 }); + var rest = helper.AblyRestPromise({ tls: false, port: 123, tlsPort: 456 }); expect(rest.baseUri('example.com')).to.equal('http://example.com:123'); }); it('Init with tls: true', function () { - var rest = helper.AblyRest({ tls: true, port: 123, tlsPort: 456 }); + var rest = helper.AblyRestPromise({ tls: true, port: 123, tlsPort: 456 }); expect(rest.baseUri('example.com')).to.equal('https://example.com:456'); }); /* init without any tls key should enable tls */ it('Init without any tls key should enable tls', function () { - var rest = helper.AblyRest({ port: 123, tlsPort: 456 }); + var rest = helper.AblyRestPromise({ port: 123, tlsPort: 456 }); expect(rest.baseUri('example.com')).to.equal('https://example.com:456'); }); it("Init with clientId set to '*' or anything other than a string or null should error", function () { expect(function () { - var rest = helper.AblyRest({ clientId: '*' }); + var rest = helper.AblyRestPromise({ clientId: '*' }); }, 'Check can’t init library with a wildcard clientId').to.throw; expect(function () { - var rest = helper.AblyRest({ clientId: 123 }); + var rest = helper.AblyRestPromise({ clientId: 123 }); }, 'Check can’t init library with a numerical clientId').to.throw; expect(function () { - var rest = helper.AblyRest({ clientId: false }); + var rest = helper.AblyRestPromise({ clientId: false }); }, 'Check can’t init library with a boolean clientId').to.throw; }); @@ -78,9 +67,6 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var rest, keyStr = helper.getTestApp().keys[0].keyStr; - rest = new Ably.Rest(keyStr); - expect(!rest.options.promises, 'Check promises defaults to false').to.be.ok; - rest = new Ably.Rest.Promise(keyStr); expect(rest.options.promises, 'Check promises default to true with promise constructor').to.be.ok; @@ -88,10 +74,6 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var AblyPromises = require('../../promises'); rest = new AblyPromises.Rest(keyStr); expect(rest.options.promises, 'Check promises default to true with promise require target').to.be.ok; - - var AblyCallbacks = require('../../callbacks'); - rest = new AblyCallbacks.Rest(keyStr); - expect(!rest.options.promises, 'Check promises default to false with callback require target').to.be.ok; } }); }); From 152f4e59c7c338beb158cddff4b1a971add0c4ec Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 18:29:09 +0100 Subject: [PATCH 101/468] test: convert rest message tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/message.test.js | 308 ++++++++++++-------------------------- 1 file changed, 96 insertions(+), 212 deletions(-) diff --git a/test/rest/message.test.js b/test/rest/message.test.js index 546f678c97..ca56219987 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -18,211 +18,121 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Authenticate with a clientId and ensure that the clientId is not sent in the Message and is implicitly added when published */ - it('Should implicitly send clientId when authenticated with clientId', function (done) { + it('Should implicitly send clientId when authenticated with clientId', async function () { var clientId = 'implicit_client_id_0', - rest = helper.AblyRest({ clientId: clientId, useBinaryProtocol: false }), + rest = helper.AblyRestPromise({ clientId: clientId, useBinaryProtocol: false }), channel = rest.channels.get('rest_implicit_client_id_0'); var originalPublish = channel._publish; channel._publish = function (requestBody) { var message = JSON.parse(requestBody)[0]; - try { - expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; - expect(!message.clientId, 'client ID is not added by the client library as it is implicit').to.be.ok; - } catch (err) { - done(err); - } + expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; + expect(!message.clientId, 'client ID is not added by the client library as it is implicit').to.be.ok; originalPublish.apply(channel, arguments); }; - channel.publish('event0', null, function (err) { - if (err) { - done(err); - return; - } - - channel.history(function (err, page) { - if (err) { - done(err); - return; - } + await channel.publish('event0', null); + var page = await channel.history(); - var message = page.items[0]; - try { - expect(message.clientId == clientId, 'Client ID was added implicitly').to.be.ok; - done(); - } catch (err) { - done(err); - } - }); - }); + var message = page.items[0]; + expect(message.clientId == clientId, 'Client ID was added implicitly').to.be.ok; }); /* Authenticate with a clientId and explicitly provide the same clientId in the Message and ensure it is published */ - it('Should publish clientId when provided explicitly in message', function (done) { + it('Should publish clientId when provided explicitly in message', async function () { var clientId = 'explicit_client_id_0', - rest = helper.AblyRest({ clientId: clientId, useBinaryProtocol: false }), + rest = helper.AblyRestPromise({ clientId: clientId, useBinaryProtocol: false }), channel = rest.channels.get('rest_explicit_client_id_0'); var originalPublish = channel._publish; channel._publish = function (requestBody) { var message = JSON.parse(requestBody)[0]; - try { - expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; - expect( - message.clientId == clientId, - 'client ID is added by the client library as it is explicit in the publish' - ).to.be.ok; - } catch (err) { - done(err); - } + expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; + expect( + message.clientId == clientId, + 'client ID is added by the client library as it is explicit in the publish' + ).to.be.ok; originalPublish.apply(channel, arguments); }; - channel.publish({ name: 'event0', clientId: clientId }, function (err) { - if (err) { - done(err); - } - - channel.history(function (err, page) { - if (err) { - done(err); - return; - } - - var message = page.items[0]; - try { - expect(message.clientId == clientId, 'Client ID was retained').to.be.ok; - done(); - } catch (err) { - done(err); - } - }); - }); + await channel.publish({ name: 'event0', clientId: clientId }); + var page = await channel.history(); + var message = page.items[0]; + expect(message.clientId == clientId, 'Client ID was retained').to.be.ok; }); /* Authenticate with a clientId and explicitly provide a different invalid clientId in the Message and expect it to not be published and be rejected */ - it('Should error when clientId sent in message is different than authenticated clientId', function (done) { + it('Should error when clientId sent in message is different than authenticated clientId', async function () { var clientId = 'explicit_client_id_0', invalidClientId = 'invalid'; - helper.AblyRest().auth.requestToken({ clientId: clientId }, function (err, token) { - try { - expect(token.clientId === clientId, 'client ID is present in the Token').to.be.ok; - } catch (err) { - done(err); - } - - // REST client uses a token string so is unaware of the clientId so cannot reject before communicating with Ably - var rest = helper.AblyRest({ token: token.token, useBinaryProtocol: false }), - channel = rest.channels.get('rest_explicit_client_id_1'); - - var originalPublish = channel._publish; - channel._publish = function (requestBody) { - var message = JSON.parse(requestBody)[0]; - try { - expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; - expect( - message.clientId == invalidClientId, - 'invalid client ID is added by the client library as it is explicit in the publish' - ).to.be.ok; - } catch (err) { - done(err); - } - originalPublish.apply(channel, arguments); - }; + var token = await helper.AblyRestPromise().auth.requestToken({ clientId: clientId }); + expect(token.clientId === clientId, 'client ID is present in the Token').to.be.ok; - channel.publish({ name: 'event0', clientId: invalidClientId }, function (err) { - if (!err) { - done(new Error('Publish should have failed with invalid clientId')); - return; - } + // REST client uses a token string so is unaware of the clientId so cannot reject before communicating with Ably + var rest = helper.AblyRestPromise({ token: token.token, useBinaryProtocol: false }), + channel = rest.channels.get('rest_explicit_client_id_1'); - channel.history(function (err, page) { - if (err) { - done(err); - return; - } + var originalPublish = channel._publish; + channel._publish = function (requestBody) { + var message = JSON.parse(requestBody)[0]; + expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; + expect( + message.clientId == invalidClientId, + 'invalid client ID is added by the client library as it is explicit in the publish' + ).to.be.ok; + originalPublish.apply(channel, arguments); + }; - try { - expect(page.items.length).to.equal(0, 'Message should not have been published'); - done(); - } catch (err) { - done(err); - } - }); - }); - }); + try { + await channel.publish({ name: 'event0', clientId: invalidClientId }); + } catch (err) { + var page = await channel.history(); + expect(page.items.length).to.equal(0, 'Message should not have been published'); + return; + } + expect.fail('Publish should have failed with invalid clientId'); }); /* TO3l8; CD2C; RSL1i */ - it('Should error when publishing message larger than maxMessageSize', function (done) { + it('Should error when publishing message larger than maxMessageSize', async function () { /* No connectionDetails mechanism for REST, so just pass the override into the constructor */ - var realtime = helper.AblyRest({ internal: { maxMessageSize: 64 } }), + var realtime = helper.AblyRestPromise({ internal: { maxMessageSize: 64 } }), channel = realtime.channels.get('maxMessageSize'); - channel.publish('foo', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', function (err) { - try { - expect(err, 'Check publish refused').to.be.ok; - expect(err.code).to.equal(40009); - done(); - } catch (err) { - done(err); - } - }); + try { + await channel.publish('foo', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + } catch (err) { + expect(err, 'Check publish refused').to.be.ok; + expect(err.code).to.equal(40009); + return; + } + expect.fail('Expected channel.publish() to throw error'); }); /* Check ids are correctly sent */ - it('Should send correct IDs when idempotentRestPublishing set to false', function (done) { - var rest = helper.AblyRest({ idempotentRestPublishing: false, useBinaryProtocol: false }), + it('Should send correct IDs when idempotentRestPublishing set to false', async function () { + var rest = helper.AblyRestPromise({ idempotentRestPublishing: false, useBinaryProtocol: false }), channel = rest.channels.get('idempotent_rest_publishing'), message = { name: 'test', id: 'idempotent-msg-id:0' }; - async.parallel( - [ - function (parCb) { - channel.publish(message, parCb); - }, - function (parCb) { - channel.publish(message, parCb); - }, - function (parCb) { - channel.publish(message, parCb); - }, - ], - function (err) { - if (err) { - done(err); - return; - } + await Promise.all([channel.publish(message), channel.publish(message), channel.publish(message)]); - channel.history(function (err, page) { - if (err) { - done(err); - return; - } - try { - expect(page.items.length).to.equal(1, 'Check only one message published'); - expect(page.items[0].id).to.equal(message.id, 'Check message id preserved in history'); - done(); - } catch (err) { - done(err); - } - }); - } - ); + var page = await channel.history(); + expect(page.items.length).to.equal(1, 'Check only one message published'); + expect(page.items[0].id).to.equal(message.id, 'Check message id preserved in history'); }); /* Check ids are added when automatic idempotent rest publishing option enabled */ - it('Should add IDs when automatic idempotent rest publishing option enabled', function (done) { + it('Should add IDs when automatic idempotent rest publishing option enabled', async function () { /* easiest way to get the host we're using for tests */ - var dummyRest = helper.AblyRest(), + var dummyRest = helper.AblyRestPromise(), host = dummyRest.options.restHost, /* Add the same host as a bunch of fallback hosts, so after the first * request 'fails' we retry on the same host using the fallback mechanism */ - rest = helper.AblyRest({ + rest = helper.AblyRestPromise({ idempotentRestPublishing: true, useBinaryProtocol: false, fallbackHosts: [host, host, host], @@ -234,27 +144,22 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async originalDoUri = Ably.Realtime.Platform.Http.doUri; channel._publish = function (requestBody) { - try { - var messageOne = JSON.parse(requestBody)[0]; - var messageTwo = JSON.parse(requestBody)[1]; - expect(messageOne.name).to.equal('one', 'Outgoing message 1 interecepted'); - expect(messageTwo.name).to.equal('two', 'Outgoing message 2 interecepted'); - idOne = messageOne.id; - idTwo = messageTwo.id; - expect(idOne, 'id set on message 1').to.be.ok; - expect(idTwo, 'id set on message 2').to.be.ok; - expect(idOne && idOne.split(':')[1]).to.equal('0', 'check zero-based index'); - expect(idTwo && idTwo.split(':')[1]).to.equal('1', 'check zero-based index'); - originalPublish.apply(channel, arguments); - } catch (err) { - done(err); - } + var messageOne = JSON.parse(requestBody)[0]; + var messageTwo = JSON.parse(requestBody)[1]; + expect(messageOne.name).to.equal('one', 'Outgoing message 1 interecepted'); + expect(messageTwo.name).to.equal('two', 'Outgoing message 2 interecepted'); + idOne = messageOne.id; + idTwo = messageTwo.id; + expect(idOne, 'id set on message 1').to.be.ok; + expect(idTwo, 'id set on message 2').to.be.ok; + expect(idOne && idOne.split(':')[1]).to.equal('0', 'check zero-based index'); + expect(idTwo && idTwo.split(':')[1]).to.equal('1', 'check zero-based index'); + originalPublish.apply(channel, arguments); }; Ably.Rest.Platform.Http.doUri = function (method, rest, uri, headers, body, params, callback) { originalDoUri(method, rest, uri, headers, body, params, function (err) { if (err) { - done(err); callback(err); return; } @@ -264,35 +169,19 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async Ably.Rest.Platform.Http.doUri = originalDoUri; }; - channel.publish([{ name: 'one' }, { name: 'two' }], function (err) { - if (err) { - done(err); - return; - } - - channel.history({ direction: 'forwards' }, function (err, page) { - if (err) { - done(err); - return; - } - /* TODO uncomment when idempotent publishing works on sandbox - * until then, test with ABLY_ENV=idempotent-dev - test.equal(page.items.length, 2, 'Only one message (with two items) should have been published'); - */ - try { - expect(page.items[0].id).to.equal(idOne, 'Check message id 1 preserved in history'); - expect(page.items[1].id).to.equal(idTwo, 'Check message id 1 preserved in history'); - done(); - } catch (err) { - done(err); - } - }); - }); + await channel.publish([{ name: 'one' }, { name: 'two' }]); + var page = await channel.history({ direction: 'forwards' }); + /* TODO uncomment when idempotent publishing works on sandbox + * until then, test with ABLY_ENV=idempotent-dev + * test.equal(page.items.length, 2, 'Only one message (with two items) should have been published'); + */ + expect(page.items[0].id).to.equal(idOne, 'Check message id 1 preserved in history'); + expect(page.items[1].id).to.equal(idTwo, 'Check message id 1 preserved in history'); }); if (typeof Promise !== undefined) { it('Rest publish promise', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); + var rest = helper.AblyRestPromise({ internal: { promises: true } }); var channel = rest.channels.get('publishpromise'); channel @@ -318,31 +207,26 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); } - it('Rest publish params', function (done) { - var rest = helper.AblyRest(), + it('Rest publish params', async function () { + var rest = helper.AblyRestPromise(), channel = rest.channels.get('publish_params'); + var originalPublish = channel._publish; + /* Stub out _publish to check params */ - var i = 0; channel._publish = function (requestBody, headers, params) { - try { - expect(params && params.testParam).to.equal('testParamValue'); - } catch (err) { - done(err); - } - if (++i === 8) { - done(); - } + expect(params && params.testParam).to.equal('testParamValue'); + originalPublish.apply(channel, arguments); }; - channel.publish('foo', 'bar', { testParam: 'testParamValue' }); - channel.publish('foo', { data: 'data' }, { testParam: 'testParamValue' }); - channel.publish('foo', { data: 'data' }, { testParam: 'testParamValue' }, noop); - channel.publish('foo', null, { testParam: 'testParamValue' }); - channel.publish(null, 'foo', { testParam: 'testParamValue' }); - channel.publish({ name: 'foo', data: 'bar' }, { testParam: 'testParamValue' }); - channel.publish([{ name: 'foo', data: 'bar' }], { testParam: 'testParamValue' }); - channel.publish([{ name: 'foo', data: 'bar' }], { testParam: 'testParamValue' }, noop); + await channel.publish('foo', 'bar', { testParam: 'testParamValue' }); + await channel.publish('foo', { data: 'data' }, { testParam: 'testParamValue' }); + await channel.publish('foo', { data: 'data' }, { testParam: 'testParamValue' }, noop); + await channel.publish('foo', null, { testParam: 'testParamValue' }); + await channel.publish(null, 'foo', { testParam: 'testParamValue' }); + await channel.publish({ name: 'foo', data: 'bar' }, { testParam: 'testParamValue' }); + await channel.publish([{ name: 'foo', data: 'bar' }], { testParam: 'testParamValue' }); + await channel.publish([{ name: 'foo', data: 'bar' }], { testParam: 'testParamValue' }, noop); }); }); }); From 8b9e0f9364fed9a2501ace2466495dc7fcb3bad2 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 26 Apr 2023 18:29:38 +0100 Subject: [PATCH 102/468] test: remove legacy rest message promise test Co-authored-by: Lawrence Forooghian --- test/rest/message.test.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/test/rest/message.test.js b/test/rest/message.test.js index ca56219987..bb62a52791 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -179,34 +179,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(page.items[1].id).to.equal(idTwo, 'Check message id 1 preserved in history'); }); - if (typeof Promise !== undefined) { - it('Rest publish promise', function (done) { - var rest = helper.AblyRestPromise({ internal: { promises: true } }); - var channel = rest.channels.get('publishpromise'); - - channel - .publish('name', 'data') - .then(function () { - return channel.history(); - }) - .then(function (page) { - var message = page.items[0]; - try { - expect( - message.data == 'data', - 'Check publish and history promise methods both worked as expected' - ).to.be.ok; - done(); - } catch (err) { - done(err); - } - }) - ['catch'](function (err) { - done(err); - }); - }); - } - it('Rest publish params', async function () { var rest = helper.AblyRestPromise(), channel = rest.channels.get('publish_params'); From d76a03fe6a5f683812fa8622ce9b0ad29600abdb Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 15:30:18 +0100 Subject: [PATCH 103/468] test: convert rest presence tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/presence.test.js | 161 ++++++++++++++----------------------- 1 file changed, 61 insertions(+), 100 deletions(-) diff --git a/test/rest/presence.test.js b/test/rest/presence.test.js index 4d36d63665..d21c22b50c 100644 --- a/test/rest/presence.test.js +++ b/test/rest/presence.test.js @@ -21,52 +21,44 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } describe('rest/presence', function () { + this.timeout(60 * 1000); + before(function (done) { helper.setupApp(function () { - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); cipherConfig = helper.getTestApp().cipherConfig; done(); }); }); function presence_simple(operation) { - return function (done) { - try { - var cipherParams = cipherParamsFromConfig(cipherConfig); - var channel = rest.channels.get('persisted:presence_fixtures', { cipher: cipherParams }); - channel.presence[operation](function (err, resultPage) { - if (err) { - done(err); - return; - } - var presenceMessages = resultPage.items; - expect(presenceMessages.length).to.equal(6, 'Verify correct number of messages found'); - if (presenceMessages.length != 6) { - console.log('presenceMessages: ', JSON.stringify(presenceMessages)); - } - var encodedMessage = arrFind(presenceMessages, function (msg) { - return msg.clientId == 'client_encoded'; - }); - var decodedMessage = arrFind(presenceMessages, function (msg) { - return msg.clientId == 'client_decoded'; - }); - var boolMessage = arrFind(presenceMessages, function (msg) { - return msg.clientId == 'client_bool'; - }); - var intMessage = arrFind(presenceMessages, function (msg) { - return msg.clientId == 'client_int'; - }); - expect(encodedMessage.data).to.deep.equal(decodedMessage.data, 'Verify message decoding works correctly'); - expect(encodedMessage.encoding).to.equal(null, 'Decoding should remove encoding field'); - expect(decodedMessage.encoding).to.equal(null, 'Decoding should remove encoding field'); - expect(boolMessage.data).to.equal('true', 'should not attempt to parse string data when no encoding field'); - expect(intMessage.data).to.equal('24', 'should not attempt to parse string data when no encoding field'); - expect(boolMessage.action).to.equal(operation === 'get' ? 'present' : 'enter', 'appropriate action'); - done(); - }); - } catch (err) { - done(err); + return async function () { + var cipherParams = cipherParamsFromConfig(cipherConfig); + var channel = rest.channels.get('persisted:presence_fixtures', { cipher: cipherParams }); + var resultPage = await channel.presence[operation](); + var presenceMessages = resultPage.items; + expect(presenceMessages.length).to.equal(6, 'Verify correct number of messages found'); + if (presenceMessages.length != 6) { + console.log('presenceMessages: ', JSON.stringify(presenceMessages)); } + var encodedMessage = arrFind(presenceMessages, function (msg) { + return msg.clientId == 'client_encoded'; + }); + var decodedMessage = arrFind(presenceMessages, function (msg) { + return msg.clientId == 'client_decoded'; + }); + var boolMessage = arrFind(presenceMessages, function (msg) { + return msg.clientId == 'client_bool'; + }); + var intMessage = arrFind(presenceMessages, function (msg) { + return msg.clientId == 'client_int'; + }); + expect(encodedMessage.data).to.deep.equal(decodedMessage.data, 'Verify message decoding works correctly'); + expect(encodedMessage.encoding).to.equal(null, 'Decoding should remove encoding field'); + expect(decodedMessage.encoding).to.equal(null, 'Decoding should remove encoding field'); + expect(boolMessage.data).to.equal('true', 'should not attempt to parse string data when no encoding field'); + expect(intMessage.data).to.equal('24', 'should not attempt to parse string data when no encoding field'); + expect(boolMessage.action).to.equal(operation === 'get' ? 'present' : 'enter', 'appropriate action'); }; } @@ -75,75 +67,44 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Ensure that calling JSON strinfigy on the Presence object converts the action string value back to a numeric value which the API requires */ - it('Presence message JSON serialisation', function (done) { + it('Presence message JSON serialisation', async function () { var channel = rest.channels.get('persisted:presence_fixtures'); - channel.presence.get(function (err, resultPage) { - if (err) { - done(err); - return; - } - var presenceMessages = resultPage.items; - var presenceBool = arrFind(presenceMessages, function (msg) { - return msg.clientId == 'client_bool'; - }); - try { - expect(JSON.parse(JSON.stringify(presenceBool)).action).to.equal(1); // present - presenceBool.action = 'leave'; - expect(JSON.parse(JSON.stringify(presenceBool)).action).to.equal(3); // leave - done(); - } catch (err) { - done(err); - } + var resultPage = await channel.presence.get(); + var presenceMessages = resultPage.items; + var presenceBool = arrFind(presenceMessages, function (msg) { + return msg.clientId == 'client_bool'; }); + expect(JSON.parse(JSON.stringify(presenceBool)).action).to.equal(1); // present + presenceBool.action = 'leave'; + expect(JSON.parse(JSON.stringify(presenceBool)).action).to.equal(3); // leave }); - it('Presence get limits and filtering', function (done) { - try { - var channel = rest.channels.get('persisted:presence_fixtures'); + it('Presence get limits and filtering', async function () { + var channel = rest.channels.get('persisted:presence_fixtures'); - var tests = [ - // Result limit - function (cb) { - channel.presence.get({ limit: 3 }, function (err, resultPage) { - if (err) cb(err); - var presenceMessages = resultPage.items; - expect(presenceMessages.length).to.equal(3, 'Verify correct number of messages found'); - cb(); - }); - }, - // Filter by clientId - function (cb) { - channel.presence.get({ clientId: 'client_json' }, function (err, resultPage) { - if (err) cb(err); - var presenceMessages = resultPage.items; - expect(presenceMessages.length).to.equal(1, 'Verify correct number of messages found'); - cb(); - }); - }, - // Filter by connectionId - function (cb) { - channel.presence.get(function (err, resultPage) { - if (err) cb(err); - channel.presence.get({ connectionId: resultPage.items[0].connectionId }, function (err, resultPage) { - if (err) cb(err); - var presenceMessages = resultPage.items; - expect(presenceMessages.length).to.equal(6, 'Verify correct number of messages found'); - cb(); - }); - }); - }, - ]; + var tests = [ + // Result limit + async function () { + var resultPage = await channel.presence.get({ limit: 3 }); + var presenceMessages = resultPage.items; + expect(presenceMessages.length).to.equal(3, 'Verify correct number of messages found'); + }, + // Filter by clientId + async function (cb) { + var resultPage = await channel.presence.get({ clientId: 'client_json' }); + var presenceMessages = resultPage.items; + expect(presenceMessages.length).to.equal(1, 'Verify correct number of messages found'); + }, + // Filter by connectionId + async function () { + var resultPage = await channel.presence.get(); + var resultPage2 = await channel.presence.get({ connectionId: resultPage.items[0].connectionId }); + var presenceMessages = resultPage2.items; + expect(presenceMessages.length).to.equal(6, 'Verify correct number of messages found'); + }, + ]; - async.parallel(tests, function (err) { - if (err) { - done(err); - return; - } - done(); - }); - } catch (err) { - done(err); - } + await Promise.all(tests); }); if (typeof Promise !== 'undefined') { From 4ef4a989c2e1c24d7160662259f8a78f61ea7965 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 15:30:33 +0100 Subject: [PATCH 104/468] test: remove legacy rest presence promise test Co-authored-by: Lawrence Forooghian --- test/rest/presence.test.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/rest/presence.test.js b/test/rest/presence.test.js index d21c22b50c..7e7f519084 100644 --- a/test/rest/presence.test.js +++ b/test/rest/presence.test.js @@ -106,21 +106,5 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async await Promise.all(tests); }); - - if (typeof Promise !== 'undefined') { - it('presencePromise', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); - var channel = rest.channels.get('some_channel'); - - channel.presence - .get() - .then(function () { - done(); - }) - ['catch'](function (err) { - done(err); - }); - }); - } }); }); From 844af87e202a0dd5456a70e575bf58ab678f1721 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:14:30 +0100 Subject: [PATCH 105/468] test: convert rest push tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/push.test.js | 467 ++++++++++++++--------------------------- 1 file changed, 154 insertions(+), 313 deletions(-) diff --git a/test/rest/push.test.js b/test/rest/push.test.js index ca75676c6b..44337ffc13 100644 --- a/test/rest/push.test.js +++ b/test/rest/push.test.js @@ -39,7 +39,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - it('Get subscriptions', function (done) { + it('Get subscriptions', async function () { var subscribes = []; var deletes = []; var subsByChannel = {}; @@ -51,58 +51,31 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } subsByChannel[sub.channel].push(sub); - var rest = helper.AblyRest({ clientId: sub.clientId }); - subscribes.push(function (callback) { - rest.push.admin.channelSubscriptions.save(sub, callback); - }); - deletes.push(function (callback) { - rest.push.admin.channelSubscriptions.remove(sub, callback); - }); + var rest = helper.AblyRestPromise({ clientId: sub.clientId }); + subscribes.push(() => rest.push.admin.channelSubscriptions.save(sub)); + deletes.push(() => rest.push.admin.channelSubscriptions.remove(sub)); })(i); } - var rest = helper.AblyRest(); - - async.series( - [ - function (callback) { - async.parallel(subscribes, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo1' }, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo2' }, callback); - }, - function (callback) { - async.parallel(deletes, callback); - }, - ], - function (err, result) { - if (err) { - done(err); - return; - } - try { - testIncludesUnordered(untyped(result[1].items), untyped(subsByChannel['pushenabled:foo1'])); - testIncludesUnordered(untyped(result[2].items), untyped(subsByChannel['pushenabled:foo2'])); - done(); - } catch (err) { - done(err); - } - } - ); + var rest = helper.AblyRestPromise(); + + await Promise.all(subscribes.map((sub) => sub())); + + var res1 = await rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo1' }); + var res2 = await rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo2' }); + + await Promise.all(deletes.map((del) => del())); + + testIncludesUnordered(untyped(res1.items), untyped(subsByChannel['pushenabled:foo1'])); + testIncludesUnordered(untyped(res2.items), untyped(subsByChannel['pushenabled:foo2'])); }); - it('Publish', function (done) { - var realtime = helper.AblyRealtime(); + it('Publish', async function () { + try { + var realtime = helper.AblyRealtimePromise(); - var channel = realtime.channels.get('pushenabled:foo'); - channel.attach(function (err) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } + var channel = realtime.channels.get('pushenabled:foo'); + await channel.attach(); var pushPayload = { notification: { title: 'Test message', body: 'Test message body' }, @@ -117,29 +90,34 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ablyUrl: baseUri, }; - channel.subscribe('__ably_push__', function (msg) { - var receivedPushPayload = JSON.parse(msg.data); - try { + var prom = new Promise(function (resolve, reject) { + channel.subscribe('__ably_push__', function (msg) { expect(receivedPushPayload.data).to.deep.equal(pushPayload.data); expect(receivedPushPayload.notification.title).to.deep.equal(pushPayload.notification.title); expect(receivedPushPayload.notification.body).to.deep.equal(pushPayload.notification.body); - closeAndFinish(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } + resolve(); + }); }); - realtime.push.admin.publish(pushRecipient, pushPayload, function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - }); - }); + await realtime.push.admin.publish(pushRecipient, pushPayload); + realtime.close(); + } catch (err) { + realtime.close(); + throw err; + } }); if (typeof Promise !== 'undefined') { +<<<<<<< HEAD it('Publish promise', function (done) { var realtime = helper.AblyRealtime({ internal: { promises: true } }); +||||||| parent of 8295a1a8 (test: convert rest push tests to Promise API) + it('Publish promise', function (done) { + var realtime = helper.AblyRealtime({ promises: true }); +======= + it('Publish promise', async function () { + var realtime = helper.AblyRealtime({ promises: true }); +>>>>>>> 8295a1a8 (test: convert rest push tests to Promise API) var channelName = 'pushenabled:publish_promise'; var channel = realtime.channels.get(channelName); channel.attach(function (err) { @@ -185,43 +163,21 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); } - it('deviceRegistrations save', function (done) { - var rest = helper.AblyRest(); - - async.series( - [ - function (callback) { - rest.push.admin.deviceRegistrations.save(testDevice, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.get(testDevice.id, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.remove(testDevice.id, callback); - }, - ], - function (err, result) { - if (err) { - done(err); - return; - } - var saved = result[0]; - var got = result[1]; - try { - expect(got.push.state).to.equal('ACTIVE'); - delete got.metadata; // Ignore these properties for testing - delete got.push.state; - testIncludesUnordered(untyped(got), testDevice_withoutSecret); - testIncludesUnordered(untyped(saved), testDevice_withoutSecret); - done(); - } catch (err) { - done(err); - } - } - ); + it('deviceRegistrations save', async function () { + var rest = helper.AblyRestPromise(); + + var saved = await rest.push.admin.deviceRegistrations.save(testDevice); + var got = await rest.push.admin.deviceRegistrations.get(testDevice.id); + await rest.push.admin.deviceRegistrations.remove(testDevice.id); + + expect(got.push.state).to.equal('ACTIVE'); + delete got.metadata; // Ignore these properties for testing + delete got.push.state; + testIncludesUnordered(untyped(got), testDevice_withoutSecret); + testIncludesUnordered(untyped(saved), testDevice_withoutSecret); }); - it('deviceRegistrations get and list', function (done) { + it('deviceRegistrations get and list', async function () { var registrations = []; var deletes = []; var devices = []; @@ -262,113 +218,65 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async devices.push(device); devices_withoutSecret.push(device_withoutSecret); - var rest = helper.AblyRest({ clientId: device.clientId }); - registrations.push(function (callback) { - rest.push.admin.deviceRegistrations.save(device, callback); + var rest = helper.AblyRestPromise({ clientId: device.clientId }); + registrations.push(function () { + return rest.push.admin.deviceRegistrations.save(device); }); - deletes.push(function (callback) { - rest.push.admin.deviceRegistrations.remove('device' + (i + 1), callback); + deletes.push(function () { + return rest.push.admin.deviceRegistrations.remove('device' + (i + 1)); }); })(i); } - var rest = helper.AblyRest(); - - async.series( - [ - function (callback) { - async.parallel(registrations, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.list(null, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.list({ clientId: 'testClient1' }, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.list({ clientId: 'testClient2' }, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.get(devices[0].id, callback); - }, - function (callback) { - async.parallel( - [ - function (callback) { - rest.push.admin.deviceRegistrations.removeWhere({ clientId: 'testClient1' }, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.removeWhere({ clientId: 'testClient2' }, callback); - }, - ], - callback - ); - }, - function (callback) { - async.parallel(deletes, callback); - }, - ], - function (err, result) { - if (err) { - done(err); - return; - } - try { - expect(numberOfDevices).to.equal(result[0].length); - testIncludesUnordered(untyped(result[1].items), untyped(devices_withoutSecret)); - testIncludesUnordered(untyped(result[2].items), untyped(devicesByClientId['testClient1'])); - testIncludesUnordered(untyped(result[3].items), untyped(devicesByClientId['testClient2'])); - testIncludesUnordered(untyped(result[4]), untyped(devices[0])); - done(); - } catch (err) { - done(err); - } - } - ); + var rest = helper.AblyRestPromise(); + + var res0 = await Promise.all(registrations.map((x) => x())); + var res1 = await rest.push.admin.deviceRegistrations.list(null); + var res2 = await rest.push.admin.deviceRegistrations.list({ clientId: 'testClient1' }); + var res3 = await rest.push.admin.deviceRegistrations.list({ clientId: 'testClient2' }); + var res4 = await rest.push.admin.deviceRegistrations.get(devices[0].id); + + await Promise.all([ + rest.push.admin.deviceRegistrations.removeWhere({ clientId: 'testClient1' }), + rest.push.admin.deviceRegistrations.removeWhere({ clientId: 'testClient2' }), + ]); + + await Promise.all(deletes.map((x) => x())); + + expect(numberOfDevices).to.equal(res0.length); + testIncludesUnordered(untyped(res1.items), untyped(devices_withoutSecret)); + testIncludesUnordered(untyped(res2.items), untyped(devicesByClientId['testClient1'])); + testIncludesUnordered(untyped(res3.items), untyped(devicesByClientId['testClient2'])); + testIncludesUnordered(untyped(res4), untyped(devices[0])); }); - it('deviceRegistrations remove removeWhere', function (done) { - var rest = helper.AblyRest(); - - async.series( - [ - function (callback) { - rest.push.admin.deviceRegistrations.save(testDevice, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.remove(testDevice.id, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.get(testDevice.id, function (err, result) { - expect(err && err.statusCode).to.equal(404, 'Check device reg not found after removal'); - callback(null); - }); - }, - function (callback) { - rest.push.admin.deviceRegistrations.save(testDevice, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.removeWhere({ deviceId: testDevice.id }, callback); - }, - function (callback) { - rest.push.admin.deviceRegistrations.get(testDevice.id, function (err, result) { - expect(err && err.statusCode).to.equal(404, 'Check device reg not found after removal'); - callback(null); - }); - }, - ], - function (err, result) { - if (err) { - done(err); - } - done(); - } - ); + it('deviceRegistrations remove removeWhere', async function () { + var rest = helper.AblyRestPromise(); + + await rest.push.admin.deviceRegistrations.save(testDevice); + await rest.push.admin.deviceRegistrations.remove(testDevice.id); + + try { + await rest.push.admin.deviceRegistrations.get(testDevice.id); + expect.fail('Expected push.admin.deviceRegistrations.get() to throw'); + } catch (err) { + expect(err.statusCode).to.equal(404, 'Check device reg not found after removal'); + } + + await rest.push.admin.deviceRegistrations.save(testDevice); + await rest.push.admin.deviceRegistrations.removeWhere({ deviceId: testDevice.id }); + + try { + await rest.push.admin.deviceRegistrations.get(testDevice.id); + expect.fail('Expected push.admin.deviceRegistrations.get() to throw'); + } catch (err) { + expect(err.statusCode).to.equal(404, 'Check device reg not found after removal'); + } }); if (typeof Promise !== undefined) { it('deviceRegistrations promise', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); + var rest = helper.AblyRestPromise({ internal: { promises: true } }); /* save */ rest.push.admin.deviceRegistrations @@ -404,43 +312,22 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); } - it('channelSubscriptions save', function (done) { - var rest = helper.AblyRest({ clientId: 'testClient' }); + it('channelSubscriptions save', async function () { + var rest = helper.AblyRestPromise({ clientId: 'testClient' }); var subscription = { clientId: 'testClient', channel: 'pushenabled:foo' }; - async.series( - [ - function (callback) { - rest.push.admin.channelSubscriptions.save(subscription, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo' }, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.remove(subscription, callback); - }, - ], - function (err, result) { - if (err) { - done(err); - return; - } - var saved = result[0]; - var sub = result[1].items[0]; - try { - expect(subscription.clientId).to.equal(saved.clientId); - expect(subscription.channel).to.equal(saved.channel); - expect(subscription.clientId).to.equal(sub.clientId); - expect(subscription.channel).to.equal(sub.channel); - done(); - } catch (err) { - done(err); - } - } - ); + var saved = await rest.push.admin.channelSubscriptions.save(subscription); + var result = await rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo' }); + var sub = result.items[0]; + await rest.push.admin.channelSubscriptions.remove(subscription); + + expect(subscription.clientId).to.equal(saved.clientId); + expect(subscription.channel).to.equal(saved.channel); + expect(subscription.clientId).to.equal(sub.clientId); + expect(subscription.channel).to.equal(sub.channel); }); - it('channelSubscriptions get', function (done) { + it('channelSubscriptions get', async function () { var subscribes = []; var deletes = []; var subsByChannel = {}; @@ -452,121 +339,75 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } subsByChannel[sub.channel].push(sub); - var rest = helper.AblyRest(); - subscribes.push(function (callback) { - rest.push.admin.channelSubscriptions.save(sub, callback); + var rest = helper.AblyRestPromise(); + subscribes.push(function () { + return rest.push.admin.channelSubscriptions.save(sub); }); - deletes.push(function (callback) { - rest.push.admin.channelSubscriptions.remove({ clientId: 'testClient' + i }, callback); + deletes.push(function () { + return rest.push.admin.channelSubscriptions.remove({ clientId: 'testClient' + i }); }); })(i); } - var rest = helper.AblyRest(); - - async.series( - [ - function (callback) { - async.parallel(subscribes, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo1' }, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo2' }, callback); - }, - function (callback) { - async.parallel(deletes, callback); - }, - ], - function (err, result) { - if (err) { - done(err); - return; - } - try { - testIncludesUnordered(untyped(result[1].items), untyped(subsByChannel['pushenabled:foo1'])); - testIncludesUnordered(untyped(result[2].items), untyped(subsByChannel['pushenabled:foo2'])); - done(); - } catch (err) { - done(err); - } - } - ); + var rest = helper.AblyRestPromise(); + + await Promise.all(subscribes.map((x) => x())); + + var res1 = await rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo1' }); + var res2 = await rest.push.admin.channelSubscriptions.list({ channel: 'pushenabled:foo2' }); + + await Promise.all(deletes.map((x) => x())); + + testIncludesUnordered(untyped(res1.items), untyped(subsByChannel['pushenabled:foo1'])); + testIncludesUnordered(untyped(res2.items), untyped(subsByChannel['pushenabled:foo2'])); }); - exports.push_channelSubscriptions_remove = function (test) { - var rest = helper.AblyRest({ clientId: 'testClient' }); + it('push_channelSubscriptions_remove', async function () { + var rest = helper.AblyRestPromise({ clientId: 'testClient' }); var subscription = { clientId: 'testClient', channel: 'pushenabled:foo' }; - async.series( - [ - function (callback) { - rest.push.admin.channelSubscriptions.save(subscription, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.remove(subscription, callback); - }, - ], - function (err, result) { - if (err) { - test.ok(false, err.message); - test.done(); - return; - } - test.done(); - } - ); - }; + await rest.push.admin.channelSubscriptions.save(subscription); + await rest.push.admin.channelSubscriptions.remove(subscription); + }); - it('channelSubscriptions listChannels', function (done) { + it('channelSubscriptions listChannels', async function () { var subscribes = []; var deletes = []; for (var i = 0; i < 5; i++) { (function (i) { var sub = { channel: 'pushenabled:listChannels' + ((i % 2) + 1), clientId: 'testClient' + ((i % 3) + 1) }; - var rest = helper.AblyRest({ clientId: sub.clientId }); + var rest = helper.AblyRestPromise({ clientId: sub.clientId }); subscribes.push(function (callback) { - rest.push.admin.channelSubscriptions.save(sub, callback); + return rest.push.admin.channelSubscriptions.save(sub); }); - deletes.push(function (callback) { - rest.push.admin.channelSubscriptions.remove(sub, callback); + deletes.push(function () { + return rest.push.admin.channelSubscriptions.remove(sub); }); })(i); } - var rest = helper.AblyRest(); - - async.series( - [ - function (callback) { - async.parallel(subscribes, callback); - }, - function (callback) { - rest.push.admin.channelSubscriptions.listChannels(null, callback); - }, - function (callback) { - async.parallel(deletes, callback); - }, - ], - function (err, result) { - if (err) { - done(err); - return; - } - try { - testIncludesUnordered(['pushenabled:listChannels1', 'pushenabled:listChannels2'], result[1].items); - done(); - } catch (err) { - done(err); - } - } - ); + var rest = helper.AblyRestPromise(); + + await Promise.all(subscribes.map((x) => x())); + + var result = await rest.push.admin.channelSubscriptions.listChannels(null); + + await Promise.all(deletes.map((x) => x())); + + testIncludesUnordered(['pushenabled:listChannels1', 'pushenabled:listChannels2'], result.items); }); if (typeof Promise !== 'undefined') { +<<<<<<< HEAD it('channelSubscriptions promise', function (done) { var rest = helper.AblyRest({ internal: { promises: true } }); +||||||| parent of 8295a1a8 (test: convert rest push tests to Promise API) + it('channelSubscriptions promise', function (done) { + var rest = helper.AblyRest({ promises: true }); +======= + it('channelSubscriptions promise', async function () { + var rest = helper.AblyRestPromise({ promises: true }); +>>>>>>> 8295a1a8 (test: convert rest push tests to Promise API) var channelId = 'pushenabled:channelsubscriptions_promise'; var subscription = { clientId: 'testClient', channel: channelId }; From 469229b06e1a040bc3989c9cd784631be22cf322 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:15:05 +0100 Subject: [PATCH 106/468] test: remove legacy rest push promise tests Co-authored-by: Lawrence Forooghian --- test/rest/push.test.js | 134 ----------------------------------------- 1 file changed, 134 deletions(-) diff --git a/test/rest/push.test.js b/test/rest/push.test.js index 44337ffc13..5bfe1eb4e0 100644 --- a/test/rest/push.test.js +++ b/test/rest/push.test.js @@ -107,62 +107,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } }); - if (typeof Promise !== 'undefined') { -<<<<<<< HEAD - it('Publish promise', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); -||||||| parent of 8295a1a8 (test: convert rest push tests to Promise API) - it('Publish promise', function (done) { - var realtime = helper.AblyRealtime({ promises: true }); -======= - it('Publish promise', async function () { - var realtime = helper.AblyRealtime({ promises: true }); ->>>>>>> 8295a1a8 (test: convert rest push tests to Promise API) - var channelName = 'pushenabled:publish_promise'; - var channel = realtime.channels.get(channelName); - channel.attach(function (err) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - - var pushPayload = { - notification: { title: 'Test message', body: 'Test message body' }, - data: { foo: 'bar' }, - }; - - var baseUri = realtime.baseUri(Ably.Rest.Platform.Defaults.getHost(realtime.options)); - var pushRecipient = { - transportType: 'ablyChannel', - channel: 'pushenabled:foo', - ablyKey: realtime.options.key, - ablyUrl: baseUri, - }; - - channel.subscribe('__ably_push__', function (msg) { - var receivedPushPayload = JSON.parse(msg.data); - try { - expect(receivedPushPayload.data).to.deep.equal(pushPayload.data); - expect(receivedPushPayload.notification.title).to.deep.equal(pushPayload.notification.title); - expect(receivedPushPayload.notification.body).to.deep.equal(pushPayload.notification.body); - closeAndFinish(done, realtime, err); - } catch (err) { - done(err); - } - }); - - realtime.push.admin - .publish(pushRecipient, pushPayload) - .then(function () { - closeAndFinish(done, realtime); - }) - ['catch'](function (err) { - closeAndFinish(done, realtime, err); - }); - }); - }); - } - it('deviceRegistrations save', async function () { var rest = helper.AblyRestPromise(); @@ -274,44 +218,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } }); - if (typeof Promise !== undefined) { - it('deviceRegistrations promise', function (done) { - var rest = helper.AblyRestPromise({ internal: { promises: true } }); - - /* save */ - rest.push.admin.deviceRegistrations - .save(testDevice) - .then(function (saved) { - expect(saved.push.state).to.equal('ACTIVE'); - testIncludesUnordered(untyped(saved), testDevice_withoutSecret); - /* get */ - return rest.push.admin.deviceRegistrations.get(testDevice.id); - }) - .then(function (got) { - expect(got.push.state).to.equal('ACTIVE'); - delete got.metadata; // Ignore these properties for testing - delete got.push.state; - testIncludesUnordered(untyped(got), testDevice_withoutSecret); - /* list */ - return rest.push.admin.deviceRegistrations.list({ clientId: testDevice.clientId }); - }) - .then(function (result) { - expect(result.items.length).to.equal(1); - var got = result.items[0]; - expect(got.push.state).to.equal('ACTIVE'); - testIncludesUnordered(untyped(got), testDevice_withoutSecret); - /* remove */ - return rest.push.admin.deviceRegistrations.removeWhere({ deviceId: testDevice.id }); - }) - .then(function () { - done(); - }) - ['catch'](function (err) { - done(err); - }); - }); - } - it('channelSubscriptions save', async function () { var rest = helper.AblyRestPromise({ clientId: 'testClient' }); var subscription = { clientId: 'testClient', channel: 'pushenabled:foo' }; @@ -397,46 +303,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testIncludesUnordered(['pushenabled:listChannels1', 'pushenabled:listChannels2'], result.items); }); - if (typeof Promise !== 'undefined') { -<<<<<<< HEAD - it('channelSubscriptions promise', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); -||||||| parent of 8295a1a8 (test: convert rest push tests to Promise API) - it('channelSubscriptions promise', function (done) { - var rest = helper.AblyRest({ promises: true }); -======= - it('channelSubscriptions promise', async function () { - var rest = helper.AblyRestPromise({ promises: true }); ->>>>>>> 8295a1a8 (test: convert rest push tests to Promise API) - var channelId = 'pushenabled:channelsubscriptions_promise'; - var subscription = { clientId: 'testClient', channel: channelId }; - - rest.push.admin.channelSubscriptions - .save(subscription) - .then(function (saved) { - expect(subscription.clientId).to.equal(saved.clientId); - expect(subscription.channel).to.equal(saved.channel); - return rest.push.admin.channelSubscriptions.list({ channel: channelId }); - }) - .then(function (result) { - var sub = result.items[0]; - expect(subscription.clientId).to.equal(sub.clientId); - expect(subscription.channel).to.equal(sub.channel); - return rest.push.admin.channelSubscriptions.listChannels(null); - }) - .then(function (result) { - expect(Utils.arrIn(result.items, channelId)).to.be.ok; - return rest.push.admin.channelSubscriptions.remove(subscription); - }) - .then(function () { - done(); - }) - ['catch'](function (err) { - done(err); - }); - }); - } - function untyped(x) { return JSON.parse(JSON.stringify(x)); } From 1e698e2e501694c34adaf6b6ee6cc23fc2eec2ea Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:21:52 +0100 Subject: [PATCH 107/468] test: convert rest request tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/request.test.js | 301 +++++++++++++------------------------- 1 file changed, 101 insertions(+), 200 deletions(-) diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 6f931e0c90..d122ed430e 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -5,7 +5,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var expect = chai.expect; var utils = helper.Utils; var echoServerHost = 'echo.ably.io'; - var restTestOnJsonMsgpack = helper.restTestOnJsonMsgpack; + var restTestOnJsonMsgpackAsync = helper.restTestOnJsonMsgpackAsync; var Defaults = Ably.Rest.Platform.Defaults; describe('rest/request', function () { @@ -22,7 +22,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - restTestOnJsonMsgpack('request_version', function (done, rest) { + restTestOnJsonMsgpackAsync('request_version', function (rest) { const version = 150; // arbitrarily chosen function testRequestHandler(_, __, ___, headers) { @@ -37,239 +37,140 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async rest.http.do = testRequestHandler; - rest.request('get', '/time' /* arbitrarily chosen */, version, null, null, null, function (err, res) {}); + rest.request('get', '/time' /* arbitrarily chosen */, version, null, null, null); }); - restTestOnJsonMsgpack('request_time', function (done, rest) { - rest.request('get', '/time', Defaults.protocolVersion, null, null, null, function (err, res) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(res.statusCode).to.equal(200, 'Check statusCode'); - expect(res.success).to.equal(true, 'Check success'); - expect(utils.isArray(res.items), true, 'Check array returned').to.be.ok; - expect(res.items.length).to.equal(1, 'Check array was of length 1'); - done(); - } catch (err) { - done(err); - } - }); + restTestOnJsonMsgpackAsync('request_time', async function (rest) { + const res = await rest.request('get', '/time', Defaults.protocolVersion, null, null, null); + expect(res.statusCode).to.equal(200, 'Check statusCode'); + expect(res.success).to.equal(true, 'Check success'); + expect(utils.isArray(res.items), true, 'Check array returned').to.be.ok; + expect(res.items.length).to.equal(1, 'Check array was of length 1'); }); - restTestOnJsonMsgpack('request_404', function (done, rest) { + restTestOnJsonMsgpackAsync('request_404', async function (rest) { /* NB: can't just use /invalid or something as the CORS preflight will * fail. Need something superficially a valid path but where the actual * request fails */ - rest.request( + const res = await rest.request( 'get', '/keys/ablyjs.test/requestToken', Defaults.protocolVersion, null, null, - null, - function (err, res) { - try { - expect(err).to.equal( - null, - 'Check that we do not get an error response for a failure that returns an actual ably error code' - ); - expect(res.success).to.equal(false, 'Check res.success is false for a failure'); - expect(res.statusCode).to.equal(404, 'Check HPR.statusCode is 404'); - expect(res.errorCode).to.equal(40400, 'Check HPR.errorCode is 40400'); - expect(res.errorMessage, 'Check have an HPR.errorMessage').to.be.ok; - done(); - } catch (err) { - done(err); - } - } + null ); + expect(res.success).to.equal(false, 'Check res.success is false for a failure'); + expect(res.statusCode).to.equal(404, 'Check HPR.statusCode is 404'); + expect(res.errorCode).to.equal(40400, 'Check HPR.errorCode is 40400'); + expect(res.errorMessage, 'Check have an HPR.errorMessage').to.be.ok; }); /* With a network issue, should get an actual err, not an HttpPaginatedResponse with error members */ - it('request_network_error', function (done) { - rest = helper.AblyRest({ restHost: helper.unroutableAddress }); - rest.request('get', '/time', Defaults.protocolVersion, null, null, null, function (err, res) { - try { - expect(err, 'Check get an err').to.be.ok; - expect(!res, 'Check do not get a res').to.be.ok; - done(); - } catch (err) { - done(err); - } - }); + it('request_network_error', async function () { + rest = helper.AblyRestPromise({ restHost: helper.unroutableAddress }); + try { + var res = await rest.request('get', '/time', Defaults.protocolVersion, null, null, null); + } catch (err) { + expect(err, 'Check get an err').to.be.ok; + expect(!res, 'Check do not get a res').to.be.ok; + return; + } + expect.fail('Expected rest.request to throw'); }); /* Use the request feature to publish, then retrieve (one at a time), some messages */ - restTestOnJsonMsgpack('request_post_get_messages', function (done, rest, channelName) { + restTestOnJsonMsgpackAsync('request_post_get_messages', async function (rest, channelName) { var channelPath = '/channels/' + channelName + '/messages', msgone = { name: 'faye', data: 'whittaker' }, msgtwo = { name: 'martin', data: 'reed' }; - async.waterfall( - [ - function (cb) { - rest.request('post', channelPath, Defaults.protocolVersion, null, msgone, null, function (err, res) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(res.statusCode).to.equal(201, 'Check statusCode is 201'); - expect(res.success).to.equal(true, 'Check post was a success'); - expect(res.items && res.items.length).to.equal(1, 'Check number of results is as expected'); - cb(); - } catch (err) { - cb(err); - } - }); - }, - function (cb) { - rest.request('post', channelPath, Defaults.protocolVersion, null, msgtwo, null, function (err, res) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(res.statusCode).to.equal(201, 'Check statusCode is 201'); - expect(res.items && res.items.length).to.equal(1, 'Check number of results is as expected'); - cb(); - } catch (err) { - cb(err); - } - }); - }, - function (cb) { - rest.request( - 'get', - channelPath, - Defaults.protocolVersion, - { limit: 1, direction: 'forwards' }, - null, - null, - function (err, res) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(res.statusCode).to.equal(200, 'Check statusCode is 200'); - expect(res.items.length).to.equal(1, 'Check only one msg returned'); - expect(res.items[0].name).to.equal(msgone.name, 'Check name is as expected'); - expect(res.items[0].data).to.equal(msgone.data, 'Check data is as expected'); - expect(res.hasNext, 'Check hasNext is true').to.be.ok; - cb(null, res.next); - } catch (err) { - cb(err); - } - } - ); - }, - function (next, cb) { - next(function (err, res) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(res.statusCode).to.equal(200, 'Check statusCode is 200'); - expect(res.success).to.equal(true, 'Check success'); - expect(res.items.length).to.equal(1, 'Check only one msg returned'); - expect(res.items[0].name).to.equal(msgtwo.name, 'Check name is as expected'); - expect(res.items[0].data).to.equal(msgtwo.data, 'Check data is as expected'); - cb(); - } catch (err) { - cb(err); - } - }); - }, - function (cb) { - /* Finally check the messages the 'normal' way to make sure everything's as expected */ - rest.channels.get(channelName).history(function (err, res) { - try { - expect(!err, err && helper.displayError(err)).to.be.ok; - expect(res.items.length).to.equal(2, 'Check both msgs returned'); - expect(res.items[0].name).to.equal(msgtwo.name, 'Check name is as expected'); - expect(res.items[0].data).to.equal(msgtwo.data, 'Check data is as expected'); - cb(); - } catch (err) { - cb(err); - } - }); - }, - ], - function (err) { - if (err) { - done(err); - return; - } - done(); - } + var res = await rest.request('post', channelPath, Defaults.protocolVersion, null, msgone, null); + expect(res.statusCode).to.equal(201, 'Check statusCode is 201'); + expect(res.success).to.equal(true, 'Check post was a success'); + expect(res.items && res.items.length).to.equal(1, 'Check number of results is as expected'); + + res = await rest.request('post', channelPath, Defaults.protocolVersion, null, msgtwo, null); + expect(res.statusCode).to.equal(201, 'Check statusCode is 201'); + expect(res.items && res.items.length).to.equal(1, 'Check number of results is as expected'); + + res = await rest.request( + 'get', + channelPath, + Defaults.protocolVersion, + { limit: 1, direction: 'forwards' }, + null, + null ); + expect(res.statusCode).to.equal(200, 'Check statusCode is 200'); + expect(res.items.length).to.equal(1, 'Check only one msg returned'); + expect(res.items[0].name).to.equal(msgone.name, 'Check name is as expected'); + expect(res.items[0].data).to.equal(msgone.data, 'Check data is as expected'); + expect(res.hasNext, 'Check hasNext is true').to.be.ok; + + res = await res.next(); + expect(res.statusCode).to.equal(200, 'Check statusCode is 200'); + expect(res.success).to.equal(true, 'Check success'); + expect(res.items.length).to.equal(1, 'Check only one msg returned'); + expect(res.items[0].name).to.equal(msgtwo.name, 'Check name is as expected'); + expect(res.items[0].data).to.equal(msgtwo.data, 'Check data is as expected'); + + /* Finally check the messages the 'normal' way to make sure everything's as expected */ + res = await rest.channels.get(channelName).history(); + expect(res.items.length).to.equal(2, 'Check both msgs returned'); + expect(res.items[0].name).to.equal(msgtwo.name, 'Check name is as expected'); + expect(res.items[0].data).to.equal(msgtwo.data, 'Check data is as expected'); }); - restTestOnJsonMsgpack('request_batch_api_success', function (done, rest, name) { + restTestOnJsonMsgpackAsync('request_batch_api_success', async function (rest, name) { var body = { channels: [name + '1', name + '2'], messages: { data: 'foo' } }; - rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}, function (err, res) { - try { - expect(err).to.equal(null, 'Check that we do not get an error response for a success'); - expect(res.success).to.equal(true, 'Check res.success is true for a success'); - expect(res.statusCode).to.equal(201, 'Check res.statusCode is 201 for a success'); - expect(res.errorCode).to.equal(null, 'Check res.errorCode is null for a success'); - expect(res.errorMessage).to.equal(null, 'Check res.errorMessage is null for a success'); - - expect( - !res.items[0].batchResponse, - 'Check no batchResponse, since items is now just a flat array of channel responses' - ).to.be.ok; - expect(res.items.length).to.equal(2, 'Verify batched response includes response for each channel'); - expect(!res.items[0].error, 'Verify channel1 response is not an error').to.be.ok; - expect(res.items[0].channel).to.equal(name + '1', 'Verify channel1 response includes correct channel'); - expect(!res.items[1].error, 'Verify channel2 response is not an error').to.be.ok; - expect(res.items[1].channel).to.equal(name + '2', 'Verify channel2 response includes correct channel'); - done(); - } catch (err) { - done(err); - } - }); + const res = await rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}); + expect(res.success).to.equal(true, 'Check res.success is true for a success'); + expect(res.statusCode).to.equal(201, 'Check res.statusCode is 201 for a success'); + expect(res.errorCode).to.equal(null, 'Check res.errorCode is null for a success'); + expect(res.errorMessage).to.equal(null, 'Check res.errorMessage is null for a success'); + + expect( + !res.items[0].batchResponse, + 'Check no batchResponse, since items is now just a flat array of channel responses' + ).to.be.ok; + expect(res.items.length).to.equal(2, 'Verify batched response includes response for each channel'); + expect(!res.items[0].error, 'Verify channel1 response is not an error').to.be.ok; + expect(res.items[0].channel).to.equal(name + '1', 'Verify channel1 response includes correct channel'); + expect(!res.items[1].error, 'Verify channel2 response is not an error').to.be.ok; + expect(res.items[1].channel).to.equal(name + '2', 'Verify channel2 response includes correct channel'); }); - restTestOnJsonMsgpack.skip('request_batch_api_partial_success', function (done, rest, name) { + restTestOnJsonMsgpackAsync.skip('request_batch_api_partial_success', async function (rest, name) { var body = { channels: [name, '[invalid', ''], messages: { data: 'foo' } }; - rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}, function (err, res) { - try { - expect(err).to.equal(null, 'Check that we do not get an error response for a partial success'); - expect(res.success).to.equal(false, 'Check res.success is false for a partial failure'); - expect(res.statusCode).to.equal(400, 'Check HPR.statusCode is 400 for a partial failure'); - expect(res.errorCode).to.equal(40020, 'Check HPR.errorCode is 40020 for a partial failure'); - expect(res.errorMessage, 'Check have an HPR.errorMessage').to.be.ok; - - var response = res.items[0]; - expect(response.error.code).to.equal(40020, 'Verify response has an errorCode'); - expect(response.batchResponse.length).to.equal( - 3, - 'Verify batched response includes response for each channel' - ); - expect(response.batchResponse[0].channel).to.equal(name, 'Verify channel1 response includes correct channel'); - expect(!response.batchResponse[0].error, 'Verify first channel response is not an error').to.be.ok; - expect(response.batchResponse[1].error.code).to.equal( - 40010, - 'Verify [invalid response includes an error with the right code' - ); - expect(response.batchResponse[2].error.code).to.equal( - 40010, - 'Verify empty channel response includes an error with the right code' - ); - done(); - } catch (err) { - done(err); - } - }); + var res = await rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}); + expect(res.success).to.equal(false, 'Check res.success is false for a partial failure'); + expect(res.statusCode).to.equal(400, 'Check HPR.statusCode is 400 for a partial failure'); + expect(res.errorCode).to.equal(40020, 'Check HPR.errorCode is 40020 for a partial failure'); + expect(res.errorMessage, 'Check have an HPR.errorMessage').to.be.ok; + + var response = res.items[0]; + expect(response.error.code).to.equal(40020, 'Verify response has an errorCode'); + expect(response.batchResponse.length).to.equal(3, 'Verify batched response includes response for each channel'); + expect(response.batchResponse[0].channel).to.equal(name, 'Verify channel1 response includes correct channel'); + expect(!response.batchResponse[0].error, 'Verify first channel response is not an error').to.be.ok; + expect(response.batchResponse[1].error.code).to.equal( + 40010, + 'Verify [invalid response includes an error with the right code' + ); + expect(response.batchResponse[2].error.code).to.equal( + 40010, + 'Verify empty channel response includes an error with the right code' + ); }); utils.arrForEach(['put', 'patch', 'delete'], function (method) { - it('check' + method, function (done) { - var restEcho = helper.AblyRest({ useBinaryProtocol: false, restHost: echoServerHost, tls: true }); - restEcho.request(method, '/methods', Defaults.protocolVersion, {}, {}, {}, function (err, res) { - if (err) { - done(err); - } else { - try { - expect(res.items[0] && res.items[0].method).to.equal(method); - done(); - } catch (err) { - done(err); - } - } - }); + it('check' + method, async function () { + var restEcho = helper.AblyRestPromise({ useBinaryProtocol: false, restHost: echoServerHost, tls: true }); + var res = await restEcho.request(method, '/methods', Defaults.protocolVersion, {}, {}, {}); + expect(res.items[0] && res.items[0].method).to.equal(method); }); }); From b69496369e8a577a242c192a65f5b30f23835166 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:22:17 +0100 Subject: [PATCH 108/468] test: remove legacy rest request promise test Co-authored-by: Lawrence Forooghian --- test/rest/request.test.js | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/test/rest/request.test.js b/test/rest/request.test.js index d122ed430e..8f1279f22d 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -17,7 +17,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } - rest = helper.AblyRest({ useBinaryProtocol: false }); + rest = helper.AblyRestPromise({ useBinaryProtocol: false }); done(); }); }); @@ -173,24 +173,5 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(res.items[0] && res.items[0].method).to.equal(method); }); }); - - if (typeof Promise !== 'undefined') { - it('request_promise', function (done) { - var client = helper.AblyRest({ internal: { promises: true } }); - - client - .request('get', '/time', Defaults.protocolVersion, null, null, null) - .then(function (res) { - expect(res.statusCode).to.equal(200, 'Check statusCode'); - expect(res.success).to.equal(true, 'Check success'); - expect(utils.isArray(res.items), true, 'Check array returned').to.be.ok; - expect(res.items.length).to.equal(1, 'Check array was of length 1'); - done(); - }) - ['catch'](function (err) { - done(err); - }); - }); - } }); }); From f8829974b49320b9d0bf597e90d8f2e759d473d0 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:30:45 +0100 Subject: [PATCH 109/468] test: convert rest stats tests to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/stats.test.js | 707 ++++++++++++++-------------------------- 1 file changed, 241 insertions(+), 466 deletions(-) diff --git a/test/rest/stats.test.js b/test/rest/stats.test.js index b9a3c04a26..d3e8b64b8e 100644 --- a/test/rest/stats.test.js +++ b/test/rest/stats.test.js @@ -64,7 +64,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { before(function (done) { // force a new app to be created with first argument true so that stats are not effected by other tests helper.setupApp(true, function () { - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); helper.createStats(helper.getTestApp(), statsFixtures, function (err) { if (err) { done(err); @@ -79,525 +79,300 @@ define(['shared_helper', 'chai'], function (helper, chai) { * Using an interval ID string format, check minute-level inbound and outbound stats match fixture data (forwards) * @spec : (RSC6b4) */ - it('appstats_minute0', function (done) { - rest.stats( - { - start: lastYear + '-02-03:15:03', - end: lastYear + '-02-03:15:05', - direction: 'forwards', - }, - function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length).to.equal(3, 'Verify 3 stat records found'); - - var totalInbound = 0, - totalOutbound = 0; - for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; - } - - expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); - expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); - done(); - } catch (err) { - done(err); - } - } - ); + it('appstats_minute0', async function () { + var page = await rest.stats({ + start: lastYear + '-02-03:15:03', + end: lastYear + '-02-03:15:05', + direction: 'forwards', + }); + var stats = page.items; + expect(stats.length).to.equal(3, 'Verify 3 stat records found'); + + var totalInbound = 0, + totalOutbound = 0; + for (var i = 0; i < stats.length; i++) { + totalInbound += stats[i].inbound.all.messages.count; + totalOutbound += stats[i].outbound.all.messages.count; + } + + expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); + expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); }); /** * Using milliseconds since epoch, check minute-level inbound and outbound stats match fixture data (forwards) * @spec : (RSC6b4) */ - it('appstats_minute1', function (done) { - rest.stats( - { - start: firstIntervalEpoch, - end: secondIntervalEpoch, - direction: 'forwards', - }, - function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length).to.equal(3, 'Verify 3 stat records found'); - - var totalInbound = 0, - totalOutbound = 0; - for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; - } - - expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); - expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); - done(); - } catch (err) { - done(err); - } - } - ); + it('appstats_minute1', async function () { + var page = await rest.stats({ + start: firstIntervalEpoch, + end: secondIntervalEpoch, + direction: 'forwards', + }); + var stats = page.items; + expect(stats.length).to.equal(3, 'Verify 3 stat records found'); + + var totalInbound = 0, + totalOutbound = 0; + for (var i = 0; i < stats.length; i++) { + totalInbound += stats[i].inbound.all.messages.count; + totalOutbound += stats[i].outbound.all.messages.count; + } + + expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); + expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); }); /** * Check hour-level inbound and outbound stats match fixture data (forwards) * @spec : (RSC6b4) */ - it('appstats_hour0', function (done) { - rest.stats( - { - start: lastYear + '-02-03:15', - end: lastYear + '-02-03:18', - direction: 'forwards', - by: 'hour', - }, - function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length).to.equal(1, 'Verify 1 stat record found'); - - var totalInbound = 0, - totalOutbound = 0; - for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; - } - - expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); - expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); - done(); - } catch (err) { - done(err); - } - } - ); + it('appstats_hour0', async function () { + var page = await rest.stats({ + start: lastYear + '-02-03:15', + end: lastYear + '-02-03:18', + direction: 'forwards', + by: 'hour', + }); + var stats = page.items; + expect(stats.length).to.equal(1, 'Verify 1 stat record found'); + + var totalInbound = 0, + totalOutbound = 0; + for (var i = 0; i < stats.length; i++) { + totalInbound += stats[i].inbound.all.messages.count; + totalOutbound += stats[i].outbound.all.messages.count; + } + + expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); + expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); }); /** * Check day-level stats exist (forwards) * @spec : (RSC6b4) */ - it.skip('appstats_day0', function (done) { - rest.stats( - { - end: lastYear + '-02-03', - direction: 'forwards', - by: 'day', - }, - function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; - - var totalInbound = 0, - totalOutbound = 0; - for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; - } - - expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); - expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); - done(); - } catch (err) { - done(err); - } - } - ); + it.skip('appstats_day0', async function () { + var page = await rest.stats({ + end: lastYear + '-02-03', + direction: 'forwards', + by: 'day', + }); + var stats = page.items; + expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; + + var totalInbound = 0, + totalOutbound = 0; + for (var i = 0; i < stats.length; i++) { + totalInbound += stats[i].inbound.all.messages.count; + totalOutbound += stats[i].outbound.all.messages.count; + } + + expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); + expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); }); /** * Check month-level stats exist (forwards) * @spec : (RSC6b4) */ - it.skip('appstats_month0', function (done) { - rest.stats( - { - end: lastYear + '-02', - direction: 'forwards', - by: 'month', - }, - function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; - - var totalInbound = 0, - totalOutbound = 0; - for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; - } - - expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); - expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); - done(); - } catch (err) { - done(err); - } - } - ); + it.skip('appstats_month0', async function () { + var page = await rest.stats({ + end: lastYear + '-02', + direction: 'forwards', + by: 'month', + }); + var stats = page.items; + expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; + + var totalInbound = 0, + totalOutbound = 0; + for (var i = 0; i < stats.length; i++) { + totalInbound += stats[i].inbound.all.messages.count; + totalOutbound += stats[i].outbound.all.messages.count; + } + + expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); + expect(totalOutbound).to.equal(20 + 10 + 40, 'Verify all outbound messages found'); }); /** * Check limit query param (backwards) * @spec : (RSC6b3) */ - it('appstats_limit_backwards', function (done) { - rest.stats( - { - end: lastYear + '-02-03:15:04', - direction: 'backwards', - limit: 1, - }, - function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; - - var totalInbound = 0, - totalOutbound = 0; - for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; - } - - expect(totalInbound).to.equal(60, 'Verify all inbound messages found'); - expect(totalOutbound).to.equal(10, 'Verify all outbound messages found'); - done(); - } catch (err) { - done(err); - } - } - ); + it('appstats_limit_backwards', async function () { + var page = await rest.stats({ + end: lastYear + '-02-03:15:04', + direction: 'backwards', + limit: 1, + }); + var stats = page.items; + expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; + + var totalInbound = 0, + totalOutbound = 0; + for (var i = 0; i < stats.length; i++) { + totalInbound += stats[i].inbound.all.messages.count; + totalOutbound += stats[i].outbound.all.messages.count; + } + + expect(totalInbound).to.equal(60, 'Verify all inbound messages found'); + expect(totalOutbound).to.equal(10, 'Verify all outbound messages found'); }); /** * Check limit query param (forwards) * @spec : (RSC6b3) */ - it('appstats_limit_forwards', function (done) { - rest.stats( - { - end: lastYear + '-02-03:15:04', - direction: 'forwards', - limit: 1, - }, - function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; - - var totalInbound = 0, - totalOutbound = 0; - for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; - } - - expect(totalInbound).to.equal(50, 'Verify all inbound messages found'); - expect(totalOutbound).to.equal(20, 'Verify all outbound messages found'); - done(); - } catch (err) { - done(err); - } - } - ); + it('appstats_limit_forwards', async function () { + var page = await rest.stats({ + end: lastYear + '-02-03:15:04', + direction: 'forwards', + limit: 1, + }); + var stats = page.items; + expect(stats.length == 1, 'Verify 1 stat records found').to.be.ok; + + var totalInbound = 0, + totalOutbound = 0; + for (var i = 0; i < stats.length; i++) { + totalInbound += stats[i].inbound.all.messages.count; + totalOutbound += stats[i].outbound.all.messages.count; + } + + expect(totalInbound).to.equal(50, 'Verify all inbound messages found'); + expect(totalOutbound).to.equal(20, 'Verify all outbound messages found'); }); /** * Check query pagination (backwards) * @spec : (RSC6b2) */ - it('appstats_pagination_backwards', function (done) { - rest.stats( - { - end: lastYear + '-02-03:15:05', - direction: 'backwards', - limit: 1, - }, - function (err, page) { - if (err) { - done(err); - return; - } - - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(7000, 'Verify all published message data found'); - - /* get next page */ - expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; - page.next(function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(6000, 'Verify all published message data found'); - - /* get next page */ - expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; - } catch (err) { - done(err); - return; - } - page.next(function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(5000, 'Verify all published message data found'); - - /* verify no further pages */ - expect(page.isLast(), 'Verify last page').to.be.ok; - } catch (err) { - done(err); - return; - } - - page.first(function (err, page) { - if (err) { - done(err); - return; - } - try { - var totalData = 0; - var stats = page.items; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(7000, 'Verify all published message data found'); - - /* that's it */ - done(); - } catch (err) { - done(err); - return; - } - }); - }); - }); - } - ); + it('appstats_pagination_backwards', async function () { + var page = await rest.stats({ + end: lastYear + '-02-03:15:05', + direction: 'backwards', + limit: 1, + }); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(7000, 'Verify all published message data found'); + + /* get next page */ + expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; + var page = await page.next(); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(6000, 'Verify all published message data found'); + + /* get next page */ + expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; + var page = await page.next(); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(5000, 'Verify all published message data found'); + + /* verify no further pages */ + expect(page.isLast(), 'Verify last page').to.be.ok; + + var page = await page.first(); + var totalData = 0; + var stats = page.items; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(7000, 'Verify all published message data found'); }); /** * Check query pagination (forwards) * @spec : (RSC6b2) */ - it('appstats_pagination_forwards', function (done) { - rest.stats( - { - end: lastYear + '-02-03:15:05', - direction: 'forwards', - limit: 1, - }, - function (err, page) { - if (err) { - done(err); - return; - } - - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(5000, 'Verify all published message data found'); - - /* get next page */ - expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; - } catch (err) { - done(err); - return; - } - page.next(function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(6000, 'Verify all published message data found'); - - /* get next page */ - expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; - } catch (err) { - done(err); - return; - } - page.next(function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(7000, 'Verify all published message data found'); - - /* verify no further pages */ - expect(page.isLast(), 'Verify last page').to.be.ok; - } catch (err) { - done(err); - return; - } - - page.first(function (err, page) { - if (err) { - done(err); - return; - } - try { - var totalData = 0; - var stats = page.items; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(5000, 'Verify all published message data found'); - - /* that's it */ - done(); - } catch (err) { - done(err); - } - }); - }); - }); - } - ); + it('appstats_pagination_forwards', async function () { + var page = await rest.stats({ + end: lastYear + '-02-03:15:05', + direction: 'forwards', + limit: 1, + }); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(5000, 'Verify all published message data found'); + + /* get next page */ + expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; + var page = await page.next(); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(6000, 'Verify all published message data found'); + + /* get next page */ + expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; + var page = await page.next(); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(7000, 'Verify all published message data found'); + + /* verify no further pages */ + expect(page.isLast(), 'Verify last page').to.be.ok; + + var page = await page.first(); + var totalData = 0; + var stats = page.items; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(5000, 'Verify all published message data found'); }); /** * Check query pagination omitted (defaults to backwards) * @spec : (RSC6b2) */ - it('appstats_pagination_omitted', function (done) { - rest.stats( - { - end: lastYear + '-02-03:15:05', - limit: 1, - }, - function (err, page) { - if (err) { - done(err); - return; - } - - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(7000, 'Verify all published message data found'); - - /* get next page */ - expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; - } catch (err) { - done(err); - return; - } - page.next(function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(6000, 'Verify all published message data found'); - - /* get next page */ - expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; - } catch (err) { - done(err); - return; - } - page.next(function (err, page) { - if (err) { - done(err); - return; - } - try { - var stats = page.items; - expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; - var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(5000, 'Verify all published message data found'); - - /* verify no further pages */ - expect(page.isLast(), 'Verify last page').to.be.ok; - } catch (err) { - done(err); - return; - } - - page.first(function (err, page) { - if (err) { - done(err); - return; - } - try { - var totalData = 0; - var stats = page.items; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; - expect(totalData).to.equal(7000, 'Verify all published message data found'); - - /* that's it */ - done(); - } catch (err) { - done(err); - } - }); - }); - }); - } - ); + it('appstats_pagination_omitted', async function () { + var page = await rest.stats({ + end: lastYear + '-02-03:15:05', + limit: 1, + }); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(7000, 'Verify all published message data found'); + + /* get next page */ + expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; + var page = await page.next(); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(6000, 'Verify all published message data found'); + + /* get next page */ + expect(page.hasNext(), 'Verify next page rel link present').to.be.ok; + var page = await page.next(); + var stats = page.items; + expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; + var totalData = 0; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(5000, 'Verify all published message data found'); + + /* verify no further pages */ + expect(page.isLast(), 'Verify last page').to.be.ok; + + var page = await page.first(); + var totalData = 0; + var stats = page.items; + for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + expect(totalData).to.equal(7000, 'Verify all published message data found'); }); if (typeof Promise !== 'undefined') { From 2448be717c3384605c65b110a9f1c762eeac29ef Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:31:04 +0100 Subject: [PATCH 110/468] test: remove legacy rest stats promise test Co-authored-by: Lawrence Forooghian --- test/rest/stats.test.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/rest/stats.test.js b/test/rest/stats.test.js index d3e8b64b8e..b733be0c45 100644 --- a/test/rest/stats.test.js +++ b/test/rest/stats.test.js @@ -374,21 +374,5 @@ define(['shared_helper', 'chai'], function (helper, chai) { for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; expect(totalData).to.equal(7000, 'Verify all published message data found'); }); - - if (typeof Promise !== 'undefined') { - it('stats_promise', function (done) { - var client = helper.AblyRest({ internal: { promises: true } }); - - client - .stats() - .then(function () { - console.log('here'); - done(); - }) - ['catch'](function (err) { - done(err); - }); - }); - } }); }); From 44808edb31ae0dc9b18e7cf46c89ecfa62115353 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:32:10 +0100 Subject: [PATCH 111/468] test: convert rest status test to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/status.test.js | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/test/rest/status.test.js b/test/rest/status.test.js index 91cc27b6e0..ef36d1f632 100644 --- a/test/rest/status.test.js +++ b/test/rest/status.test.js @@ -15,29 +15,23 @@ define(['shared_helper', 'chai'], function (helper, chai) { done(err); return; } - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); done(); }); }); - it('status0', function (done) { + it('status0', async function () { var channel = rest.channels.get('status0'); - channel.status(function (err, channelDetails) { - try { - expect(channelDetails.channelId).to.equal('status0'); - expect(channelDetails.status.isActive).to.be.a('boolean'); - var metrics = channelDetails.status.occupancy.metrics; - expect(metrics.connections).to.be.a('number'); - expect(metrics.presenceConnections).to.be.a('number'); - expect(metrics.presenceMembers).to.be.a('number'); - expect(metrics.presenceSubscribers).to.be.a('number'); - expect(metrics.publishers).to.be.a('number'); - expect(metrics.subscribers).to.be.a('number'); - done(); - } catch (err) { - done(err); - } - }); + var channelDetails = await channel.status(); + expect(channelDetails.channelId).to.equal('status0'); + expect(channelDetails.status.isActive).to.be.a('boolean'); + var metrics = channelDetails.status.occupancy.metrics; + expect(metrics.connections).to.be.a('number'); + expect(metrics.presenceConnections).to.be.a('number'); + expect(metrics.presenceMembers).to.be.a('number'); + expect(metrics.presenceSubscribers).to.be.a('number'); + expect(metrics.publishers).to.be.a('number'); + expect(metrics.subscribers).to.be.a('number'); }); if (typeof Promise !== 'undefined') { From e9081a30aca92168b86876a7aef15a21025abfc0 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:32:34 +0100 Subject: [PATCH 112/468] test: remove legacy rest status promise test Co-authored-by: Lawrence Forooghian --- test/rest/status.test.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/test/rest/status.test.js b/test/rest/status.test.js index ef36d1f632..91063551e0 100644 --- a/test/rest/status.test.js +++ b/test/rest/status.test.js @@ -33,33 +33,5 @@ define(['shared_helper', 'chai'], function (helper, chai) { expect(metrics.publishers).to.be.a('number'); expect(metrics.subscribers).to.be.a('number'); }); - - if (typeof Promise !== 'undefined') { - it('statusPromise', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); - var channel = rest.channels.get('statusPromise'); - channel - .status() - .then(function (channelDetails) { - try { - expect(channelDetails.channelId).to.equal('statusPromise'); - expect(channelDetails.status.isActive).to.be.a('boolean'); - var metrics = channelDetails.status.occupancy.metrics; - expect(metrics.connections).to.be.a('number'); - expect(metrics.presenceConnections).to.be.a('number'); - expect(metrics.presenceMembers).to.be.a('number'); - expect(metrics.presenceSubscribers).to.be.a('number'); - expect(metrics.publishers).to.be.a('number'); - expect(metrics.subscribers).to.be.a('number'); - done(); - } catch (err) { - done(err); - } - }) - ['catch'](function (err) { - done(err); - }); - }); - } }); }); From edcecfa4000b25cb46b93174730d8c69a13a80df Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:36:54 +0100 Subject: [PATCH 113/468] test: convert rest time test to Promise API Co-authored-by: Lawrence Forooghian --- test/rest/time.test.js | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/test/rest/time.test.js b/test/rest/time.test.js index f05025a1f0..73d9399557 100644 --- a/test/rest/time.test.js +++ b/test/rest/time.test.js @@ -12,28 +12,18 @@ define(['shared_helper', 'chai'], function (helper, chai) { done(err); return; } - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); done(); }); }); - it('time0', function (done) { - rest.time(function (err, serverTime) { - if (err) { - done(err); - return; - } - try { - var localFiveMinutesAgo = utils.now() - 5 * 60 * 1000; - expect( - serverTime > localFiveMinutesAgo, - 'Verify returned time matches current local time with 5 minute leeway for badly synced local clocks' - ).to.be.ok; - done(); - } catch (err) { - done(err); - } - }); + it('time0', async function () { + var serverTime = await rest.time(); + var localFiveMinutesAgo = utils.now() - 5 * 60 * 1000; + expect( + serverTime > localFiveMinutesAgo, + 'Verify returned time matches current local time with 5 minute leeway for badly synced local clocks' + ).to.be.ok; }); if (typeof Promise !== 'undefined') { From 6796bed1322646509fce8738276dc8affac5def7 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Thu, 27 Apr 2023 17:37:13 +0100 Subject: [PATCH 114/468] test: remove legacy rest time promise test Co-authored-by: Lawrence Forooghian --- test/rest/time.test.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/rest/time.test.js b/test/rest/time.test.js index 73d9399557..62fba2c66b 100644 --- a/test/rest/time.test.js +++ b/test/rest/time.test.js @@ -25,19 +25,5 @@ define(['shared_helper', 'chai'], function (helper, chai) { 'Verify returned time matches current local time with 5 minute leeway for badly synced local clocks' ).to.be.ok; }); - - if (typeof Promise !== 'undefined') { - it('timePromise', function (done) { - var rest = helper.AblyRest({ internal: { promises: true } }); - rest - .time() - .then(function () { - done(); - }) - ['catch'](function (err) { - done(err); - }); - }); - } }); }); From 17d2aaa66112ae0202635edfb013f984665d49d6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 20 Jun 2023 14:26:03 -0300 Subject: [PATCH 115/468] Remove test of Ably.Rest.Callbacks --- test/realtime/api.test.js | 2 -- test/rest/api.test.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/test/realtime/api.test.js b/test/realtime/api.test.js index 1a4dae77be..36f3dd080b 100644 --- a/test/realtime/api.test.js +++ b/test/realtime/api.test.js @@ -7,8 +7,6 @@ define(['ably', 'chai'], function (Ably, chai) { it('Client constructors', function () { expect(typeof Ably.Realtime).to.equal('function'); expect(typeof Ably.Realtime.Promise).to.equal('function'); - expect(typeof Ably.Realtime.Callbacks).to.equal('function'); - expect(Ably.Realtime.Callbacks).to.equal(Ably.Realtime); }); it('Crypto', function () { diff --git a/test/rest/api.test.js b/test/rest/api.test.js index dd3610c982..02950ca883 100644 --- a/test/rest/api.test.js +++ b/test/rest/api.test.js @@ -7,8 +7,6 @@ define(['ably', 'chai'], function (Ably, chai) { it('Client constructors', function () { expect(typeof Ably.Rest).to.equal('function'); expect(typeof Ably.Rest.Promise).to.equal('function'); - expect(typeof Ably.Rest.Callbacks).to.equal('function'); - expect(Ably.Rest.Callbacks).to.equal(Ably.Rest); }); it('Crypto', function () { From a36fe51833917dc40a3e3c1364b5cd2f01c99369 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 20 Jun 2023 12:00:24 -0300 Subject: [PATCH 116/468] Remove "Async" from name of restTestOnJsonMsgpackAsync --- test/common/modules/shared_helper.js | 19 ++----------------- test/rest/history.test.js | 10 +++++----- test/rest/request.test.js | 14 +++++++------- 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index d9a6b7216e..bbb47e4907 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -149,20 +149,6 @@ define([ }; function restTestOnJsonMsgpack(name, testFn, skip) { - var itFn = skip ? it.skip : it; - itFn(name + ' with binary protocol', function (done) { - testFn(done, new clientModule.AblyRest({ useBinaryProtocol: true }), name + '_binary'); - }); - itFn(name + ' with text protocol', function (done) { - testFn(done, new clientModule.AblyRest({ useBinaryProtocol: false }), name + '_text'); - }); - } - - restTestOnJsonMsgpack.skip = function (name, testFn) { - restTestOnJsonMsgpack(name, testFn, true); - }; - - function restTestOnJsonMsgpackAsync(name, testFn, skip) { var itFn = skip ? it.skip : it; itFn(name + ' with binary protocol', async function () { await testFn(new clientModule.AblyRestPromise({ useBinaryProtocol: true }), name + '_binary'); @@ -172,8 +158,8 @@ define([ }); } - restTestOnJsonMsgpackAsync.skip = function (name, testFn) { - restTestOnJsonMsgpackAsync(name, testFn, true); + restTestOnJsonMsgpack.skip = function (name, testFn) { + restTestOnJsonMsgpack(name, testFn, true); }; function clearTransportPreference() { @@ -242,7 +228,6 @@ define([ becomeSuspended: becomeSuspended, testOnAllTransports: testOnAllTransports, restTestOnJsonMsgpack: restTestOnJsonMsgpack, - restTestOnJsonMsgpackAsync: restTestOnJsonMsgpackAsync, availableTransports: availableTransports, bestTransport: bestTransport, clearTransportPreference: clearTransportPreference, diff --git a/test/rest/history.test.js b/test/rest/history.test.js index a4d7420ee5..98eab3e5ab 100644 --- a/test/rest/history.test.js +++ b/test/rest/history.test.js @@ -4,7 +4,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var rest; var expect = chai.expect; var exports = {}; - var restTestOnJsonMsgpackAsync = helper.restTestOnJsonMsgpackAsync; + var restTestOnJsonMsgpack = helper.restTestOnJsonMsgpack; var utils = helper.Utils; var testMessages = [ { name: 'event0', data: 'some data' }, @@ -27,7 +27,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); }); - restTestOnJsonMsgpackAsync('history_simple', async function (rest, channelName) { + restTestOnJsonMsgpack('history_simple', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); /* first, send a number of events to this channel */ @@ -53,7 +53,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { ); }); - restTestOnJsonMsgpackAsync('history_multiple', async function (rest, channelName) { + restTestOnJsonMsgpack('history_multiple', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); /* first, send a number of events to this channel */ @@ -76,7 +76,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { ); }); - restTestOnJsonMsgpackAsync('history_simple_paginated_b', async function (rest, channelName) { + restTestOnJsonMsgpack('history_simple_paginated_b', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); /* first, send a number of events to this channel */ @@ -225,7 +225,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { ); }); - restTestOnJsonMsgpackAsync('history_encoding_errors', async function (rest, channelName) { + restTestOnJsonMsgpack('history_encoding_errors', async function (rest, channelName) { var testchannel = rest.channels.get('persisted:' + channelName); var badMessage = { name: 'jsonUtf8string', encoding: 'json/utf-8', data: '{"foo":"bar"}' }; testchannel.publish(badMessage); diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 8f1279f22d..844d7f75cd 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -5,7 +5,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var expect = chai.expect; var utils = helper.Utils; var echoServerHost = 'echo.ably.io'; - var restTestOnJsonMsgpackAsync = helper.restTestOnJsonMsgpackAsync; + var restTestOnJsonMsgpack = helper.restTestOnJsonMsgpack; var Defaults = Ably.Rest.Platform.Defaults; describe('rest/request', function () { @@ -22,7 +22,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - restTestOnJsonMsgpackAsync('request_version', function (rest) { + restTestOnJsonMsgpack('request_version', function (rest) { const version = 150; // arbitrarily chosen function testRequestHandler(_, __, ___, headers) { @@ -40,7 +40,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async rest.request('get', '/time' /* arbitrarily chosen */, version, null, null, null); }); - restTestOnJsonMsgpackAsync('request_time', async function (rest) { + restTestOnJsonMsgpack('request_time', async function (rest) { const res = await rest.request('get', '/time', Defaults.protocolVersion, null, null, null); expect(res.statusCode).to.equal(200, 'Check statusCode'); expect(res.success).to.equal(true, 'Check success'); @@ -48,7 +48,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(res.items.length).to.equal(1, 'Check array was of length 1'); }); - restTestOnJsonMsgpackAsync('request_404', async function (rest) { + restTestOnJsonMsgpack('request_404', async function (rest) { /* NB: can't just use /invalid or something as the CORS preflight will * fail. Need something superficially a valid path but where the actual * request fails */ @@ -80,7 +80,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); /* Use the request feature to publish, then retrieve (one at a time), some messages */ - restTestOnJsonMsgpackAsync('request_post_get_messages', async function (rest, channelName) { + restTestOnJsonMsgpack('request_post_get_messages', async function (rest, channelName) { var channelPath = '/channels/' + channelName + '/messages', msgone = { name: 'faye', data: 'whittaker' }, msgtwo = { name: 'martin', data: 'reed' }; @@ -122,7 +122,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(res.items[0].data).to.equal(msgtwo.data, 'Check data is as expected'); }); - restTestOnJsonMsgpackAsync('request_batch_api_success', async function (rest, name) { + restTestOnJsonMsgpack('request_batch_api_success', async function (rest, name) { var body = { channels: [name + '1', name + '2'], messages: { data: 'foo' } }; const res = await rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}); @@ -142,7 +142,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(res.items[1].channel).to.equal(name + '2', 'Verify channel2 response includes correct channel'); }); - restTestOnJsonMsgpackAsync.skip('request_batch_api_partial_success', async function (rest, name) { + restTestOnJsonMsgpack.skip('request_batch_api_partial_success', async function (rest, name) { var body = { channels: [name, '[invalid', ''], messages: { data: 'foo' } }; var res = await rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}); From a1c35929b9539cad1c7e574a4d029078496545a8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 20 Jun 2023 16:56:05 -0300 Subject: [PATCH 117/468] Remove parts of test relating to setOptions calling its callback immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As part of #1213, I’ll be updating this test to use the Promise-based API, to which the concept of "calls the callback before returning" can’t be detected, since the JavaScript engine always calls Promise callbacks asynchronously. --- test/realtime/channel.test.js | 40 ----------------------------------- 1 file changed, 40 deletions(-) diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index 13993e6f54..5e463a9eae 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -686,23 +686,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.series( [ - function (cb) { - var setOptionsReturned = false; - channel.setOptions( - { - params: params, - modes: modes, - }, - function () { - expect( - !setOptionsReturned, - 'setOptions failed to call back immediately, when no reattach is required' - ).to.be.ok; - cb(); - } - ); - setOptionsReturned = true; - }, function (cb) { channel.attach(cb); }, @@ -715,7 +698,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelUpdated = true; }); - var setOptionsReturned = false; channel.setOptions( { params: params, @@ -724,10 +706,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Wait a tick so we don' depend on whether the update event runs the * channelUpdated listener or the setOptions listener first */ Ably.Realtime.Platform.Config.nextTick(function () { - expect( - setOptionsReturned, - 'setOptions should return immediately and call back after the reattach' - ).to.be.ok; expect( channelUpdated, 'Check channel went to the server to update the channel params' @@ -736,7 +714,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); } ); - setOptionsReturned = true; }, function (cb) { var channelUpdated = false; @@ -744,34 +721,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelUpdated = true; }); - var setOptionsReturned = false; channel.setOptions( { modes: modes, }, function () { Ably.Realtime.Platform.Config.nextTick(function () { - expect( - setOptionsReturned, - 'setOptions should return immediately and call back after the reattach' - ).to.be.ok; expect(channelUpdated, 'Check channel went to the server to update the channel mode').to.be.ok; cb(); }); } ); - setOptionsReturned = true; - }, - function (cb) { - var setOptionsReturned = false; - channel.setOptions({}, function () { - expect( - !setOptionsReturned, - 'setOptions failed to call back immediately, when no reattach is required' - ).to.be.ok; - cb(); - }); - setOptionsReturned = true; }, ], function (err) { From 050b95b370478c7e0aab96472efc0363a35a9a44 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 20 Jun 2023 17:46:09 -0300 Subject: [PATCH 118/468] Prepare realtime channel publish disallowed test for conversion to Promise API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we switch to using the Promise-based API, we’ll need to wait for the result of `publish`, so let’s put the supporting code in place for that. --- test/realtime/message.test.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index 03143bac53..3cd4f663e5 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -440,22 +440,25 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - /* publish events */ - var restChannel = rest.channels.get('publishDisallowed'); - for (var i = 0; i < testArguments.length; i++) { - try { - restChannel.publish.apply(restChannel, testArguments[i]); - closeAndFinish(done, realtime, new Error('Exception was not raised')); - } catch (err) { + (async function () { + /* publish events */ + var restChannel = rest.channels.get('publishDisallowed'); + for (var i = 0; i < testArguments.length; i++) { try { - expect(err.code).to.equal(40013, 'Invalid data type exception raised'); + restChannel.publish.apply(restChannel, testArguments[i]); + closeAndFinish(done, realtime, new Error('Exception was not raised')); } catch (err) { - closeAndFinish(done, realtime, err); - return; + try { + expect(err.code).to.equal(40013, 'Invalid data type exception raised'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } } } - } - closeAndFinish(done, realtime); + })().then(() => { + closeAndFinish(done, realtime); + }); }); }); monitorConnection(done, realtime); From 43a2d1d91d71c84595476e072c44b76729ee93a1 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 14 Jun 2023 14:21:22 -0300 Subject: [PATCH 119/468] =?UTF-8?q?Update=20Realtime=20tests=20to=20use=20?= =?UTF-8?q?SDK=E2=80=99s=20Promise-based=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’ve chosen to do this with the lightest touch possible — that is, maintaining the tests’ callback-based approach and simply bridging the SDK’s Promise-based API back to callbacks. I did this for the sake of reviewability and not accidentally changing the behaviour of the tests in some subtle way that I’d then have to put time into understanding. It would be good to, at some point, update the structure of the tests to use `async` / `await`, to improve readability and to make them reflect how the users actually interact with the Promise-based API in the real world. I’ve split out into the separate task #1348. Note that I haven’t made any changes to the calls to EventEmitter’s `once` or `whenState` methods — we’re going to keep the callback-based versions of those methods, since there is no Promise equivalent of being able to turn them off using `off`. Resolves #1213. --- test/browser/connection.test.js | 32 +-- test/browser/http.test.js | 7 +- test/browser/simple.test.js | 7 +- test/common/modules/shared_helper.js | 14 ++ test/realtime/auth.test.js | 249 ++++++++++---------- test/realtime/channel.test.js | 183 +++++++-------- test/realtime/connection.test.js | 25 +- test/realtime/connectivity.test.js | 17 +- test/realtime/crypto.test.js | 43 ++-- test/realtime/delta.test.js | 23 +- test/realtime/encoding.test.js | 46 ++-- test/realtime/event_emitter.test.js | 34 +-- test/realtime/failure.test.js | 41 ++-- test/realtime/history.test.js | 15 +- test/realtime/init.test.js | 47 ++-- test/realtime/message.test.js | 157 +++++++------ test/realtime/presence.test.js | 330 ++++++++++++++------------- test/realtime/reauth.test.js | 17 +- test/realtime/resume.test.js | 51 +++-- test/realtime/sync.test.js | 35 +-- test/realtime/upgrade.test.js | 43 ++-- 21 files changed, 734 insertions(+), 682 deletions(-) diff --git a/test/browser/connection.test.js b/test/browser/connection.test.js index d615c133c8..3ae2abdba3 100644 --- a/test/browser/connection.test.js +++ b/test/browser/connection.test.js @@ -51,7 +51,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('device_going_offline_causes_disconnected_state', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), connection = realtime.connection, offlineEvent = new Event('offline', { bubbles: true }); @@ -94,7 +94,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { it('device_going_online_causes_disconnected_connection_to_reconnect_immediately', function (done) { /* Give up trying to connect fairly quickly */ - var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 1000 }), + var realtime = helper.AblyRealtimePromise({ realtimeRequestTimeout: 1000 }), connection = realtime.connection, onlineEvent = new Event('online', { bubbles: true }); @@ -139,7 +139,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { it('device_going_online_causes_suspended_connection_to_reconnect_immediately', function (done) { /* move to suspended state after 2s of being disconnected */ - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ disconnectedRetryTimeout: 500, realtimeRequestTimeout: 500, connectionStateTtl: 2000, @@ -183,7 +183,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('device_going_online_causes_connecting_connection_to_retry_attempt', function (done) { - var realtime = helper.AblyRealtime({}), + var realtime = helper.AblyRealtimePromise({}), connection = realtime.connection, onlineEvent = new Event('online', { bubbles: true }), oldTransport, @@ -223,7 +223,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { cb(true); }, }, - realtime = helper.AblyRealtime(realtimeOpts), + realtime = helper.AblyRealtimePromise(realtimeOpts), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -242,7 +242,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { return; } - var newRealtime = helper.AblyRealtime(realtimeOpts); + var newRealtime = helper.AblyRealtimePromise(realtimeOpts); newRealtime.connection.once('connected', function () { try { expect( @@ -264,7 +264,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { cb(false); }, }; - var realtime = helper.AblyRealtime(realtimeOpts), + var realtime = helper.AblyRealtimePromise(realtimeOpts), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -283,7 +283,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { return; } - var newRealtime = helper.AblyRealtime(realtimeOpts); + var newRealtime = helper.AblyRealtimePromise(realtimeOpts); newRealtime.connection.once('connected', function () { try { expect( @@ -301,7 +301,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('page_refresh_with_close_on_unload', function (done) { - var realtime = helper.AblyRealtime({ closeOnUnload: true }), + var realtime = helper.AblyRealtimePromise({ closeOnUnload: true }), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -321,7 +321,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('page_refresh_with_manual_recovery', function (done) { - var realtime = helper.AblyRealtime({ closeOnUnload: false }), + var realtime = helper.AblyRealtimePromise({ closeOnUnload: false }), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -342,7 +342,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { return; } - var newRealtime = helper.AblyRealtime({ recover: recoveryKey }); + var newRealtime = helper.AblyRealtimePromise({ recover: recoveryKey }); newRealtime.connection.once('connected', function () { try { expect( @@ -359,7 +359,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('persist_preferred_transport', function (done) { - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); realtime.connection.connectionManager.on(function (transport) { if (this.event === 'transport.active' && transport.shortName === 'web_socket') { @@ -381,7 +381,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var transportPreferenceName = 'ably-transport-preference'; window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'web_socket' })); - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); realtime.connection.connectionManager.on(function (transport) { if (this.event === 'transport.active') { @@ -400,7 +400,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { it('use_persisted_transport1', function (done) { window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'xhr_streaming' })); - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); realtime.connection.connectionManager.on(function (transport) { if (this.event === 'transport.active') { @@ -417,7 +417,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('browser_transports', function (done) { - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); try { expect(realtime.connection.connectionManager.baseTransport).to.equal('xhr_polling'); expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal([ @@ -437,7 +437,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { * realtime) */ it('connection behaviour with a proxy through which streaming is broken', function (done) { - const realtime = helper.AblyRealtime({ + const realtime = helper.AblyRealtimePromise({ transportParams: { simulateNoStreamingProxy: true, maxStreamDuration: 7500, diff --git a/test/browser/http.test.js b/test/browser/http.test.js index dfb93c9032..8c810f04b0 100644 --- a/test/browser/http.test.js +++ b/test/browser/http.test.js @@ -3,6 +3,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var rest; var expect = chai.expect; + var whenPromiseSettles = helper.whenPromiseSettles; describe('rest/http/fetch', function () { this.timeout(60 * 1000); @@ -11,7 +12,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { initialXhrSupported = Ably.Rest.Platform.Config.xhrSupported; Ably.Rest.Platform.Config.xhrSupported = false; helper.setupApp(function () { - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); done(); }); }); @@ -33,7 +34,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('Should succeed in using fetch to publish a message', function (done) { const channel = rest.channels.get('http_test_channel'); - channel.publish('test', 'Testing fetch support', (err) => { + whenPromiseSettles(channel.publish('test', 'Testing fetch support'), (err) => { expect(err).to.not.exist; done(); }); @@ -41,7 +42,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('Should pass errors correctly', function (done) { const channel = rest.channels.get(''); - channel.publish('test', 'Invalid message', (err) => { + whenPromiseSettles(channel.publish('test', 'Invalid message'), (err) => { expect(err).to.exist; done(); }); diff --git a/test/browser/simple.test.js b/test/browser/simple.test.js index 18ea08d6b7..e4ca140ec1 100644 --- a/test/browser/simple.test.js +++ b/test/browser/simple.test.js @@ -2,6 +2,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var expect = chai.expect; + var whenPromiseSettles = helper.whenPromiseSettles; describe('browser/simple', function () { this.timeout(60 * 1000); @@ -22,7 +23,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { function realtimeConnection(transports) { var options = {}; if (transports) options.transports = transports; - return helper.AblyRealtime(options); + return helper.AblyRealtimePromise(options); } function failWithin(timeInSeconds, done, ably, description) { @@ -71,7 +72,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { ably.connection.on('connected', function () { connectionTimeout.stop(); heartbeatTimeout = failWithin(25, done, ably, 'wait for heartbeat'); - ably.connection.ping(function (err) { + whenPromiseSettles(ably.connection.ping(), function (err) { heartbeatTimeout.stop(); done(err); ably.close(); @@ -115,7 +116,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { receiveMessagesTimeout = failWithin(15, done, ably, 'wait for published messages to be received'); timer = setInterval(function () { - channel.publish('event0', 'Hello world at: ' + new Date(), function (err) { + whenPromiseSettles(channel.publish('event0', 'Hello world at: ' + new Date()), function (err) { sentCbCount++; checkFinish(); }); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index bbb47e4907..8af3164629 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -57,6 +57,19 @@ define([ }); } + /** + * Uses a callback to communicate the result of a `Promise`. The first argument passed to the callback will be either an error (when the promise is rejected) or `null` (when the promise is fulfilled). In the case where the promise is fulfilled, the resulting value will be passed to the callback as a second argument. + */ + function whenPromiseSettles(promise, callback) { + promise + .then((result) => { + callback(null, result); + }) + .catch((err) => { + callback(err); + }); + } + function simulateDroppedConnection(realtime) { // Go into the 'disconnected' state before actually disconnecting the transports // to avoid the instantaneous reconnect attempt that would be triggered in @@ -237,5 +250,6 @@ define([ unroutableAddress: unroutableAddress, arrFind: arrFind, arrFilter: arrFilter, + whenPromiseSettles: whenPromiseSettles, }); }); diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index bfb6604154..b36ca70159 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -15,6 +15,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var http = new Ably.Realtime.Platform.Http(); var jwtTestChannelName = 'JWT_test' + String(Math.floor(Math.random() * 10000) + 1); var echoServer = 'https://echo.ably.io'; + var whenPromiseSettles = helper.whenPromiseSettles; /* * Helper function to fetch JWT tokens from the echo server @@ -39,14 +40,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var rest = helper.AblyRest({ queryTime: true }); - rest.time(function (err, time) { + var rest = helper.AblyRestPromise({ queryTime: true }); + whenPromiseSettles(rest.time(), function (err, time) { if (err) { done(err); return; } else { currentTime = time; - rest.auth.requestToken({}, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken({}), function (err, tokenDetails) { try { expect(!err, err && displayError(err)).to.be.ok; done(); @@ -63,8 +64,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Base token generation case */ it('authbase0', function (done) { - var realtime = helper.AblyRealtime(); - realtime.auth.requestToken(function (err, tokenDetails) { + var realtime = helper.AblyRealtimePromise(); + whenPromiseSettles(realtime.auth.requestToken(), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -87,8 +88,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_json', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken(null, null, function (err, tokenDetails) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -96,7 +97,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authPath = echoServer + '/?type=json&body=' + encodeURIComponent(JSON.stringify(tokenDetails)); - realtime = helper.AblyRealtime({ authUrl: authPath }); + realtime = helper.AblyRealtimePromise({ authUrl: authPath }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -112,8 +113,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_post_json', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken(null, null, function (err, tokenDetails) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -121,7 +122,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authUrl = echoServer + '/?type=json&'; - realtime = helper.AblyRealtime({ authUrl: authUrl, authMethod: 'POST', authParams: tokenDetails }); + realtime = helper.AblyRealtimePromise({ authUrl: authUrl, authMethod: 'POST', authParams: tokenDetails }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -137,8 +138,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_plainText', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken(null, null, function (err, tokenDetails) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -146,7 +147,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authPath = echoServer + '/?type=text&body=' + tokenDetails['token']; - realtime = helper.AblyRealtime({ authUrl: authPath }); + realtime = helper.AblyRealtimePromise({ authUrl: authPath }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -162,9 +163,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthCallback_tokenRequestResponse', function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var authCallback = function (tokenParams, callback) { - rest.auth.createTokenRequest(tokenParams, null, function (err, tokenRequest) { + whenPromiseSettles(rest.auth.createTokenRequest(tokenParams, null), function (err, tokenRequest) { if (err) { closeAndFinish(done, realtime, err); return; @@ -178,7 +179,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtime({ authCallback: authCallback }); + realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -199,10 +200,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthCallback_tokenDetailsResponse', function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var clientId = 'test clientid'; var authCallback = function (tokenParams, callback) { - rest.auth.requestToken(tokenParams, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -217,7 +218,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtime({ authCallback: authCallback, clientId: clientId }); + realtime = helper.AblyRealtimePromise({ authCallback: authCallback, clientId: clientId }); realtime.connection.on('connected', function () { try { @@ -236,9 +237,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthCallback_tokenStringResponse', function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var authCallback = function (tokenParams, callback) { - rest.auth.requestToken(tokenParams, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -252,7 +253,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtime({ authCallback: authCallback }); + realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -274,8 +275,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_mixed_authParams_qsParams', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.createTokenRequest(null, null, function (err, tokenRequest) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.createTokenRequest(null, null), function (err, tokenRequest) { if (err) { closeAndFinish(done, realtime, err); return; @@ -295,7 +296,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; var authPath = echoServer + '/qs_to_body' + utils.toQueryString(lowerPrecedenceTokenRequestParts); - realtime = helper.AblyRealtime({ authUrl: authPath, authParams: higherPrecedenceTokenRequestParts }); + realtime = helper.AblyRealtimePromise({ authUrl: authPath, authParams: higherPrecedenceTokenRequestParts }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -309,10 +310,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and check that the connection inherits the clientId from the tokenDetails */ it('auth_clientid_inheritance', function (done) { - var rest = helper.AblyRest(), + var rest = helper.AblyRestPromise(), testClientId = 'testClientId'; var authCallback = function (tokenParams, callback) { - rest.auth.requestToken({ clientId: testClientId }, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; @@ -321,7 +322,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -349,13 +350,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance2', function (done) { var clientRealtime, testClientId = 'test client id'; - var rest = helper.AblyRest(); - rest.auth.requestToken({ clientId: testClientId }, function (err, tokenDetails) { + var rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; } - clientRealtime = helper.AblyRealtime({ token: tokenDetails, clientId: 'WRONG' }); + clientRealtime = helper.AblyRealtimePromise({ token: tokenDetails, clientId: 'WRONG' }); clientRealtime.connection.once('failed', function (stateChange) { try { expect(stateChange.reason.code).to.equal(40102); @@ -375,13 +376,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance3', function (done) { var realtime, testClientId = 'test client id'; - var rest = helper.AblyRest(); - rest.auth.requestToken({ clientId: '*' }, function (err, tokenDetails) { + var rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken({ clientId: '*' }), function (err, tokenDetails) { if (err) { done(err); return; } - realtime = helper.AblyRealtime({ token: tokenDetails.token, clientId: 'test client id' }); + realtime = helper.AblyRealtimePromise({ token: tokenDetails.token, clientId: 'test client id' }); realtime.connection.on('connected', function () { try { expect(realtime.auth.clientId).to.equal(testClientId); @@ -403,13 +404,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance4', function (done) { var realtime, testClientId = 'test client id'; - var rest = helper.AblyRest(); - rest.auth.requestToken({ clientId: '*' }, function (err, tokenDetails) { + var rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken({ clientId: '*' }), function (err, tokenDetails) { if (err) { done(err); return; } - realtime = helper.AblyRealtime({ token: tokenDetails, clientId: 'test client id' }); + realtime = helper.AblyRealtimePromise({ token: tokenDetails, clientId: 'test client id' }); realtime.connection.on('connected', function () { try { expect(realtime.auth.clientId).to.equal(testClientId); @@ -431,13 +432,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance5', function (done) { var clientRealtime, testClientId = 'test client id'; - var rest = helper.AblyRest(); - rest.auth.requestToken({ clientId: testClientId }, function (err, tokenDetails) { + var rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; } - clientRealtime = helper.AblyRealtime({ token: tokenDetails.token }); + clientRealtime = helper.AblyRealtimePromise({ token: tokenDetails.token }); clientRealtime.connection.on('connected', function () { try { expect(clientRealtime.auth.clientId).to.equal(testClientId); @@ -456,7 +457,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ function authCallback_failures(realtimeOptions, expectFailure) { return function (done) { - var realtime = helper.AblyRealtime(realtimeOptions); + var realtime = helper.AblyRealtimePromise(realtimeOptions); realtime.connection.on(function (stateChange) { if (stateChange.previous !== 'initialized') { try { @@ -606,8 +607,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('authUrl_403_previously_active', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken(null, null, function (err, tokenDetails) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -615,13 +616,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authPath = echoServer + '/?type=json&body=' + encodeURIComponent(JSON.stringify(tokenDetails)); - realtime = helper.AblyRealtime({ authUrl: authPath }); + realtime = helper.AblyRealtimePromise({ authUrl: authPath }); realtime.connection.on('connected', function () { /* replace the authUrl and reauth */ - realtime.auth.authorize( - null, - { authUrl: echoServer + '/respondwith?status=403' }, + whenPromiseSettles( + realtime.auth.authorize(null, { authUrl: echoServer + '/respondwith?status=403' }), function (err, tokenDetails) { try { expect(err && err.statusCode).to.equal(403, 'Check err statusCode'); @@ -649,14 +649,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_token_expires', function (realtimeOpts) { return function (done) { var clientRealtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); - rest.auth.requestToken({ ttl: 5000 }, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken({ ttl: 5000 }, null), function (err, tokenDetails) { if (err) { done(err); return; } - clientRealtime = helper.AblyRealtime(mixin(realtimeOpts, { tokenDetails: tokenDetails, queryTime: true })); + clientRealtime = helper.AblyRealtimePromise( + mixin(realtimeOpts, { tokenDetails: tokenDetails, queryTime: true }) + ); clientRealtime.connection.on('failed', function () { closeAndFinish(done, clientRealtime, new Error('Failed to connect before token expired')); @@ -683,7 +685,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and all subsequent requests use the time offset */ it('auth_query_time_once', function (done) { - var rest = helper.AblyRest({ queryTime: true }), + var rest = helper.AblyRestPromise({ queryTime: true }), timeRequestCount = 0, originalTime = rest.time; @@ -706,7 +708,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var asyncFns = []; for (var i = 0; i < 10; i++) { asyncFns.push(function (callback) { - rest.auth.createTokenRequest({}, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.createTokenRequest({}, null), function (err, tokenDetails) { if (err) { return callback(err); } @@ -741,11 +743,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_tokenDetails_expiry_with_authcallback', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var clientId = 'test clientid'; var authCallback = function (tokenParams, callback) { tokenParams.ttl = 5000; - rest.auth.requestToken(tokenParams, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -754,7 +756,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); + realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); monitorConnection(done, realtime); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { @@ -782,11 +784,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_token_string_expiry_with_authcallback', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var clientId = 'test clientid'; var authCallback = function (tokenParams, callback) { tokenParams.ttl = 5000; - rest.auth.requestToken(tokenParams, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -795,7 +797,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); + realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); monitorConnection(done, realtime); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { @@ -822,35 +824,40 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_token_string_expiry_with_token', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var clientId = 'test clientid'; - rest.auth.requestToken({ ttl: 5000, clientId: clientId }, null, function (err, tokenDetails) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - realtime = helper.AblyRealtime(mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId })); - realtime.connection.once('connected', function () { - realtime.connection.once('disconnected', function (stateChange) { - try { - expect(stateChange.reason.code).to.equal(40142, 'Verify correct disconnect code'); - } catch (err) { - done(err); - return; - } - realtime.connection.once('failed', function (stateChange) { - /* Library has no way to generate a new token, so should fail */ + whenPromiseSettles( + rest.auth.requestToken({ ttl: 5000, clientId: clientId }, null), + function (err, tokenDetails) { + if (err) { + closeAndFinish(done, realtime, err); + return; + } + realtime = helper.AblyRealtimePromise( + mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId }) + ); + realtime.connection.once('connected', function () { + realtime.connection.once('disconnected', function (stateChange) { try { - expect(stateChange.reason.code).to.equal(40171, 'Verify correct cause failure code'); - realtime.close(); - done(); + expect(stateChange.reason.code).to.equal(40142, 'Verify correct disconnect code'); } catch (err) { done(err); + return; } + realtime.connection.once('failed', function (stateChange) { + /* Library has no way to generate a new token, so should fail */ + try { + expect(stateChange.reason.code).to.equal(40171, 'Verify correct cause failure code'); + realtime.close(); + done(); + } catch (err) { + done(err); + } + }); }); }); - }); - }); + } + ); }; }); @@ -860,15 +867,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_expired_token_string', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var clientId = 'test clientid'; - rest.auth.requestToken({ ttl: 1, clientId: clientId }, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken({ ttl: 1, clientId: clientId }, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; } setTimeout(function () { - realtime = helper.AblyRealtime(mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId })); + realtime = helper.AblyRealtimePromise( + mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId }) + ); realtime.connection.once('failed', function (stateChange) { try { expect(stateChange.reason.code).to.equal(40171, 'Verify correct failure code'); @@ -899,13 +908,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports.skip('reauth_authCallback', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); var firstTime = true; var authCallback = function (tokenParams, callback) { tokenParams.clientId = '*'; tokenParams.capability = firstTime ? { wrong: ['*'] } : { right: ['*'] }; firstTime = false; - rest.auth.requestToken(tokenParams, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -914,10 +923,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback })); + realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { authCallback: authCallback })); realtime.connection.once('connected', function () { var channel = realtime.channels.get('right'); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { try { expect(err, 'Check using first token, without channel attach capability').to.be.ok; expect(err.code).to.equal(40160, 'Check expected error code'); @@ -927,14 +936,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } /* soon after connected, reauth */ - realtime.auth.authorize(null, null, function (err) { + whenPromiseSettles(realtime.auth.authorize(null, null), function (err) { try { expect(!err, err && displayError(err)).to.be.ok; } catch (err) { done(err); return; } - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { try { expect(!err, 'Check using second token, with channel attach capability').to.be.ok; closeAndFinish(done, realtime); @@ -951,7 +960,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RSA10j */ it('authorize_updates_stored_details', function (done) { - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ autoConnect: false, defaultTokenParams: { version: 1 }, token: '1', @@ -982,10 +991,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Inject a fake AUTH message from realtime, check that we reauth and send our own in reply */ it('mocked_reauth', function (done) { - var rest = helper.AblyRest(), + var rest = helper.AblyRestPromise(), authCallback = function (tokenParams, callback) { // Request a token (should happen twice) - rest.auth.requestToken(tokenParams, null, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); return; @@ -993,7 +1002,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async callback(null, tokenDetails); }); }, - realtime = helper.AblyRealtime({ authCallback: authCallback, transports: [helper.bestTransport] }); + realtime = helper.AblyRealtimePromise({ authCallback: authCallback, transports: [helper.bestTransport] }); realtime.connection.once('connected', function () { var transport = realtime.connection.connectionManager.activeProtocol.transport, @@ -1029,7 +1038,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -1062,7 +1071,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -1093,10 +1102,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.once('connected', function () { var channel = realtime.channels.get(jwtTestChannelName); - channel.publish('greeting', 'Hello World!', function (err) { + whenPromiseSettles(channel.publish('greeting', 'Hello World!'), function (err) { try { expect(err.code).to.equal(40160, 'Verify publish denied code'); expect(err.statusCode).to.equal(401, 'Verify publish denied status code'); @@ -1122,7 +1131,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var publishEvent = 'publishEvent', messageData = 'Hello World!'; - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.once('connected', function () { var channel = realtime.channels.get(jwtTestChannelName); channel.subscribe(publishEvent, function (msg) { @@ -1149,7 +1158,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { try { @@ -1176,7 +1185,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.once('connected', function () { var originalToken = realtime.auth.tokenDetails.token; realtime.connection.once('update', function () { @@ -1203,7 +1212,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } - var realtime = helper.AblyRealtime({ token: token }); + var realtime = helper.AblyRealtimePromise({ token: token }); realtime.connection.once('connected', function () { try { expect(token).to.equal(realtime.auth.tokenDetails.token, 'Verify that token is the same'); @@ -1219,8 +1228,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RTN14b */ it('reauth_consistently_expired_token', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken({ ttl: 1 }, function (err, token) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken({ ttl: 1 }), function (err, token) { if (err) { done(err); return; @@ -1232,7 +1241,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; /* Wait a few ms to ensure token is expired */ setTimeout(function () { - realtime = helper.AblyRealtime({ authCallback: authCallback, disconnectedRetryTimeout: 15000 }); + realtime = helper.AblyRealtimePromise({ authCallback: authCallback, disconnectedRetryTimeout: 15000 }); /* Wait 5s, expect to have seen two attempts to get a token -- so the * authCallback called twice -- and the connection to now be sitting in * the disconnected state */ @@ -1252,8 +1261,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RSA4b1 - only autoremove expired tokens if have a server time offset set */ it('expired_token_no_autoremove_when_dont_have_servertime', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken(function (err, token) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken(), function (err, token) { if (err) { done(err); return; @@ -1265,7 +1274,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async authCallbackCallCount++; callback(null, token); }; - realtime = helper.AblyRealtime({ authCallback: authCallback }); + realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { expect(authCallbackCallCount).to.equal(1, 'Check we did not autoremove an expired token ourselves'); @@ -1280,8 +1289,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RSA4b1 second case */ it('expired_token_autoremove_when_have_servertime', function (done) { var realtime, - rest = helper.AblyRest(); - rest.auth.requestToken(function (err, token) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken(), function (err, token) { if (err) { done(err); return; @@ -1293,9 +1302,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async authCallbackCallCount++; callback(null, token); }; - realtime = helper.AblyRealtime({ authCallback: authCallback, autoConnect: false }); + realtime = helper.AblyRealtimePromise({ authCallback: authCallback, autoConnect: false }); /* Set the server time offset */ - realtime.time(function () { + whenPromiseSettles(realtime.time(), function () { realtime.connect(); realtime.connection.on('connected', function () { try { @@ -1314,38 +1323,38 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Check that only the last authorize matters */ it('multiple_concurrent_authorize', function (done) { - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ logLevel: 4, useTokenAuth: true, defaultTokenParams: { capability: { wrong: ['*'] } }, }); realtime.connection.once('connected', function () { - realtime.auth.authorize({ capability: { stillWrong: ['*'] } }, function (err) { + whenPromiseSettles(realtime.auth.authorize({ capability: { stillWrong: ['*'] } }), function (err) { try { expect(!err, 'Check first authorize cb was called').to.be.ok; } catch (err) { done(err); } }); - realtime.auth.authorize({ capability: { alsoNope: ['*'] } }, function (err) { + whenPromiseSettles(realtime.auth.authorize({ capability: { alsoNope: ['*'] } }), function (err) { try { expect(!err, 'Check second authorize cb was called').to.be.ok; } catch (err) { done(err); } }); - realtime.auth.authorize({ capability: { wtfAreYouThinking: ['*'] } }, function (err) { + whenPromiseSettles(realtime.auth.authorize({ capability: { wtfAreYouThinking: ['*'] } }), function (err) { try { expect(!err, 'Check third authorize one cb was called').to.be.ok; } catch (err) { done(err); } }); - realtime.auth.authorize({ capability: { right: ['*'] } }, function (err) { + whenPromiseSettles(realtime.auth.authorize({ capability: { right: ['*'] } }), function (err) { if (err) { closeAndFinish(done, realtime, err); } - realtime.channels.get('right').attach(function (err) { + whenPromiseSettles(realtime.channels.get('right').attach(), function (err) { try { expect(!err, (err && displayError(err)) || 'Successfully attached').to.be.ok; closeAndFinish(done, realtime); @@ -1359,7 +1368,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('authorize_immediately_after_init', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ useTokenAuth: true, defaultTokenParams: { capability: { wrong: ['*'] } }, }); @@ -1368,7 +1377,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); }); realtime.connection.once('connected', function () { - realtime.channels.get('right').attach(function (err) { + whenPromiseSettles(realtime.channels.get('right').attach(), function (err) { try { expect(!err, (err && displayError(err)) || 'Successfully attached').to.be.ok; closeAndFinish(done, realtime); diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index 5e463a9eae..9447865aa6 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -9,6 +9,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var monitorConnection = helper.monitorConnection; var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; var testOnAllTransports = helper.testOnAllTransports; + var whenPromiseSettles = helper.whenPromiseSettles; /* Helpers */ function randomString() { @@ -28,7 +29,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async callback(); }); - testChannel.publish(eventName, null, function (err) { + whenPromiseSettles(testChannel.publish(eventName, null), function (err) { if (received) return; if (err) callback(err); timeout = setTimeout(function () { @@ -52,7 +53,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async callback('checkCantSubscribe: unexpectedly received message'); }); - testChannel.publish(eventName, null, function (err) { + whenPromiseSettles(testChannel.publish(eventName, null), function (err) { if (received) return; if (err) callback(err); timeout = setTimeout(function () { @@ -65,13 +66,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function checkCanPublish(channel) { return function (callback) { - channel.publish(null, null, callback); + whenPromiseSettles(channel.publish(null, null), callback); }; } function checkCantPublish(channel) { return function (callback) { - channel.publish(null, null, function (err) { + whenPromiseSettles(channel.publish(null, null), function (err) { if (err && err.code === 40160) { callback(); } else { @@ -84,7 +85,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function checkCanEnterPresence(channel) { return function (callback) { var clientId = randomString(); - channel.presence.enterClient(clientId, null, function (err) { + whenPromiseSettles(channel.presence.enterClient(clientId, null), function (err) { channel.presence.leaveClient(clientId); callback(err); }); @@ -93,7 +94,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function checkCantEnterPresence(channel) { return function (callback) { - channel.presence.enterClient(randomString(), null, function (err) { + whenPromiseSettles(channel.presence.enterClient(randomString(), null), function (err) { if (err && err.code === 40160) { callback(); } else { @@ -117,7 +118,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async callback(); }); - testChannel.presence.enterClient(clientId, null, function (err) { + whenPromiseSettles(testChannel.presence.enterClient(clientId, null), function (err) { if (received) return; if (err) callback(err); timeout = setTimeout(function () { @@ -143,7 +144,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async callback('checkCantPresenceSubscribe: unexpectedly received message'); }); - testChannel.presence.enterClient(clientId, null, function (err) { + whenPromiseSettles(testChannel.presence.enterClient(clientId, null), function (err) { if (received) return; if (err) callback(err); timeout = setTimeout(function () { @@ -176,7 +177,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelinit0', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { try { /* set options on init */ @@ -209,10 +210,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach0', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var channel0 = realtime.channels.get('channelattach0'); - channel0.attach(function (err) { + whenPromiseSettles(channel0.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); } @@ -232,9 +233,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach2', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); var channel2 = realtime.channels.get('channelattach2'); - channel2.attach(function (err) { + whenPromiseSettles(channel2.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -256,14 +257,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var channel0 = realtime.channels.get('channelattach3'); - channel0.attach(function (err) { + whenPromiseSettles(channel0.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); } - channel0.detach(function (err) { + whenPromiseSettles(channel0.detach(), function (err) { if (err) { closeAndFinish(done, realtime, err); } @@ -294,10 +295,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattachempty', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.once('connected', function () { var channel0 = realtime.channels.get(''); - channel0.attach(function (err) { + whenPromiseSettles(channel0.attach(), function (err) { if (err) { setTimeout(function () { try { @@ -326,10 +327,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattachinvalid', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.once('connected', function () { var channel = realtime.channels.get(':hell'); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { try { expect(channel.errorReason.code).to.equal(40010, 'Attach error was set as the channel errorReason'); @@ -364,9 +365,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('publish_no_attach', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.once('connected', function () { - realtime.channels.get('publish_no_attach').publish(function (err) { + whenPromiseSettles(realtime.channels.get('publish_no_attach').publish(), function (err) { if (err) { closeAndFinish(done, realtime, new Error('Unexpected attach failure: ' + helper.displayError(err))); return; @@ -387,9 +388,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach_publish_invalid', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.once('connected', function () { - realtime.channels.get(':hell').publish(function (err) { + whenPromiseSettles(realtime.channels.get(':hell').publish(), function (err) { if (err) { try { expect(err.code).to.equal(40010, 'correct error code'); @@ -416,12 +417,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach_invalid_twice', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.once('connected', function () { - realtime.channels.get(':hell').attach(function (err) { + whenPromiseSettles(realtime.channels.get(':hell').attach(), function (err) { if (err) { /* attempt second attach */ - realtime.channels.get(':hell').attach(function (err) { + whenPromiseSettles(realtime.channels.get(':hell').attach(), function (err) { if (err) { setTimeout(function () { try { @@ -452,11 +453,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channelattachOnceOrIfAfter', function (done) { try { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), channel = realtime.channels.get('channelattachOnceOrIf'), firedImmediately = false; - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { channel.whenState('attached', function () { firedImmediately = true; }); @@ -478,7 +479,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channelattachOnceOrIfBefore', function (done) { try { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), channel = realtime.channels.get('channelattachOnceOrIf'), firedImmediately = false; @@ -503,7 +504,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsBasicChannelsGet'; try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -513,7 +514,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async params: params, }; var channel = realtime.channels.get(testName, channelOptions); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -527,7 +528,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtime(); + var testRealtime = helper.AblyRealtimePromise(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -556,7 +557,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsBasicSetOptions'; try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -567,7 +568,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; var channel = realtime.channels.get(testName); channel.setOptions(channelOptions); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -576,7 +577,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(channel.params).to.deep.equal(params, 'Check result params'); expect(channel.modes).to.deep.equal(['subscribe'], 'Check result modes'); - var testRealtime = helper.AblyRealtime(); + var testRealtime = helper.AblyRealtimePromise(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -605,7 +606,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'subscribeAfterSetOptions'; try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var channel = realtime.channels.get(testName); channel.setOptions({ @@ -634,7 +635,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('channelGetShouldThrowWhenWouldCauseReattach', function (done) { var testName = 'channelGetShouldThrowWhenWouldCauseReattach'; try { - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -643,7 +644,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channel = realtime.channels.get(testName, { params: params, }); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -675,7 +676,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'setOptionsCallbackBehaviour'; try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -698,10 +699,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelUpdated = true; }); - channel.setOptions( - { + whenPromiseSettles( + channel.setOptions({ params: params, - }, + }), function () { /* Wait a tick so we don' depend on whether the update event runs the * channelUpdated listener or the setOptions listener first */ @@ -721,10 +722,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelUpdated = true; }); - channel.setOptions( - { + whenPromiseSettles( + channel.setOptions({ modes: modes, - }, + }), function () { Ably.Realtime.Platform.Config.nextTick(function () { expect(channelUpdated, 'Check channel went to the server to update the channel mode').to.be.ok; @@ -751,7 +752,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsModesAndChannelModes'; try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var paramsModes = ['presence', 'subscribe']; var params = { @@ -762,7 +763,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async modes: ['publish', 'presence_subscribe'], }; var channel = realtime.channels.get(testName, channelOptions); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -776,7 +777,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtime(); + var testRealtime = helper.AblyRealtimePromise(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -805,14 +806,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelModes'; try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var modes = ['publish', 'presence_subscribe']; var channelOptions = { modes: modes, }; var channel = realtime.channels.get(testName, channelOptions); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -825,7 +826,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtime(); + var testRealtime = helper.AblyRealtimePromise(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -854,7 +855,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsDeltaAndModes'; try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.on('connected', function () { var modes = ['publish', 'subscribe', 'presence_subscribe']; var channelOptions = { @@ -862,7 +863,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async params: { delta: 'vcdiff' }, }; var channel = realtime.channels.get(testName, channelOptions); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -876,7 +877,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtime(); + var testRealtime = helper.AblyRealtimePromise(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -905,13 +906,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var testName = 'attachWithInvalidChannelParams'; var defaultChannelModes = 'presence,publish,subscribe,presence_subscribe'; try { - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); realtime.connection.on('connected', function () { var channel = realtime.channels.get(testName); async.series( [ function (cb) { - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { cb(err); }); }, @@ -919,7 +920,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelOptions = { modes: 'subscribe', }; - channel.setOptions(channelOptions, function (err) { + whenPromiseSettles(channel.setOptions(channelOptions), function (err) { expect(err.code).to.equal(40000, 'Check channelOptions validation error code'); expect(err.statusCode).to.equal(400, 'Check channelOptions validation error statusCode'); expect(channel.modes).to.deep.equal( @@ -933,7 +934,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelOptions = { modes: [1, 'subscribe'], }; - channel.setOptions(channelOptions, function (err) { + whenPromiseSettles(channel.setOptions(channelOptions), function (err) { expect(err.code).to.equal(40000, 'Check channelOptions validation error code'); expect(err.statusCode).to.equal(400, 'Check channelOptions validation error statusCode'); expect(channel.modes).to.deep.equal( @@ -947,7 +948,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelOptions = { params: 'test', }; - channel.setOptions(channelOptions, function (err) { + whenPromiseSettles(channel.setOptions(channelOptions), function (err) { expect(err.code).to.equal(40000, 'Check channelOptions validation error code'); expect(err.statusCode).to.equal(400, 'Check channelOptions validation error statusCode'); expect(channel.params).to.deep.equal({}, 'Check channel options params'); @@ -959,7 +960,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelOptions = { params: { nonexistent: 'foo' }, }; - channel.setOptions(channelOptions, function () { + whenPromiseSettles(channel.setOptions(channelOptions), function () { expect(channel.params).to.deep.equal({}, 'Check channel params'); cb(); }); @@ -968,7 +969,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelOptions = { modes: undefined, }; - channel.setOptions(channelOptions, function (err) { + whenPromiseSettles(channel.setOptions(channelOptions), function (err) { expect(err.code).to.equal(40000, 'Check channelOptions validation error code'); expect(err.statusCode).to.equal(400, 'Check channelOptions validation error statusCode'); expect(channel.params).to.deep.equal({}, 'Check channel options params result'); @@ -983,7 +984,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelOptions = { modes: ['susribe'], }; - channel.setOptions(channelOptions, function (err) { + whenPromiseSettles(channel.setOptions(channelOptions), function (err) { expect(err.code).to.equal(40000, 'Check channelOptions validation error code'); expect(err.statusCode).to.equal(400, 'Check channelOptions validation error statusCode'); expect(channel.params).to.deep.equal({}, 'Check channel options params result'); @@ -1011,10 +1012,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channelsubscribe0', function (done) { try { - var realtime = helper.AblyRealtime({ useBinaryProtocol: true }); + var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: true }); realtime.connection.on('connected', function () { var channel6 = realtime.channels.get('channelsubscribe0'); - channel6.attach(function (err) { + whenPromiseSettles(channel6.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -1047,28 +1048,28 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var messagesReceived = 0; try { - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); var channelByEvent, channelByListener, channelAll; var unsubscribeTest = function () { channelByEvent.unsubscribe('event', listenerByEvent); channelByListener.unsubscribe(listenerNoEvent); channelAll.unsubscribe(); - channelByEvent.publish('event', 'data', function (err) { + whenPromiseSettles(channelByEvent.publish('event', 'data'), function (err) { try { expect(!err, 'Error publishing single event: ' + err).to.be.ok; } catch (err) { closeAndFinish(done, realtime, err); return; } - channelByListener.publish(null, 'data', function (err) { + whenPromiseSettles(channelByListener.publish(null, 'data'), function (err) { try { expect(!err, 'Error publishing any event: ' + err).to.be.ok; } catch (err) { closeAndFinish(done, realtime, err); return; } - channelAll.publish(null, 'data', function (err) { + whenPromiseSettles(channelAll.publish(null, 'data'), function (err) { try { expect(!err, 'Error publishing any event: ' + err).to.be.ok; expect(messagesReceived).to.equal(3, 'Only three messages should be received by the listeners'); @@ -1122,7 +1123,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * immediate reattach. If that fails, it should go into suspended */ it('server_sent_detached', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached', channel = realtime.channels.get(channelName); @@ -1134,7 +1135,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { /* Sabotage the reattach attempt, then simulate a server-sent detach */ @@ -1173,7 +1174,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * result in the channel becoming suspended */ it('server_sent_detached_while_attaching', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached_while_attaching', channel = realtime.channels.get(channelName); @@ -1197,7 +1198,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); }); }; - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { try { expect(err.code).to.equal(50000, 'check error is propogated to the attach callback'); expect(channel.state).to.equal('suspended', 'check channel goes into suspended'); @@ -1213,12 +1214,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * A server-sent ERROR, with channel field, should fail the channel */ it('server_sent_error', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), channelName = 'server_sent_error', channel = realtime.channels.get(channelName); realtime.connection.once('connected', function () { - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -1250,7 +1251,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * should emit an UPDATE event on the channel */ it('server_sent_attached_err', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), channelName = 'server_sent_attached_err', channel = realtime.channels.get(channelName); @@ -1262,7 +1263,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { channel.once(function (stateChange) { @@ -1294,11 +1295,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check that queueMessages: false disables queuing for connection queue state */ it('publish_no_queueing', function (done) { - var realtime = helper.AblyRealtime({ queueMessages: false }), + var realtime = helper.AblyRealtimePromise({ queueMessages: false }), channel = realtime.channels.get('publish_no_queueing'); /* try a publish while not yet connected */ - channel.publish('foo', 'bar', function (err) { + whenPromiseSettles(channel.publish('foo', 'bar'), function (err) { try { expect(err, 'Check publish while disconnected/connecting is rejected').to.be.ok; closeAndFinish(done, realtime); @@ -1313,7 +1314,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channel_attach_timeout', function (done) { /* Use a fixed transport as attaches are resent when the transport changes */ - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport], realtimeRequestTimeout: 2000, channelRetryTimeout: 100, @@ -1332,7 +1333,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { expect(err, 'Channel attach timed out as expected').to.be.ok; expect(err && err.code).to.equal(90007, 'Attach timeout err passed to attach callback'); expect(channel.state).to.equal('suspended', 'Check channel state goes to suspended'); @@ -1361,7 +1362,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('suspended_connection', function (done) { /* Use a fixed transport as attaches are resent when the transport changes */ /* Browsers throttle setTimeouts to min 1s in in active tabs; having timeouts less than that screws with the relative timings */ - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport], channelRetryTimeout: 1010, suspendedRetryTimeout: 1100, @@ -1377,7 +1378,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { /* Have the connection go into the suspended state, and check that the @@ -1429,7 +1430,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RTL5i */ it('attached_while_detaching', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached', channel = realtime.channels.get(channelName); @@ -1441,7 +1442,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { /* Sabotage the detach attempt, detach, then simulate a server-sent attached while @@ -1472,12 +1473,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async // RTL5j it('detaching from suspended channel transitions channel to detached state', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }); + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }); var channelName = 'detach_from_suspended'; var channel = realtime.channels.get(channelName); channel.state = 'suspended'; - channel.detach(function () { + whenPromiseSettles(channel.detach(), function () { expect(channel.state).to.equal( 'detached', 'Check that detach on suspended channel results in detached channel' @@ -1488,13 +1489,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async // RTL5b it('detaching from failed channel results in error', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }); + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }); var channelName = 'detach_from_failed'; var channel = realtime.channels.get(channelName); channel.state = 'failed'; - channel.detach(function (err) { + whenPromiseSettles(channel.detach(), function (err) { if (!err) { done(new Error('expected detach to return error response')); return; @@ -1504,7 +1505,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('rewind works on channel after reattaching', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }); + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }); var channelName = 'rewind_after_detach'; var channel = realtime.channels.get(channelName); var channelOpts = { params: { rewind: '1' } }; @@ -1513,7 +1514,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var subscriber = function (message) { expect(message.data).to.equal('message'); channel.unsubscribe(subscriber); - channel.detach(function (err) { + whenPromiseSettles(channel.detach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index 260e6fdae7..3922e745fb 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -6,6 +6,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; var displayError = helper.displayError; var monitorConnection = helper.monitorConnection; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/connection', function () { this.timeout(60 * 1000); @@ -22,7 +23,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionPing', function (done) { var realtime; try { - realtime = helper.AblyRealtime(); + realtime = helper.AblyRealtimePromise(); realtime.connection.on('connected', function () { try { realtime.connection.ping(); @@ -40,9 +41,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionPingWithCallback', function (done) { var realtime; try { - realtime = helper.AblyRealtime(); + realtime = helper.AblyRealtimePromise(); realtime.connection.on('connected', function () { - realtime.connection.ping(function (err, responseTime) { + whenPromiseSettles(realtime.connection.ping(), function (err, responseTime) { if (err) { closeAndFinish(done, realtime, err); return; @@ -65,7 +66,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionAttributes', function (done) { var realtime; try { - realtime = helper.AblyRealtime({ logLevel: 4 }); + realtime = helper.AblyRealtimePromise({ logLevel: 4 }); realtime.connection.on('connected', function () { try { const recoveryContext = JSON.parse(realtime.connection.recoveryKey); @@ -77,7 +78,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } var channel = realtime.channels.get('connectionattributes'); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -95,7 +96,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.publish('name', 'data', cb); + whenPromiseSettles(channel.publish('name', 'data'), cb); }, ], function (err) { @@ -130,7 +131,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelSerials: {}, }); try { - realtime = helper.AblyRealtime({ recover: fakeRecoveryKey }); + realtime = helper.AblyRealtimePromise({ recover: fakeRecoveryKey }); realtime.connection.on('connected', function (stateChange) { try { expect(stateChange.reason.code).to.equal( @@ -166,13 +167,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * without being merged with new messages) */ it('connectionQueuing', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), channel = realtime.channels.get('connectionQueuing'), connectionManager = realtime.connection.connectionManager; realtime.connection.once('connected', function () { var transport = connectionManager.activeProtocol.transport; - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -188,7 +189,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async [ function (cb) { /* Sabotaged publish */ - channel.publish('first', null, function (err) { + whenPromiseSettles(channel.publish('first', null), function (err) { try { expect(!err, 'Check publish happened (eventually) without err').to.be.ok; } catch (err) { @@ -247,7 +248,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Inject a new CONNECTED with different connectionDetails; check they're used */ it('connectionDetails', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), connectionManager = realtime.connection.connectionManager; realtime.connection.once('connected', function () { connectionManager.once('connectiondetails', function (details) { @@ -286,7 +287,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { describe('connection_promise', function () { it('ping', function (done) { - var client = helper.AblyRealtime({ internal: { promises: true } }); + var client = helper.AblyRealtimePromise({ internal: { promises: true } }); client.connection .once('connected') diff --git a/test/realtime/connectivity.test.js b/test/realtime/connectivity.test.js index ea4edc9177..ca3cfd4949 100644 --- a/test/realtime/connectivity.test.js +++ b/test/realtime/connectivity.test.js @@ -47,7 +47,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { } it('succeeds with scheme', function (done) { - new helper.AblyRealtime(options(urlScheme + successUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtimePromise(options(urlScheme + successUrl)).http.checkConnectivity(function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; } catch (err) { @@ -59,7 +59,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('fails with scheme', function (done) { - new helper.AblyRealtime(options(urlScheme + failUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtimePromise(options(urlScheme + failUrl)).http.checkConnectivity(function (err, res) { try { expect(!res, 'Connectivity check expected to return false').to.be.ok; done(); @@ -70,7 +70,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('succeeds with querystring', function (done) { - new helper.AblyRealtime(options(successUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtimePromise(options(successUrl)).http.checkConnectivity(function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; done(); @@ -81,7 +81,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('fails with querystring', function (done) { - new helper.AblyRealtime(options(failUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtimePromise(options(failUrl)).http.checkConnectivity(function (err, res) { try { expect(!res, 'Connectivity check expected to return false').to.be.ok; done(); @@ -92,7 +92,10 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('succeeds with plain url', function (done) { - new helper.AblyRealtime(options('sandbox-rest.ably.io/time')).http.checkConnectivity(function (err, res) { + new helper.AblyRealtimePromise(options('sandbox-rest.ably.io/time')).http.checkConnectivity(function ( + err, + res + ) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; done(); @@ -103,7 +106,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('fails with plain url', function (done) { - new helper.AblyRealtime(options('echo.ably.io')).http.checkConnectivity(function (err, res) { + new helper.AblyRealtimePromise(options('echo.ably.io')).http.checkConnectivity(function (err, res) { try { expect(!res, 'Connectivity check expected to return false').to.be.ok; done(); @@ -115,7 +118,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('disable_connectivity_check', function (done) { - new helper.AblyRealtime({ + new helper.AblyRealtimePromise({ connectivityCheckUrl: 'notarealhost', disableConnectivityCheck: true, }).http.checkConnectivity(function (err, res) { diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index f54ffc3cd3..a430aefa23 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -11,12 +11,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var msgpack = typeof window == 'object' ? Ably.msgpack : require('@ably/msgpack-js'); var testOnAllTransports = helper.testOnAllTransports; var closeAndFinish = helper.closeAndFinish; + var whenPromiseSettles = helper.whenPromiseSettles; function attachChannels(channels, callback) { async.map( channels, function (channel, cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, callback ); @@ -61,7 +62,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(new Error('Unable to get test assets; err = ' + displayError(err))); return; } - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); var key = BufferUtils.base64Decode(testData.key); var iv = BufferUtils.base64Decode(testData.iv); var channel = realtime.channels.get(channelName, { cipher: { key: key, iv: iv } }); @@ -408,11 +409,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* For single_send tests we test the 'shortcut' way of setting the cipher * in channels.get. No callback, but that's ok even for node which has * async iv generation since the publish is on an attach cb */ - var realtime = helper.AblyRealtime(realtimeOpts), + var realtime = helper.AblyRealtimePromise(realtimeOpts), channel = realtime.channels.get('single_send', { cipher: { key: key } }), messageText = 'Test message for single_send - ' + JSON.stringify(realtimeOpts); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -458,7 +459,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var realtime = helper.AblyRealtime({ useBinaryProtocol: !text }); + var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: !text }); var channelName = 'multiple_send_' + (text ? 'text_' : 'binary_') + iterations + '_' + delay, channel = realtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')'; @@ -526,8 +527,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtime(txOpts), - rxRealtime = helper.AblyRealtime(rxOpts), + var txRealtime = helper.AblyRealtimePromise(txOpts), + rxRealtime = helper.AblyRealtimePromise(rxOpts), channelName = 'single_send_separate_realtimes'; var messageText = 'Test message for single_send_separate_realtimes', txChannel = txRealtime.channels.get(channelName), @@ -602,8 +603,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtime(), - rxRealtime = helper.AblyRealtime(), + var txRealtime = helper.AblyRealtimePromise(), + rxRealtime = helper.AblyRealtimePromise(), channelName = 'publish_immediately', messageText = 'Test message'; @@ -644,8 +645,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtime(); - var rxRealtime = helper.AblyRealtime(); + var txRealtime = helper.AblyRealtimePromise(); + var rxRealtime = helper.AblyRealtimePromise(); var channelName = 'single_send_key_mismatch', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', @@ -653,8 +654,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel( [ - Crypto.generateRandomKey, - Crypto.generateRandomKey, + function (cb) { + Crypto.generateRandomKey(cb); + }, + function (cb) { + Crypto.generateRandomKey(cb); + }, function (cb) { attachChannels([txChannel, rxChannel], cb); }, @@ -706,8 +711,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtime(); - var rxRealtime = helper.AblyRealtime(); + var txRealtime = helper.AblyRealtimePromise(); + var rxRealtime = helper.AblyRealtimePromise(); var channelName = 'single_send_unencrypted', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', @@ -749,8 +754,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtime(); - var rxRealtime = helper.AblyRealtime(); + var txRealtime = helper.AblyRealtimePromise(); + var rxRealtime = helper.AblyRealtimePromise(); var channelName = 'single_send_encrypted_unhandled', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', @@ -793,8 +798,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtime(); - var rxRealtime = helper.AblyRealtime(); + var txRealtime = helper.AblyRealtimePromise(); + var rxRealtime = helper.AblyRealtimePromise(); var channelName = 'set_cipher_params', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', diff --git a/test/realtime/delta.test.js b/test/realtime/delta.test.js index 9b42c428ee..b66d5951c5 100644 --- a/test/realtime/delta.test.js +++ b/test/realtime/delta.test.js @@ -5,6 +5,7 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v var displayError = helper.displayError; var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; + var whenPromiseSettles = helper.whenPromiseSettles; var testData = [ { foo: 'bar', count: 1, status: 'active' }, { foo: 'bar', count: 2, status: 'active' }, @@ -43,14 +44,14 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v var testName = 'deltaPlugin'; try { var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ plugins: { vcdiff: testVcdiffDecoder, }, }); var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); } @@ -92,16 +93,16 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v var testName = 'unusedPlugin'; try { var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ plugins: { vcdiff: testVcdiffDecoder, }, }); var channel = realtime.channels.get(testName); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { - closeAndFinish(doner, realtime, err); + closeAndFinish(done, realtime, err); } channel.subscribe(function (message) { try { @@ -132,14 +133,14 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v var testName = 'lastMessageNotFoundRecovery'; try { var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ plugins: { vcdiff: testVcdiffDecoder, }, }); var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); } @@ -200,14 +201,14 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v }, }; - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ plugins: { vcdiff: failingTestVcdiffDecoder, }, }); var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); } @@ -245,10 +246,10 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v /* Check that channel becomes failed if we get deltas when we don't have a vcdiff plugin */ it('noPlugin', function (done) { try { - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); var channel = realtime.channels.get('noPlugin', { params: { delta: 'vcdiff' } }); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); } diff --git a/test/realtime/encoding.test.js b/test/realtime/encoding.test.js index aeef0b94f7..b7837a8ab5 100644 --- a/test/realtime/encoding.test.js +++ b/test/realtime/encoding.test.js @@ -8,6 +8,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var encodingFixturesPath = helper.testResourcesPath + 'messages-encoding.json'; var closeAndFinish = helper.closeAndFinish; var Defaults = Ably.Rest.Platform.Defaults; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/encoding', function () { this.timeout(60 * 1000); @@ -31,8 +32,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } - var realtime = helper.AblyRealtime({ useBinaryProtocol: false }), - binaryrealtime = helper.AblyRealtime({ useBinaryProtocol: true }), + var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: false }), + binaryrealtime = helper.AblyRealtimePromise({ useBinaryProtocol: true }), channelName = 'message_decoding', channelPath = '/channels/' + channelName + '/messages', channel = realtime.channels.get(channelName), @@ -41,10 +42,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel( [ function (attachCb) { - channel.attach(attachCb); + whenPromiseSettles(channel.attach(), attachCb); }, function (attachCb) { - binarychannel.attach(attachCb); + whenPromiseSettles(binarychannel.attach(), attachCb); }, ], function (err) { @@ -96,13 +97,15 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (parallelCb) { - realtime.request( - 'post', - channelPath, - Defaults.protocolVersion, - null, - { name: name, data: encodingSpec.data, encoding: encodingSpec.encoding }, - null, + whenPromiseSettles( + realtime.request( + 'post', + channelPath, + Defaults.protocolVersion, + null, + { name: name, data: encodingSpec.data, encoding: encodingSpec.encoding }, + null + ), function (err) { parallelCb(err); } @@ -130,8 +133,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(new Error('Unable to get test assets; err = ' + displayError(err))); return; } - var realtime = helper.AblyRealtime({ useBinaryProtocol: false }), - binaryrealtime = helper.AblyRealtime({ useBinaryProtocol: true }), + var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: false }), + binaryrealtime = helper.AblyRealtimePromise({ useBinaryProtocol: true }), channelName = 'message_encoding', channelPath = '/channels/' + channelName + '/messages', channel = realtime.channels.get(channelName), @@ -140,10 +143,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel( [ function (attachCb) { - channel.attach(attachCb); + whenPromiseSettles(channel.attach(), attachCb); }, function (attachCb) { - binarychannel.attach(attachCb); + whenPromiseSettles(binarychannel.attach(), attachCb); }, ], function (err) { @@ -165,10 +168,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel( [ function (parallelCb) { - channel.publish(name, data, parallelCb); + whenPromiseSettles(channel.publish(name, data), parallelCb); }, function (parallelCb) { - binarychannel.publish(name, data, parallelCb); + whenPromiseSettles(binarychannel.publish(name, data), parallelCb); }, ], function (err) { @@ -176,13 +179,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async eachOfCb(err); return; } - realtime.request( - 'get', - channelPath, - Defaults.protocolVersion, - null, - null, - null, + whenPromiseSettles( + realtime.request('get', channelPath, Defaults.protocolVersion, null, null, null), function (err, resultPage) { if (err) { eachOfCb(err); diff --git a/test/realtime/event_emitter.test.js b/test/realtime/event_emitter.test.js index 4575594f21..36043e2b7d 100644 --- a/test/realtime/event_emitter.test.js +++ b/test/realtime/event_emitter.test.js @@ -28,7 +28,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { /* Note: realtime now sends an ATTACHED post-upgrade, which can race with * the DETACHED if the DETACH is only sent just after upgrade. Remove * bestTransport with 1.1 spec which has IDs in ATTACHs */ - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), index, expectedConnectionEvents = ['connecting', 'connected', 'closing', 'closed'], expectedChannelEvents = ['attaching', 'attached', 'detaching', 'detached']; @@ -74,7 +74,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('emitCallsAllCallbacksIgnoringExceptions', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = false, eventEmitter = realtime.connection; @@ -99,7 +99,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('onceCalledOnlyOnce', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), onCallbackCalled = 0, onceCallbackCalled = 0, eventEmitter = realtime.connection; @@ -127,7 +127,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('onceCallbackDoesNotImpactOnCallback', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -153,7 +153,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllMatchingListeners', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -182,7 +182,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllListeners', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -211,7 +211,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllMatchingEventListeners', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -240,7 +240,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllMatchingEvents', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -276,7 +276,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { * for each previously registered event name */ it('offRemovesEmptyEventNameListeners', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), eventEmitter = realtime.connection; var callback = function () {}; @@ -304,7 +304,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('arrayOfEvents', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -343,7 +343,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('arrayOfEventsWithOnce', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -370,7 +370,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { /* check that listeners added in a listener cb are not called during that * emit instance */ it('listenerAddedInListenerCb', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), eventEmitter = realtime.connection, firstCbCalled = false, secondCbCalled = false; @@ -397,7 +397,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { /* check that listeners removed in a listener cb are still called in that * emit instance (but only once) */ it('listenerRemovedInListenerCb', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), eventEmitter = realtime.connection, onCbCalledTimes = 0, onceCbCalledTimes = 0, @@ -442,7 +442,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { if (typeof Promise !== 'undefined') { describe('event_emitter_promise', function () { it('whenState', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); + var realtime = helper.AblyRealtimePromise({ internal: { promises: true } }); var eventEmitter = realtime.connection; eventEmitter @@ -456,7 +456,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('once', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); + var realtime = helper.AblyRealtimePromise({ internal: { promises: true } }); var eventEmitter = realtime.connection; eventEmitter @@ -470,7 +470,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('anyEventsWithOnce', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), eventEmitter = realtime.connection; const p = eventEmitter.once(); @@ -483,7 +483,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('arrayOfEventsWithOnce', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), eventEmitter = realtime.connection; const p = eventEmitter.once(['a', 'b', 'c']); diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index 142ac9c5f1..c8576e578d 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -8,6 +8,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var simulateDroppedConnection = helper.simulateDroppedConnection; var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; var availableTransports = helper.availableTransports; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/failure', function () { this.timeout(60 * 1000); @@ -28,7 +29,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { var failure_test = function (transports) { return function (cb) { - var realtime = helper.AblyRealtime({ key: 'this.is:wrong', transports: transports }); + var realtime = helper.AblyRealtimePromise({ key: 'this.is:wrong', transports: transports }); realtime.connection.on('failed', function (connectionStateChange) { try { expect(realtime.connection.errorReason.code).to.equal(40400, 'wrong error reason code on connection.'); @@ -77,7 +78,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { var break_test = function (transports) { return function (cb) { - var realtime = helper.AblyRealtime({ transports: transports }); + var realtime = helper.AblyRealtimePromise({ transports: transports }); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function () { cb(null, realtime); @@ -116,7 +117,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var lifecycleTest = function (transports) { return function (cb) { var connectionEvents = []; - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ transports: transports, realtimeHost: 'invalid', restHost: 'invalid', @@ -188,7 +189,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async utils.arrForEach(availableTransports, function (transport) { it('disconnected_backoff_' + transport, function (done) { var disconnectedRetryTimeout = 150; - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ disconnectedRetryTimeout: disconnectedRetryTimeout, realtimeHost: 'invalid', restHost: 'invalid', @@ -219,13 +220,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check operations on a failed channel give the right errors */ it('failed_channel', function (done) { - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); var failChan; var channelFailedCode = 90001; var tests = [ function (callback) { - failChan.publish('event', 'data', function (err) { + whenPromiseSettles(failChan.publish('event', 'data'), function (err) { try { expect(err, 'publish failed').to.be.ok; expect(err.code).to.equal(channelFailedCode, 'publish failure code'); @@ -247,7 +248,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (callback) { - failChan.presence.enterClient('clientId', function (err) { + whenPromiseSettles(failChan.presence.enterClient('clientId'), function (err) { try { expect(err, 'presence enter failed').to.be.ok; expect(err.code).to.equal(channelFailedCode, 'presence enter failure code'); @@ -258,7 +259,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (callback) { - failChan.presence.leaveClient('clientId', function (err) { + whenPromiseSettles(failChan.presence.leaveClient('clientId'), function (err) { try { expect(err, 'presence leave failed').to.be.ok; expect(err.code).to.equal(channelFailedCode, 'presence leave failure code'); @@ -291,7 +292,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (callback) { - failChan.presence.get(function (err) { + whenPromiseSettles(failChan.presence.get(), function (err) { try { expect(err, 'presence get failed').to.be.ok; expect(err.code).to.equal(channelFailedCode, 'presence get failure code'); @@ -306,7 +307,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { realtime.connection.once('connected', function () { failChan = realtime.channels.get('::'); - failChan.attach(function (err) { + whenPromiseSettles(failChan.attach(), function (err) { try { expect(err, 'channel attach failed').to.be.ok; expect(failChan.state).to.equal('failed', 'channel in failed state'); @@ -325,7 +326,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('attach_timeout', function (done) { - var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 2000, channelRetryTimeout: 1000 }), + var realtime = helper.AblyRealtimePromise({ realtimeRequestTimeout: 2000, channelRetryTimeout: 1000 }), channel = realtime.channels.get('failed_attach'), originalProcessMessage = channel.processMessage.bind(channel); @@ -337,7 +338,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; realtime.connection.once('connected', function () { - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { try { expect(err.code).to.equal(90007, 'check channel error code'); expect(err.statusCode).to.equal(408, 'check timeout statusCode'); @@ -361,7 +362,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async utils.arrForEach(availableTransports, function (transport) { it('channel_backoff_' + transport, function (done) { var channelRetryTimeout = 150; - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ channelRetryTimeout: channelRetryTimeout, transports: [transport], }), @@ -381,7 +382,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime.connection.on('connected', function () { realtime.options.timeouts.realtimeRequestTimeout = 1; - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { var lastSuspended = performance.now(); channel.on(function (stateChange) { @@ -422,7 +423,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function nack_on_connection_failure(failureFn, expectedRealtimeState, expectedNackCode) { return function (done) { /* Use one transport because stubbing out transport#onProtocolMesage */ - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), channel = realtime.channels.get('nack_on_connection_failure'); async.series( @@ -433,7 +434,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { var transport = realtime.connection.connectionManager.activeProtocol.transport, @@ -445,7 +446,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async originalOnProtocolMessage.apply(this, arguments); } }; - channel.publish('foo', 'bar', function (err) { + whenPromiseSettles(channel.publish('foo', 'bar'), function (err) { try { expect(err, 'Publish failed as expected').to.be.ok; expect(realtime.connection.state).to.equal( @@ -507,7 +508,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); it('idle_transport_timeout', function (done) { - var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 2000 }), + var realtime = helper.AblyRealtimePromise({ realtimeRequestTimeout: 2000 }), originalOnProtocolMessage; realtime.connection.connectionManager.on('transport.pending', function (transport) { @@ -545,7 +546,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { /* Use the echoserver as a fallback host because it doesn't support * websockets, so it'll fail to connect, which we can detect */ - var realtime = helper.AblyRealtime(utils.mixin({ fallbackHosts: ['echo.ably.io'] }, realtimeOpts)), + var realtime = helper.AblyRealtimePromise(utils.mixin({ fallbackHosts: ['echo.ably.io'] }, realtimeOpts)), connection = realtime.connection, connectionManager = connection.connectionManager; @@ -585,7 +586,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var testMessage2 = { foo: 'bar', count: 2, status: 'active' }; try { - var sender_realtime = helper.AblyRealtime(); + var sender_realtime = helper.AblyRealtimePromise(); var sender_channel = sender_realtime.channels.get(testName); var messageReceived = false; diff --git a/test/realtime/history.test.js b/test/realtime/history.test.js index 1017749c00..73112e343f 100644 --- a/test/realtime/history.test.js +++ b/test/realtime/history.test.js @@ -11,11 +11,12 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; + var whenPromiseSettles = helper.whenPromiseSettles; var parallelPublishMessages = function (done, channel, messages, callback) { var publishTasks = utils.arrMap(messages, function (event) { return function (publishCb) { - channel.publish(event.name, event.data, publishCb); + whenPromiseSettles(channel.publish(event.name, event.data), publishCb); }; }); @@ -46,8 +47,8 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); it('history_until_attach', function (done) { - var rest = helper.AblyRest(); - var realtime = helper.AblyRealtime(); + var rest = helper.AblyRestPromise(); + var realtime = helper.AblyRealtimePromise(); var restChannel = rest.channels.get('persisted:history_until_attach'); /* first, send a number of events to this channel before attaching */ @@ -56,7 +57,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { try { realtime.connection.whenState('connected', function () { var rtChannel = realtime.channels.get('persisted:history_until_attach'); - rtChannel.attach(function (err) { + whenPromiseSettles(rtChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -72,7 +73,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var tests = [ function (callback) { - rtChannel.history(function (err, resultPage) { + whenPromiseSettles(rtChannel.history(), function (err, resultPage) { if (err) { callback(err); } @@ -89,7 +90,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); }, function (callback) { - rtChannel.history({ untilAttach: false }, function (err, resultPage) { + whenPromiseSettles(rtChannel.history({ untilAttach: false }), function (err, resultPage) { if (err) { callback(err); } @@ -106,7 +107,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); }, function (callback) { - rtChannel.history({ untilAttach: true }, function (err, resultPage) { + whenPromiseSettles(rtChannel.history({ untilAttach: true }), function (err, resultPage) { if (err) { callback(err); } diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index cc3557b1ce..e494f2708e 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -4,6 +4,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var expect = chai.expect; var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/init', function () { this.timeout(60 * 1000); @@ -28,7 +29,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('initbase0', function (done) { var realtime; try { - realtime = helper.AblyRealtime({ transports: ['web_socket', 'xhr_streaming'] }); + realtime = helper.AblyRealtimePromise({ transports: ['web_socket', 'xhr_streaming'] }); realtime.connection.on('connected', function () { /* check api version */ var transport = realtime.connection.connectionManager.activeProtocol.transport; @@ -53,7 +54,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = new helper.Ably.Realtime(keyStr); + realtime = new helper.Ably.Realtime.Promise(keyStr); try { expect(realtime.options.key).to.equal(keyStr); @@ -72,17 +73,17 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('init_token_string', function (done) { try { /* first generate a token ... */ - var rest = helper.AblyRest(); + var rest = helper.AblyRestPromise(); var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; - rest.auth.requestToken(null, testKeyOpts, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(null, testKeyOpts), function (err, tokenDetails) { if (err) { done(err); return; } var tokenStr = tokenDetails.token, - realtime = new helper.Ably.Realtime(tokenStr); + realtime = new helper.Ably.Realtime.Promise(tokenStr); try { expect(realtime.options.token).to.equal(tokenStr); @@ -102,7 +103,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true }); + realtime = helper.AblyRealtimePromise({ key: keyStr, useTokenAuth: true }); expect(realtime.options.key).to.equal(keyStr); expect(realtime.auth.method).to.equal('token'); expect(realtime.auth.clientId).to.equal(undefined); @@ -127,7 +128,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtime({ + realtime = helper.AblyRealtimePromise({ key: keyStr, useTokenAuth: true, defaultTokenParams: { clientId: '*', ttl: 123456 }, @@ -155,7 +156,11 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true, defaultTokenParams: { clientId: 'test' } }); + realtime = helper.AblyRealtimePromise({ + key: keyStr, + useTokenAuth: true, + defaultTokenParams: { clientId: 'test' }, + }); expect(realtime.auth.clientId).to.equal(undefined); realtime.connection.on('connected', function () { try { @@ -177,7 +182,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtime({ + realtime = helper.AblyRealtimePromise({ key: keyStr, useTokenAuth: true, clientId: 'yes', @@ -203,7 +208,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { try { var keyStr = helper.getTestApp().keys[0].keyStr; expect(function () { - realtime = new helper.Ably.Realtime({ key: keyStr, useTokenAuth: false, clientId: 'foo' }); + realtime = new helper.Ably.Realtime.Promise({ key: keyStr, useTokenAuth: false, clientId: 'foo' }); }).to.throw; done(); } catch (err) { @@ -217,7 +222,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { /* want to check the default host when no custom environment or custom * host set, so not using helpers.realtime this time, which will use a * test env */ - var realtime = new Ably.Realtime({ key: 'not_a.real:key', autoConnect: false }); + var realtime = new Ably.Realtime.Promise({ key: 'not_a.real:key', autoConnect: false }); var defaultHost = realtime.connection.connectionManager.httpHosts[0]; expect(defaultHost).to.equal('rest.ably.io', 'Verify correct default rest host chosen'); realtime.close(); @@ -230,7 +235,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { /* check changing the default timeouts */ it('init_timeouts', function (done) { try { - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ key: 'not_a.real:key', disconnectedRetryTimeout: 123, suspendedRetryTimeout: 456, @@ -263,7 +268,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { /* check changing the default fallback hosts and changing httpMaxRetryCount */ it('init_fallbacks', function (done) { try { - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ key: 'not_a.real:key', restHost: 'a', httpMaxRetryCount: 2, @@ -311,7 +316,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('node_transports', function (done) { var realtime; try { - realtime = helper.AblyRealtime({ transports: helper.availableTransports }); + realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }); expect(realtime.connection.connectionManager.baseTransport).to.equal('comet'); expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal(['web_socket']); closeAndFinish(done, realtime); @@ -326,7 +331,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('init_and_connection_details', function (done) { try { var keyStr = helper.getTestApp().keys[0].keyStr; - var realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true }); + var realtime = helper.AblyRealtimePromise({ key: keyStr, useTokenAuth: true }); realtime.connection.connectionManager.once('transport.pending', function (state) { var transport = realtime.connection.connectionManager.pendingTransports[0], originalOnProtocolMessage = transport.onProtocolMessage; @@ -365,7 +370,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('init_fallbacks_once_connected', function (done) { - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimePromise({ httpMaxRetryCount: 3, fallbackHosts: ['a', 'b', 'c'], }); @@ -385,8 +390,8 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('init_fallbacks_once_connected_2', function (done) { - var goodHost = helper.AblyRest().options.realtimeHost; - var realtime = helper.AblyRealtime({ + var goodHost = helper.AblyRestPromise().options.realtimeHost; + var realtime = helper.AblyRealtimePromise({ httpMaxRetryCount: 3, restHost: 'a', fallbackHosts: [goodHost, 'b', 'c'], @@ -414,18 +419,12 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { return { key: keyStr, autoConnect: false }; }; - realtime = new Ably.Realtime(getOptions()); - expect(!realtime.options.promises, 'Check promises defaults to false').to.be.ok; - realtime = new Ably.Realtime.Promise(getOptions()); expect(realtime.options.promises, 'Check promises default to true with promise constructor').to.be.ok; if (!isBrowser && typeof require == 'function') { realtime = new require('../../promises').Realtime(getOptions()); expect(realtime.options.promises, 'Check promises default to true with promise require target').to.be.ok; - - realtime = new require('../../callbacks').Realtime(getOptions()); - expect(!realtime.options.promises, 'Check promises default to false with callback require target').to.be.ok; } done(); } catch (err) { diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index 3cd4f663e5..c5463cb692 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -9,10 +9,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; var monitorConnection = helper.monitorConnection; var testOnAllTransports = helper.testOnAllTransports; + var whenPromiseSettles = helper.whenPromiseSettles; var publishIntervalHelper = function (currentMessageNum, channel, dataFn, onPublish) { return function () { - channel.publish('event0', dataFn(), function () { + whenPromiseSettles(channel.publish('event0', dataFn()), function () { onPublish(); }); }; @@ -38,14 +39,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('publishonce', function (done) { try { /* set up realtime */ - var realtime = helper.AblyRealtime(); - var rest = helper.AblyRest(); + var realtime = helper.AblyRealtimePromise(); + var rest = helper.AblyRestPromise(); /* connect and attach */ realtime.connection.on('connected', function () { var testMsg = 'Hello world'; var rtChannel = realtime.channels.get('publishonce'); - rtChannel.attach(function (err) { + whenPromiseSettles(rtChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -79,10 +80,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('publishfast', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); realtime.connection.once('connected', function () { var channel = realtime.channels.get('publishfast_' + String(Math.random()).substr(2)); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -100,7 +101,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function (cb) { var ackd = 0; var publish = function (i) { - channel.publish('event', i.toString(), function (err) { + whenPromiseSettles(channel.publish('event', i.toString()), function (err) { try { expect( !err, @@ -143,8 +144,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var txRealtime, rxRealtime; try { - txRealtime = helper.AblyRealtime(utils.mixin(realtimeOpts, { autoConnect: false })); - rxRealtime = helper.AblyRealtime(); + txRealtime = helper.AblyRealtimePromise(utils.mixin(realtimeOpts, { autoConnect: false })); + rxRealtime = helper.AblyRealtimePromise(); var txChannel = txRealtime.channels.get('publishQueued_' + String(Math.random()).substr(2)); var rxChannel = rxRealtime.channels.get(txChannel.name); @@ -156,7 +157,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - rxChannel.attach(function (err) { + whenPromiseSettles(rxChannel.attach(), function (err) { cb(err); }); }, @@ -180,7 +181,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function (parCb) { var ackd = 0; var publish = function (i) { - txChannel.publish('event', { num: i }, function (err) { + whenPromiseSettles(txChannel.publish('event', { num: i }), function (err) { try { expect( !err, @@ -231,8 +232,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('publishEcho', function (done) { // set up two realtimes - var rtNoEcho = helper.AblyRealtime({ echoMessages: false }), - rtEcho = helper.AblyRealtime({ echoMessages: true }), + var rtNoEcho = helper.AblyRealtimePromise({ echoMessages: false }), + rtEcho = helper.AblyRealtimePromise({ echoMessages: true }), rtNoEchoChannel = rtNoEcho.channels.get('publishecho'), rtEchoChannel = rtEcho.channels.get('publishecho'), testMsg1 = 'Hello', @@ -259,7 +260,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; // attach rtNoEchoChannel - rtNoEchoChannel.attach(function (err) { + whenPromiseSettles(rtNoEchoChannel.attach(), function (err) { try { expect(!err, 'Attached to rtNoEchoChannel with no error').to.be.ok; } catch (err) { @@ -275,7 +276,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); // attach rtEchoChannel - rtEchoChannel.attach(function (err) { + whenPromiseSettles(rtEchoChannel.attach(), function (err) { try { expect(!err, 'Attached to rtEchoChannel with no error').to.be.ok; } catch (err) { @@ -291,7 +292,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); // publish testMsg1 via rtNoEcho - rtNoEchoChannel.publish('event0', testMsg1, function () { + whenPromiseSettles(rtNoEchoChannel.publish('event0', testMsg1), function () { // publish testMsg2 via rtEcho rtEchoChannel.publish('event0', testMsg2); }); @@ -322,13 +323,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - realtime = helper.AblyRealtime(); - var rest = helper.AblyRest(); + realtime = helper.AblyRealtimePromise(); + var rest = helper.AblyRestPromise(); /* connect and attach */ realtime.connection.on('connected', function () { var rtChannel = realtime.channels.get('publishVariations'); - rtChannel.attach(function (err) { + whenPromiseSettles(rtChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -428,13 +429,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - var realtime = helper.AblyRealtime(); - var rest = helper.AblyRest(); + var realtime = helper.AblyRealtimePromise(); + var rest = helper.AblyRestPromise(); /* connect and attach */ realtime.connection.on('connected', function () { var rtChannel = realtime.channels.get('publishDisallowed'); - rtChannel.attach(function (err) { + whenPromiseSettles(rtChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -445,7 +446,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var restChannel = rest.channels.get('publishDisallowed'); for (var i = 0; i < testArguments.length; i++) { try { - restChannel.publish.apply(restChannel, testArguments[i]); + await restChannel.publish.apply(restChannel, testArguments[i]); closeAndFinish(done, realtime, new Error('Exception was not raised')); } catch (err) { try { @@ -482,13 +483,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - var realtime = helper.AblyRealtime(); - var rest = helper.AblyRest(); + var realtime = helper.AblyRealtimePromise(); + var rest = helper.AblyRestPromise(); /* connect and attach */ realtime.connection.on('connected', function () { var rtChannel = realtime.channels.get('publishEncodings'); - rtChannel.attach(function (err) { + whenPromiseSettles(rtChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -535,7 +536,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testArguments, function iterator(item, callback) { try { - restChannel.publish(item, function (err) { + whenPromiseSettles(restChannel.publish(item), function (err) { try { expect(!err, 'Successfully published').to.be.ok; } catch (err) { @@ -564,8 +565,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('restpublish', function (done) { var count = 10; - var rest = helper.AblyRest(); - var realtime = helper.AblyRealtime(); + var rest = helper.AblyRestPromise(); + var realtime = helper.AblyRealtimePromise(); var messagesSent = []; var sendchannel = rest.channels.get('restpublish'); var recvchannel = realtime.channels.get('restpublish'); @@ -603,7 +604,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async --cbCount; checkFinish(); }; - var realtime = helper.AblyRealtime(realtimeOpts); + var realtime = helper.AblyRealtimePromise(realtimeOpts); var channel = realtime.channels.get('publish ' + JSON.stringify(realtimeOpts)); /* subscribe to event */ channel.subscribe( @@ -626,7 +627,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async and is implicitly added when published */ it('implicit_client_id_0', function (done) { var clientId = 'implicit_client_id_0', - realtime = helper.AblyRealtime({ clientId: clientId }); + realtime = helper.AblyRealtimePromise({ clientId: clientId }); realtime.connection.once('connected', function () { var transport = realtime.connection.connectionManager.activeProtocol.transport, @@ -665,7 +666,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('explicit_client_id_0', function (done) { var clientId = 'explicit_client_id_0', /* Use a fixed transport as intercepting transport.send */ - realtime = helper.AblyRealtime({ clientId: clientId, transports: [helper.bestTransport] }); + realtime = helper.AblyRealtimePromise({ clientId: clientId, transports: [helper.bestTransport] }); realtime.connection.once('connected', function () { var transport = realtime.connection.connectionManager.activeProtocol.transport, @@ -685,7 +686,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channel = realtime.channels.get('explicit_client_id_0'); /* subscribe to event */ - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { try { expect(!err, err && helper.displayError(err)).to.be.ok; @@ -709,7 +710,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.publish({ name: 'event0', clientId: clientId }, function (err) { + whenPromiseSettles(channel.publish({ name: 'event0', clientId: clientId }), function (err) { cb(err); }); }, @@ -727,9 +728,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('explicit_client_id_1', function (done) { var clientId = 'explicit_client_id_1', invalidClientId = 'invalid', - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); - rest.auth.requestToken({ clientId: clientId }, function (err, token) { + whenPromiseSettles(rest.auth.requestToken({ clientId: clientId }), function (err, token) { if (err) { done(err); return; @@ -742,11 +743,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } /* Use a fixed transport as intercepting transport.send */ - var realtime = helper.AblyRealtime({ token: token.token, transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ token: token.token, transports: [helper.bestTransport] }), channel = realtime.channels.get('explicit_client_id_1'); // Publish before authentication to ensure the client library does not reject the message as the clientId is not known - channel.publish({ name: 'event0', clientId: invalidClientId }, function (err) { + whenPromiseSettles(channel.publish({ name: 'event0', clientId: invalidClientId }), function (err) { try { expect(err, 'Message was not published').to.be.ok; } catch (err) { @@ -782,7 +783,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('subscribe_with_event_array', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), channel = realtime.channels.get('subscribe_with_event_array'); async.series( @@ -793,7 +794,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { cb(err); }); }, @@ -819,9 +820,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (innercb) { - channel.publish([{ name: 'a' }, { name: 'b' }, { name: 'c' }, { name: 'd' }], function (err) { - innercb(err); - }); + whenPromiseSettles( + channel.publish([{ name: 'a' }, { name: 'b' }, { name: 'c' }, { name: 'd' }]), + function (err) { + innercb(err); + } + ); }, ], outercb @@ -835,12 +839,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('subscribe_with_filter_object', function (done) { - const realtime = helper.AblyRealtime(); + const realtime = helper.AblyRealtimePromise(); const channel = realtime.channels.get('subscribe_with_filter_object'); function send(cb) { - channel.publish( - [ + whenPromiseSettles( + channel.publish([ { name: 'correct', extras: { @@ -871,7 +875,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, }, }, - ], + ]), cb ); } @@ -902,7 +906,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - return channel.attach(cb); + return whenPromiseSettles(channel.attach(), cb); }, function (cb) { return async.parallel([subscribe, send], cb); @@ -915,12 +919,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('unsubscribe_with_filter_object', function (done) { - const realtime = helper.AblyRealtime(); + const realtime = helper.AblyRealtimePromise(); const channel = realtime.channels.get('unsubscribe_with_filter_object'); function send(cb) { - channel.publish( - [ + whenPromiseSettles( + channel.publish([ { name: 'incorrect', extras: { @@ -930,7 +934,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, }, }, - ], + ]), cb ); } @@ -961,7 +965,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - return channel.attach(cb); + return whenPromiseSettles(channel.attach(), cb); }, unsubscribe, send, @@ -973,7 +977,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('extras_field', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), channel = realtime.channels.get('extras_field'), extras = { headers: { some: 'metadata' } }; @@ -984,7 +988,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async cb(); }); }, - channel.attach.bind(channel), + function (cb) { + whenPromiseSettles(channel.attach(), cb); + }, function (outercb) { async.parallel( [ @@ -1001,7 +1007,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (innercb) { - channel.publish([{ name: 'a', extras: extras }], innercb); + whenPromiseSettles(channel.publish([{ name: 'a', extras: extras }]), innercb); }, ], outercb @@ -1016,22 +1022,25 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* TO3l8; CD2C; RSL1i */ it('maxMessageSize', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), connectionManager = realtime.connection.connectionManager, channel = realtime.channels.get('maxMessageSize'); realtime.connection.once('connected', function () { connectionManager.once('connectiondetails', function (details) { - channel.publish('foo', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', function (err) { - try { - expect(err, 'Check publish refused').to.be.ok; - expect(err.code).to.equal(40009); - } catch (err) { - closeAndFinish(done, realtime, err); - return; + whenPromiseSettles( + channel.publish('foo', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), + function (err) { + try { + expect(err, 'Check publish refused').to.be.ok; + expect(err.code).to.equal(40009); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); } - closeAndFinish(done, realtime); - }); + ); }); var connectionDetails = connectionManager.connectionDetails; connectionDetails.maxMessageSize = 64; @@ -1047,7 +1056,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RTL6d: publish a series of messages that exercise various bundling * constraints, check they're satisfied */ it.skip('bundling', function (done) { - var realtime = helper.AblyRealtime({ maxMessageSize: 256, autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ maxMessageSize: 256, autoConnect: false }), channelOne = realtime.channels.get('bundlingOne'), channelTwo = realtime.channels.get('bundlingTwo'); @@ -1109,10 +1118,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('idempotentRealtimePublishing', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), channel = realtime.channels.get('idempotentRealtimePublishing'); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -1144,7 +1153,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (typeof Promise !== 'undefined') { it('publishpromise', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); + var realtime = helper.AblyRealtimePromise({ internal: { promises: true } }); var channel = realtime.channels.get('publishpromise'); var publishPromise = realtime.connection @@ -1242,8 +1251,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - var realtime = helper.AblyRealtime({ key: helper.getTestApp().keys[5].keyStr }); - var rest = helper.AblyRest(); + var realtime = helper.AblyRealtimePromise({ key: helper.getTestApp().keys[5].keyStr }); + var rest = helper.AblyRestPromise(); realtime.connection.on('connected', function () { var rtFilteredChannel = realtime.channels.getDerived('chan', filterOption); @@ -1252,7 +1261,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var filteredMessages = []; var unFilteredMessages = []; /* subscribe to event */ - rtFilteredChannel.attach(function (err) { + whenPromiseSettles(rtFilteredChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -1267,7 +1276,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } }); - rtUnfilteredChannel.attach(function (err) { + whenPromiseSettles(rtUnfilteredChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 1c90d988d9..0f5d08bbfb 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -6,6 +6,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; + var whenPromiseSettles = helper.whenPromiseSettles; function extractClientIds(presenceSet) { return utils @@ -28,10 +29,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var createListenerChannel = function (channelName, callback) { var channel, realtime; try { - realtime = helper.AblyRealtime(); + realtime = helper.AblyRealtimePromise(); realtime.connection.on('connected', function () { channel = realtime.channels.get(channelName); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { callback(err, realtime, channel); }); }); @@ -100,8 +101,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } // Create authTokens associated with specific clientIds try { - rest = helper.AblyRest(); - rest.auth.requestToken({ clientId: testClientId }, function (err, tokenDetails) { + rest = helper.AblyRestPromise(); + whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; @@ -114,7 +115,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - rest.auth.requestToken({ clientId: testClientId2 }, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId2 }), function (err, tokenDetails) { if (err) { done(err); return; @@ -142,16 +143,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'attachAndEnter'; var attachAndEnter = function (cb) { /* set up authenticated connection */ - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.enter('Test client data (enter0)', function (err) { + whenPromiseSettles(clientChannel.presence.enter('Test client data (enter0)'), function (err) { cb(err, clientRealtime); }); }); @@ -168,11 +169,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithoutAttach', function (done) { var channelName = 'enterWithoutAttach'; var enterWithoutAttach = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.presence.enter('Test client data (enterWithoutAttach)', function (err) { + whenPromiseSettles(clientChannel.presence.enter('Test client data (enterWithoutAttach)'), function (err) { cb(err, clientRealtime); }); }); @@ -188,9 +189,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithoutConnect', function (done) { var channelName = 'enterWithoutConnect'; var enterWithoutConnect = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.presence.enter('Test client data (enterWithoutConnect)', function (err) { + whenPromiseSettles(clientChannel.presence.enter('Test client data (enterWithoutConnect)'), function (err) { cb(err, clientRealtime); }); monitorConnection(done, clientRealtime); @@ -218,7 +219,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); listenerFor('enter', testClientId)(presenceChannel, function () { if (!raceFinished) { @@ -230,19 +231,19 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { closeAndFinish(done, [listenerRealtime, clientRealtime], err); return; } - clientChannel.detach(function (err) { + whenPromiseSettles(clientChannel.detach(), function (err) { if (err) { closeAndFinish(done, [listenerRealtime, clientRealtime], err); return; } }); }); - clientChannel.presence.enter('Test client data (enter3)', function (err) { + whenPromiseSettles(clientChannel.presence.enter('Test client data (enter3)'), function (err) { // Note: either an error (pending messages failed to send due to detach) // or a success (pending messages were pushed out before the detach) // is an acceptable result. Throwing an uncaught exception (the behaviour @@ -270,16 +271,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'enterWithCallback'; var enterWithCallback = function (cb) { /* set up authenticated connection */ - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.enter(function (err) { + whenPromiseSettles(clientChannel.presence.enter(), function (err) { cb(err, clientRealtime); }); }); @@ -296,11 +297,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithNothing', function (done) { var channelName = 'enterWithNothing'; var enterWithNothing = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; @@ -321,11 +322,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithData', function (done) { var channelName = 'enterWithData'; var enterWithData = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; @@ -345,7 +346,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * has valid action string */ it('presenceMessageAction', function (done) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); var channelName = 'presenceMessageAction'; var clientChannel = clientRealtime.channels.get(channelName); var presence = clientChannel.presence; @@ -385,24 +386,24 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterDetachEnter = function (cb) { - var clientRealtime = helper.AblyRealtime({ + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken, transports: [helper.bestTransport], }); // NB remove besttransport in 1.1 spec, see attachdetach0 var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.once('connected', function () { - clientChannel.presence.enter('first', function (err) { + whenPromiseSettles(clientChannel.presence.enter('first'), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.detach(function (err) { + whenPromiseSettles(clientChannel.detach(), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.enter('second', function (err) { + whenPromiseSettles(clientChannel.presence.enter('second'), function (err) { cb(err, clientRealtime); }); }); @@ -420,10 +421,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterInvalid', function (done) { var clientRealtime; try { - clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); var clientChannel = clientRealtime.channels.get(''); clientRealtime.connection.once('connected', function () { - clientChannel.presence.enter('clientId', function (err) { + whenPromiseSettles(clientChannel.presence.enter('clientId'), function (err) { if (err) { try { expect(err.code).to.equal(40010, 'Correct error code'); @@ -449,22 +450,22 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterAndLeave', function (done) { var channelName = 'enterAndLeave'; var enterAndLeave = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.enter('Test client data (leave0)', function (err) { + whenPromiseSettles(clientChannel.presence.enter('Test client data (leave0)'), function (err) { if (err) { cb(err, clientRealtime); return; } }); - clientChannel.presence.leave(function (err) { + whenPromiseSettles(clientChannel.presence.leave(), function (err) { cb(err, clientRealtime); }); }); @@ -498,20 +499,20 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterUpdate = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.enter('Original data', function (err) { + whenPromiseSettles(clientChannel.presence.enter('Original data'), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.update(newData, function (err) { + whenPromiseSettles(clientChannel.presence.update(newData), function (err) { cb(err, clientRealtime); }); }); @@ -532,7 +533,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var eventListener = function (channel, callback) { var presenceHandler = function () { /* Should be ENTER, but may be PRESENT in a race */ - channel.presence.get(function (err, presenceMembers) { + whenPromiseSettles(channel.presence.get(), function (err, presenceMembers) { if (err) { callback(err); return; @@ -551,16 +552,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterGet = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.enter(testData, function (err) { + whenPromiseSettles(clientChannel.presence.enter(testData), function (err) { cb(err, clientRealtime); }); }); @@ -576,7 +577,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('presenceSubscribeUnattached', function (done) { var channelName = 'subscribeUnattached'; - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); var clientRealtime2; clientRealtime.connection.on('connected', function () { var clientChannel = clientRealtime.channels.get(channelName); @@ -590,7 +591,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, [clientRealtime, clientRealtime2]); }); /* Technically a race, but c2 connecting and attaching should take longer than c1 attaching */ - clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); + clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { var clientChannel2 = clientRealtime2.channels.get(channelName); clientChannel2.presence.enter('data'); @@ -605,20 +606,20 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceGetUnattached', function (done) { var channelName = 'getUnattached'; var testData = 'some data'; - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.presence.enter(testData, function (err) { + whenPromiseSettles(clientChannel.presence.enter(testData), function (err) { if (err) { closeAndFinish(done, clientRealtime, err); return; } - var clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); + var clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { var clientChannel2 = clientRealtime2.channels.get(channelName); /* GET without attaching */ - clientChannel2.presence.get(function (err, presenceMembers) { + whenPromiseSettles(clientChannel2.presence.get(), function (err, presenceMembers) { if (err) { closeAndFinish(done, [clientRealtime, clientRealtime2], err); return; @@ -649,7 +650,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var presenceHandler = function () { // Ignore the first (enter) event if (this.event == 'leave') { - channel.presence.get(function (err, presenceMembers) { + whenPromiseSettles(channel.presence.get(), function (err, presenceMembers) { if (err) { callback(err); return; @@ -668,21 +669,21 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterLeaveGet = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.enter('testClientData', function (err) { + whenPromiseSettles(clientChannel.presence.enter('testClientData'), function (err) { if (err) { cb(err, clientRealtime); return; } - clientChannel.presence.leave(function (err) { + whenPromiseSettles(clientChannel.presence.leave(), function (err) { cb(err, clientRealtime); }); }); @@ -702,7 +703,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'history'; var testClientData = 'Test client data (history0)'; var queryPresenceHistory = function (channel) { - channel.presence.history(function (err, resultPage) { + whenPromiseSettles(channel.presence.history(), function (err, resultPage) { if (err) { closeAndFinish(done, clientRealtime, err); return; @@ -725,26 +726,26 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; try { /* set up authenticated connection */ - clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); - clientChannel.attach(function (err) { + whenPromiseSettles(clientChannel.attach(), function (err) { if (err) { closeAndFinish(done, clientRealtime, err); return; } - clientChannel.presence.enter(testClientData, function (err) { + whenPromiseSettles(clientChannel.presence.enter(testClientData), function (err) { if (err) { closeAndFinish(done, clientRealtime, err); return; } - clientChannel.presence.leave(function (err) { + whenPromiseSettles(clientChannel.presence.leave(), function (err) { if (err) { closeAndFinish(done, clientRealtime, err); return; } - clientChannel.detach(function (err) { + whenPromiseSettles(clientChannel.detach(), function (err) { if (err) { closeAndFinish(done, clientRealtime, err); return; @@ -774,23 +775,23 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'secondConnection'; try { /* set up authenticated connection */ - clientRealtime1 = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime1 = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime1.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel1 = clientRealtime1.channels.get(channelName); - clientChannel1.attach(function (err) { + whenPromiseSettles(clientChannel1.attach(), function (err) { if (err) { closeAndFinish(done, clientRealtime1, err); return; } - clientChannel1.presence.enter('Test client data (attach0)', function (err) { + whenPromiseSettles(clientChannel1.presence.enter('Test client data (attach0)'), function (err) { if (err) { closeAndFinish(done, clientRealtime1, err); return; } }); clientChannel1.presence.subscribe('enter', function () { - clientChannel1.presence.get(function (err, presenceMembers1) { + whenPromiseSettles(clientChannel1.presence.get(), function (err, presenceMembers1) { if (err) { closeAndFinish(done, clientRealtime1, err); return; @@ -803,18 +804,18 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } /* now set up second connection and attach */ /* set up authenticated connection */ - clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); + clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { /* get channel, attach */ var clientChannel2 = clientRealtime2.channels.get(channelName); - clientChannel2.attach(function (err) { + whenPromiseSettles(clientChannel2.attach(), function (err) { if (err) { closeAndFinish(done, [clientRealtime1, clientRealtime2], err); return; } clientChannel2.presence.subscribe('present', function () { /* get the channel members and verify testclient is there */ - clientChannel2.presence.get(function (err, presenceMembers2) { + whenPromiseSettles(clientChannel2.presence.get(), function (err, presenceMembers2) { if (err) { closeAndFinish(done, [clientRealtime1, clientRealtime2], err); return; @@ -858,16 +859,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async [ function (cb1) { var data = 'Test client data (member0-1)'; - clientRealtime1 = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime1 = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); clientRealtime1.connection.on('connected', function () { /* get channel, attach, and enter */ clientChannel1 = clientRealtime1.channels.get(channelName); - clientChannel1.attach(function (err) { + whenPromiseSettles(clientChannel1.attach(), function (err) { if (err) { cb1(err); return; } - clientChannel1.presence.enter(data, function (err) { + whenPromiseSettles(clientChannel1.presence.enter(data), function (err) { if (err) { cb1(err); return; @@ -880,17 +881,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb2) { var data = 'Test client data (member0-2)'; - clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); + clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { /* get channel, attach */ clientChannel2 = clientRealtime2.channels.get(channelName); - clientChannel2.attach(function (err) { + whenPromiseSettles(clientChannel2.attach(), function (err) { if (err) { cb2(err); return; } var enterPresence = function (onEnterCB) { - clientChannel2.presence.enter(data, function (err) { + whenPromiseSettles(clientChannel2.presence.enter(data), function (err) { if (err) { cb2(err); return; @@ -934,7 +935,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async [ /* First test: no filters */ function (cb) { - clientChannel2.presence.get(function (err, members) { + whenPromiseSettles(clientChannel2.presence.get(), function (err, members) { if (err) { return cb(err); } @@ -953,7 +954,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, /* Second test: filter by clientId */ function (cb) { - clientChannel2.presence.get({ clientId: testClientId }, function (err, members) { + whenPromiseSettles(clientChannel2.presence.get({ clientId: testClientId }), function (err, members) { if (err) { return cb(err); } @@ -969,25 +970,28 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, /* Third test: filter by connectionId */ function (cb) { - clientChannel2.presence.get({ connectionId: clientRealtime1.connection.id }, function (err, members) { - if (err) { - return cb(err); - } - try { - expect(members.length).to.equal( - 1, - 'Verify only one member present when filtered by connectionId' - ); - expect(members[0].connectionId).to.equal( - clientRealtime1.connection.id, - 'Verify connectionId filter works' - ); - } catch (err) { - cb(err); - return; + whenPromiseSettles( + clientChannel2.presence.get({ connectionId: clientRealtime1.connection.id }), + function (err, members) { + if (err) { + return cb(err); + } + try { + expect(members.length).to.equal( + 1, + 'Verify only one member present when filtered by connectionId' + ); + expect(members[0].connectionId).to.equal( + clientRealtime1.connection.id, + 'Verify connectionId filter works' + ); + } catch (err) { + cb(err); + return; + } + cb(); } - cb(); - }); + ); }, ], function (err) { @@ -1021,11 +1025,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterAfterClose = function (cb) { - var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.once('connected', function () { /* get channel and enter (should automatically attach) */ - clientChannel.presence.enter('first', function (err) { + whenPromiseSettles(clientChannel.presence.enter('first'), function (err) { if (err) { cb(err, clientRealtime); return; @@ -1034,7 +1038,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async clientRealtime.connection.whenState('closed', function () { clientRealtime.connection.once('connected', function () { //Should automatically reattach - clientChannel.presence.enter('second', function (err) { + whenPromiseSettles(clientChannel.presence.enter('second'), function (err) { cb(err, clientRealtime); }); }); @@ -1055,11 +1059,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var clientRealtime; var channelName = 'enterClosed'; try { - clientRealtime = helper.AblyRealtime(); + clientRealtime = helper.AblyRealtimePromise(); var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.on('connected', function () { clientRealtime.close(); - clientChannel.presence.enterClient('clientId', function (err) { + whenPromiseSettles(clientChannel.presence.enterClient('clientId'), function (err) { try { expect(err.code).to.equal(80017, 'presence enter failed with correct code'); expect(err.statusCode).to.equal(400, 'presence enter failed with correct statusCode'); @@ -1081,7 +1085,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('presenceClientIdIsImplicit', function (done) { var clientId = 'implicitClient', - client = helper.AblyRealtime({ clientId: clientId }); + client = helper.AblyRealtimePromise({ clientId: clientId }); var channel = client.channels.get('presenceClientIdIsImplicit'), presence = channel.presence; @@ -1097,17 +1101,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async originalSendPresence.apply(channel, arguments); }; - presence.enter(null, function (err) { + whenPromiseSettles(presence.enter(null), function (err) { if (err) { closeAndFinish(done, client, err); return; } - presence.update(null, function (err) { + whenPromiseSettles(presence.update(null), function (err) { if (err) { closeAndFinish(done, client, err); return; } - presence.leave(null, function (err) { + whenPromiseSettles(presence.leave(null), function (err) { if (err) { closeAndFinish(done, client, err); return; @@ -1131,8 +1135,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async transports: [helper.bestTransport], }; - var realtimeBin = helper.AblyRealtime(utils.mixin(options, { useBinaryProtocol: true })); - var realtimeJson = helper.AblyRealtime(utils.mixin(options, { useBinaryProtocol: false })); + var realtimeBin = helper.AblyRealtimePromise(utils.mixin(options, { useBinaryProtocol: true })); + var realtimeJson = helper.AblyRealtimePromise(utils.mixin(options, { useBinaryProtocol: false })); var runTest = function (realtime, callback) { realtime.connection.connectionManager.once('transport.active', function (transport) { @@ -1188,7 +1192,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'enter_inherited_clientid'; var authCallback = function (tokenParams, callback) { - rest.auth.requestToken({ clientId: testClientId }, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; @@ -1198,7 +1202,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; var enterInheritedClientId = function (cb) { - var realtime = helper.AblyRealtime({ authCallback: authCallback }); + var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); var channel = realtime.channels.get(channelName); realtime.connection.on('connected', function () { try { @@ -1207,7 +1211,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async cb(err); return; } - channel.presence.enter('test data', function (err) { + whenPromiseSettles(channel.presence.enter('test data'), function (err) { cb(err, realtime); }); }); @@ -1226,12 +1230,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'enter_before_know_clientid'; var enterInheritedClientId = function (cb) { - rest.auth.requestToken({ clientId: testClientId }, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; } - var realtime = helper.AblyRealtime({ token: tokenDetails.token, autoConnect: false }); + var realtime = helper.AblyRealtimePromise({ token: tokenDetails.token, autoConnect: false }); var channel = realtime.channels.get(channelName); try { expect(realtime.auth.clientId).to.equal(undefined, 'no clientId when entering'); @@ -1239,7 +1243,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); return; } - channel.presence.enter('test data', function (err) { + whenPromiseSettles(channel.presence.enter('test data'), function (err) { try { expect(realtime.auth.clientId).to.equal(testClientId, 'clientId has been set by the time we entered'); } catch (err) { @@ -1262,8 +1266,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('presence_refresh_on_detach', function (done) { var channelName = 'presence_refresh_on_detach'; - var realtime = helper.AblyRealtime(); - var observer = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); + var observer = helper.AblyRealtimePromise(); var realtimeChannel = realtime.channels.get(channelName); var observerChannel = observer.channels.get(channelName); @@ -1287,10 +1291,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel( [ function (enterCb) { - realtimeChannel.presence.enterClient('one', enterCb); + whenPromiseSettles(realtimeChannel.presence.enterClient('one'), enterCb); }, function (enterCb) { - realtimeChannel.presence.enterClient('two', enterCb); + whenPromiseSettles(realtimeChannel.presence.enterClient('two'), enterCb); }, ], cb @@ -1298,7 +1302,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } function checkPresence(first, second, cb) { - observerChannel.presence.get(function (err, presenceMembers) { + whenPromiseSettles(observerChannel.presence.get(), function (err, presenceMembers) { var clientIds = utils .arrMap(presenceMembers, function (msg) { return msg.clientId; @@ -1320,10 +1324,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel( [ function (innerCb) { - realtimeChannel.presence.leaveClient('two', innerCb); + whenPromiseSettles(realtimeChannel.presence.leaveClient('two'), innerCb); }, function (innerCb) { - realtimeChannel.presence.enterClient('three', innerCb); + whenPromiseSettles(realtimeChannel.presence.enterClient('three'), innerCb); }, ], cb @@ -1350,17 +1354,17 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async [ waitForBothConnect, function (cb) { - realtimeChannel.attach(cb); + whenPromiseSettles(realtimeChannel.attach(), cb); }, enterOneAndTwo, function (cb) { - observerChannel.attach(cb); + whenPromiseSettles(observerChannel.attach(), cb); }, function (cb) { checkPresence('one', 'two', cb); }, function (cb) { - observerChannel.detach(cb); + whenPromiseSettles(observerChannel.detach(), cb); }, swapTwoForThree, attachAndListen, @@ -1376,8 +1380,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presence_detach_during_sync', function (done) { var channelName = 'presence_detach_during_sync'; - var enterer = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); - var detacher = helper.AblyRealtime(); + var enterer = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var detacher = helper.AblyRealtimePromise(); var entererChannel = enterer.channels.get(channelName); var detacherChannel = detacher.channels.get(channelName); @@ -1401,13 +1405,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async [ waitForBothConnect, function (cb) { - entererChannel.presence.enter(cb); + whenPromiseSettles(entererChannel.presence.enter(), cb); }, function (cb) { - detacherChannel.attach(cb); + whenPromiseSettles(detacherChannel.attach(), cb); }, function (cb) { - detacherChannel.detach(cb); + whenPromiseSettles(detacherChannel.detach(), cb); }, function (cb) { try { @@ -1432,7 +1436,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * presence set */ it('presence_auto_reenter', function (done) { var channelName = 'presence_auto_reenter'; - var realtime = helper.AblyRealtime(); + var realtime = helper.AblyRealtimePromise(); var channel = realtime.channels.get(channelName); async.series( @@ -1443,7 +1447,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { if (!channel.presence.syncComplete) { @@ -1512,7 +1516,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { if (err) { cb(err); return; @@ -1551,20 +1555,20 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Request a token without the capabilities to be in the presence set */ var tokenParams = { clientId: 'me', capability: {} }; tokenParams.capability[channelName] = ['publish', 'subscribe']; - rest.auth.requestToken(tokenParams, function (err, tokenDetails) { + whenPromiseSettles(rest.auth.requestToken(tokenParams), function (err, tokenDetails) { token = tokenDetails; cb(err); }); }, function (cb) { - realtime = helper.AblyRealtime({ tokenDetails: token }); + realtime = helper.AblyRealtimePromise({ tokenDetails: token }); channel = realtime.channels.get(channelName); realtime.connection.once('connected', function () { cb(); }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { if (!channel.presence.syncComplete) { @@ -1574,7 +1578,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } }, function (cb) { - channel.presence.get(function (err, members) { + whenPromiseSettles(channel.presence.get(), function (err, members) { try { expect(members.length).to.equal(0, 'Check no-one in presence set'); } catch (err) { @@ -1616,7 +1620,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.presence.get(function (err, members) { + whenPromiseSettles(channel.presence.get(), function (err, members) { try { expect(members.length).to.equal(0, 'Check no-one in presence set'); } catch (err) { @@ -1636,7 +1640,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Enter ten clients while attaching, finish the attach, check they were all entered correctly */ it('multiple_pending', function (done) { /* single transport to avoid upgrade stalling due to the stubbed attachImpl */ - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), channel = realtime.channels.get('multiple_pending'), originalAttachImpl = channel.attachImpl; @@ -1667,7 +1671,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { try { expect(results.length).to.equal(10, 'Check all ten clients are there'); } catch (err) { @@ -1688,10 +1692,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check that a LEAVE message is published for anyone in the local presence * set but missing from a sync */ it('leave_published_for_member_missing_from_sync', function (done) { - var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), + var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }), continuousClientId = 'continuous', goneClientId = 'gone', - continuousRealtime = helper.AblyRealtime({ clientId: continuousClientId }), + continuousRealtime = helper.AblyRealtimePromise({ clientId: continuousClientId }), channelName = 'leave_published_for_member_missing_from_sync', channel = realtime.channels.get(channelName), continuousChannel = continuousRealtime.channels.get(channelName); @@ -1706,10 +1710,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - continuousChannel.attach(cb); + whenPromiseSettles(continuousChannel.attach(), cb); }, function (cb) { - continuousChannel.presence.enter(cb); + whenPromiseSettles(continuousChannel.presence.enter(), cb); }, function (cb) { realtime.connection.whenState('connected', function () { @@ -1717,10 +1721,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { - channel.presence.get({ waitForSync: true }, function (err, members) { + whenPromiseSettles(channel.presence.get({ waitForSync: true }), function (err, members) { try { expect(members && members.length).to.equal(1, 'Check one member present'); } catch (err) { @@ -1753,7 +1757,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.presence.get(function (err, members) { + whenPromiseSettles(channel.presence.get(), function (err, members) { try { expect(members && members.length).to.equal(2, 'Check two members present'); } catch (err) { @@ -1778,7 +1782,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.sync(); }, function (cb) { - channel.presence.get({ waitForSync: true }, function (err, members) { + whenPromiseSettles(channel.presence.get({ waitForSync: true }), function (err, members) { try { expect(members && members.length).to.equal(1, 'Check back to one member present'); expect(members && members[0] && members[0].clientId).to.equal( @@ -1803,7 +1807,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check that a LEAVE message is published for anyone in the local presence * set if get an ATTACHED with no HAS_PRESENCE */ it('leave_published_for_members_on_presenceless_attached', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), channelName = 'leave_published_for_members_on_presenceless_attached', channel = realtime.channels.get(channelName), fakeClientId = 'faker'; @@ -1817,7 +1821,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { /* Inject a member locally */ @@ -1842,7 +1846,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.presence.get(function (err, members) { + whenPromiseSettles(channel.presence.get(), function (err, members) { try { expect(members && members.length).to.equal(1, 'Check one member present'); } catch (err) { @@ -1873,7 +1877,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); }, function (cb) { - channel.presence.get(function (err, members) { + whenPromiseSettles(channel.presence.get(), function (err, members) { try { expect(members && members.length).to.equal(0, 'Check no members present'); } catch (err) { @@ -1895,9 +1899,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and only members that changed between ATTACHED states should result in * presence events */ it('suspended_preserves_presence', function (done) { - var mainRealtime = helper.AblyRealtime({ clientId: 'main', logLevel: 4 }), - continuousRealtime = helper.AblyRealtime({ clientId: 'continuous', logLevel: 4 }), - leavesRealtime = helper.AblyRealtime({ clientId: 'leaves', logLevel: 4 }), + var mainRealtime = helper.AblyRealtimePromise({ clientId: 'main', logLevel: 4 }), + continuousRealtime = helper.AblyRealtimePromise({ clientId: 'continuous', logLevel: 4 }), + leavesRealtime = helper.AblyRealtimePromise({ clientId: 'leaves', logLevel: 4 }), channelName = 'suspended_preserves_presence', mainChannel = mainRealtime.channels.get(channelName); @@ -1914,10 +1918,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { - channel.presence.enter(cb); + whenPromiseSettles(channel.presence.enter(), cb); }, ], outerCb @@ -1946,7 +1950,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel([waitFor('leaves'), enter(leavesRealtime)], cb); }, function (cb) { - mainChannel.presence.get(function (err, members) { + whenPromiseSettles(mainChannel.presence.get(), function (err, members) { try { expect(members.length).to.equal(3, 'Check all three expected members here'); } catch (err) { @@ -1960,7 +1964,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async helper.becomeSuspended(mainRealtime, cb); }, function (cb) { - mainChannel.presence.get(function (err) { + whenPromiseSettles(mainChannel.presence.get(), function (err) { /* Check RTP11d: get() returns an error by default */ try { expect(err, 'Check error returned by get() while suspended').to.be.ok; @@ -1973,7 +1977,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - mainChannel.presence.get({ waitForSync: false }, function (err, members) { + whenPromiseSettles(mainChannel.presence.get({ waitForSync: false }), function (err, members) { /* Check RTP11d: get() works while suspended if waitForSync: false */ try { expect(!err, 'Check no error returned by get() while suspended if waitForSync: false').to.be.ok; @@ -2010,7 +2014,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async setTimeout(cb, 1000); }, function (cb) { - mainChannel.presence.get(function (err, members) { + whenPromiseSettles(mainChannel.presence.get(), function (err, members) { try { expect(members && members.length).to.equal(3, 'Check three expected members here'); } catch (err) { @@ -2033,13 +2037,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * comparisons. */ it('presence_many_updates', function (done) { - var client = helper.AblyRealtime({ clientId: testClientId }); + var client = helper.AblyRealtimePromise({ clientId: testClientId }); var channel = client.channels.get('presence_many_updates'), presence = channel.presence, numUpdates = 0; - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, client, err); } @@ -2050,7 +2054,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.timesSeries( 15, function (i, cb) { - presence.update(i.toString(), cb); + whenPromiseSettles(presence.update(i.toString()), cb); }, function (err) { if (err) { @@ -2077,7 +2081,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var options = { clientId: testClientId, internal: { promises: true } }; it('enter_get', function (done) { - var client = helper.AblyRealtime(options); + var client = helper.AblyRealtimePromise(options); var channel = client.channels.get('presence_promise_get'); var testData = 'test_presence_data'; @@ -2106,7 +2110,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('update', function (done) { - var client = helper.AblyRealtime(options); + var client = helper.AblyRealtimePromise(options); var channel = client.channels.get('presence_promise_update'); var testData1 = 'test_presence_data1'; var testData2 = 'test_presence_data2'; @@ -2158,7 +2162,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('enterClient_leaveClient', function (done) { - var client = helper.AblyRealtime(options); + var client = helper.AblyRealtimePromise(options); var channel = client.channels.get('presence_promise_leaveClient'); var idx = 0; @@ -2208,7 +2212,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('history', function (done) { - var client = helper.AblyRealtime(options); + var client = helper.AblyRealtimePromise(options); var channel = client.channels.get('presence_promise_history'); channel.presence .history() @@ -2222,7 +2226,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('subscribe', function (done) { - var client = helper.AblyRealtime(options); + var client = helper.AblyRealtimePromise(options); var channel = client.channels.get('presence_promise_subscribe'); channel.presence .subscribe(function () {}) diff --git a/test/realtime/reauth.test.js b/test/realtime/reauth.test.js index de5f1ab36d..87b44ab77d 100644 --- a/test/realtime/reauth.test.js +++ b/test/realtime/reauth.test.js @@ -6,6 +6,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var rest; var mixin = helper.Utils.mixin; var displayError = helper.displayError; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/reauth', function () { this.timeout(60 * 1000); @@ -16,7 +17,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { done(err); return; } - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); done(); }); }); @@ -25,7 +26,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function getToken(tokenParams) { return function (state, callback) { - rest.auth.requestToken(tokenParams, null, function (err, token) { + whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, token) { callback(err, mixin(state, { token: token })); }); }; @@ -43,7 +44,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function connectWithToken() { return function (state, callback) { - var realtime = helper.AblyRealtime(mixin({ token: state.token }, state.realtimeOpts)); + var realtime = helper.AblyRealtimePromise(mixin({ token: state.token }, state.realtimeOpts)); realtime.connection.once('connected', function () { callback(null, mixin(state, { realtime: realtime })); }); @@ -72,7 +73,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { async.parallel( [ function (cb) { - state.realtime.auth.authorize(null, { token: state.token }, cb); + whenPromiseSettles(state.realtime.auth.authorize(null, { token: state.token }), cb); }, function (cb) { state.realtime.connection.on('update', function (stateChange) { @@ -90,7 +91,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function attach(channelName) { return function (state, callback) { var channel = state.realtime.channels.get(channelName); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { callback(err, state); }); }; @@ -146,7 +147,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function checkCantAttach(channelName) { return function (state, callback) { var channel = state.realtime.channels.get(channelName); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err && err.code === 40160) { callback(null, state); } else { @@ -159,7 +160,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function checkCanPublish(channelName) { return function (state, callback) { var channel = state.realtime.channels.get(channelName); - channel.publish(null, null, function (err) { + whenPromiseSettles(channel.publish(null, null), function (err) { callback(err, state); }); }; @@ -168,7 +169,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function checkCantPublish(channelName) { return function (state, callback) { var channel = state.realtime.channels.get(channelName); - channel.publish(null, null, function (err) { + whenPromiseSettles(channel.publish(null, null), function (err) { if (err && err.code === 40160) { callback(null, state); } else { diff --git a/test/realtime/resume.test.js b/test/realtime/resume.test.js index 31b1dab3c6..1c0e96ce32 100644 --- a/test/realtime/resume.test.js +++ b/test/realtime/resume.test.js @@ -6,6 +6,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var simulateDroppedConnection = helper.simulateDroppedConnection; var testOnAllTransports = helper.testOnAllTransports; var bestTransport = helper.bestTransport; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/resume', function () { this.timeout(120 * 1000); @@ -31,7 +32,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { receivingChannel.unsubscribe(event); callback(); }); - sendingChannel.publish(event, message, function (err) { + whenPromiseSettles(sendingChannel.publish(event, message), function (err) { if (err) callback(err); }); } @@ -43,15 +44,15 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function resume_inactive(done, channelName, txOpts, rxOpts) { var count = 5; - var txRest = helper.AblyRest(mixin(txOpts)); - var rxRealtime = helper.AblyRealtime(mixin(rxOpts)); + var txRest = helper.AblyRestPromise(mixin(txOpts)); + var rxRealtime = helper.AblyRealtimePromise(mixin(rxOpts)); var rxChannel = rxRealtime.channels.get(channelName); var txChannel = txRest.channels.get(channelName); var rxCount = 0; function phase0(callback) { - rxChannel.attach(callback); + whenPromiseSettles(rxChannel.attach(), callback); } function phase1(callback) { @@ -154,15 +155,15 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function resume_active(done, channelName, txOpts, rxOpts) { var count = 5; - var txRest = helper.AblyRest(mixin(txOpts)); - var rxRealtime = helper.AblyRealtime(mixin(rxOpts)); + var txRest = helper.AblyRestPromise(mixin(txOpts)); + var rxRealtime = helper.AblyRealtimePromise(mixin(rxOpts)); var rxChannel = rxRealtime.channels.get(channelName); var txChannel = txRest.channels.get(channelName); var rxCount = 0; function phase0(callback) { - rxChannel.attach(callback); + whenPromiseSettles(rxChannel.attach(), callback); } function phase1(callback) { @@ -188,7 +189,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var txCount = 0; function ph2TxOnce() { - txChannel.publish('sentWhileDisconnected', 'phase 2, message ' + txCount, function (err) { + whenPromiseSettles(txChannel.publish('sentWhileDisconnected', 'phase 2, message ' + txCount), function (err) { if (err) callback(err); }); if (++txCount == count) { @@ -275,7 +276,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'resume_lost_continuity', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtime(realtimeOpts), + var realtime = helper.AblyRealtimePromise(realtimeOpts), connection = realtime.connection, attachedChannelName = 'resume_lost_continuity_attached', suspendedChannelName = 'resume_lost_continuity_suspended', @@ -291,7 +292,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }, function (cb) { suspendedChannel.state = 'suspended'; - attachedChannel.attach(cb); + whenPromiseSettles(attachedChannel.attach(), cb); }, function (cb) { /* Sabotage the resume */ @@ -341,7 +342,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'resume_token_error', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtime(mixin(realtimeOpts, { useTokenAuth: true })), + var realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { useTokenAuth: true })), badtoken, connection = realtime.connection; @@ -353,7 +354,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); }, function (cb) { - realtime.auth.requestToken({ ttl: 1 }, null, function (err, token) { + whenPromiseSettles(realtime.auth.requestToken({ ttl: 1 }, null), function (err, token) { badtoken = token; cb(err); }); @@ -394,7 +395,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'resume_fatal_error', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtime(realtimeOpts), + var realtime = helper.AblyRealtimePromise(realtimeOpts), connection = realtime.connection; async.series( @@ -444,7 +445,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { * TODO: enable once realtime supports this */ it('channel_resumed_flag', function (done) { - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), realtimeTwo, recoveryKey, connection = realtime.connection, @@ -475,7 +476,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { helper.becomeSuspended(realtime, cb); }, function (cb) { - realtimeTwo = helper.AblyRealtime({ recover: recoveryKey }); + realtimeTwo = helper.AblyRealtimePromise({ recover: recoveryKey }); realtimeTwo.connection.once('connected', function (stateChange) { if (stateChange.reason) { cb(stateChange.reason); @@ -508,7 +509,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { * Check the library doesn't try to resume once the connectionStateTtl has expired */ it('no_resume_once_suspended', function (done) { - var realtime = helper.AblyRealtime(), + var realtime = helper.AblyRealtimePromise(), connection = realtime.connection, channelName = 'no_resume_once_suspended'; @@ -547,7 +548,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { */ it('no_resume_last_activity', function (done) { /* Specify a best transport so that upgrade activity doesn't reset the last activity timer */ - var realtime = helper.AblyRealtime({ transports: [bestTransport] }), + var realtime = helper.AblyRealtimePromise({ transports: [bestTransport] }), connection = realtime.connection, connectionManager = connection.connectionManager; @@ -573,11 +574,11 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var testName = 'resume_rewind_1'; var testMessage = { foo: 'bar', count: 1, status: 'active' }; try { - var sender_realtime = helper.AblyRealtime(); + var sender_realtime = helper.AblyRealtimePromise(); var sender_channel = sender_realtime.channels.get(testName); sender_channel.subscribe(function (message) { - var receiver_realtime = helper.AblyRealtime(); + var receiver_realtime = helper.AblyRealtimePromise(); var receiver_channel = receiver_realtime.channels.get(testName, { params: { rewind: 1 } }); receiver_channel.subscribe(function (message) { @@ -591,7 +592,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { return; } - var resumed_receiver_realtime = helper.AblyRealtime(); + var resumed_receiver_realtime = helper.AblyRealtimePromise(); var connectionManager = resumed_receiver_realtime.connection.connectionManager; var sendOrig = connectionManager.send; @@ -629,8 +630,8 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { it('recover multiple channels', function (done) { const NUM_MSGS = 5; - const txRest = helper.AblyRest(); - const rxRealtime = helper.AblyRealtime( + const txRest = helper.AblyRestPromise(); + const rxRealtime = helper.AblyRealtimePromise( { transports: [helper.bestTransport], }, @@ -643,7 +644,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { const rxChannels = channelNames.map((name) => rxRealtime.channels.get(name)); function attachChannels(callback) { - async.each(rxChannels, (channel, cb) => channel.attach(cb), callback); + async.each(rxChannels, (channel, cb) => whenPromiseSettles(channel.attach(), cb), callback); } function publishSubscribeWhileConnectedOnce(callback) { @@ -673,7 +674,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { channelNames, (name, cb) => { const tx = txRest.channels.get(name); - tx.publish('sentWhileDisconnected', null, cb); + whenPromiseSettles(tx.publish('sentWhileDisconnected', null), cb); }, callback ); @@ -738,7 +739,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { return; } - rxRealtimeRecover = helper.AblyRealtime({ recover: recoveryKey }); + rxRealtimeRecover = helper.AblyRealtimePromise({ recover: recoveryKey }); rxRecoverChannels = channelNames.map((name) => rxRealtimeRecover.channels.get(name)); subscribeRecoveredMessages(function (err) { diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index 5f62dabe98..2359fd830e 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -7,6 +7,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var closeAndFinish = helper.closeAndFinish; var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; var monitorConnection = helper.monitorConnection; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/sync', function () { this.timeout(60 * 1000); @@ -43,7 +44,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * different presence set */ it('sync_existing_set', async function () { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), channelName = 'syncexistingset', channel = realtime.channels.get(channelName); @@ -92,7 +93,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { try { expect(results.length).to.equal(2, 'Check correct number of results'); expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; @@ -135,7 +136,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { try { expect(results.length).to.equal(2, 'Check correct number of results'); expect(channel.presence.syncComplete, 'Check in sync').to.be.ok; @@ -163,7 +164,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * middle of the sync should should discard the former, but not the latter * */ it('sync_member_arrives_in_middle', async function () { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), channelName = 'sync_member_arrives_in_middle', channel = realtime.channels.get(channelName); @@ -240,7 +241,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async err ? reject(err) : resolve(); }; - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { if (err) { closeAndFinish(done, realtime, err); return; @@ -265,7 +266,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Presence message that was in the sync arrives again as a normal message, after it's come in the sync */ it('sync_member_arrives_normally_after_came_in_sync', async function () { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), channelName = 'sync_member_arrives_normally_after_came_in_sync', channel = realtime.channels.get(channelName); @@ -326,7 +327,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async err ? reject(err) : resolve(); }; - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { if (err) { closeAndFinish(done, realtime, err); return; @@ -348,7 +349,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Presence message that will be in the sync arrives as a normal message, before it comes in the sync */ it('sync_member_arrives_normally_before_comes_in_sync', async function () { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), channelName = 'sync_member_arrives_normally_before_comes_in_sync', channel = realtime.channels.get(channelName); @@ -409,7 +410,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async err ? reject(err) : resolve(); }; - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { if (err) { closeAndFinish(done, realtime, err); return; @@ -432,7 +433,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * index, and synthesized leaves, check that the end result is correct */ it('presence_ordering', async function () { - var realtime = helper.AblyRealtime({ autoConnect: false }), + var realtime = helper.AblyRealtimePromise({ autoConnect: false }), channelName = 'sync_ordering', channel = realtime.channels.get(channelName); @@ -561,7 +562,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var done = function (err) { err ? reject(err) : resolve(); }; - channel.presence.get(function (err, results) { + whenPromiseSettles(channel.presence.get(), function (err, results) { if (err) { closeAndFinish(done, realtime, err); return; @@ -588,8 +589,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presence_sync_interruptus', function (done) { var channelName = 'presence_sync_interruptus'; var interrupterClientId = 'dark_horse'; - var enterer = helper.AblyRealtime(); - var syncer = helper.AblyRealtime(); + var enterer = helper.AblyRealtimePromise(); + var syncer = helper.AblyRealtimePromise(); var entererChannel = enterer.channels.get(channelName); var syncerChannel = syncer.channels.get(channelName); @@ -613,13 +614,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async [ waitForBothConnect, function (cb) { - entererChannel.attach(cb); + whenPromiseSettles(entererChannel.attach(), cb); }, function (cb) { async.times( 110, function (i, presCb) { - entererChannel.presence.enterClient(i.toString(), null, presCb); + whenPromiseSettles(entererChannel.presence.enterClient(i.toString(), null), presCb); }, cb ); @@ -645,10 +646,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); } }; - syncerChannel.attach(cb); + whenPromiseSettles(syncerChannel.attach(), cb); }, function (cb) { - syncerChannel.presence.get(function (err, presenceSet) { + whenPromiseSettles(syncerChannel.presence.get(), function (err, presenceSet) { try { expect(presenceSet && presenceSet.length).to.equal(111, 'Check everyone’s in presence set'); } catch (err) { diff --git a/test/realtime/upgrade.test.js b/test/realtime/upgrade.test.js index 08c785e4b5..674035ad47 100644 --- a/test/realtime/upgrade.test.js +++ b/test/realtime/upgrade.test.js @@ -18,6 +18,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; var bestTransport = helper.bestTransport; + var whenPromiseSettles = helper.whenPromiseSettles; if (bestTransport === 'web_socket') { describe('realtime/upgrade', function () { @@ -29,7 +30,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai done(err); return; } - rest = helper.AblyRest(); + rest = helper.AblyRestPromise(); done(); }); }); @@ -42,13 +43,13 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('publishpreupgrade', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* connect and attach */ realtime.connection.on('connected', function () { //console.log('publishpreupgrade: connected'); var testMsg = 'Hello world'; var rtChannel = realtime.channels.get('publishpreupgrade'); - rtChannel.attach(function (err) { + whenPromiseSettles(rtChannel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -67,7 +68,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai /* publish event */ var restChannel = rest.channels.get('publishpreupgrade'); - restChannel.publish('event0', testMsg, function (err) { + whenPromiseSettles(restChannel.publish('event0', testMsg), function (err) { if (err) { closeAndFinish(done, realtime, err); } @@ -86,7 +87,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('publishpostupgrade0', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* subscribe to event */ var rtChannel = realtime.channels.get('publishpostupgrade0'); @@ -113,7 +114,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai if (transport.toString().match(/wss?\:/)) { if (rtChannel.state == 'attached') { //console.log('*** publishpostupgrade0: publishing (channel attached on transport active) ...'); - restChannel.publish('event0', testMsg, function (err) { + whenPromiseSettles(restChannel.publish('event0', testMsg), function (err) { //console.log('publishpostupgrade0: publish returned err = ' + displayError(err)); if (err) { closeAndFinish(done, realtime, err); @@ -122,7 +123,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai } else { rtChannel.on('attached', function () { //console.log('*** publishpostupgrade0: publishing (channel attached after wait) ...'); - restChannel.publish('event0', testMsg, function (err) { + whenPromiseSettles(restChannel.publish('event0', testMsg), function (err) { //console.log('publishpostupgrade0: publish returned err = ' + displayError(err)); if (err) { closeAndFinish(done, realtime, err); @@ -144,7 +145,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('publishpostupgrade1', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* subscribe to event */ var rtChannel = realtime.channels.get('publishpostupgrade1'); @@ -219,7 +220,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai checkFinish(); }; var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); var channel = realtime.channels.get('upgradepublish0'); /* subscribe to event */ channel.subscribe( @@ -253,7 +254,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai checkFinish(); }; var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); var channel = realtime.channels.get('upgradepublish1'); /* subscribe to event */ channel.subscribe( @@ -278,7 +279,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; var cometDeactivated = false; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* check that we see the transport we're interested in get activated, * and that we see the comet transport deactivated */ var failTimer = setTimeout(function () { @@ -320,7 +321,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat0', function (done) { var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -353,7 +354,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat1', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -386,7 +387,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat2', function (done) { var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -432,7 +433,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat3', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtime(transportOpts); + var realtime = helper.AblyRealtimePromise(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -479,7 +480,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai try { /* on base transport active */ - realtime = helper.AblyRealtime({ transports: helper.availableTransports }); + realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }); realtime.connection.connectionManager.once('transport.active', function (transport) { expect( transport.toString().indexOf('/comet/') > -1, @@ -512,7 +513,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai /* Check events not still paused */ var channel = realtime.channels.get('unrecoverableUpgrade'); - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -534,7 +535,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai * seamlessly transferred to the websocket transport and published there */ it('message_timeout_stalling_upgrade', function (done) { - var realtime = helper.AblyRealtime({ transports: helper.availableTransports, httpRequestTimeout: 3000 }), + var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports, httpRequestTimeout: 3000 }), channel = realtime.channels.get('timeout1'), connectionManager = realtime.connection.connectionManager; @@ -557,7 +558,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai }); }, function (cb) { - channel.publish('event', null, function (err) { + whenPromiseSettles(channel.publish('event', null), function (err) { try { expect(!err, 'Successfully published message').to.be.ok; } catch (err) { @@ -580,7 +581,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai * and subsequent connections do not upgrade */ it('persist_transport_prefs', function (done) { - var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), + var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }), connection = realtime.connection, connectionManager = connection.connectionManager; @@ -642,7 +643,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai * Check that upgrades succeed even if the original transport dies before the sync */ it('upgrade_original_transport_dies', function (done) { - var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), + var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }), connection = realtime.connection, connectionManager = connection.connectionManager; From 45b9c3bc652502a8cd84aba6fcb11fcce5517e6f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 22 Jun 2023 09:59:10 -0300 Subject: [PATCH 120/468] Remove obsolete Promise-based Realtime tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No longer needed as of 43a2d1d. I’ve kept the tests for EventEmitter (since, as mentioned in aforementioned commit, the main body of the tests uses the callback-based API), and also for init.test.js, which tests the result of `require('../../promises')`. --- test/realtime/connection.test.js | 26 ----- test/realtime/message.test.js | 45 --------- test/realtime/presence.test.js | 165 ------------------------------- 3 files changed, 236 deletions(-) diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index 3922e745fb..d0923de040 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -283,31 +283,5 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); monitorConnection(done, realtime); }); - - if (typeof Promise !== 'undefined') { - describe('connection_promise', function () { - it('ping', function (done) { - var client = helper.AblyRealtimePromise({ internal: { promises: true } }); - - client.connection - .once('connected') - .then(function () { - client.connection - .ping() - .then(function (responseTime) { - expect(typeof responseTime).to.equal('number', 'check that a responseTime returned'); - expect(responseTime > 0, 'check that responseTime was positive').to.be.ok; - closeAndFinish(done, client); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }); - }); - } }); }); diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index c5463cb692..623f1c0946 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -1151,51 +1151,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - if (typeof Promise !== 'undefined') { - it('publishpromise', function (done) { - var realtime = helper.AblyRealtimePromise({ internal: { promises: true } }); - var channel = realtime.channels.get('publishpromise'); - - var publishPromise = realtime.connection - .once('connected') - .then(function (connectionStateChange) { - expect(connectionStateChange.current).to.equal( - 'connected', - 'Check promise is resolved with a connectionStateChange' - ); - return channel.attach(); - }) - .then(function () { - return channel.publish('name', 'data'); - }) - ['catch'](function (err) { - closeAndFinish(done, realtime, err); - }); - - var subscribePromise; - var messagePromise = new Promise(function (msgResolve) { - subscribePromise = channel.subscribe('name', function (msg) { - msgResolve(); - }); - }); - - var channelFailedPromise = realtime.channels - .get(':invalid') - .attach() - ['catch'](function (err) { - expect(err.code).to.equal(40010, 'Check err passed through correctly'); - }); - - Promise.all([publishPromise, subscribePromise, messagePromise, channelFailedPromise]) - .then(function () { - closeAndFinish(done, realtime); - }) - ['catch'](function (err) { - closeAndFinish(done, realtime, err); - }); - }); - } - it('subscribes to filtered channel', function (done) { var testData = [ { diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 0f5d08bbfb..4324c8f333 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -2075,170 +2075,5 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); }); }); - - if (typeof Promise !== 'undefined') { - describe('presence_promise', function () { - var options = { clientId: testClientId, internal: { promises: true } }; - - it('enter_get', function (done) { - var client = helper.AblyRealtimePromise(options); - var channel = client.channels.get('presence_promise_get'); - var testData = 'test_presence_data'; - - channel.presence - .subscribe(function () { - channel.presence - .get() - .then(function (members) { - expect(members.length).to.equal(1, 'Expect test client to be the only member present'); - expect(members[0].clientId).to.equal(testClientId, 'Expected test clientId to be correct'); - expect(members[0].data).to.equal(testData, 'Expected data to be correct'); - closeAndFinish(done, client); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }) - .then(function () { - channel.presence.enter(testData)['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }); - - it('update', function (done) { - var client = helper.AblyRealtimePromise(options); - var channel = client.channels.get('presence_promise_update'); - var testData1 = 'test_presence_data1'; - var testData2 = 'test_presence_data2'; - var idx = 0; - - channel.presence - .subscribe(function () { - if (idx === 0) { - idx++; - channel.presence - .get() - .then(function (members) { - expect(members.length).to.equal(1, 'Expect test client to be the only member present'); - expect(members[0].clientId).to.equal(testClientId, 'Expected test clientId to be correct'); - expect(members[0].data).to.equal(testData1, 'Expected data to be correct'); - channel.presence.update(testData2)['catch'](function (err) { - // If idx == 2 this means the update was succesful but the connection was closed before it was ACKed - if (idx !== 2) { - closeAndFinish(done, client, err); - } - }); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - } else { - idx++; - channel.presence - .get() - .then(function (members) { - expect(members.length).to.equal(1, 'Expect test client to be the only member present'); - expect(members[0].clientId).to.equal(testClientId, 'Expected test clientId to be correct'); - expect(members[0].data).to.equal(testData2, 'Expected data to be correct'); - closeAndFinish(done, client); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - } - }) - .then(function () { - channel.presence.enter(testData1)['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }); - - it('enterClient_leaveClient', function (done) { - var client = helper.AblyRealtimePromise(options); - var channel = client.channels.get('presence_promise_leaveClient'); - var idx = 0; - - channel.presence - .subscribe(function () { - if (idx === 0) { - idx++; - channel.presence - .get() - .then(function (members) { - expect(members.length).to.equal(1, 'Expect test client to be the only member present'); - expect(members[0].clientId).to.equal(testClientId, 'Expected test clientId to be correct'); - channel.presence.leaveClient(testClientId)['catch'](function (err) { - // If idx == 2 this means the leave was succesful but the connection was closed before it was ACKed - if (idx !== 2) { - closeAndFinish(done, client, err); - } - }); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - } else { - idx++; - channel.presence - .get() - .then(function (members) { - expect(members.length).to.equal(0, 'Expect test client to no longer be present'); - closeAndFinish(done, client); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - } - }) - .then(function () { - channel.presence - .enterClient(testClientId) - .then(function () {}) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }); - - it('history', function (done) { - var client = helper.AblyRealtimePromise(options); - var channel = client.channels.get('presence_promise_history'); - channel.presence - .history() - .then(function (page) { - // Tests for promisified PaginatedResource exist elsewhere in the repo so are omitted here - closeAndFinish(done, client); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }); - - it('subscribe', function (done) { - var client = helper.AblyRealtimePromise(options); - var channel = client.channels.get('presence_promise_subscribe'); - channel.presence - .subscribe(function () {}) - .then(function () { - expect(channel.state).to.equal('attached'); - closeAndFinish(done, client); - }) - ['catch'](function (err) { - closeAndFinish(done, client, err); - }); - }); - }); - } }); }); From d0f034ecc0f1d63fda1893a3f7208da75b2955d2 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 19 Jun 2023 16:28:18 -0300 Subject: [PATCH 121/468] Remove "Promise" from name of Ably*Promise helpers Thus replacing the (unused as of 6fe4914) callback versions. --- test/browser/connection.test.js | 32 +++---- test/browser/http.test.js | 2 +- test/browser/simple.test.js | 2 +- test/common/modules/client_module.js | 10 --- test/common/modules/shared_helper.js | 6 +- test/realtime/auth.test.js | 126 +++++++++++++-------------- test/realtime/channel.test.js | 76 ++++++++-------- test/realtime/connection.test.js | 12 +-- test/realtime/connectivity.test.js | 17 ++-- test/realtime/crypto.test.js | 30 +++---- test/realtime/delta.test.js | 10 +-- test/realtime/encoding.test.js | 8 +- test/realtime/event_emitter.test.js | 34 ++++---- test/realtime/failure.test.js | 22 ++--- test/realtime/history.test.js | 4 +- test/realtime/init.test.js | 26 +++--- test/realtime/message.test.js | 58 ++++++------ test/realtime/presence.test.js | 92 +++++++++---------- test/realtime/reauth.test.js | 4 +- test/realtime/resume.test.js | 34 ++++---- test/realtime/sync.test.js | 14 +-- test/realtime/upgrade.test.js | 30 +++---- test/rest/auth.test.js | 26 +++--- test/rest/capability.test.js | 2 +- test/rest/fallbacks.test.js | 4 +- test/rest/history.test.js | 2 +- test/rest/http.test.js | 2 +- test/rest/init.test.js | 14 +-- test/rest/message.test.js | 18 ++-- test/rest/presence.test.js | 2 +- test/rest/push.test.js | 26 +++--- test/rest/request.test.js | 6 +- test/rest/stats.test.js | 2 +- test/rest/status.test.js | 2 +- test/rest/time.test.js | 2 +- 35 files changed, 368 insertions(+), 389 deletions(-) diff --git a/test/browser/connection.test.js b/test/browser/connection.test.js index 3ae2abdba3..d615c133c8 100644 --- a/test/browser/connection.test.js +++ b/test/browser/connection.test.js @@ -51,7 +51,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('device_going_offline_causes_disconnected_state', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), connection = realtime.connection, offlineEvent = new Event('offline', { bubbles: true }); @@ -94,7 +94,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { it('device_going_online_causes_disconnected_connection_to_reconnect_immediately', function (done) { /* Give up trying to connect fairly quickly */ - var realtime = helper.AblyRealtimePromise({ realtimeRequestTimeout: 1000 }), + var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 1000 }), connection = realtime.connection, onlineEvent = new Event('online', { bubbles: true }); @@ -139,7 +139,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { it('device_going_online_causes_suspended_connection_to_reconnect_immediately', function (done) { /* move to suspended state after 2s of being disconnected */ - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ disconnectedRetryTimeout: 500, realtimeRequestTimeout: 500, connectionStateTtl: 2000, @@ -183,7 +183,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('device_going_online_causes_connecting_connection_to_retry_attempt', function (done) { - var realtime = helper.AblyRealtimePromise({}), + var realtime = helper.AblyRealtime({}), connection = realtime.connection, onlineEvent = new Event('online', { bubbles: true }), oldTransport, @@ -223,7 +223,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { cb(true); }, }, - realtime = helper.AblyRealtimePromise(realtimeOpts), + realtime = helper.AblyRealtime(realtimeOpts), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -242,7 +242,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { return; } - var newRealtime = helper.AblyRealtimePromise(realtimeOpts); + var newRealtime = helper.AblyRealtime(realtimeOpts); newRealtime.connection.once('connected', function () { try { expect( @@ -264,7 +264,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { cb(false); }, }; - var realtime = helper.AblyRealtimePromise(realtimeOpts), + var realtime = helper.AblyRealtime(realtimeOpts), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -283,7 +283,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { return; } - var newRealtime = helper.AblyRealtimePromise(realtimeOpts); + var newRealtime = helper.AblyRealtime(realtimeOpts); newRealtime.connection.once('connected', function () { try { expect( @@ -301,7 +301,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('page_refresh_with_close_on_unload', function (done) { - var realtime = helper.AblyRealtimePromise({ closeOnUnload: true }), + var realtime = helper.AblyRealtime({ closeOnUnload: true }), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -321,7 +321,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('page_refresh_with_manual_recovery', function (done) { - var realtime = helper.AblyRealtimePromise({ closeOnUnload: false }), + var realtime = helper.AblyRealtime({ closeOnUnload: false }), refreshEvent = new Event('beforeunload', { bubbles: true }); monitorConnection(done, realtime); @@ -342,7 +342,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { return; } - var newRealtime = helper.AblyRealtimePromise({ recover: recoveryKey }); + var newRealtime = helper.AblyRealtime({ recover: recoveryKey }); newRealtime.connection.once('connected', function () { try { expect( @@ -359,7 +359,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('persist_preferred_transport', function (done) { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); realtime.connection.connectionManager.on(function (transport) { if (this.event === 'transport.active' && transport.shortName === 'web_socket') { @@ -381,7 +381,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var transportPreferenceName = 'ably-transport-preference'; window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'web_socket' })); - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); realtime.connection.connectionManager.on(function (transport) { if (this.event === 'transport.active') { @@ -400,7 +400,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { it('use_persisted_transport1', function (done) { window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'xhr_streaming' })); - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); realtime.connection.connectionManager.on(function (transport) { if (this.event === 'transport.active') { @@ -417,7 +417,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('browser_transports', function (done) { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); try { expect(realtime.connection.connectionManager.baseTransport).to.equal('xhr_polling'); expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal([ @@ -437,7 +437,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { * realtime) */ it('connection behaviour with a proxy through which streaming is broken', function (done) { - const realtime = helper.AblyRealtimePromise({ + const realtime = helper.AblyRealtime({ transportParams: { simulateNoStreamingProxy: true, maxStreamDuration: 7500, diff --git a/test/browser/http.test.js b/test/browser/http.test.js index 8c810f04b0..5db5ea7894 100644 --- a/test/browser/http.test.js +++ b/test/browser/http.test.js @@ -12,7 +12,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { initialXhrSupported = Ably.Rest.Platform.Config.xhrSupported; Ably.Rest.Platform.Config.xhrSupported = false; helper.setupApp(function () { - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); done(); }); }); diff --git a/test/browser/simple.test.js b/test/browser/simple.test.js index e4ca140ec1..bfc22014d7 100644 --- a/test/browser/simple.test.js +++ b/test/browser/simple.test.js @@ -23,7 +23,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { function realtimeConnection(transports) { var options = {}; if (transports) options.transports = transports; - return helper.AblyRealtimePromise(options); + return helper.AblyRealtime(options); } function failWithin(timeInSeconds, done, ably, description) { diff --git a/test/common/modules/client_module.js b/test/common/modules/client_module.js index df26c74cff..d0806d6d4b 100644 --- a/test/common/modules/client_module.js +++ b/test/common/modules/client_module.js @@ -23,26 +23,16 @@ define(['ably', 'globals', 'test/common/modules/testapp_module'], function (Ably } function ablyRest(options) { - return new Ably.Rest(ablyClientOptions(options)); - } - - function ablyRestPromise(options) { return new Ably.Rest.Promise(ablyClientOptions(options)); } function ablyRealtime(options) { - return new Ably.Realtime(ablyClientOptions(options)); - } - - function ablyRealtimePromise(options) { return new Ably.Realtime.Promise(ablyClientOptions(options)); } return (module.exports = { Ably: Ably, AblyRest: ablyRest, - AblyRestPromise: ablyRestPromise, AblyRealtime: ablyRealtime, - AblyRealtimePromise: ablyRealtimePromise, }); }); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 8af3164629..0790322075 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -164,10 +164,10 @@ define([ function restTestOnJsonMsgpack(name, testFn, skip) { var itFn = skip ? it.skip : it; itFn(name + ' with binary protocol', async function () { - await testFn(new clientModule.AblyRestPromise({ useBinaryProtocol: true }), name + '_binary'); + await testFn(new clientModule.AblyRest({ useBinaryProtocol: true }), name + '_binary'); }); itFn(name + ' with text protocol', async function () { - await testFn(new clientModule.AblyRestPromise({ useBinaryProtocol: false }), name + '_text'); + await testFn(new clientModule.AblyRest({ useBinaryProtocol: false }), name + '_text'); }); } @@ -226,9 +226,7 @@ define([ Ably: clientModule.Ably, AblyRest: clientModule.AblyRest, - AblyRestPromise: clientModule.AblyRestPromise, AblyRealtime: clientModule.AblyRealtime, - AblyRealtimePromise: clientModule.AblyRealtimePromise, Utils: utils, loadTestData: testAppManager.loadJsonData, diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index b36ca70159..92dde3019a 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -40,7 +40,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var rest = helper.AblyRestPromise({ queryTime: true }); + var rest = helper.AblyRest({ queryTime: true }); whenPromiseSettles(rest.time(), function (err, time) { if (err) { done(err); @@ -64,7 +64,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Base token generation case */ it('authbase0', function (done) { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); whenPromiseSettles(realtime.auth.requestToken(), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); @@ -88,7 +88,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_json', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); @@ -97,7 +97,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authPath = echoServer + '/?type=json&body=' + encodeURIComponent(JSON.stringify(tokenDetails)); - realtime = helper.AblyRealtimePromise({ authUrl: authPath }); + realtime = helper.AblyRealtime({ authUrl: authPath }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -113,7 +113,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_post_json', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); @@ -122,7 +122,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authUrl = echoServer + '/?type=json&'; - realtime = helper.AblyRealtimePromise({ authUrl: authUrl, authMethod: 'POST', authParams: tokenDetails }); + realtime = helper.AblyRealtime({ authUrl: authUrl, authMethod: 'POST', authParams: tokenDetails }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -138,7 +138,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_plainText', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); @@ -147,7 +147,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authPath = echoServer + '/?type=text&body=' + tokenDetails['token']; - realtime = helper.AblyRealtimePromise({ authUrl: authPath }); + realtime = helper.AblyRealtime({ authUrl: authPath }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -163,7 +163,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthCallback_tokenRequestResponse', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var authCallback = function (tokenParams, callback) { whenPromiseSettles(rest.auth.createTokenRequest(tokenParams, null), function (err, tokenRequest) { if (err) { @@ -179,7 +179,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -200,7 +200,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthCallback_tokenDetailsResponse', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var clientId = 'test clientid'; var authCallback = function (tokenParams, callback) { whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { @@ -218,7 +218,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtimePromise({ authCallback: authCallback, clientId: clientId }); + realtime = helper.AblyRealtime({ authCallback: authCallback, clientId: clientId }); realtime.connection.on('connected', function () { try { @@ -237,7 +237,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthCallback_tokenStringResponse', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var authCallback = function (tokenParams, callback) { whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { if (err) { @@ -253,7 +253,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -275,7 +275,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('auth_useAuthUrl_mixed_authParams_qsParams', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.createTokenRequest(null, null), function (err, tokenRequest) { if (err) { closeAndFinish(done, realtime, err); @@ -296,7 +296,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; var authPath = echoServer + '/qs_to_body' + utils.toQueryString(lowerPrecedenceTokenRequestParts); - realtime = helper.AblyRealtimePromise({ authUrl: authPath, authParams: higherPrecedenceTokenRequestParts }); + realtime = helper.AblyRealtime({ authUrl: authPath, authParams: higherPrecedenceTokenRequestParts }); realtime.connection.on('connected', function () { closeAndFinish(done, realtime); @@ -310,7 +310,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and check that the connection inherits the clientId from the tokenDetails */ it('auth_clientid_inheritance', function (done) { - var rest = helper.AblyRestPromise(), + var rest = helper.AblyRest(), testClientId = 'testClientId'; var authCallback = function (tokenParams, callback) { whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { @@ -322,7 +322,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -350,13 +350,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance2', function (done) { var clientRealtime, testClientId = 'test client id'; - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; } - clientRealtime = helper.AblyRealtimePromise({ token: tokenDetails, clientId: 'WRONG' }); + clientRealtime = helper.AblyRealtime({ token: tokenDetails, clientId: 'WRONG' }); clientRealtime.connection.once('failed', function (stateChange) { try { expect(stateChange.reason.code).to.equal(40102); @@ -376,13 +376,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance3', function (done) { var realtime, testClientId = 'test client id'; - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ clientId: '*' }), function (err, tokenDetails) { if (err) { done(err); return; } - realtime = helper.AblyRealtimePromise({ token: tokenDetails.token, clientId: 'test client id' }); + realtime = helper.AblyRealtime({ token: tokenDetails.token, clientId: 'test client id' }); realtime.connection.on('connected', function () { try { expect(realtime.auth.clientId).to.equal(testClientId); @@ -404,13 +404,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance4', function (done) { var realtime, testClientId = 'test client id'; - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ clientId: '*' }), function (err, tokenDetails) { if (err) { done(err); return; } - realtime = helper.AblyRealtimePromise({ token: tokenDetails, clientId: 'test client id' }); + realtime = helper.AblyRealtime({ token: tokenDetails, clientId: 'test client id' }); realtime.connection.on('connected', function () { try { expect(realtime.auth.clientId).to.equal(testClientId); @@ -432,13 +432,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('auth_clientid_inheritance5', function (done) { var clientRealtime, testClientId = 'test client id'; - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); return; } - clientRealtime = helper.AblyRealtimePromise({ token: tokenDetails.token }); + clientRealtime = helper.AblyRealtime({ token: tokenDetails.token }); clientRealtime.connection.on('connected', function () { try { expect(clientRealtime.auth.clientId).to.equal(testClientId); @@ -457,7 +457,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ function authCallback_failures(realtimeOptions, expectFailure) { return function (done) { - var realtime = helper.AblyRealtimePromise(realtimeOptions); + var realtime = helper.AblyRealtime(realtimeOptions); realtime.connection.on(function (stateChange) { if (stateChange.previous !== 'initialized') { try { @@ -607,7 +607,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('authUrl_403_previously_active', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken(null, null), function (err, tokenDetails) { if (err) { closeAndFinish(done, realtime, err); @@ -616,7 +616,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var authPath = echoServer + '/?type=json&body=' + encodeURIComponent(JSON.stringify(tokenDetails)); - realtime = helper.AblyRealtimePromise({ authUrl: authPath }); + realtime = helper.AblyRealtime({ authUrl: authPath }); realtime.connection.on('connected', function () { /* replace the authUrl and reauth */ @@ -649,16 +649,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_token_expires', function (realtimeOpts) { return function (done) { var clientRealtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ ttl: 5000 }, null), function (err, tokenDetails) { if (err) { done(err); return; } - clientRealtime = helper.AblyRealtimePromise( - mixin(realtimeOpts, { tokenDetails: tokenDetails, queryTime: true }) - ); + clientRealtime = helper.AblyRealtime(mixin(realtimeOpts, { tokenDetails: tokenDetails, queryTime: true })); clientRealtime.connection.on('failed', function () { closeAndFinish(done, clientRealtime, new Error('Failed to connect before token expired')); @@ -685,7 +683,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and all subsequent requests use the time offset */ it('auth_query_time_once', function (done) { - var rest = helper.AblyRestPromise({ queryTime: true }), + var rest = helper.AblyRest({ queryTime: true }), timeRequestCount = 0, originalTime = rest.time; @@ -743,7 +741,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_tokenDetails_expiry_with_authcallback', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var clientId = 'test clientid'; var authCallback = function (tokenParams, callback) { tokenParams.ttl = 5000; @@ -756,7 +754,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); + realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); monitorConnection(done, realtime); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { @@ -784,7 +782,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_token_string_expiry_with_authcallback', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var clientId = 'test clientid'; var authCallback = function (tokenParams, callback) { tokenParams.ttl = 5000; @@ -797,7 +795,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); + realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback, clientId: clientId })); monitorConnection(done, realtime); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { @@ -824,7 +822,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_token_string_expiry_with_token', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var clientId = 'test clientid'; whenPromiseSettles( rest.auth.requestToken({ ttl: 5000, clientId: clientId }, null), @@ -833,9 +831,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); return; } - realtime = helper.AblyRealtimePromise( - mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId }) - ); + realtime = helper.AblyRealtime(mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId })); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { try { @@ -867,7 +863,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('auth_expired_token_string', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var clientId = 'test clientid'; whenPromiseSettles(rest.auth.requestToken({ ttl: 1, clientId: clientId }, null), function (err, tokenDetails) { if (err) { @@ -875,9 +871,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } setTimeout(function () { - realtime = helper.AblyRealtimePromise( - mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId }) - ); + realtime = helper.AblyRealtime(mixin(realtimeOpts, { token: tokenDetails.token, clientId: clientId })); realtime.connection.once('failed', function (stateChange) { try { expect(stateChange.reason.code).to.equal(40171, 'Verify correct failure code'); @@ -908,7 +902,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports.skip('reauth_authCallback', function (realtimeOpts) { return function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); var firstTime = true; var authCallback = function (tokenParams, callback) { tokenParams.clientId = '*'; @@ -923,7 +917,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }; - realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { authCallback: authCallback })); + realtime = helper.AblyRealtime(mixin(realtimeOpts, { authCallback: authCallback })); realtime.connection.once('connected', function () { var channel = realtime.channels.get('right'); whenPromiseSettles(channel.attach(), function (err) { @@ -960,7 +954,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RSA10j */ it('authorize_updates_stored_details', function (done) { - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ autoConnect: false, defaultTokenParams: { version: 1 }, token: '1', @@ -991,7 +985,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Inject a fake AUTH message from realtime, check that we reauth and send our own in reply */ it('mocked_reauth', function (done) { - var rest = helper.AblyRestPromise(), + var rest = helper.AblyRest(), authCallback = function (tokenParams, callback) { // Request a token (should happen twice) whenPromiseSettles(rest.auth.requestToken(tokenParams, null), function (err, tokenDetails) { @@ -1002,7 +996,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async callback(null, tokenDetails); }); }, - realtime = helper.AblyRealtimePromise({ authCallback: authCallback, transports: [helper.bestTransport] }); + realtime = helper.AblyRealtime({ authCallback: authCallback, transports: [helper.bestTransport] }); realtime.connection.once('connected', function () { var transport = realtime.connection.connectionManager.activeProtocol.transport, @@ -1038,7 +1032,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -1071,7 +1065,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { @@ -1102,7 +1096,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.once('connected', function () { var channel = realtime.channels.get(jwtTestChannelName); whenPromiseSettles(channel.publish('greeting', 'Hello World!'), function (err) { @@ -1131,7 +1125,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var publishEvent = 'publishEvent', messageData = 'Hello World!'; - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.once('connected', function () { var channel = realtime.channels.get(jwtTestChannelName); channel.subscribe(publishEvent, function (msg) { @@ -1158,7 +1152,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function (stateChange) { try { @@ -1185,7 +1179,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async getJWT(params, callback); }; - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.once('connected', function () { var originalToken = realtime.auth.tokenDetails.token; realtime.connection.once('update', function () { @@ -1212,7 +1206,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } - var realtime = helper.AblyRealtimePromise({ token: token }); + var realtime = helper.AblyRealtime({ token: token }); realtime.connection.once('connected', function () { try { expect(token).to.equal(realtime.auth.tokenDetails.token, 'Verify that token is the same'); @@ -1228,7 +1222,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RTN14b */ it('reauth_consistently_expired_token', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ ttl: 1 }), function (err, token) { if (err) { done(err); @@ -1241,7 +1235,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; /* Wait a few ms to ensure token is expired */ setTimeout(function () { - realtime = helper.AblyRealtimePromise({ authCallback: authCallback, disconnectedRetryTimeout: 15000 }); + realtime = helper.AblyRealtime({ authCallback: authCallback, disconnectedRetryTimeout: 15000 }); /* Wait 5s, expect to have seen two attempts to get a token -- so the * authCallback called twice -- and the connection to now be sitting in * the disconnected state */ @@ -1261,7 +1255,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RSA4b1 - only autoremove expired tokens if have a server time offset set */ it('expired_token_no_autoremove_when_dont_have_servertime', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken(), function (err, token) { if (err) { done(err); @@ -1274,7 +1268,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async authCallbackCallCount++; callback(null, token); }; - realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + realtime = helper.AblyRealtime({ authCallback: authCallback }); realtime.connection.on('connected', function () { try { expect(authCallbackCallCount).to.equal(1, 'Check we did not autoremove an expired token ourselves'); @@ -1289,7 +1283,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RSA4b1 second case */ it('expired_token_autoremove_when_have_servertime', function (done) { var realtime, - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken(), function (err, token) { if (err) { done(err); @@ -1302,7 +1296,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async authCallbackCallCount++; callback(null, token); }; - realtime = helper.AblyRealtimePromise({ authCallback: authCallback, autoConnect: false }); + realtime = helper.AblyRealtime({ authCallback: authCallback, autoConnect: false }); /* Set the server time offset */ whenPromiseSettles(realtime.time(), function () { realtime.connect(); @@ -1323,7 +1317,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Check that only the last authorize matters */ it('multiple_concurrent_authorize', function (done) { - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ logLevel: 4, useTokenAuth: true, defaultTokenParams: { capability: { wrong: ['*'] } }, @@ -1368,7 +1362,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('authorize_immediately_after_init', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ useTokenAuth: true, defaultTokenParams: { capability: { wrong: ['*'] } }, }); diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index 9447865aa6..aee99ab380 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -177,7 +177,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelinit0', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { try { /* set options on init */ @@ -210,7 +210,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach0', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var channel0 = realtime.channels.get('channelattach0'); whenPromiseSettles(channel0.attach(), function (err) { @@ -233,7 +233,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach2', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); var channel2 = realtime.channels.get('channelattach2'); whenPromiseSettles(channel2.attach(), function (err) { if (err) { @@ -257,7 +257,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var channel0 = realtime.channels.get('channelattach3'); whenPromiseSettles(channel0.attach(), function (err) { @@ -295,7 +295,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattachempty', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.once('connected', function () { var channel0 = realtime.channels.get(''); whenPromiseSettles(channel0.attach(), function (err) { @@ -327,7 +327,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattachinvalid', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.once('connected', function () { var channel = realtime.channels.get(':hell'); whenPromiseSettles(channel.attach(), function (err) { @@ -365,7 +365,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('publish_no_attach', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.once('connected', function () { whenPromiseSettles(realtime.channels.get('publish_no_attach').publish(), function (err) { if (err) { @@ -388,7 +388,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach_publish_invalid', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.once('connected', function () { whenPromiseSettles(realtime.channels.get(':hell').publish(), function (err) { if (err) { @@ -417,7 +417,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('channelattach_invalid_twice', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.once('connected', function () { whenPromiseSettles(realtime.channels.get(':hell').attach(), function (err) { if (err) { @@ -453,7 +453,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channelattachOnceOrIfAfter', function (done) { try { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), channel = realtime.channels.get('channelattachOnceOrIf'), firedImmediately = false; @@ -479,7 +479,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channelattachOnceOrIfBefore', function (done) { try { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), channel = realtime.channels.get('channelattachOnceOrIf'), firedImmediately = false; @@ -504,7 +504,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsBasicChannelsGet'; try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -528,7 +528,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtimePromise(); + var testRealtime = helper.AblyRealtime(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -557,7 +557,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsBasicSetOptions'; try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -577,7 +577,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(channel.params).to.deep.equal(params, 'Check result params'); expect(channel.modes).to.deep.equal(['subscribe'], 'Check result modes'); - var testRealtime = helper.AblyRealtimePromise(); + var testRealtime = helper.AblyRealtime(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -606,7 +606,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'subscribeAfterSetOptions'; try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var channel = realtime.channels.get(testName); channel.setOptions({ @@ -635,7 +635,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('channelGetShouldThrowWhenWouldCauseReattach', function (done) { var testName = 'channelGetShouldThrowWhenWouldCauseReattach'; try { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -676,7 +676,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'setOptionsCallbackBehaviour'; try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var params = { modes: 'subscribe', @@ -752,7 +752,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsModesAndChannelModes'; try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var paramsModes = ['presence', 'subscribe']; var params = { @@ -777,7 +777,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtimePromise(); + var testRealtime = helper.AblyRealtime(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -806,7 +806,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelModes'; try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var modes = ['publish', 'presence_subscribe']; var channelOptions = { @@ -826,7 +826,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtimePromise(); + var testRealtime = helper.AblyRealtime(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -855,7 +855,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var testName = 'attachWithChannelParamsDeltaAndModes'; try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.on('connected', function () { var modes = ['publish', 'subscribe', 'presence_subscribe']; var channelOptions = { @@ -877,7 +877,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var testRealtime = helper.AblyRealtimePromise(); + var testRealtime = helper.AblyRealtime(); testRealtime.connection.on('connected', function () { var testChannel = testRealtime.channels.get(testName); async.series( @@ -906,7 +906,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var testName = 'attachWithInvalidChannelParams'; var defaultChannelModes = 'presence,publish,subscribe,presence_subscribe'; try { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); realtime.connection.on('connected', function () { var channel = realtime.channels.get(testName); async.series( @@ -1012,7 +1012,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channelsubscribe0', function (done) { try { - var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: true }); + var realtime = helper.AblyRealtime({ useBinaryProtocol: true }); realtime.connection.on('connected', function () { var channel6 = realtime.channels.get('channelsubscribe0'); whenPromiseSettles(channel6.attach(), function (err) { @@ -1048,7 +1048,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var messagesReceived = 0; try { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); var channelByEvent, channelByListener, channelAll; var unsubscribeTest = function () { @@ -1123,7 +1123,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * immediate reattach. If that fails, it should go into suspended */ it('server_sent_detached', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached', channel = realtime.channels.get(channelName); @@ -1174,7 +1174,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * result in the channel becoming suspended */ it('server_sent_detached_while_attaching', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached_while_attaching', channel = realtime.channels.get(channelName); @@ -1214,7 +1214,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * A server-sent ERROR, with channel field, should fail the channel */ it('server_sent_error', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_error', channel = realtime.channels.get(channelName); @@ -1251,7 +1251,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * should emit an UPDATE event on the channel */ it('server_sent_attached_err', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), channelName = 'server_sent_attached_err', channel = realtime.channels.get(channelName); @@ -1295,7 +1295,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check that queueMessages: false disables queuing for connection queue state */ it('publish_no_queueing', function (done) { - var realtime = helper.AblyRealtimePromise({ queueMessages: false }), + var realtime = helper.AblyRealtime({ queueMessages: false }), channel = realtime.channels.get('publish_no_queueing'); /* try a publish while not yet connected */ @@ -1314,7 +1314,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('channel_attach_timeout', function (done) { /* Use a fixed transport as attaches are resent when the transport changes */ - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport], realtimeRequestTimeout: 2000, channelRetryTimeout: 100, @@ -1362,7 +1362,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('suspended_connection', function (done) { /* Use a fixed transport as attaches are resent when the transport changes */ /* Browsers throttle setTimeouts to min 1s in in active tabs; having timeouts less than that screws with the relative timings */ - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport], channelRetryTimeout: 1010, suspendedRetryTimeout: 1100, @@ -1430,7 +1430,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RTL5i */ it('attached_while_detaching', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channelName = 'server_sent_detached', channel = realtime.channels.get(channelName); @@ -1473,7 +1473,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async // RTL5j it('detaching from suspended channel transitions channel to detached state', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }); + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }); var channelName = 'detach_from_suspended'; var channel = realtime.channels.get(channelName); @@ -1489,7 +1489,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async // RTL5b it('detaching from failed channel results in error', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }); + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }); var channelName = 'detach_from_failed'; var channel = realtime.channels.get(channelName); @@ -1505,7 +1505,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('rewind works on channel after reattaching', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }); + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }); var channelName = 'rewind_after_detach'; var channel = realtime.channels.get(channelName); var channelOpts = { params: { rewind: '1' } }; diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index d0923de040..16f13c65ac 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -23,7 +23,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionPing', function (done) { var realtime; try { - realtime = helper.AblyRealtimePromise(); + realtime = helper.AblyRealtime(); realtime.connection.on('connected', function () { try { realtime.connection.ping(); @@ -41,7 +41,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionPingWithCallback', function (done) { var realtime; try { - realtime = helper.AblyRealtimePromise(); + realtime = helper.AblyRealtime(); realtime.connection.on('connected', function () { whenPromiseSettles(realtime.connection.ping(), function (err, responseTime) { if (err) { @@ -66,7 +66,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionAttributes', function (done) { var realtime; try { - realtime = helper.AblyRealtimePromise({ logLevel: 4 }); + realtime = helper.AblyRealtime({ logLevel: 4 }); realtime.connection.on('connected', function () { try { const recoveryContext = JSON.parse(realtime.connection.recoveryKey); @@ -131,7 +131,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelSerials: {}, }); try { - realtime = helper.AblyRealtimePromise({ recover: fakeRecoveryKey }); + realtime = helper.AblyRealtime({ recover: fakeRecoveryKey }); realtime.connection.on('connected', function (stateChange) { try { expect(stateChange.reason.code).to.equal( @@ -167,7 +167,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * without being merged with new messages) */ it('connectionQueuing', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channel = realtime.channels.get('connectionQueuing'), connectionManager = realtime.connection.connectionManager; @@ -248,7 +248,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Inject a new CONNECTED with different connectionDetails; check they're used */ it('connectionDetails', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), connectionManager = realtime.connection.connectionManager; realtime.connection.once('connected', function () { connectionManager.once('connectiondetails', function (details) { diff --git a/test/realtime/connectivity.test.js b/test/realtime/connectivity.test.js index ca3cfd4949..ea4edc9177 100644 --- a/test/realtime/connectivity.test.js +++ b/test/realtime/connectivity.test.js @@ -47,7 +47,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { } it('succeeds with scheme', function (done) { - new helper.AblyRealtimePromise(options(urlScheme + successUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtime(options(urlScheme + successUrl)).http.checkConnectivity(function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; } catch (err) { @@ -59,7 +59,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('fails with scheme', function (done) { - new helper.AblyRealtimePromise(options(urlScheme + failUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtime(options(urlScheme + failUrl)).http.checkConnectivity(function (err, res) { try { expect(!res, 'Connectivity check expected to return false').to.be.ok; done(); @@ -70,7 +70,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('succeeds with querystring', function (done) { - new helper.AblyRealtimePromise(options(successUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtime(options(successUrl)).http.checkConnectivity(function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; done(); @@ -81,7 +81,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('fails with querystring', function (done) { - new helper.AblyRealtimePromise(options(failUrl)).http.checkConnectivity(function (err, res) { + new helper.AblyRealtime(options(failUrl)).http.checkConnectivity(function (err, res) { try { expect(!res, 'Connectivity check expected to return false').to.be.ok; done(); @@ -92,10 +92,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('succeeds with plain url', function (done) { - new helper.AblyRealtimePromise(options('sandbox-rest.ably.io/time')).http.checkConnectivity(function ( - err, - res - ) { + new helper.AblyRealtime(options('sandbox-rest.ably.io/time')).http.checkConnectivity(function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; done(); @@ -106,7 +103,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('fails with plain url', function (done) { - new helper.AblyRealtimePromise(options('echo.ably.io')).http.checkConnectivity(function (err, res) { + new helper.AblyRealtime(options('echo.ably.io')).http.checkConnectivity(function (err, res) { try { expect(!res, 'Connectivity check expected to return false').to.be.ok; done(); @@ -118,7 +115,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('disable_connectivity_check', function (done) { - new helper.AblyRealtimePromise({ + new helper.AblyRealtime({ connectivityCheckUrl: 'notarealhost', disableConnectivityCheck: true, }).http.checkConnectivity(function (err, res) { diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index a430aefa23..ba8f8d13bb 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -62,7 +62,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(new Error('Unable to get test assets; err = ' + displayError(err))); return; } - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); var key = BufferUtils.base64Decode(testData.key); var iv = BufferUtils.base64Decode(testData.iv); var channel = realtime.channels.get(channelName, { cipher: { key: key, iv: iv } }); @@ -409,7 +409,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* For single_send tests we test the 'shortcut' way of setting the cipher * in channels.get. No callback, but that's ok even for node which has * async iv generation since the publish is on an attach cb */ - var realtime = helper.AblyRealtimePromise(realtimeOpts), + var realtime = helper.AblyRealtime(realtimeOpts), channel = realtime.channels.get('single_send', { cipher: { key: key } }), messageText = 'Test message for single_send - ' + JSON.stringify(realtimeOpts); @@ -459,7 +459,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: !text }); + var realtime = helper.AblyRealtime({ useBinaryProtocol: !text }); var channelName = 'multiple_send_' + (text ? 'text_' : 'binary_') + iterations + '_' + delay, channel = realtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')'; @@ -527,8 +527,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtimePromise(txOpts), - rxRealtime = helper.AblyRealtimePromise(rxOpts), + var txRealtime = helper.AblyRealtime(txOpts), + rxRealtime = helper.AblyRealtime(rxOpts), channelName = 'single_send_separate_realtimes'; var messageText = 'Test message for single_send_separate_realtimes', txChannel = txRealtime.channels.get(channelName), @@ -603,8 +603,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtimePromise(), - rxRealtime = helper.AblyRealtimePromise(), + var txRealtime = helper.AblyRealtime(), + rxRealtime = helper.AblyRealtime(), channelName = 'publish_immediately', messageText = 'Test message'; @@ -645,8 +645,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtimePromise(); - var rxRealtime = helper.AblyRealtimePromise(); + var txRealtime = helper.AblyRealtime(); + var rxRealtime = helper.AblyRealtime(); var channelName = 'single_send_key_mismatch', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', @@ -711,8 +711,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtimePromise(); - var rxRealtime = helper.AblyRealtimePromise(); + var txRealtime = helper.AblyRealtime(); + var rxRealtime = helper.AblyRealtime(); var channelName = 'single_send_unencrypted', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', @@ -754,8 +754,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtimePromise(); - var rxRealtime = helper.AblyRealtimePromise(); + var txRealtime = helper.AblyRealtime(); + var rxRealtime = helper.AblyRealtime(); var channelName = 'single_send_encrypted_unhandled', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', @@ -798,8 +798,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var txRealtime = helper.AblyRealtimePromise(); - var rxRealtime = helper.AblyRealtimePromise(); + var txRealtime = helper.AblyRealtime(); + var rxRealtime = helper.AblyRealtime(); var channelName = 'set_cipher_params', txChannel = txRealtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')', diff --git a/test/realtime/delta.test.js b/test/realtime/delta.test.js index b66d5951c5..006133c26d 100644 --- a/test/realtime/delta.test.js +++ b/test/realtime/delta.test.js @@ -44,7 +44,7 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v var testName = 'deltaPlugin'; try { var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ plugins: { vcdiff: testVcdiffDecoder, }, @@ -93,7 +93,7 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v var testName = 'unusedPlugin'; try { var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ plugins: { vcdiff: testVcdiffDecoder, }, @@ -133,7 +133,7 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v var testName = 'lastMessageNotFoundRecovery'; try { var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ plugins: { vcdiff: testVcdiffDecoder, }, @@ -201,7 +201,7 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v }, }; - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ plugins: { vcdiff: failingTestVcdiffDecoder, }, @@ -246,7 +246,7 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v /* Check that channel becomes failed if we get deltas when we don't have a vcdiff plugin */ it('noPlugin', function (done) { try { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); var channel = realtime.channels.get('noPlugin', { params: { delta: 'vcdiff' } }); whenPromiseSettles(channel.attach(), function (err) { diff --git a/test/realtime/encoding.test.js b/test/realtime/encoding.test.js index b7837a8ab5..f7c9e49c10 100644 --- a/test/realtime/encoding.test.js +++ b/test/realtime/encoding.test.js @@ -32,8 +32,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } - var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: false }), - binaryrealtime = helper.AblyRealtimePromise({ useBinaryProtocol: true }), + var realtime = helper.AblyRealtime({ useBinaryProtocol: false }), + binaryrealtime = helper.AblyRealtime({ useBinaryProtocol: true }), channelName = 'message_decoding', channelPath = '/channels/' + channelName + '/messages', channel = realtime.channels.get(channelName), @@ -133,8 +133,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(new Error('Unable to get test assets; err = ' + displayError(err))); return; } - var realtime = helper.AblyRealtimePromise({ useBinaryProtocol: false }), - binaryrealtime = helper.AblyRealtimePromise({ useBinaryProtocol: true }), + var realtime = helper.AblyRealtime({ useBinaryProtocol: false }), + binaryrealtime = helper.AblyRealtime({ useBinaryProtocol: true }), channelName = 'message_encoding', channelPath = '/channels/' + channelName + '/messages', channel = realtime.channels.get(channelName), diff --git a/test/realtime/event_emitter.test.js b/test/realtime/event_emitter.test.js index 36043e2b7d..4575594f21 100644 --- a/test/realtime/event_emitter.test.js +++ b/test/realtime/event_emitter.test.js @@ -28,7 +28,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { /* Note: realtime now sends an ATTACHED post-upgrade, which can race with * the DETACHED if the DETACH is only sent just after upgrade. Remove * bestTransport with 1.1 spec which has IDs in ATTACHs */ - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), index, expectedConnectionEvents = ['connecting', 'connected', 'closing', 'closed'], expectedChannelEvents = ['attaching', 'attached', 'detaching', 'detached']; @@ -74,7 +74,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('emitCallsAllCallbacksIgnoringExceptions', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = false, eventEmitter = realtime.connection; @@ -99,7 +99,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('onceCalledOnlyOnce', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), onCallbackCalled = 0, onceCallbackCalled = 0, eventEmitter = realtime.connection; @@ -127,7 +127,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('onceCallbackDoesNotImpactOnCallback', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -153,7 +153,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllMatchingListeners', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -182,7 +182,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllListeners', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -211,7 +211,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllMatchingEventListeners', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -240,7 +240,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('offRemovesAllMatchingEvents', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -276,7 +276,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { * for each previously registered event name */ it('offRemovesEmptyEventNameListeners', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), eventEmitter = realtime.connection; var callback = function () {}; @@ -304,7 +304,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('arrayOfEvents', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -343,7 +343,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('arrayOfEventsWithOnce', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), callbackCalled = 0, eventEmitter = realtime.connection; @@ -370,7 +370,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { /* check that listeners added in a listener cb are not called during that * emit instance */ it('listenerAddedInListenerCb', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), eventEmitter = realtime.connection, firstCbCalled = false, secondCbCalled = false; @@ -397,7 +397,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { /* check that listeners removed in a listener cb are still called in that * emit instance (but only once) */ it('listenerRemovedInListenerCb', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), eventEmitter = realtime.connection, onCbCalledTimes = 0, onceCbCalledTimes = 0, @@ -442,7 +442,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { if (typeof Promise !== 'undefined') { describe('event_emitter_promise', function () { it('whenState', function (done) { - var realtime = helper.AblyRealtimePromise({ internal: { promises: true } }); + var realtime = helper.AblyRealtime({ internal: { promises: true } }); var eventEmitter = realtime.connection; eventEmitter @@ -456,7 +456,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('once', function (done) { - var realtime = helper.AblyRealtimePromise({ internal: { promises: true } }); + var realtime = helper.AblyRealtime({ internal: { promises: true } }); var eventEmitter = realtime.connection; eventEmitter @@ -470,7 +470,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('anyEventsWithOnce', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), eventEmitter = realtime.connection; const p = eventEmitter.once(); @@ -483,7 +483,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('arrayOfEventsWithOnce', function (done) { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), eventEmitter = realtime.connection; const p = eventEmitter.once(['a', 'b', 'c']); diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index c8576e578d..6e2339434c 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -29,7 +29,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { var failure_test = function (transports) { return function (cb) { - var realtime = helper.AblyRealtimePromise({ key: 'this.is:wrong', transports: transports }); + var realtime = helper.AblyRealtime({ key: 'this.is:wrong', transports: transports }); realtime.connection.on('failed', function (connectionStateChange) { try { expect(realtime.connection.errorReason.code).to.equal(40400, 'wrong error reason code on connection.'); @@ -78,7 +78,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { var break_test = function (transports) { return function (cb) { - var realtime = helper.AblyRealtimePromise({ transports: transports }); + var realtime = helper.AblyRealtime({ transports: transports }); realtime.connection.once('connected', function () { realtime.connection.once('disconnected', function () { cb(null, realtime); @@ -117,7 +117,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var lifecycleTest = function (transports) { return function (cb) { var connectionEvents = []; - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ transports: transports, realtimeHost: 'invalid', restHost: 'invalid', @@ -189,7 +189,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async utils.arrForEach(availableTransports, function (transport) { it('disconnected_backoff_' + transport, function (done) { var disconnectedRetryTimeout = 150; - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ disconnectedRetryTimeout: disconnectedRetryTimeout, realtimeHost: 'invalid', restHost: 'invalid', @@ -220,7 +220,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check operations on a failed channel give the right errors */ it('failed_channel', function (done) { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); var failChan; var channelFailedCode = 90001; @@ -326,7 +326,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('attach_timeout', function (done) { - var realtime = helper.AblyRealtimePromise({ realtimeRequestTimeout: 2000, channelRetryTimeout: 1000 }), + var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 2000, channelRetryTimeout: 1000 }), channel = realtime.channels.get('failed_attach'), originalProcessMessage = channel.processMessage.bind(channel); @@ -362,7 +362,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async utils.arrForEach(availableTransports, function (transport) { it('channel_backoff_' + transport, function (done) { var channelRetryTimeout = 150; - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ channelRetryTimeout: channelRetryTimeout, transports: [transport], }), @@ -423,7 +423,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function nack_on_connection_failure(failureFn, expectedRealtimeState, expectedNackCode) { return function (done) { /* Use one transport because stubbing out transport#onProtocolMesage */ - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channel = realtime.channels.get('nack_on_connection_failure'); async.series( @@ -508,7 +508,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); it('idle_transport_timeout', function (done) { - var realtime = helper.AblyRealtimePromise({ realtimeRequestTimeout: 2000 }), + var realtime = helper.AblyRealtime({ realtimeRequestTimeout: 2000 }), originalOnProtocolMessage; realtime.connection.connectionManager.on('transport.pending', function (transport) { @@ -546,7 +546,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { /* Use the echoserver as a fallback host because it doesn't support * websockets, so it'll fail to connect, which we can detect */ - var realtime = helper.AblyRealtimePromise(utils.mixin({ fallbackHosts: ['echo.ably.io'] }, realtimeOpts)), + var realtime = helper.AblyRealtime(utils.mixin({ fallbackHosts: ['echo.ably.io'] }, realtimeOpts)), connection = realtime.connection, connectionManager = connection.connectionManager; @@ -586,7 +586,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var testMessage2 = { foo: 'bar', count: 2, status: 'active' }; try { - var sender_realtime = helper.AblyRealtimePromise(); + var sender_realtime = helper.AblyRealtime(); var sender_channel = sender_realtime.channels.get(testName); var messageReceived = false; diff --git a/test/realtime/history.test.js b/test/realtime/history.test.js index 73112e343f..a32ad2c72a 100644 --- a/test/realtime/history.test.js +++ b/test/realtime/history.test.js @@ -47,8 +47,8 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); it('history_until_attach', function (done) { - var rest = helper.AblyRestPromise(); - var realtime = helper.AblyRealtimePromise(); + var rest = helper.AblyRest(); + var realtime = helper.AblyRealtime(); var restChannel = rest.channels.get('persisted:history_until_attach'); /* first, send a number of events to this channel before attaching */ diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index e494f2708e..998a1c2623 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -29,7 +29,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('initbase0', function (done) { var realtime; try { - realtime = helper.AblyRealtimePromise({ transports: ['web_socket', 'xhr_streaming'] }); + realtime = helper.AblyRealtime({ transports: ['web_socket', 'xhr_streaming'] }); realtime.connection.on('connected', function () { /* check api version */ var transport = realtime.connection.connectionManager.activeProtocol.transport; @@ -73,7 +73,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('init_token_string', function (done) { try { /* first generate a token ... */ - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; whenPromiseSettles(rest.auth.requestToken(null, testKeyOpts), function (err, tokenDetails) { @@ -103,7 +103,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtimePromise({ key: keyStr, useTokenAuth: true }); + realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true }); expect(realtime.options.key).to.equal(keyStr); expect(realtime.auth.method).to.equal('token'); expect(realtime.auth.clientId).to.equal(undefined); @@ -128,7 +128,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtimePromise({ + realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true, defaultTokenParams: { clientId: '*', ttl: 123456 }, @@ -156,7 +156,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtimePromise({ + realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true, defaultTokenParams: { clientId: 'test' }, @@ -182,7 +182,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = helper.AblyRealtimePromise({ + realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true, clientId: 'yes', @@ -235,7 +235,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { /* check changing the default timeouts */ it('init_timeouts', function (done) { try { - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ key: 'not_a.real:key', disconnectedRetryTimeout: 123, suspendedRetryTimeout: 456, @@ -268,7 +268,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { /* check changing the default fallback hosts and changing httpMaxRetryCount */ it('init_fallbacks', function (done) { try { - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ key: 'not_a.real:key', restHost: 'a', httpMaxRetryCount: 2, @@ -316,7 +316,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('node_transports', function (done) { var realtime; try { - realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }); + realtime = helper.AblyRealtime({ transports: helper.availableTransports }); expect(realtime.connection.connectionManager.baseTransport).to.equal('comet'); expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal(['web_socket']); closeAndFinish(done, realtime); @@ -331,7 +331,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('init_and_connection_details', function (done) { try { var keyStr = helper.getTestApp().keys[0].keyStr; - var realtime = helper.AblyRealtimePromise({ key: keyStr, useTokenAuth: true }); + var realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true }); realtime.connection.connectionManager.once('transport.pending', function (state) { var transport = realtime.connection.connectionManager.pendingTransports[0], originalOnProtocolMessage = transport.onProtocolMessage; @@ -370,7 +370,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('init_fallbacks_once_connected', function (done) { - var realtime = helper.AblyRealtimePromise({ + var realtime = helper.AblyRealtime({ httpMaxRetryCount: 3, fallbackHosts: ['a', 'b', 'c'], }); @@ -390,8 +390,8 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('init_fallbacks_once_connected_2', function (done) { - var goodHost = helper.AblyRestPromise().options.realtimeHost; - var realtime = helper.AblyRealtimePromise({ + var goodHost = helper.AblyRest().options.realtimeHost; + var realtime = helper.AblyRealtime({ httpMaxRetryCount: 3, restHost: 'a', fallbackHosts: [goodHost, 'b', 'c'], diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index 623f1c0946..bcc4aa9128 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -39,8 +39,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('publishonce', function (done) { try { /* set up realtime */ - var realtime = helper.AblyRealtimePromise(); - var rest = helper.AblyRestPromise(); + var realtime = helper.AblyRealtime(); + var rest = helper.AblyRest(); /* connect and attach */ realtime.connection.on('connected', function () { @@ -80,7 +80,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async testOnAllTransports('publishfast', function (realtimeOpts) { return function (done) { try { - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); realtime.connection.once('connected', function () { var channel = realtime.channels.get('publishfast_' + String(Math.random()).substr(2)); whenPromiseSettles(channel.attach(), function (err) { @@ -144,8 +144,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return function (done) { var txRealtime, rxRealtime; try { - txRealtime = helper.AblyRealtimePromise(utils.mixin(realtimeOpts, { autoConnect: false })); - rxRealtime = helper.AblyRealtimePromise(); + txRealtime = helper.AblyRealtime(utils.mixin(realtimeOpts, { autoConnect: false })); + rxRealtime = helper.AblyRealtime(); var txChannel = txRealtime.channels.get('publishQueued_' + String(Math.random()).substr(2)); var rxChannel = rxRealtime.channels.get(txChannel.name); @@ -232,8 +232,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('publishEcho', function (done) { // set up two realtimes - var rtNoEcho = helper.AblyRealtimePromise({ echoMessages: false }), - rtEcho = helper.AblyRealtimePromise({ echoMessages: true }), + var rtNoEcho = helper.AblyRealtime({ echoMessages: false }), + rtEcho = helper.AblyRealtime({ echoMessages: true }), rtNoEchoChannel = rtNoEcho.channels.get('publishecho'), rtEchoChannel = rtEcho.channels.get('publishecho'), testMsg1 = 'Hello', @@ -323,8 +323,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - realtime = helper.AblyRealtimePromise(); - var rest = helper.AblyRestPromise(); + realtime = helper.AblyRealtime(); + var rest = helper.AblyRest(); /* connect and attach */ realtime.connection.on('connected', function () { @@ -429,8 +429,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - var realtime = helper.AblyRealtimePromise(); - var rest = helper.AblyRestPromise(); + var realtime = helper.AblyRealtime(); + var rest = helper.AblyRest(); /* connect and attach */ realtime.connection.on('connected', function () { @@ -483,8 +483,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - var realtime = helper.AblyRealtimePromise(); - var rest = helper.AblyRestPromise(); + var realtime = helper.AblyRealtime(); + var rest = helper.AblyRest(); /* connect and attach */ realtime.connection.on('connected', function () { @@ -565,8 +565,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('restpublish', function (done) { var count = 10; - var rest = helper.AblyRestPromise(); - var realtime = helper.AblyRealtimePromise(); + var rest = helper.AblyRest(); + var realtime = helper.AblyRealtime(); var messagesSent = []; var sendchannel = rest.channels.get('restpublish'); var recvchannel = realtime.channels.get('restpublish'); @@ -604,7 +604,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async --cbCount; checkFinish(); }; - var realtime = helper.AblyRealtimePromise(realtimeOpts); + var realtime = helper.AblyRealtime(realtimeOpts); var channel = realtime.channels.get('publish ' + JSON.stringify(realtimeOpts)); /* subscribe to event */ channel.subscribe( @@ -627,7 +627,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async and is implicitly added when published */ it('implicit_client_id_0', function (done) { var clientId = 'implicit_client_id_0', - realtime = helper.AblyRealtimePromise({ clientId: clientId }); + realtime = helper.AblyRealtime({ clientId: clientId }); realtime.connection.once('connected', function () { var transport = realtime.connection.connectionManager.activeProtocol.transport, @@ -666,7 +666,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('explicit_client_id_0', function (done) { var clientId = 'explicit_client_id_0', /* Use a fixed transport as intercepting transport.send */ - realtime = helper.AblyRealtimePromise({ clientId: clientId, transports: [helper.bestTransport] }); + realtime = helper.AblyRealtime({ clientId: clientId, transports: [helper.bestTransport] }); realtime.connection.once('connected', function () { var transport = realtime.connection.connectionManager.activeProtocol.transport, @@ -728,7 +728,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('explicit_client_id_1', function (done) { var clientId = 'explicit_client_id_1', invalidClientId = 'invalid', - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ clientId: clientId }), function (err, token) { if (err) { @@ -743,7 +743,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } /* Use a fixed transport as intercepting transport.send */ - var realtime = helper.AblyRealtimePromise({ token: token.token, transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ token: token.token, transports: [helper.bestTransport] }), channel = realtime.channels.get('explicit_client_id_1'); // Publish before authentication to ensure the client library does not reject the message as the clientId is not known @@ -783,7 +783,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('subscribe_with_event_array', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), channel = realtime.channels.get('subscribe_with_event_array'); async.series( @@ -839,7 +839,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('subscribe_with_filter_object', function (done) { - const realtime = helper.AblyRealtimePromise(); + const realtime = helper.AblyRealtime(); const channel = realtime.channels.get('subscribe_with_filter_object'); function send(cb) { @@ -919,7 +919,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('unsubscribe_with_filter_object', function (done) { - const realtime = helper.AblyRealtimePromise(); + const realtime = helper.AblyRealtime(); const channel = realtime.channels.get('unsubscribe_with_filter_object'); function send(cb) { @@ -977,7 +977,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('extras_field', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), channel = realtime.channels.get('extras_field'), extras = { headers: { some: 'metadata' } }; @@ -1022,7 +1022,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* TO3l8; CD2C; RSL1i */ it('maxMessageSize', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), connectionManager = realtime.connection.connectionManager, channel = realtime.channels.get('maxMessageSize'); @@ -1056,7 +1056,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* RTL6d: publish a series of messages that exercise various bundling * constraints, check they're satisfied */ it.skip('bundling', function (done) { - var realtime = helper.AblyRealtimePromise({ maxMessageSize: 256, autoConnect: false }), + var realtime = helper.AblyRealtime({ maxMessageSize: 256, autoConnect: false }), channelOne = realtime.channels.get('bundlingOne'), channelTwo = realtime.channels.get('bundlingTwo'); @@ -1118,7 +1118,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('idempotentRealtimePublishing', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), channel = realtime.channels.get('idempotentRealtimePublishing'); whenPromiseSettles(channel.attach(), function (err) { @@ -1206,8 +1206,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async try { /* set up realtime */ - var realtime = helper.AblyRealtimePromise({ key: helper.getTestApp().keys[5].keyStr }); - var rest = helper.AblyRestPromise(); + var realtime = helper.AblyRealtime({ key: helper.getTestApp().keys[5].keyStr }); + var rest = helper.AblyRest(); realtime.connection.on('connected', function () { var rtFilteredChannel = realtime.channels.getDerived('chan', filterOption); diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 4324c8f333..3ee5e48846 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -29,7 +29,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var createListenerChannel = function (channelName, callback) { var channel, realtime; try { - realtime = helper.AblyRealtimePromise(); + realtime = helper.AblyRealtime(); realtime.connection.on('connected', function () { channel = realtime.channels.get(channelName); whenPromiseSettles(channel.attach(), function (err) { @@ -101,7 +101,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } // Create authTokens associated with specific clientIds try { - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); whenPromiseSettles(rest.auth.requestToken({ clientId: testClientId }), function (err, tokenDetails) { if (err) { done(err); @@ -143,7 +143,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'attachAndEnter'; var attachAndEnter = function (cb) { /* set up authenticated connection */ - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -169,7 +169,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithoutAttach', function (done) { var channelName = 'enterWithoutAttach'; var enterWithoutAttach = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -189,7 +189,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithoutConnect', function (done) { var channelName = 'enterWithoutConnect'; var enterWithoutConnect = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); var clientChannel = clientRealtime.channels.get(channelName); whenPromiseSettles(clientChannel.presence.enter('Test client data (enterWithoutConnect)'), function (err) { cb(err, clientRealtime); @@ -219,7 +219,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); listenerFor('enter', testClientId)(presenceChannel, function () { if (!raceFinished) { @@ -271,7 +271,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'enterWithCallback'; var enterWithCallback = function (cb) { /* set up authenticated connection */ - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -297,7 +297,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithNothing', function (done) { var channelName = 'enterWithNothing'; var enterWithNothing = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -322,7 +322,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterWithData', function (done) { var channelName = 'enterWithData'; var enterWithData = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -346,7 +346,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * has valid action string */ it('presenceMessageAction', function (done) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); var channelName = 'presenceMessageAction'; var clientChannel = clientRealtime.channels.get(channelName); var presence = clientChannel.presence; @@ -386,7 +386,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterDetachEnter = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken, transports: [helper.bestTransport], @@ -421,7 +421,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterInvalid', function (done) { var clientRealtime; try { - clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); var clientChannel = clientRealtime.channels.get(''); clientRealtime.connection.once('connected', function () { whenPromiseSettles(clientChannel.presence.enter('clientId'), function (err) { @@ -450,7 +450,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceEnterAndLeave', function (done) { var channelName = 'enterAndLeave'; var enterAndLeave = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -499,7 +499,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterUpdate = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { var clientChannel = clientRealtime.channels.get(channelName); whenPromiseSettles(clientChannel.attach(), function (err) { @@ -552,7 +552,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterGet = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -577,7 +577,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('presenceSubscribeUnattached', function (done) { var channelName = 'subscribeUnattached'; - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); var clientRealtime2; clientRealtime.connection.on('connected', function () { var clientChannel = clientRealtime.channels.get(channelName); @@ -591,7 +591,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, [clientRealtime, clientRealtime2]); }); /* Technically a race, but c2 connecting and attaching should take longer than c1 attaching */ - clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); + clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { var clientChannel2 = clientRealtime2.channels.get(channelName); clientChannel2.presence.enter('data'); @@ -606,7 +606,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presenceGetUnattached', function (done) { var channelName = 'getUnattached'; var testData = 'some data'; - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -615,7 +615,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, clientRealtime, err); return; } - var clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); + var clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { var clientChannel2 = clientRealtime2.channels.get(channelName); /* GET without attaching */ @@ -669,7 +669,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterLeaveGet = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -726,7 +726,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; try { /* set up authenticated connection */ - clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel = clientRealtime.channels.get(channelName); @@ -775,7 +775,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'secondConnection'; try { /* set up authenticated connection */ - clientRealtime1 = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime1 = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime1.connection.on('connected', function () { /* get channel, attach, and enter */ var clientChannel1 = clientRealtime1.channels.get(channelName); @@ -804,7 +804,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } /* now set up second connection and attach */ /* set up authenticated connection */ - clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); + clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { /* get channel, attach */ var clientChannel2 = clientRealtime2.channels.get(channelName); @@ -859,7 +859,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async [ function (cb1) { var data = 'Test client data (member0-1)'; - clientRealtime1 = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + clientRealtime1 = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); clientRealtime1.connection.on('connected', function () { /* get channel, attach, and enter */ clientChannel1 = clientRealtime1.channels.get(channelName); @@ -881,7 +881,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb2) { var data = 'Test client data (member0-2)'; - clientRealtime2 = helper.AblyRealtimePromise({ clientId: testClientId2, tokenDetails: authToken2 }); + clientRealtime2 = helper.AblyRealtime({ clientId: testClientId2, tokenDetails: authToken2 }); clientRealtime2.connection.on('connected', function () { /* get channel, attach */ clientChannel2 = clientRealtime2.channels.get(channelName); @@ -1025,7 +1025,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.presence.subscribe(presenceHandler); }; var enterAfterClose = function (cb) { - var clientRealtime = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); + var clientRealtime = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.once('connected', function () { /* get channel and enter (should automatically attach) */ @@ -1059,7 +1059,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var clientRealtime; var channelName = 'enterClosed'; try { - clientRealtime = helper.AblyRealtimePromise(); + clientRealtime = helper.AblyRealtime(); var clientChannel = clientRealtime.channels.get(channelName); clientRealtime.connection.on('connected', function () { clientRealtime.close(); @@ -1085,7 +1085,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('presenceClientIdIsImplicit', function (done) { var clientId = 'implicitClient', - client = helper.AblyRealtimePromise({ clientId: clientId }); + client = helper.AblyRealtime({ clientId: clientId }); var channel = client.channels.get('presenceClientIdIsImplicit'), presence = channel.presence; @@ -1135,8 +1135,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async transports: [helper.bestTransport], }; - var realtimeBin = helper.AblyRealtimePromise(utils.mixin(options, { useBinaryProtocol: true })); - var realtimeJson = helper.AblyRealtimePromise(utils.mixin(options, { useBinaryProtocol: false })); + var realtimeBin = helper.AblyRealtime(utils.mixin(options, { useBinaryProtocol: true })); + var realtimeJson = helper.AblyRealtime(utils.mixin(options, { useBinaryProtocol: false })); var runTest = function (realtime, callback) { realtime.connection.connectionManager.once('transport.active', function (transport) { @@ -1202,7 +1202,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; var enterInheritedClientId = function (cb) { - var realtime = helper.AblyRealtimePromise({ authCallback: authCallback }); + var realtime = helper.AblyRealtime({ authCallback: authCallback }); var channel = realtime.channels.get(channelName); realtime.connection.on('connected', function () { try { @@ -1235,7 +1235,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } - var realtime = helper.AblyRealtimePromise({ token: tokenDetails.token, autoConnect: false }); + var realtime = helper.AblyRealtime({ token: tokenDetails.token, autoConnect: false }); var channel = realtime.channels.get(channelName); try { expect(realtime.auth.clientId).to.equal(undefined, 'no clientId when entering'); @@ -1266,8 +1266,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ it('presence_refresh_on_detach', function (done) { var channelName = 'presence_refresh_on_detach'; - var realtime = helper.AblyRealtimePromise(); - var observer = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); + var observer = helper.AblyRealtime(); var realtimeChannel = realtime.channels.get(channelName); var observerChannel = observer.channels.get(channelName); @@ -1380,8 +1380,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presence_detach_during_sync', function (done) { var channelName = 'presence_detach_during_sync'; - var enterer = helper.AblyRealtimePromise({ clientId: testClientId, tokenDetails: authToken }); - var detacher = helper.AblyRealtimePromise(); + var enterer = helper.AblyRealtime({ clientId: testClientId, tokenDetails: authToken }); + var detacher = helper.AblyRealtime(); var entererChannel = enterer.channels.get(channelName); var detacherChannel = detacher.channels.get(channelName); @@ -1436,7 +1436,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * presence set */ it('presence_auto_reenter', function (done) { var channelName = 'presence_auto_reenter'; - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); var channel = realtime.channels.get(channelName); async.series( @@ -1561,7 +1561,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (cb) { - realtime = helper.AblyRealtimePromise({ tokenDetails: token }); + realtime = helper.AblyRealtime({ tokenDetails: token }); channel = realtime.channels.get(channelName); realtime.connection.once('connected', function () { cb(); @@ -1640,7 +1640,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Enter ten clients while attaching, finish the attach, check they were all entered correctly */ it('multiple_pending', function (done) { /* single transport to avoid upgrade stalling due to the stubbed attachImpl */ - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), channel = realtime.channels.get('multiple_pending'), originalAttachImpl = channel.attachImpl; @@ -1692,10 +1692,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check that a LEAVE message is published for anyone in the local presence * set but missing from a sync */ it('leave_published_for_member_missing_from_sync', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }), + var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), continuousClientId = 'continuous', goneClientId = 'gone', - continuousRealtime = helper.AblyRealtimePromise({ clientId: continuousClientId }), + continuousRealtime = helper.AblyRealtime({ clientId: continuousClientId }), channelName = 'leave_published_for_member_missing_from_sync', channel = realtime.channels.get(channelName), continuousChannel = continuousRealtime.channels.get(channelName); @@ -1807,7 +1807,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Check that a LEAVE message is published for anyone in the local presence * set if get an ATTACHED with no HAS_PRESENCE */ it('leave_published_for_members_on_presenceless_attached', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), channelName = 'leave_published_for_members_on_presenceless_attached', channel = realtime.channels.get(channelName), fakeClientId = 'faker'; @@ -1899,9 +1899,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and only members that changed between ATTACHED states should result in * presence events */ it('suspended_preserves_presence', function (done) { - var mainRealtime = helper.AblyRealtimePromise({ clientId: 'main', logLevel: 4 }), - continuousRealtime = helper.AblyRealtimePromise({ clientId: 'continuous', logLevel: 4 }), - leavesRealtime = helper.AblyRealtimePromise({ clientId: 'leaves', logLevel: 4 }), + var mainRealtime = helper.AblyRealtime({ clientId: 'main', logLevel: 4 }), + continuousRealtime = helper.AblyRealtime({ clientId: 'continuous', logLevel: 4 }), + leavesRealtime = helper.AblyRealtime({ clientId: 'leaves', logLevel: 4 }), channelName = 'suspended_preserves_presence', mainChannel = mainRealtime.channels.get(channelName); @@ -2037,7 +2037,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * comparisons. */ it('presence_many_updates', function (done) { - var client = helper.AblyRealtimePromise({ clientId: testClientId }); + var client = helper.AblyRealtime({ clientId: testClientId }); var channel = client.channels.get('presence_many_updates'), presence = channel.presence, diff --git a/test/realtime/reauth.test.js b/test/realtime/reauth.test.js index 87b44ab77d..137d35a9b2 100644 --- a/test/realtime/reauth.test.js +++ b/test/realtime/reauth.test.js @@ -17,7 +17,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { done(err); return; } - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); done(); }); }); @@ -44,7 +44,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function connectWithToken() { return function (state, callback) { - var realtime = helper.AblyRealtimePromise(mixin({ token: state.token }, state.realtimeOpts)); + var realtime = helper.AblyRealtime(mixin({ token: state.token }, state.realtimeOpts)); realtime.connection.once('connected', function () { callback(null, mixin(state, { realtime: realtime })); }); diff --git a/test/realtime/resume.test.js b/test/realtime/resume.test.js index 1c0e96ce32..26104d9aa2 100644 --- a/test/realtime/resume.test.js +++ b/test/realtime/resume.test.js @@ -44,8 +44,8 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function resume_inactive(done, channelName, txOpts, rxOpts) { var count = 5; - var txRest = helper.AblyRestPromise(mixin(txOpts)); - var rxRealtime = helper.AblyRealtimePromise(mixin(rxOpts)); + var txRest = helper.AblyRest(mixin(txOpts)); + var rxRealtime = helper.AblyRealtime(mixin(rxOpts)); var rxChannel = rxRealtime.channels.get(channelName); var txChannel = txRest.channels.get(channelName); @@ -155,8 +155,8 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { function resume_active(done, channelName, txOpts, rxOpts) { var count = 5; - var txRest = helper.AblyRestPromise(mixin(txOpts)); - var rxRealtime = helper.AblyRealtimePromise(mixin(rxOpts)); + var txRest = helper.AblyRest(mixin(txOpts)); + var rxRealtime = helper.AblyRealtime(mixin(rxOpts)); var rxChannel = rxRealtime.channels.get(channelName); var txChannel = txRest.channels.get(channelName); @@ -276,7 +276,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'resume_lost_continuity', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtimePromise(realtimeOpts), + var realtime = helper.AblyRealtime(realtimeOpts), connection = realtime.connection, attachedChannelName = 'resume_lost_continuity_attached', suspendedChannelName = 'resume_lost_continuity_suspended', @@ -342,7 +342,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'resume_token_error', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtimePromise(mixin(realtimeOpts, { useTokenAuth: true })), + var realtime = helper.AblyRealtime(mixin(realtimeOpts, { useTokenAuth: true })), badtoken, connection = realtime.connection; @@ -395,7 +395,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'resume_fatal_error', function (realtimeOpts) { return function (done) { - var realtime = helper.AblyRealtimePromise(realtimeOpts), + var realtime = helper.AblyRealtime(realtimeOpts), connection = realtime.connection; async.series( @@ -445,7 +445,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { * TODO: enable once realtime supports this */ it('channel_resumed_flag', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), realtimeTwo, recoveryKey, connection = realtime.connection, @@ -476,7 +476,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { helper.becomeSuspended(realtime, cb); }, function (cb) { - realtimeTwo = helper.AblyRealtimePromise({ recover: recoveryKey }); + realtimeTwo = helper.AblyRealtime({ recover: recoveryKey }); realtimeTwo.connection.once('connected', function (stateChange) { if (stateChange.reason) { cb(stateChange.reason); @@ -509,7 +509,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { * Check the library doesn't try to resume once the connectionStateTtl has expired */ it('no_resume_once_suspended', function (done) { - var realtime = helper.AblyRealtimePromise(), + var realtime = helper.AblyRealtime(), connection = realtime.connection, channelName = 'no_resume_once_suspended'; @@ -548,7 +548,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { */ it('no_resume_last_activity', function (done) { /* Specify a best transport so that upgrade activity doesn't reset the last activity timer */ - var realtime = helper.AblyRealtimePromise({ transports: [bestTransport] }), + var realtime = helper.AblyRealtime({ transports: [bestTransport] }), connection = realtime.connection, connectionManager = connection.connectionManager; @@ -574,11 +574,11 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var testName = 'resume_rewind_1'; var testMessage = { foo: 'bar', count: 1, status: 'active' }; try { - var sender_realtime = helper.AblyRealtimePromise(); + var sender_realtime = helper.AblyRealtime(); var sender_channel = sender_realtime.channels.get(testName); sender_channel.subscribe(function (message) { - var receiver_realtime = helper.AblyRealtimePromise(); + var receiver_realtime = helper.AblyRealtime(); var receiver_channel = receiver_realtime.channels.get(testName, { params: { rewind: 1 } }); receiver_channel.subscribe(function (message) { @@ -592,7 +592,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { return; } - var resumed_receiver_realtime = helper.AblyRealtimePromise(); + var resumed_receiver_realtime = helper.AblyRealtime(); var connectionManager = resumed_receiver_realtime.connection.connectionManager; var sendOrig = connectionManager.send; @@ -630,8 +630,8 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { it('recover multiple channels', function (done) { const NUM_MSGS = 5; - const txRest = helper.AblyRestPromise(); - const rxRealtime = helper.AblyRealtimePromise( + const txRest = helper.AblyRest(); + const rxRealtime = helper.AblyRealtime( { transports: [helper.bestTransport], }, @@ -739,7 +739,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { return; } - rxRealtimeRecover = helper.AblyRealtimePromise({ recover: recoveryKey }); + rxRealtimeRecover = helper.AblyRealtime({ recover: recoveryKey }); rxRecoverChannels = channelNames.map((name) => rxRealtimeRecover.channels.get(name)); subscribeRecoveredMessages(function (err) { diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index 2359fd830e..3c306d9bcc 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -44,7 +44,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * different presence set */ it('sync_existing_set', async function () { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'syncexistingset', channel = realtime.channels.get(channelName); @@ -164,7 +164,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * middle of the sync should should discard the former, but not the latter * */ it('sync_member_arrives_in_middle', async function () { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_member_arrives_in_middle', channel = realtime.channels.get(channelName); @@ -266,7 +266,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Presence message that was in the sync arrives again as a normal message, after it's come in the sync */ it('sync_member_arrives_normally_after_came_in_sync', async function () { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_member_arrives_normally_after_came_in_sync', channel = realtime.channels.get(channelName); @@ -349,7 +349,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * Presence message that will be in the sync arrives as a normal message, before it comes in the sync */ it('sync_member_arrives_normally_before_comes_in_sync', async function () { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_member_arrives_normally_before_comes_in_sync', channel = realtime.channels.get(channelName); @@ -433,7 +433,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * index, and synthesized leaves, check that the end result is correct */ it('presence_ordering', async function () { - var realtime = helper.AblyRealtimePromise({ autoConnect: false }), + var realtime = helper.AblyRealtime({ autoConnect: false }), channelName = 'sync_ordering', channel = realtime.channels.get(channelName); @@ -589,8 +589,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('presence_sync_interruptus', function (done) { var channelName = 'presence_sync_interruptus'; var interrupterClientId = 'dark_horse'; - var enterer = helper.AblyRealtimePromise(); - var syncer = helper.AblyRealtimePromise(); + var enterer = helper.AblyRealtime(); + var syncer = helper.AblyRealtime(); var entererChannel = enterer.channels.get(channelName); var syncerChannel = syncer.channels.get(channelName); diff --git a/test/realtime/upgrade.test.js b/test/realtime/upgrade.test.js index 674035ad47..3ae14f839c 100644 --- a/test/realtime/upgrade.test.js +++ b/test/realtime/upgrade.test.js @@ -30,7 +30,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai done(err); return; } - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); done(); }); }); @@ -43,7 +43,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('publishpreupgrade', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* connect and attach */ realtime.connection.on('connected', function () { //console.log('publishpreupgrade: connected'); @@ -87,7 +87,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('publishpostupgrade0', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* subscribe to event */ var rtChannel = realtime.channels.get('publishpostupgrade0'); @@ -145,7 +145,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('publishpostupgrade1', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* subscribe to event */ var rtChannel = realtime.channels.get('publishpostupgrade1'); @@ -220,7 +220,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai checkFinish(); }; var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); var channel = realtime.channels.get('upgradepublish0'); /* subscribe to event */ channel.subscribe( @@ -254,7 +254,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai checkFinish(); }; var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); var channel = realtime.channels.get('upgradepublish1'); /* subscribe to event */ channel.subscribe( @@ -279,7 +279,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; var cometDeactivated = false; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* check that we see the transport we're interested in get activated, * and that we see the comet transport deactivated */ var failTimer = setTimeout(function () { @@ -321,7 +321,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat0', function (done) { var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -354,7 +354,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat1', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -387,7 +387,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat2', function (done) { var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -433,7 +433,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai it('upgradeheartbeat3', function (done) { var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; try { - var realtime = helper.AblyRealtimePromise(transportOpts); + var realtime = helper.AblyRealtime(transportOpts); /* when we see the transport we're interested in get activated, * listen for the heartbeat event */ @@ -480,7 +480,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai try { /* on base transport active */ - realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }); + realtime = helper.AblyRealtime({ transports: helper.availableTransports }); realtime.connection.connectionManager.once('transport.active', function (transport) { expect( transport.toString().indexOf('/comet/') > -1, @@ -535,7 +535,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai * seamlessly transferred to the websocket transport and published there */ it('message_timeout_stalling_upgrade', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports, httpRequestTimeout: 3000 }), + var realtime = helper.AblyRealtime({ transports: helper.availableTransports, httpRequestTimeout: 3000 }), channel = realtime.channels.get('timeout1'), connectionManager = realtime.connection.connectionManager; @@ -581,7 +581,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai * and subsequent connections do not upgrade */ it('persist_transport_prefs', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }), + var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), connection = realtime.connection, connectionManager = connection.connectionManager; @@ -643,7 +643,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai * Check that upgrades succeed even if the original transport dies before the sync */ it('upgrade_original_transport_dies', function (done) { - var realtime = helper.AblyRealtimePromise({ transports: helper.availableTransports }), + var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), connection = realtime.connection, connectionManager = connection.connectionManager; diff --git a/test/rest/auth.test.js b/test/rest/auth.test.js index e1a9825be1..c1aed009bd 100644 --- a/test/rest/auth.test.js +++ b/test/rest/auth.test.js @@ -12,7 +12,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as before(function (done) { helper.setupApp(function () { - rest = helper.AblyRestPromise({ queryTime: true }); + rest = helper.AblyRest({ queryTime: true }); rest .time() .then(function (time) { @@ -46,7 +46,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as it('Generate token and init library with it', async function () { var tokenDetails = await rest.auth.requestToken(); expect(tokenDetails.token, 'Verify token value').to.be.ok; - helper.AblyRestPromise({ token: tokenDetails.token }); + helper.AblyRest({ token: tokenDetails.token }); }); it('Token generation with explicit timestamp', async function () { @@ -179,7 +179,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); it('Token generation with defaultTokenParams set and no tokenParams passed in', async function () { - var rest1 = helper.AblyRestPromise({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); + var rest1 = helper.AblyRest({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); var tokenDetails = await rest1.auth.requestToken(); expect(tokenDetails.token, 'Verify token value').to.be.ok; expect(tokenDetails.clientId).to.equal('foo', 'Verify client id from defaultTokenParams used'); @@ -187,7 +187,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); it('Token generation: if tokenParams passed in, defaultTokenParams should be ignored altogether, not merged', async function () { - var rest1 = helper.AblyRestPromise({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); + var rest1 = helper.AblyRest({ defaultTokenParams: { ttl: 123, clientId: 'foo' } }); var tokenDetails = await rest1.auth.requestToken({ clientId: 'bar' }, null); expect(tokenDetails.clientId).to.equal( 'bar', @@ -289,10 +289,10 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as var keys = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; var authParams = utils.mixin(keys, params); var authUrl = echoServer + '/createJWT' + utils.toQueryString(authParams); - var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); + var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); var tokenDetails = await restJWTRequester.auth.requestToken(); - var restClient = helper.AblyRestPromise({ token: tokenDetails.token }); + var restClient = helper.AblyRest({ token: tokenDetails.token }); await restClient.stats(); }); } @@ -315,10 +315,10 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as it('JWT request with invalid key', async function () { var keys = { keyName: 'invalid.invalid', keySecret: 'invalidinvalid' }; var authUrl = echoServer + '/createJWT' + utils.toQueryString(keys); - var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); + var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); var tokenDetails = await restJWTRequester.auth.requestToken(); - var restClient = helper.AblyRestPromise({ token: tokenDetails.token }); + var restClient = helper.AblyRest({ token: tokenDetails.token }); try { var stats = await restClient.stats(); } catch (err) { @@ -336,7 +336,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as var currentKey = helper.getTestApp().keys[0]; var keys = { keyName: currentKey.keyName, keySecret: currentKey.keySecret }; var authUrl = echoServer + '/createJWT' + utils.toQueryString(keys); - var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); + var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); var authCallback = function (tokenParams, callback) { restJWTRequester.auth.requestToken().then(function (tokenDetails) { @@ -344,7 +344,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); }; - var restClient = helper.AblyRestPromise({ authCallback: authCallback }); + var restClient = helper.AblyRest({ authCallback: authCallback }); var stats = await restClient.stats(); }); @@ -354,7 +354,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as it('Rest JWT with authCallback and invalid keys', async function () { var keys = { keyName: 'invalid.invalid', keySecret: 'invalidinvalid' }; var authUrl = echoServer + '/createJWT' + utils.toQueryString(keys); - var restJWTRequester = helper.AblyRestPromise({ authUrl: authUrl }); + var restJWTRequester = helper.AblyRest({ authUrl: authUrl }); var authCallback = function (tokenParams, callback) { restJWTRequester.auth.requestToken().then(function (tokenDetails) { @@ -362,7 +362,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); }; - var restClient = helper.AblyRestPromise({ authCallback: authCallback }); + var restClient = helper.AblyRest({ authCallback: authCallback }); try { await restClient.stats(); } catch (err) { @@ -383,7 +383,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as } /* Example client-side using the token */ - var restClient = helper.AblyRestPromise({ authCallback: authCallback }); + var restClient = helper.AblyRest({ authCallback: authCallback }); var channel = restClient.channels.get('auth_concurrent'); await Promise.all([channel.history(), channel.history()]); diff --git a/test/rest/capability.test.js b/test/rest/capability.test.js index 0c88b94bfc..6684cce8b4 100644 --- a/test/rest/capability.test.js +++ b/test/rest/capability.test.js @@ -20,7 +20,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { before(function (done) { helper.setupApp(function () { - rest = helper.AblyRestPromise({ queryTime: true }); + rest = helper.AblyRest({ queryTime: true }); testApp = helper.getTestApp(); rest .time() diff --git a/test/rest/fallbacks.test.js b/test/rest/fallbacks.test.js index df716ecede..209b519291 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -14,14 +14,14 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { done(err); return; } - goodHost = helper.AblyRestPromise().options.restHost; + goodHost = helper.AblyRest().options.restHost; done(); }); }); /* RSC15f */ it('Store working fallback', async function () { - var rest = helper.AblyRestPromise({ + var rest = helper.AblyRest({ restHost: helper.unroutableHost, fallbackHosts: [goodHost], httpRequestTimeout: 3000, diff --git a/test/rest/history.test.js b/test/rest/history.test.js index 98eab3e5ab..34b7778445 100644 --- a/test/rest/history.test.js +++ b/test/rest/history.test.js @@ -22,7 +22,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { before(function (done) { helper.setupApp(function () { - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); done(); }); }); diff --git a/test/rest/http.test.js b/test/rest/http.test.js index ff363d3b19..315f751551 100644 --- a/test/rest/http.test.js +++ b/test/rest/http.test.js @@ -9,7 +9,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { this.timeout(60 * 1000); before(function (done) { helper.setupApp(function () { - rest = helper.AblyRestPromise({ + rest = helper.AblyRest({ agents: { 'custom-agent': '0.1.2', }, diff --git a/test/rest/init.test.js b/test/rest/init.test.js index 11f9b5a11d..f4debf54f4 100644 --- a/test/rest/init.test.js +++ b/test/rest/init.test.js @@ -25,7 +25,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('Init with token string', async function () { /* first generate a token ... */ - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; var tokenDetails = await rest.auth.requestToken(null, testKeyOpts); @@ -36,30 +36,30 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('Init with tls: false', function () { - var rest = helper.AblyRestPromise({ tls: false, port: 123, tlsPort: 456 }); + var rest = helper.AblyRest({ tls: false, port: 123, tlsPort: 456 }); expect(rest.baseUri('example.com')).to.equal('http://example.com:123'); }); it('Init with tls: true', function () { - var rest = helper.AblyRestPromise({ tls: true, port: 123, tlsPort: 456 }); + var rest = helper.AblyRest({ tls: true, port: 123, tlsPort: 456 }); expect(rest.baseUri('example.com')).to.equal('https://example.com:456'); }); /* init without any tls key should enable tls */ it('Init without any tls key should enable tls', function () { - var rest = helper.AblyRestPromise({ port: 123, tlsPort: 456 }); + var rest = helper.AblyRest({ port: 123, tlsPort: 456 }); expect(rest.baseUri('example.com')).to.equal('https://example.com:456'); }); it("Init with clientId set to '*' or anything other than a string or null should error", function () { expect(function () { - var rest = helper.AblyRestPromise({ clientId: '*' }); + var rest = helper.AblyRest({ clientId: '*' }); }, 'Check can’t init library with a wildcard clientId').to.throw; expect(function () { - var rest = helper.AblyRestPromise({ clientId: 123 }); + var rest = helper.AblyRest({ clientId: 123 }); }, 'Check can’t init library with a numerical clientId').to.throw; expect(function () { - var rest = helper.AblyRestPromise({ clientId: false }); + var rest = helper.AblyRest({ clientId: false }); }, 'Check can’t init library with a boolean clientId').to.throw; }); diff --git a/test/rest/message.test.js b/test/rest/message.test.js index bb62a52791..389e0b7d74 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -20,7 +20,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async and is implicitly added when published */ it('Should implicitly send clientId when authenticated with clientId', async function () { var clientId = 'implicit_client_id_0', - rest = helper.AblyRestPromise({ clientId: clientId, useBinaryProtocol: false }), + rest = helper.AblyRest({ clientId: clientId, useBinaryProtocol: false }), channel = rest.channels.get('rest_implicit_client_id_0'); var originalPublish = channel._publish; @@ -42,7 +42,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async and ensure it is published */ it('Should publish clientId when provided explicitly in message', async function () { var clientId = 'explicit_client_id_0', - rest = helper.AblyRestPromise({ clientId: clientId, useBinaryProtocol: false }), + rest = helper.AblyRest({ clientId: clientId, useBinaryProtocol: false }), channel = rest.channels.get('rest_explicit_client_id_0'); var originalPublish = channel._publish; @@ -68,11 +68,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var clientId = 'explicit_client_id_0', invalidClientId = 'invalid'; - var token = await helper.AblyRestPromise().auth.requestToken({ clientId: clientId }); + var token = await helper.AblyRest().auth.requestToken({ clientId: clientId }); expect(token.clientId === clientId, 'client ID is present in the Token').to.be.ok; // REST client uses a token string so is unaware of the clientId so cannot reject before communicating with Ably - var rest = helper.AblyRestPromise({ token: token.token, useBinaryProtocol: false }), + var rest = helper.AblyRest({ token: token.token, useBinaryProtocol: false }), channel = rest.channels.get('rest_explicit_client_id_1'); var originalPublish = channel._publish; @@ -99,7 +99,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* TO3l8; CD2C; RSL1i */ it('Should error when publishing message larger than maxMessageSize', async function () { /* No connectionDetails mechanism for REST, so just pass the override into the constructor */ - var realtime = helper.AblyRestPromise({ internal: { maxMessageSize: 64 } }), + var realtime = helper.AblyRest({ internal: { maxMessageSize: 64 } }), channel = realtime.channels.get('maxMessageSize'); try { @@ -114,7 +114,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Check ids are correctly sent */ it('Should send correct IDs when idempotentRestPublishing set to false', async function () { - var rest = helper.AblyRestPromise({ idempotentRestPublishing: false, useBinaryProtocol: false }), + var rest = helper.AblyRest({ idempotentRestPublishing: false, useBinaryProtocol: false }), channel = rest.channels.get('idempotent_rest_publishing'), message = { name: 'test', id: 'idempotent-msg-id:0' }; @@ -128,11 +128,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Check ids are added when automatic idempotent rest publishing option enabled */ it('Should add IDs when automatic idempotent rest publishing option enabled', async function () { /* easiest way to get the host we're using for tests */ - var dummyRest = helper.AblyRestPromise(), + var dummyRest = helper.AblyRest(), host = dummyRest.options.restHost, /* Add the same host as a bunch of fallback hosts, so after the first * request 'fails' we retry on the same host using the fallback mechanism */ - rest = helper.AblyRestPromise({ + rest = helper.AblyRest({ idempotentRestPublishing: true, useBinaryProtocol: false, fallbackHosts: [host, host, host], @@ -180,7 +180,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('Rest publish params', async function () { - var rest = helper.AblyRestPromise(), + var rest = helper.AblyRest(), channel = rest.channels.get('publish_params'); var originalPublish = channel._publish; diff --git a/test/rest/presence.test.js b/test/rest/presence.test.js index 7e7f519084..1c9640631f 100644 --- a/test/rest/presence.test.js +++ b/test/rest/presence.test.js @@ -25,7 +25,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async before(function (done) { helper.setupApp(function () { - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); cipherConfig = helper.getTestApp().cipherConfig; done(); }); diff --git a/test/rest/push.test.js b/test/rest/push.test.js index 5bfe1eb4e0..9d251ffe68 100644 --- a/test/rest/push.test.js +++ b/test/rest/push.test.js @@ -51,13 +51,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } subsByChannel[sub.channel].push(sub); - var rest = helper.AblyRestPromise({ clientId: sub.clientId }); + var rest = helper.AblyRest({ clientId: sub.clientId }); subscribes.push(() => rest.push.admin.channelSubscriptions.save(sub)); deletes.push(() => rest.push.admin.channelSubscriptions.remove(sub)); })(i); } - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); await Promise.all(subscribes.map((sub) => sub())); @@ -72,7 +72,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('Publish', async function () { try { - var realtime = helper.AblyRealtimePromise(); + var realtime = helper.AblyRealtime(); var channel = realtime.channels.get('pushenabled:foo'); await channel.attach(); @@ -108,7 +108,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('deviceRegistrations save', async function () { - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); var saved = await rest.push.admin.deviceRegistrations.save(testDevice); var got = await rest.push.admin.deviceRegistrations.get(testDevice.id); @@ -162,7 +162,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async devices.push(device); devices_withoutSecret.push(device_withoutSecret); - var rest = helper.AblyRestPromise({ clientId: device.clientId }); + var rest = helper.AblyRest({ clientId: device.clientId }); registrations.push(function () { return rest.push.admin.deviceRegistrations.save(device); }); @@ -172,7 +172,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async })(i); } - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); var res0 = await Promise.all(registrations.map((x) => x())); var res1 = await rest.push.admin.deviceRegistrations.list(null); @@ -195,7 +195,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('deviceRegistrations remove removeWhere', async function () { - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); await rest.push.admin.deviceRegistrations.save(testDevice); await rest.push.admin.deviceRegistrations.remove(testDevice.id); @@ -219,7 +219,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('channelSubscriptions save', async function () { - var rest = helper.AblyRestPromise({ clientId: 'testClient' }); + var rest = helper.AblyRest({ clientId: 'testClient' }); var subscription = { clientId: 'testClient', channel: 'pushenabled:foo' }; var saved = await rest.push.admin.channelSubscriptions.save(subscription); @@ -245,7 +245,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } subsByChannel[sub.channel].push(sub); - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); subscribes.push(function () { return rest.push.admin.channelSubscriptions.save(sub); }); @@ -255,7 +255,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async })(i); } - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); await Promise.all(subscribes.map((x) => x())); @@ -269,7 +269,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('push_channelSubscriptions_remove', async function () { - var rest = helper.AblyRestPromise({ clientId: 'testClient' }); + var rest = helper.AblyRest({ clientId: 'testClient' }); var subscription = { clientId: 'testClient', channel: 'pushenabled:foo' }; await rest.push.admin.channelSubscriptions.save(subscription); @@ -282,7 +282,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async for (var i = 0; i < 5; i++) { (function (i) { var sub = { channel: 'pushenabled:listChannels' + ((i % 2) + 1), clientId: 'testClient' + ((i % 3) + 1) }; - var rest = helper.AblyRestPromise({ clientId: sub.clientId }); + var rest = helper.AblyRest({ clientId: sub.clientId }); subscribes.push(function (callback) { return rest.push.admin.channelSubscriptions.save(sub); }); @@ -292,7 +292,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async })(i); } - var rest = helper.AblyRestPromise(); + var rest = helper.AblyRest(); await Promise.all(subscribes.map((x) => x())); diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 844d7f75cd..7055f46191 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -17,7 +17,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async done(err); return; } - rest = helper.AblyRestPromise({ useBinaryProtocol: false }); + rest = helper.AblyRest({ useBinaryProtocol: false }); done(); }); }); @@ -68,7 +68,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* With a network issue, should get an actual err, not an HttpPaginatedResponse with error members */ it('request_network_error', async function () { - rest = helper.AblyRestPromise({ restHost: helper.unroutableAddress }); + rest = helper.AblyRest({ restHost: helper.unroutableAddress }); try { var res = await rest.request('get', '/time', Defaults.protocolVersion, null, null, null); } catch (err) { @@ -168,7 +168,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async utils.arrForEach(['put', 'patch', 'delete'], function (method) { it('check' + method, async function () { - var restEcho = helper.AblyRestPromise({ useBinaryProtocol: false, restHost: echoServerHost, tls: true }); + var restEcho = helper.AblyRest({ useBinaryProtocol: false, restHost: echoServerHost, tls: true }); var res = await restEcho.request(method, '/methods', Defaults.protocolVersion, {}, {}, {}); expect(res.items[0] && res.items[0].method).to.equal(method); }); diff --git a/test/rest/stats.test.js b/test/rest/stats.test.js index b733be0c45..bda82edfb5 100644 --- a/test/rest/stats.test.js +++ b/test/rest/stats.test.js @@ -64,7 +64,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { before(function (done) { // force a new app to be created with first argument true so that stats are not effected by other tests helper.setupApp(true, function () { - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); helper.createStats(helper.getTestApp(), statsFixtures, function (err) { if (err) { done(err); diff --git a/test/rest/status.test.js b/test/rest/status.test.js index 91063551e0..45e4ee144f 100644 --- a/test/rest/status.test.js +++ b/test/rest/status.test.js @@ -15,7 +15,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { done(err); return; } - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); done(); }); }); diff --git a/test/rest/time.test.js b/test/rest/time.test.js index 62fba2c66b..71d0ee28ab 100644 --- a/test/rest/time.test.js +++ b/test/rest/time.test.js @@ -12,7 +12,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { done(err); return; } - rest = helper.AblyRestPromise(); + rest = helper.AblyRest(); done(); }); }); From a0105eb80ef6a9c133b4fc861cd2fc0aea71afc5 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 22 Jun 2023 10:49:19 -0300 Subject: [PATCH 122/468] Change Crypto.generateRandomKey API to use Promises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds the missing Promise-based version of this method. Since we’re going to be removing the callbacks API in #1199 anyway, I’ve just replaced the callbacks version of the method. Resolves #1345. --- ably.d.ts | 4 +-- src/platform/nodejs/lib/util/crypto.ts | 23 ++++++++--------- src/platform/web/lib/util/crypto.ts | 24 ++++++++---------- test/realtime/crypto.test.js | 34 +++++++++++++------------- 4 files changed, 39 insertions(+), 46 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 0a85fd2602..f3f4a028d7 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2963,9 +2963,9 @@ declare namespace Types { * Generates a random key to be used in the encryption of the channel. If the language cryptographic randomness primitives are blocking or async, a callback is used. The callback returns a generated binary key. * * @param keyLength - The length of the key, in bits, to be generated. If not specified, this is equal to the default `keyLength` of the default algorithm: for AES this is 256 bits. - * @param callback - A function which, upon success, will be called with the generated key as a binary, for example, a byte array. Upon failure, the function will be called with information about the error. + * @returns A promise which, upon success, will be fulfilled with the generated key as a binary, for example, a byte array. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. */ - generateRandomKey(keyLength?: number, callback?: Types.StandardCallback): void; + generateRandomKey(keyLength?: number): Promise; /** * Returns a {@link CipherParams} object, using the default values for any fields not supplied by the {@link CipherParamOptions} object. * diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index 3a19dc93fe..1d99b060af 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -190,19 +190,16 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { * @param keyLength (optional) the required keyLength in bits * @param callback (optional) (err, key) */ - static generateRandomKey(keyLength?: number, callback?: API.Types.StandardCallback) { - if (arguments.length == 1 && typeof keyLength == 'function') { - callback = keyLength; - keyLength = undefined; - } - - generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { - if (callback !== undefined) { - const errorInfo = err - ? new ErrorInfo('Failed to generate random key: ' + err.message, 500, 50000, err) - : null; - callback(errorInfo, buf); - } + static async generateRandomKey(keyLength?: number): Promise { + return new Promise((resolve, reject) => { + generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { + if (err) { + const errorInfo = new ErrorInfo('Failed to generate random key: ' + err.message, 500, 50000, err); + reject(errorInfo); + } else { + resolve(buf!); + } + }); }); } diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 92168b5801..23c572d9dd 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -181,21 +181,17 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe * Generate a random encryption key from the supplied keylength (or the * default keyLength if none supplied) as an ArrayBuffer * @param keyLength (optional) the required keyLength in bits - * @param callback (optional) (err, key) */ - static generateRandomKey(keyLength?: number, callback?: API.Types.StandardCallback) { - if (arguments.length == 1 && typeof keyLength == 'function') { - callback = keyLength; - keyLength = undefined; - } - - generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { - if (callback !== undefined) { - const errorInfo = err - ? new ErrorInfo('Failed to generate random key: ' + err.message, 400, 50000, err) - : null; - callback(errorInfo, buf ?? undefined); - } + static async generateRandomKey(keyLength?: number): Promise { + return new Promise((resolve, reject) => { + generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { + if (err) { + const errorInfo = new ErrorInfo('Failed to generate random key: ' + err.message, 400, 50000, err); + reject(errorInfo); + } else { + resolve(buf!); + } + }); }); } diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index ba8f8d13bb..8952bcf71e 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -119,7 +119,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* generateRandomKey with an explicit keyLength */ it('generateRandomKey0', function (done) { - Crypto.generateRandomKey(64, function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(64), function (err, key) { if (err) { done(err); return; @@ -136,7 +136,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* generateRandomKey with no keyLength should generate 256-bit keys */ it('generateRandomKey1', function (done) { - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { done(err); return; @@ -151,7 +151,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('getDefaultParams_withResultOfGenerateRandomKey', function (done) { - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { done(err); } @@ -168,7 +168,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('getDefaultParams_ArrayBuffer_key', function (done) { - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { done(err); } @@ -184,7 +184,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('getDefaultParams_base64_key', function (done) { - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { done(err); return; @@ -201,7 +201,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('getDefaultParams_check_keylength', function (done) { - Crypto.generateRandomKey(64, function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(64), function (err, key) { if (err) { done(err); return; @@ -216,7 +216,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); it('getDefaultParams_preserves_custom_algorithms', function (done) { - Crypto.generateRandomKey(64, function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(64), function (err, key) { if (err) { done(err); return; @@ -401,7 +401,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } - Crypto.generateRandomKey(keyLength, function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(keyLength), function (err, key) { if (err) { closeAndFinish(done, realtime, err); return; @@ -464,7 +464,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel = realtime.channels.get(channelName), messageText = 'Test message (' + channelName + ')'; - Crypto.generateRandomKey(128, function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(128), function (err, key) { channel.setOptions({ cipher: { key: key } }); try { expect(channel.channelOptions.cipher.algorithm).to.equal('aes'); @@ -534,7 +534,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async txChannel = txRealtime.channels.get(channelName), rxChannel = rxRealtime.channels.get(channelName); - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { closeAndFinish(done, realtime, err); return; @@ -608,7 +608,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channelName = 'publish_immediately', messageText = 'Test message'; - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { closeAndFinish(done, [txRealtime, rxRealtime], err); return; @@ -655,10 +655,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.parallel( [ function (cb) { - Crypto.generateRandomKey(cb); + whenPromiseSettles(Crypto.generateRandomKey(), cb); }, function (cb) { - Crypto.generateRandomKey(cb); + whenPromiseSettles(Crypto.generateRandomKey(), cb); }, function (cb) { attachChannels([txChannel, rxChannel], cb); @@ -723,7 +723,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, [txRealtime, rxRealtime], err); return; } - Crypto.generateRandomKey(function (err, rxKey) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, rxKey) { if (err) { closeAndFinish(done, [txRealtime, rxRealtime], err); return; @@ -766,7 +766,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, [txRealtime, rxRealtime], err); return; } - Crypto.generateRandomKey(function (err, txKey) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, txKey) { if (err) { closeAndFinish(done, [txRealtime, rxRealtime], err); return; @@ -811,7 +811,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async attachChannels([txChannel, rxChannel], cb); }; var setInitialOptions = function (cb) { - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { closeAndFinish(done, [txRealtime, rxRealtime], err); return; @@ -844,7 +844,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; var createSecondKey = function (cb) { - Crypto.generateRandomKey(function (err, key) { + whenPromiseSettles(Crypto.generateRandomKey(), function (err, key) { if (err) { closeAndFinish(done, [txRealtime, rxRealtime], err); return; From 3dddbda02d02b54e44716d99230efe8992d28e61 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 22 Jun 2023 14:26:34 -0300 Subject: [PATCH 123/468] Remove Platform.Config.Promise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m not sure what purpose this was serving — Promise can just be accessed as a global variable. --- src/common/lib/util/defaults.ts | 2 +- src/common/lib/util/eventemitter.ts | 8 ++++---- src/common/types/IPlatformConfig.d.ts | 1 - src/platform/nativescript/config.js | 1 - src/platform/nodejs/config.ts | 1 - src/platform/react-native/config.ts | 1 - src/platform/web/config.ts | 1 - 7 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 47803a22e0..57a764bf14 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -233,7 +233,7 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie options.idempotentRestPublishing = true; } - if (options.internal?.promises && !Platform.Config.Promise) { + if (options.internal?.promises && typeof Promise === 'undefined') { Logger.logAction( Logger.LOG_ERROR, 'Defaults.normaliseOptions', diff --git a/src/common/lib/util/eventemitter.ts b/src/common/lib/util/eventemitter.ts index d24e448e8b..25605e77c8 100644 --- a/src/common/lib/util/eventemitter.ts +++ b/src/common/lib/util/eventemitter.ts @@ -241,9 +241,9 @@ class EventEmitter { once(...args: unknown[]): void | Promise { const argCount = args.length; - if ((argCount === 0 || (argCount === 1 && typeof args[0] !== 'function')) && Platform.Config.Promise) { + if ((argCount === 0 || (argCount === 1 && typeof args[0] !== 'function')) && typeof Promise !== 'undefined') { const event = args[0]; - return new Platform.Config.Promise((resolve) => { + return new Promise((resolve) => { this.once(event as string | string[] | null, resolve); }); } @@ -300,8 +300,8 @@ class EventEmitter { if (typeof targetState !== 'string' || typeof currentState !== 'string') { throw 'whenState requires a valid event String argument'; } - if (typeof listener !== 'function' && Platform.Config.Promise) { - return new Platform.Config.Promise((resolve) => { + if (typeof listener !== 'function' && typeof Promise !== 'undefined') { + return new Promise((resolve) => { EventEmitter.prototype.whenState.apply( this, [targetState, currentState, resolve].concat(listenerArgs as any[]) as any diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index f4766ea2e5..ecb0e4868d 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -16,7 +16,6 @@ export interface IPlatformConfig { inspect: (value: unknown) => string; stringByteSize: Buffer.byteLength; addEventListener?: typeof window.addEventListener | typeof global.addEventListener | null; - Promise: typeof Promise; getRandomValues?: (arr: ArrayBufferView, callback?: (error: Error | null) => void) => void; userAgent?: string | null; inherits?: typeof import('util').inherits; diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index a68d0e7468..31090e0240 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -47,7 +47,6 @@ var Config = { }, TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder, - Promise: global.Promise, getRandomValues: function (arr, callback) { var bytes = randomBytes(arr.length); for (var i = 0; i < arr.length; i++) { diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 2a99da4ad7..716be3b12a 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -30,7 +30,6 @@ const Config: IPlatformConfig = { callback(null); } }, - Promise: global && global.Promise, }; export default Config; diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index 81c7cd362c..f6e6f29a2e 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -32,7 +32,6 @@ export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { }, TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder, - Promise: global.Promise, getRandomArrayBuffer: (function (RNRandomBytes) { return function (byteLength: number, callback: (err: Error | null, result: ArrayBuffer | null) => void) { RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 0877337f3e..87536361ad 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -59,7 +59,6 @@ const Config: IPlatformConfig = { }, TextEncoder: globalObject.TextEncoder, TextDecoder: globalObject.TextDecoder, - Promise: globalObject.Promise, getRandomValues: (function (crypto) { if (crypto === undefined) { return undefined; From 9f1c38bbadfffa4b0918cbfb9ce0c933b4da3934 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 22 Jun 2023 14:37:36 -0300 Subject: [PATCH 124/468] Remove all checks for Promise availability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In version 2.0 of the library we’re assuming Promise is always available. --- src/common/lib/util/defaults.ts | 9 --- src/common/lib/util/eventemitter.ts | 4 +- test/realtime/event_emitter.test.js | 92 ++++++++++++++--------------- test/realtime/init.test.js | 36 ++++++----- 4 files changed, 64 insertions(+), 77 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 57a764bf14..6f8f83123a 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -233,15 +233,6 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie options.idempotentRestPublishing = true; } - if (options.internal?.promises && typeof Promise === 'undefined') { - Logger.logAction( - Logger.LOG_ERROR, - 'Defaults.normaliseOptions', - '{promises: true} was specified, but no Promise constructor found; disabling promises' - ); - options.internal.promises = false; - } - let connectivityCheckParams = null; let connectivityCheckUrl = options.connectivityCheckUrl; if (options.connectivityCheckUrl) { diff --git a/src/common/lib/util/eventemitter.ts b/src/common/lib/util/eventemitter.ts index 25605e77c8..4044343b31 100644 --- a/src/common/lib/util/eventemitter.ts +++ b/src/common/lib/util/eventemitter.ts @@ -241,7 +241,7 @@ class EventEmitter { once(...args: unknown[]): void | Promise { const argCount = args.length; - if ((argCount === 0 || (argCount === 1 && typeof args[0] !== 'function')) && typeof Promise !== 'undefined') { + if (argCount === 0 || (argCount === 1 && typeof args[0] !== 'function')) { const event = args[0]; return new Promise((resolve) => { this.once(event as string | string[] | null, resolve); @@ -300,7 +300,7 @@ class EventEmitter { if (typeof targetState !== 'string' || typeof currentState !== 'string') { throw 'whenState requires a valid event String argument'; } - if (typeof listener !== 'function' && typeof Promise !== 'undefined') { + if (typeof listener !== 'function') { return new Promise((resolve) => { EventEmitter.prototype.whenState.apply( this, diff --git a/test/realtime/event_emitter.test.js b/test/realtime/event_emitter.test.js index 4575594f21..c20acbb885 100644 --- a/test/realtime/event_emitter.test.js +++ b/test/realtime/event_emitter.test.js @@ -439,62 +439,60 @@ define(['shared_helper', 'chai'], function (helper, chai) { closeAndFinish(done, realtime); }); - if (typeof Promise !== 'undefined') { - describe('event_emitter_promise', function () { - it('whenState', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); - var eventEmitter = realtime.connection; - - eventEmitter - .whenState('connected') - .then(function () { - closeAndFinish(done, realtime); - }) - ['catch'](function (err) { - closeAndFinish(done, realtime, err); - }); - }); - - it('once', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); - var eventEmitter = realtime.connection; - - eventEmitter - .once('connected') - .then(function () { - closeAndFinish(done, realtime); - }) - ['catch'](function (err) { - closeAndFinish(done, realtime, err); - }); - }); - - it('anyEventsWithOnce', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), - eventEmitter = realtime.connection; - - const p = eventEmitter.once(); - eventEmitter.emit('b'); - p.then(function () { + describe('event_emitter_promise', function () { + it('whenState', function (done) { + var realtime = helper.AblyRealtime({ internal: { promises: true } }); + var eventEmitter = realtime.connection; + + eventEmitter + .whenState('connected') + .then(function () { closeAndFinish(done, realtime); - }).catch(function (err) { + }) + ['catch'](function (err) { closeAndFinish(done, realtime, err); }); - }); + }); - it('arrayOfEventsWithOnce', function (done) { - var realtime = helper.AblyRealtime({ autoConnect: false }), - eventEmitter = realtime.connection; + it('once', function (done) { + var realtime = helper.AblyRealtime({ internal: { promises: true } }); + var eventEmitter = realtime.connection; - const p = eventEmitter.once(['a', 'b', 'c']); - eventEmitter.emit('b'); - p.then(function () { + eventEmitter + .once('connected') + .then(function () { closeAndFinish(done, realtime); - }).catch(function (err) { + }) + ['catch'](function (err) { closeAndFinish(done, realtime, err); }); + }); + + it('anyEventsWithOnce', function (done) { + var realtime = helper.AblyRealtime({ autoConnect: false }), + eventEmitter = realtime.connection; + + const p = eventEmitter.once(); + eventEmitter.emit('b'); + p.then(function () { + closeAndFinish(done, realtime); + }).catch(function (err) { + closeAndFinish(done, realtime, err); + }); + }); + + it('arrayOfEventsWithOnce', function (done) { + var realtime = helper.AblyRealtime({ autoConnect: false }), + eventEmitter = realtime.connection; + + const p = eventEmitter.once(['a', 'b', 'c']); + eventEmitter.emit('b'); + p.then(function () { + closeAndFinish(done, realtime); + }).catch(function (err) { + closeAndFinish(done, realtime, err); }); }); - } + }); }); }); diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 998a1c2623..4b85dd005f 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -410,27 +410,25 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); }); - if (typeof Promise === 'undefined') { - it('init_callbacks_promises', function (done) { - try { - var realtime, - keyStr = helper.getTestApp().keys[0].keyStr, - getOptions = function () { - return { key: keyStr, autoConnect: false }; - }; + it('init_callbacks_promises', function (done) { + try { + var realtime, + keyStr = helper.getTestApp().keys[0].keyStr, + getOptions = function () { + return { key: keyStr, autoConnect: false }; + }; - realtime = new Ably.Realtime.Promise(getOptions()); - expect(realtime.options.promises, 'Check promises default to true with promise constructor').to.be.ok; + realtime = new Ably.Realtime.Promise(getOptions()); + expect(realtime.options.promises, 'Check promises default to true with promise constructor').to.be.ok; - if (!isBrowser && typeof require == 'function') { - realtime = new require('../../promises').Realtime(getOptions()); - expect(realtime.options.promises, 'Check promises default to true with promise require target').to.be.ok; - } - done(); - } catch (err) { - done(err); + if (!isBrowser && typeof require == 'function') { + realtime = new require('../../promises').Realtime(getOptions()); + expect(realtime.options.promises, 'Check promises default to true with promise require target').to.be.ok; } - }); - } + done(); + } catch (err) { + done(err); + } + }); }); }); From 471f112f4bb5fb03815f45996e7c70fb16c6dfea Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 27 Jun 2023 11:58:11 -0300 Subject: [PATCH 125/468] Expand documentation for channelEventCallback and connectionEventCallback --- ably.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index f3f4a028d7..9a7bddca9d 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1440,13 +1440,13 @@ declare namespace Types { */ type errorCallback = (error?: ErrorInfo | null) => void; /** - * The callback used by {@link RealtimeChannelCallbacks.whenState}. + * The callback used by {@link RealtimeChannelCallbacks.whenState} and for the events emitted by {@link RealtimeChannelBase}. * * @param changeStateChange - The state change that occurred. */ type channelEventCallback = (changeStateChange: ChannelStateChange) => void; /** - * The callback used by {@link ConnectionCallbacks.whenState}. + * The callback used by {@link ConnectionCallbacks.whenState} and for the events emitted by {@link ConnectionBase}. * * @param connectionStateChange - The state change that occurred. */ From 2a2ed49e40e6ce9a8b1a119b66b2f3eeaf235054 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 22 Jun 2023 14:56:55 -0300 Subject: [PATCH 126/468] Remove the callbacks public API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’ve decided that in version 2 of the SDK, we’ll only offer a promise-based API. Note that this doesn’t change the use of callbacks internally in the SDK; that’s a separate thing we might wish to do in the future. Resolves #1199. --- README.md | 320 +++------ ably.d.ts | 790 +-------------------- callbacks.d.ts | 2 - callbacks.js | 6 - docs/landing-page.md | 7 + docs/landing-pages/choose-library.html | 31 - docs/landing-pages/default.md | 7 - docs/landing-pages/promises.md | 7 - package.json | 4 +- src/common/lib/client/auth.ts | 8 +- src/common/lib/client/channel.ts | 12 +- src/common/lib/client/connection.ts | 7 +- src/common/lib/client/paginatedresource.ts | 6 +- src/common/lib/client/presence.ts | 12 +- src/common/lib/client/push.ts | 52 +- src/common/lib/client/realtime.ts | 10 +- src/common/lib/client/realtimechannel.ts | 31 +- src/common/lib/client/realtimepresence.ts | 27 +- src/common/lib/client/rest.ts | 27 +- src/common/lib/util/defaults.ts | 1 - src/common/types/ClientOptions.ts | 2 - test/realtime/event_emitter.test.js | 4 +- test/realtime/init.test.js | 21 - test/rest/init.test.js | 14 - 24 files changed, 159 insertions(+), 1249 deletions(-) delete mode 100644 callbacks.d.ts delete mode 100644 callbacks.js create mode 100644 docs/landing-page.md delete mode 100644 docs/landing-pages/choose-library.html delete mode 100644 docs/landing-pages/default.md delete mode 100644 docs/landing-pages/promises.md diff --git a/README.md b/README.md index 03c9c1e3bc..a120817518 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,6 @@ and require as: var Ably = require('ably'); ``` -For the version of the library where async methods return promises, use `var Ably = require('ably/promises');` instead. For the explicitly-callback-based variant use `require('ably/callbacks')`– see [Async API style](#async-api-style). - For usage, jump to [Using the Realtime API](#using-the-realtime-api) or [Using the REST API](#using-the-rest-api). #### Serverside usage with webpack @@ -92,21 +90,11 @@ let client = new Ably.Realtime(options); /* inferred type Ably.Realtime */ let channel = client.channels.get('feed'); /* inferred type Ably.Types.RealtimeChannel */ ``` -For the version of the library where async methods return promises, use `import * as Ably from 'ably/promises';` instead. For the explicitly-callback-based variant use `import * as Ably from 'ably/callbacks'` – see [Async API style](#async-api-style). - Intellisense in IDEs with TypeScript support is supported: ![TypeScript suggestions](./resources/typescript-demo.gif) -If you need to explicitly import the type definitions, see [ably.d.ts](./ably.d.ts) (or `promises.d.ts` if you're requiring the library as `ably/promises`). - -## Async API style - -This library exposes two API variants. Firstly, the original (and presently the default) callback-based API, which follows the usual Node.js error-first callback style. Second, a promises-based API. With the promises variant, you can still pass a callback to methods and the callback will work as expected, but if you do not pass a callback, the method will return a promise. The API in use can be selected explicitly by requiring that specific variant when requiring/importing the library (or in the case of the browser version, when instantiating it). The usage instructions below make reference to both variants. - -For this library version, and for all future 1.x versions, the callback-based API will be the default. This means that the promises-based variant will need to be explicitly selected, to avoid breaking backwards compatibility. A move to the promises-based variant as the default is likely at the next major release (i.e. 2.x onwards). - -For usage, jump to [Using the async API style](#using-the-async-api-style). +If you need to explicitly import the type definitions, see [ably.d.ts](./ably.d.ts). ## NativeScript @@ -128,13 +116,6 @@ var client = new Ably.Realtime(key: string); // which must contain at least one auth option, i.e. at least // one of: key, token, tokenDetails, authUrl, or authCallback var client = new Ably.Realtime(options: ClientOptions); - -// For a version of the library where async methods return promises if -// you don't pass a callback: -var client = new Ably.Realtime.Promise(options: string | ClientOptions); - -// For the explicitly-callback-based variant (see 'Async API style' below): -var client = new Ably.Rest.Callbacks(options: string | ClientOptions); ``` ### Connection @@ -195,36 +176,26 @@ If you would like to inspect the `Message` instances in order to identify whethe ```javascript // Publish a single message with name and data -channel.publish('greeting', 'Hello World!'); - -// Optionally, you can use a callback to be notified of success or failure -channel.publish('greeting', 'Hello World!', function(err) { - if(err) { - console.log('publish failed with error ' + err); - } else { - console.log('publish succeeded'); - } -}) +await channel.publish('greeting', 'Hello World!'); // Publish several messages at once -channel.publish([{name: 'greeting', data: 'Hello World!'}, ...], callback); +await channel.publish([{name: 'greeting', data: 'Hello World!'}, ...]); ``` ### Querying the History ```javascript -channel.history(function(err, messagesPage) { - messagesPage // PaginatedResult - messagesPage.items // array of Message - messagesPage.items[0].data // payload for first message - messagesPage.items.length // number of messages in the current page of history - messagesPage.hasNext() // true if there are further pages - messagesPage.isLast() // true if this page is the last page - messagesPage.next(function(nextPage) { ... }); // retrieves the next page as PaginatedResult -}); +const messagesPage = channel.history() +messagesPage // PaginatedResult +messagesPage.items // array of Message +messagesPage.items[0].data // payload for first message +messagesPage.items.length // number of messages in the current page of history +messagesPage.hasNext() // true if there are further pages +messagesPage.isLast() // true if this page is the last page +const nextPage = await messagesPage.next(); // retrieves the next page as PaginatedResult // Can optionally take an options param, see https://www.ably.com/docs/rest-api/#message-history -channel.history({start: ..., end: ..., limit: ..., direction: ...}, function(err, messagesPage) { ...}); +const messagesPage = await channel.history({start: ..., end: ..., limit: ..., direction: ...}); ``` ### Presence on a channel @@ -232,9 +203,8 @@ channel.history({start: ..., end: ..., limit: ..., direction: ...}, function(err Getting presence: ```javascript -channel.presence.get(function (err, presenceSet) { - presenceSet; // array of PresenceMessages -}); +const presenceSet = channel.presence.get(); +presenceSet; // array of PresenceMessages ``` Note that presence#get on a realtime channel does not return a @@ -243,17 +213,14 @@ PaginatedResult, as the library maintains a local copy of the presence set. Entering (and leaving) the presence set: ```javascript -channel.presence.enter('my status', function (err) { - // now I am entered -}); +await channel.presence.enter('my status'); +// now I am entered -channel.presence.update('new status', function (err) { - // my presence data is updated -}); +await channel.presence.update('new status'); +// my presence data is updated -channel.presence.leave(function (err) { - // I've left the presence set -}); +await channel.presence.leave() +// I've left the presence set ``` If you are using a client which is allowed to use any clientId -- @@ -263,24 +230,23 @@ https://www.ably.com/docs/general/authentication for more information), you can use ```javascript -channel.presence.enterClient('myClientId', 'status', function(err) { ... }); +await channel.presence.enterClient('myClientId', 'status'); // and similiarly, updateClient and leaveClient ``` ### Querying the Presence History ```javascript -channel.presence.history(function(err, messagesPage) { // PaginatedResult - messagesPage.items // array of PresenceMessage - messagesPage.items[0].data // payload for first message - messagesPage.items.length // number of messages in the current page of history - messagesPage.hasNext() // true if there are further pages - messagesPage.isLast() // true if this page is the last page - messagesPage.next(function(nextPage) { ... }); // retrieves the next page as PaginatedResult -}); +const messagesPage = channel.presence.history(); // PaginatedResult +messagesPage.items // array of PresenceMessage +messagesPage.items[0].data // payload for first message +messagesPage.items.length // number of messages in the current page of history +messagesPage.hasNext() // true if there are further pages +messagesPage.isLast() // true if this page is the last page +const nextPage = await messagesPage.next(); // retrieves the next page as PaginatedResult // Can optionally take an options param, see https://www.ably.com/docs/rest-api/#message-history -channel.presence.history({start: ..., end: ..., limit: ..., direction: ...}, function(err, messagesPage) { ...}); +const messagesPage = await channel.presence.history({start: ..., end: ..., limit: ..., direction: ...); ``` ### Symmetrical end-to-end encrypted payloads on a channel @@ -290,24 +256,22 @@ When a 128 bit or 256 bit key is provided to the library, the `data` attributes ```javascript // Generate a random 256-bit key for demonstration purposes (in // practice you need to create one and distribute it to clients yourselves) -Ably.Realtime.Crypto.generateRandomKey(function (err, key) { - var channel = client.channels.get('channelName', { cipher: { key: key } }); +const key = await Ably.Realtime.Crypto.generateRandomKey(); +var channel = client.channels.get('channelName', { cipher: { key: key } }); - channel.subscribe(function (message) { - message.name; // 'name is not encrypted' - message.data; // 'sensitive data is encrypted' - }); - - channel.publish('name is not encrypted', 'sensitive data is encrypted'); +channel.subscribe(function (message) { + message.name; // 'name is not encrypted' + message.data; // 'sensitive data is encrypted' }); + +channel.publish('name is not encrypted', 'sensitive data is encrypted'); ``` -You can also change the key on an existing channel using setOptions (which takes a callback which is called after the new encryption settings have taken effect): +You can also change the key on an existing channel using setOptions (which completes after the new encryption settings have taken effect): ```javascript -channel.setOptions({cipher: {key: }}, function() { - // New encryption settings are in effect -}) +await channel.setOptions({cipher: {key: }}); +// New encryption settings are in effect ``` ### Message interactions @@ -340,13 +304,6 @@ var client = new Ably.Rest(key: string); // which must contain at least one auth option, i.e. at least // one of: key, token, tokenDetails, authUrl, or authCallback var client = new Ably.Rest(options: ClientOptions); - -// For a version of the library where async methods return promises if -// you don't pass a callback: -var client = new Ably.Rest.Promise(options: string | ClientOptions); - -// For the explicitly-callback-based variant (see 'Async API style' above): -var client = new Ably.Rest.Callbacks(options: string | ClientOptions); ``` Given: @@ -359,75 +316,67 @@ var channel = client.channels.get('test'); ```javascript // Publish a single message with name and data -channel.publish('greeting', 'Hello World!'); - -// Optionally, you can use a callback to be notified of success or failure -channel.publish('greeting', 'Hello World!', function(err) { - if(err) { - console.log('publish failed with error ' + err); - } else { - console.log('publish succeeded'); - } -}) +try { + channel.publish('greeting', 'Hello World!'); + console.log('publish succeeded'); +} catch (err) { + console.log('publish failed with error ' + err); +} // Publish several messages at once -channel.publish([{name: 'greeting', data: 'Hello World!'}, ...], callback); +await channel.publish([{name: 'greeting', data: 'Hello World!'}, ...]); ``` ### Querying the History ```javascript -channel.history(function(err, messagesPage) { - messagesPage // PaginatedResult - messagesPage.items // array of Message - messagesPage.items[0].data // payload for first message - messagesPage.items.length // number of messages in the current page of history - messagesPage.hasNext() // true if there are further pages - messagesPage.isLast() // true if this page is the last page - messagesPage.next(function(nextPage) { ... }); // retrieves the next page as PaginatedResult -}); +const messagesPage = await channel.history(); +messagesPage // PaginatedResult +messagesPage.items // array of Message +messagesPage.items[0].data // payload for first message +messagesPage.items.length // number of messages in the current page of history +messagesPage.hasNext() // true if there are further pages +messagesPage.isLast() // true if this page is the last page +const nextPage = await messagesPage.next(); // retrieves the next page as PaginatedResult // Can optionally take an options param, see https://www.ably.com/docs/rest-api/#message-history -channel.history({start: ..., end: ..., limit: ..., direction: ...}, function(err, messagesPage) { ...}); +await channel.history({start: ..., end: ..., limit: ..., direction: ...}); ``` ### Presence on a channel ```javascript -channel.presence.get(function(err, presencePage) { // PaginatedResult - presencePage.items // array of PresenceMessage - presencePage.items[0].data // payload for first message - presencePage.items.length // number of messages in the current page of members - presencePage.hasNext() // true if there are further pages - presencePage.isLast() // true if this page is the last page - presencePage.next(function(nextPage) { ... }); // retrieves the next page as PaginatedResult -}); +const presencePage = await channel.presence.get() // PaginatedResult +presencePage.items // array of PresenceMessage +presencePage.items[0].data // payload for first message +presencePage.items.length // number of messages in the current page of members +presencePage.hasNext() // true if there are further pages +presencePage.isLast() // true if this page is the last page +const nextPage = await presencePage.next(); // retrieves the next page as PaginatedResult ``` ### Querying the Presence History ```javascript -channel.presence.history(function(err, messagesPage) { // PaginatedResult - messagesPage.items // array of PresenceMessage - messagesPage.items[0].data // payload for first message - messagesPage.items.length // number of messages in the current page of history - messagesPage.hasNext() // true if there are further pages - messagesPage.isLast() // true if this page is the last page - messagesPage.next(function(nextPage) { ... }); // retrieves the next page as PaginatedResult -}); +const messagesPage = channel.presence.history(); // PaginatedResult +messagesPage.items // array of PresenceMessage +messagesPage.items[0].data // payload for first message +messagesPage.items.length // number of messages in the current page of history +messagesPage.hasNext() // true if there are further pages +messagesPage.isLast() // true if this page is the last page +const nextPage = await messagesPage.next(); // retrieves the next page as PaginatedResult // Can optionally take an options param, see https://www.ably.com/docs/rest-api/#message-history -channel.history({start: ..., end: ..., limit: ..., direction: ...}, function(err, messagesPage) { ...}); +const messagesPage = channel.history({start: ..., end: ..., limit: ..., direction: ...}); ``` ### Getting the status of a channel ```javascript -channel.status(function(err, channelDetails) { - channelDetails.channelId // The name of the channel - channelDetails.status.isActive // A boolean indicating whether the channel is active - channelDetails.status.occupancy // Contains metadata relating to the occupants of the channel -}); +const channelDetails = await channel.status(); +channelDetails.channelId // The name of the channel +channelDetails.status.isActive // A boolean indicating whether the channel is active +channelDetails.status.occupancy // Contains metadata relating to the occupants of the channel ``` ### Generate Token and Token Request @@ -438,134 +387,49 @@ explanation of Ably's authentication mechanism. Requesting a token: ```javascript -client.auth.requestToken(function(err, tokenDetails) { - // tokenDetails is instance of TokenDetails - // see https://www.ably.com/docs/rest/authentication/#token-details for its properties +const tokenDetails = await client.auth.requestToken(); +// tokenDetails is instance of TokenDetails +// see https://www.ably.com/docs/rest/authentication/#token-details for its properties - // Now we have the token, we can send it to someone who can instantiate a client with it: - var clientUsingToken = new Ably.Realtime(tokenDetails.token); -}); +// Now we have the token, we can send it to someone who can instantiate a client with it: +var clientUsingToken = new Ably.Realtime(tokenDetails.token); // requestToken can take two optional params // tokenParams: https://www.ably.com/docs/rest/authentication/#token-params // authOptions: https://www.ably.com/docs/rest/authentication/#auth-options -client.auth.requestToken(tokenParams, authOptions, function(err, tokenDetails) { ... }); +const tokenDetails = await client.auth.requestToken(tokenParams, authOptions); ``` Creating a token request (for example, on a server in response to a request by a client using the `authCallback` or `authUrl` mechanisms): ```javascript -client.auth.createTokenRequest(function(err, tokenRequest) { - // now send the tokenRequest back to the client, which will - // use it to request a token and connect to Ably -}); +const tokenRequest = await client.auth.createTokenRequest(); +// now send the tokenRequest back to the client, which will +// use it to request a token and connect to Ably // createTokenRequest can take two optional params // tokenParams: https://www.ably.com/docs/rest/authentication/#token-params // authOptions: https://www.ably.com/docs/rest/authentication/#auth-options -client.auth.createTokenRequest(tokenParams, authOptions, function(err, tokenRequest) { ... }); +const tokenRequest = await client.auth.createTokenRequest(tokenParams, authOptions); ``` ### Fetching your application's stats ```javascript -client.stats(function(err, statsPage) { // statsPage as PaginatedResult - statsPage.items // array of Stats - statsPage.items[0].inbound.rest.messages.count; // total messages published over REST - statsPage.items.length; // number of stats in the current page of history - statsPage.hasNext() // true if there are further pages - statsPage.isLast() // true if this page is the last page - statsPage.next(function(nextPage) { ... }); // retrieves the next page as PaginatedResult -}); +const statsPage = await client.stats() // statsPage as PaginatedResult +statsPage.items // array of Stats +statsPage.items[0].inbound.rest.messages.count; // total messages published over REST +statsPage.items.length; // number of stats in the current page of history +statsPage.hasNext() // true if there are further pages +statsPage.isLast() // true if this page is the last page +const nextPage = await statsPage.next(); // retrieves the next page as PaginatedResult ``` ### Fetching the Ably service time ```javascript -client.time(function(err, time) { ... }); // time is in ms since epoch -``` - -## Using the async API style - -### Realtime Example - -```ts -import * as Ably from 'ably/promises'; - -const client = new Ably.Realtime.Promise(options); - -const ablyRealtimePromiseExample = async () => { - const channel = client.channels.get('myChannel'); - - // Attaching to a channel - await channel.attach(); - - // Getting presence on a channel - const presenceMessage = await channel.presence.get(); - console.log(presenceMessage); - - // Updating presence on a client - await channel.presence.enter(); - await channel.presence.update('new status'); - await channel.presence.leave(); - - // Publishing a message - await channel.publish('greeting', 'Hello, World!'); - - // Querying history - const history = await channel.history({ limit: 25 }); - console.log(history); - - client.close(); -}; - -ablyRealtimePromiseExample(); -``` - -### REST Example - -```ts -import * as Ably from 'ably/promises'; - -const client = new Ably.Rest.Promise(options); - -const ablyRestPromiseExample = async () => { - const channel = client.channels.get('myChannel'); - - // Publishing a message - await channel.publish('greeting', 'Hello, World!'); - - // Getting presence on a channel - const presenceMessage = await channel.presence.get(); - console.log(presenceMessage); - - // Querying history - const history = await channel.history({ limit: 25 }); - console.log(await history.current()); - - // Getting the status of a channel - const channelDetails = await channel.status(); - console.log(channelDetails); - - // Requesting a token - const token = await client.auth.requestToken(tokenParams); - - // Creating a token request - const tokenRequest = await client.auth.createTokenRequest(); - - // Fetching your application's stats - const stats = await client.stats(); - console.log(stats); - - // Fetching the Ably service time - const time = await client.time(); - console.log(`Ably service time: ${time}`); - - client.close(); -}; - -ablyRestPromiseExample(); +const time = await client.time(); // time is in ms since epoch ``` ## Delta Plugin diff --git a/ably.d.ts b/ably.d.ts index 9a7bddca9d..852b7ad5ab 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -890,7 +890,7 @@ declare namespace Types { } /** - * Contains the properties of a request for a token to Ably. Tokens are generated using {@link AuthCallbacks.requestToken} or {@link AuthPromise.requestToken}. + * Contains the properties of a request for a token to Ably. Tokens are generated using {@link AuthPromise.requestToken}. */ interface TokenRequest { /** @@ -1001,9 +1001,7 @@ declare namespace Types { /** * The `RestHistoryParams` interface describes the parameters accepted by the following methods: * - * - {@link PresenceCallbacks.history} * - {@link PresencePromise.history} - * - {@link ChannelCallbacks.history} * - {@link ChannelPromise.history} */ interface RestHistoryParams { @@ -1032,10 +1030,7 @@ declare namespace Types { } /** - * The `RestPresenceParams` interface describes the parameters accepted by the following methods: - * - * - {@link PresenceCallbacks.get} - * - {@link PresencePromise.get} + * The `RestPresenceParams` interface describes the parameters accepted by {@link PresencePromise.get}. */ interface RestPresenceParams { /** @@ -1055,10 +1050,7 @@ declare namespace Types { } /** - * The `RealtimePresenceParams` interface describes the parameters accepted by the following methods: - * - * - {@link RealtimePresenceCallbacks.get} - * - {@link RealtimePresencePromise.get} + * The `RealtimePresenceParams` interface describes the parameters accepted by {@link RealtimePresencePromise.get}. */ interface RealtimePresenceParams { /** @@ -1080,9 +1072,7 @@ declare namespace Types { /** * The `RealtimeHistoryParams` interface describes the parameters accepted by the following methods: * - * - {@link RealtimePresenceCallbacks.history} * - {@link RealtimePresencePromise.history} - * - {@link RealtimeChannelCallbacks.history} * - {@link RealtimeChannelPromise.history} */ interface RealtimeHistoryParams { @@ -1310,8 +1300,6 @@ declare namespace Types { /** * The `DeviceRegistrationParams` interface describes the parameters accepted by the following methods: * - * - {@link PushDeviceRegistrationsCallbacks.list} - * - {@link PushDeviceRegistrationsCallbacks.removeWhere} * - {@link PushDeviceRegistrationsPromise.list} * - {@link PushDeviceRegistrationsPromise.removeWhere} */ @@ -1337,8 +1325,6 @@ declare namespace Types { /** * The `PushChannelSubscriptionParams` interface describes the parameters accepted by the following methods: * - * - {@link PushChannelSubscriptionsCallbacks.list} - * - {@link PushChannelSubscriptionsCallbacks.removeWhere} * - {@link PushChannelSubscriptionsPromise.list} * - {@link PushChannelSubscriptionsPromise.removeWhere} */ @@ -1362,10 +1348,7 @@ declare namespace Types { } /** - * The `PushChannelsParams` interface describes the parameters accepted by the following methods: - * - * - {@link PushChannelSubscriptionsCallbacks.listChannels} - * - {@link PushChannelSubscriptionsPromise.listChannels} + * The `PushChannelsParams` interface describes the parameters accepted by {@link PushChannelSubscriptionsPromise.listChannels}. */ interface PushChannelsParams { /** @@ -1377,9 +1360,7 @@ declare namespace Types { /** * The `StatsParams` interface describes the parameters accepted by the following methods: * - * - {@link RestCallbacks.stats} * - {@link RestPromise.stats} - * - {@link RealtimeCallbacks.stats} * - {@link RealtimePromise.stats} */ interface StatsParams { @@ -1416,17 +1397,6 @@ declare namespace Types { } // Common Listeners - /** - * A standard callback format used in most areas of the callback API. - * - * @param err - An error object if the request failed. - * @param result - The result of the request, if any. - */ - type StandardCallback = (err: ErrorInfo | null, result?: T) => void; - /** - * A {@link StandardCallback} which returns a {@link PaginatedResult}. - */ - type paginatedResultCallback = StandardCallback>; /** * A callback which returns only a single argument, used for {@link RealtimeChannelBase} subscriptions. * @@ -1434,47 +1404,17 @@ declare namespace Types { */ type messageCallback = (message: T) => void; /** - * A callback which returns only an error, or null, when complete. - * - * @param error - The error if the request failed, or null not. - */ - type errorCallback = (error?: ErrorInfo | null) => void; - /** - * The callback used by {@link RealtimeChannelCallbacks.whenState} and for the events emitted by {@link RealtimeChannelBase}. + * The callback used for the events emitted by {@link RealtimeChannelBase}. * * @param changeStateChange - The state change that occurred. */ type channelEventCallback = (changeStateChange: ChannelStateChange) => void; /** - * The callback used by {@link ConnectionCallbacks.whenState} and for the events emitted by {@link ConnectionBase}. + * The callback used for the events emitted by {@link ConnectionBase}. * * @param connectionStateChange - The state change that occurred. */ type connectionEventCallback = (connectionStateChange: ConnectionStateChange) => void; - /** - * The callback used by {@link RestCallbacks.time} and {@link RealtimeCallbacks.time}. - * - * @param timeCallback - The time in milliseconds since the Unix epoch. - */ - type timeCallback = StandardCallback; - /** - * The callback used by {@link RealtimePresenceCallbacks.get}. - * - * @param realtimePresenceGetCallback - An array of {@link PresenceMessage} objects. - */ - type realtimePresenceGetCallback = StandardCallback; - /** - * The callback used by {@link AuthCallbacks.authorize}. - * - * @param tokenDetailsCallback - A {@link TokenDetails} object. - */ - type tokenDetailsCallback = StandardCallback; - /** - * The callback used by {@link AuthCallbacks.createTokenRequest}. - * - * @param tokenRequestCallback - A {@link TokenRequest} object - */ - type tokenRequestCallback = StandardCallback; /** * The callback used by {@link recoverConnectionCallback}. * @@ -1592,7 +1532,7 @@ declare namespace Types { // Classes /** - * The `RestBase` class acts as a base class for the {@link RestCallbacks} and {@link RestPromise} classes. + * The `RestBase` class acts as a base class for the {@link RestPromise} class. */ class RestBase { /** @@ -1621,71 +1561,6 @@ declare namespace Types { static PresenceMessage: Types.PresenceMessageStatic; } - /** - * A client that offers a simple stateless API to interact directly with Ably's REST API. - */ - class RestCallbacks extends RestBase { - /** - * A promisified version of the library (use this if you prefer to use Promises or async/await instead of callbacks) - */ - static Promise: typeof Types.RestPromise; - /** - * A callback based version of the library - */ - static Callbacks: typeof Types.RestCallbacks; - /** - * An {@link Types.AuthCallbacks} object. - */ - auth: Types.AuthCallbacks; - /** - * A {@link Types.Channels} object. - */ - channels: Types.Channels; - /** - * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. - * - * @param method - The request method to use, such as `GET`, `POST`. - * @param path - The request path. - * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. - * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. - * @param body - The JSON body of the request. - * @param headers - Additional HTTP headers to include in the request. - * @param callback - A function which, upon success, will be called with an {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the function will be called with information about the error. - */ - request( - method: string, - path: string, - version: number, - params?: any, - body?: any[] | any, - headers?: any, - callback?: Types.StandardCallback> - ): void; - /** - * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). - * - * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link Types.StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link Types.StatsParams} interface. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the function will be called with information about the error. - */ - stats(params?: StatsParams | any, callback?: Types.paginatedResultCallback): void; - /** - * Queries the REST `/stats` API and retrieves your application's usage statistics, using the default parameters described in the {@link Types.StatsParams} interface. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). - * - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the function will be called with information about the error. - */ - stats(callback?: Types.paginatedResultCallback): void; - /** - * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link Types.TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link Types.ClientOptions.queryTime} property instead of this method. - * - * @param callback - A function which, upon success, will be called with the time as milliseconds since the Unix epoch. Upon failure, the function will be called with information about the error. - */ - time(callback?: Types.timeCallback): void; - /** - * A {@link Types.PushCallbacks} object. - */ - push: Types.PushCallbacks; - } - /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ @@ -1694,10 +1569,6 @@ declare namespace Types { * A promisified version of the library (use this if you prefer to use Promises or async/await instead of callbacks) */ static Promise: typeof Types.RestPromise; - /** - * A callback based version of the library - */ - static Callbacks: typeof Types.RestCallbacks; /** * An {@link Types.AuthPromise} object. */ @@ -1752,10 +1623,6 @@ declare namespace Types { * A promisified version of the library (use this if you prefer to use Promises or async/await instead of callbacks) */ static Promise: typeof Types.RealtimePromise; - /** - * A callback based version of the library - */ - static Callbacks: typeof Types.RealtimeCallbacks; /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. */ @@ -1770,67 +1637,6 @@ declare namespace Types { connect(): void; } - /** - * A client that extends the functionality of {@link RestCallbacks} and provides additional realtime-specific features. - */ - class RealtimeCallbacks extends RealtimeBase { - /** - * An {@link Types.AuthCallbacks} object. - */ - auth: Types.AuthCallbacks; - /** - * A {@link Types.Channels} object. - */ - channels: Types.Channels; - /** - * A {@link Types.ConnectionCallbacks} object. - */ - connection: Types.ConnectionCallbacks; - /** - * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. - * - * @param method - The request method to use, such as `GET`, `POST`. - * @param path - The request path. - * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. - * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. - * @param body - The JSON body of the request. - * @param headers - Additional HTTP headers to include in the request. - * @param callback - A function which, upon success, will be called with the {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the function will be called with information about the error. - */ - request( - method: string, - path: string, - version: number, - params?: any, - body?: any[] | any, - headers?: any, - callback?: Types.StandardCallback> - ): void; - /** - * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). - * - * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link Types.StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the function will be called with information about the error. - */ - stats(params: StatsParams | any, callback: Types.paginatedResultCallback): void; - /** - * Queries the REST `/stats` API and retrieves your application's usage statistics, using the default parameters described in the {@link Types.StatsParams} interface. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). - * - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the function will be called with information about the error. - */ - stats(callback: Types.paginatedResultCallback): void; - /** - * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link Types.TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link Types.ClientOptions.queryTime} property instead of this method. - * - * @param callback - A function which, upon success, will be called with the time as milliseconds since the Unix epoch. Upon failure, the function will be called with information about the error. - */ - time(callback?: Types.timeCallback): void; - /** - * A {@link Types.PushCallbacks} object. - */ - push: Types.PushCallbacks; - } - /** * A client that extends the functionality of {@link RestPromise} and provides additional realtime-specific features. */ @@ -1886,7 +1692,7 @@ declare namespace Types { } /** - * The `AuthBase` class acts as a base class for the {@link AuthCallbacks} and {@link AuthPromise} classes. + * The `AuthBase` class acts as a base class for the {@link AuthPromise} class. */ class AuthBase { /** @@ -1895,83 +1701,6 @@ declare namespace Types { clientId: string; } - /** - * Creates Ably {@link TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients. - */ - class AuthCallbacks extends AuthBase { - /** - * Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token has been obtained. Also stores any {@link TokenParams} and {@link AuthOptions} passed in as the new defaults, to be used for all subsequent implicit or explicit token requests. Any {@link TokenParams} and {@link AuthOptions} objects passed in entirely replace, as opposed to being merged with, the current client library saved values. - * - * @param tokenParams - A {@link TokenParams} object. - * @param authOptions - An {@link AuthOptions} object. - * @param callback - A function which, upon success, will be called with a {@link TokenDetails} object. Upon failure, the function will be called with information about the error. - */ - authorize(tokenParams?: TokenParams, authOptions?: AuthOptions, callback?: tokenDetailsCallback): void; - /** - * Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token has been obtained. Also stores any {@link TokenParams} passed in as the new default, to be used for all subsequent implicit or explicit token requests. Any {@link TokenParams} object passed in entirely replaces, as opposed to being merged with, the current client library saved value. - * - * @param tokenParams - A {@link TokenParams} object. - * @param callback - A function which, upon success, will be called with a {@link TokenDetails} object. Upon failure, the function will be called with information about the error. - */ - authorize(tokenParams?: TokenParams, callback?: tokenDetailsCallback): void; - /** - * Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token has been obtained. - * - * @param callback - A function which, upon success, will be called with a {@link TokenDetails} object. Upon failure, the function will be called with information about the error. - */ - authorize(callback?: tokenDetailsCallback): void; - /** - * Creates and signs an Ably {@link TokenRequest} based on the specified (or if none specified, the client library stored) {@link TokenParams} and {@link AuthOptions}. Note this can only be used when the API `key` value is available locally. Otherwise, the Ably {@link TokenRequest} must be obtained from the key owner. Use this to generate an Ably {@link TokenRequest} in order to implement an Ably Token request callback for use by other clients. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param tokenParams - A {@link TokenParams} object. - * @param authOptions - An {@link AuthOptions} object. - * @param callback - A function which, upon success, will be called with a {@link TokenRequest} object. Upon failure, the function will be called with information about the error. - */ - createTokenRequest( - tokenParams?: TokenParams | null, - authOptions?: AuthOptions | null, - callback?: tokenRequestCallback - ): void; - /** - * Creates and signs an Ably {@link TokenRequest} based on the specified (or if none specified, the client library stored) {@link TokenParams}. Note this can only be used when the API `key` value is available locally. Otherwise, the Ably {@link TokenParams} must be obtained from the key owner. Use this to generate an Ably {@link TokenRequest} in order to implement an Ably Token request callback for use by other clients. When the {@link TokenRequest} is omitted or `null`, the default token parameters for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param tokenParams - A {@link TokenParams} object. - * @param callback - A function which, upon success, will be called with a {@link TokenRequest} object. Upon failure, the function will be called with information about the error. - */ - createTokenRequest(tokenParams?: TokenParams | null, callback?: tokenRequestCallback): void; - /** - * Creates and signs an Ably {@link TokenRequest} based on the the client library stored {@link TokenParams} and {@link AuthOptions}. Note this can only be used when the API `key` value is available locally. Otherwise, the Ably {@link TokenRequest} must be obtained from the key owner. Use this to generate an Ably {@link TokenRequest} in order to implement an Ably Token request callback for use by other clients. The default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param callback - A function which, upon success, will be called with a {@link TokenRequest} object. Upon failure, the function will be called with information about the error. - */ - createTokenRequest(callback?: tokenRequestCallback): void; - /** - * Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams} and {@link AuthOptions}. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param TokenParams - A {@link TokenParams} object. - * @param authOptions - An {@link AuthOptions} object. - * @param callback - A function which, upon success, will be called with a {@link TokenDetails} object. Upon failure, the function will be called with information about the error. - */ - requestToken( - TokenParams?: TokenParams | null, - authOptions?: AuthOptions | null, - callback?: tokenDetailsCallback - ): void; - /** - * Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams}. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param TokenParams - A {@link TokenParams} object. - * @param callback - A function which, upon success, will be called with a {@link TokenDetails} object. Upon failure, the function will be called with information about the error. - */ - requestToken(TokenParams?: TokenParams | null, callback?: tokenDetailsCallback): void; - /** - * Calls the `requestToken` REST API endpoint to obtain an Ably Token. The default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param callback - A function which, upon success, will be called with a {@link TokenDetails} object. Upon failure, the function will be called with information about the error. - */ - requestToken(callback?: tokenDetailsCallback): void; - } - /** * Creates Ably {@link TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients. */ @@ -2002,38 +1731,6 @@ declare namespace Types { requestToken(TokenParams?: TokenParams, authOptions?: AuthOptions): Promise; } - /** - * Enables the retrieval of the current and historic presence set for a channel. - */ - class PresenceCallbacks { - /** - * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns a {@link Types.PaginatedResult} object, containing an array of {@link PresenceMessage} objects. - * - * @param params - A set of parameters which are used to specify which presence members should be retrieved. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - get(params?: RestPresenceParams, callback?: paginatedResultCallback): void; - /** - * Retrieves the current members present on the channel and the metadata for each member, such as their [PresenceAction]{@link PresenceAction} and ID. - * - * @param callback - A function which, upon success, will be called with a [PaginatedResult]{@link PaginatedResult} object, containing an array of [PresenceMessage]{@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - get(callback?: paginatedResultCallback): void; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which messages should be retrieved. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - history(params: RestHistoryParams, callback?: paginatedResultCallback): void; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. - * - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - history(callback: paginatedResultCallback): void; - } - /** * Enables the retrieval of the current and historic presence set for a channel. */ @@ -2055,7 +1752,7 @@ declare namespace Types { } /** - * The `RealtimePresenceBase` class acts as a base class for the {@link RealtimePresenceCallbacks} and {@link RealtimePresencePromise} classes. + * The `RealtimePresenceBase` class acts as a base class for the {@link RealtimePresencePromise} class. */ class RealtimePresenceBase { /** @@ -2100,128 +1797,6 @@ declare namespace Types { unsubscribe(): void; } - /** - * Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. - */ - class RealtimePresenceCallbacks extends RealtimePresenceBase { - /** - * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns an array of {@link PresenceMessage} objects. - * - * @param params - A set of parameters which are used to specify which presence members should be retrieved. - * @param callback - A function which, upon success, will be called with an array of {@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - get(params?: RealtimePresenceParams, callback?: realtimePresenceGetCallback): void; - /** - * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns an array of {@link PresenceMessage} objects. - * - * @param callback - A function which, upon success, will be called with an array of {@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - get(callback?: realtimePresenceGetCallback): void; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which presence messages should be retrieved. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - history(params?: RealtimeHistoryParams, callback?: paginatedResultCallback): void; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. - * - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the function will be called with information about the error. - */ - history(callback?: paginatedResultCallback): void; - /** - * Registers a listener that is called each time a {@link PresenceMessage} matching a given {@link PresenceAction}, or an action within an array of {@link PresenceAction | `PresenceAction`s}, is received on the channel, such as a new member entering the presence set. - * - * @param presence - A {@link PresenceAction} or an array of {@link PresenceAction | `PresenceAction`s} to register the listener for. - * @param listener - An event listener function. - * @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - subscribe( - presence: PresenceAction | Array, - listener?: messageCallback, - callbackWhenAttached?: errorCallback - ): void; - /** - * Registers a listener that is called each time a {@link PresenceMessage} is received on the channel, such as a new member entering the presence set. - * - * @param listener - An event listener function. - * @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - subscribe(listener: messageCallback, callbackWhenAttached?: errorCallback): void; - /** - * Enters the presence set for the channel, passing a `data` payload. A `clientId` is required to be present on a channel. - * - * @param data - The payload associated with the presence member. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - enter(data?: any, callback?: errorCallback): void; - /** - * Enters the presence set for the channel. A `clientId` is required to be present on a channel. - * - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - enter(callback?: errorCallback): void; - /** - * Updates the `data` payload for a presence member. If called before entering the presence set, this is treated as an {@link PresenceAction.ENTER} event. - * - * @param data - The payload to update for the presence member. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - update(data?: any, callback?: errorCallback): void; - /** - * Leaves the presence set for the channel. A client must have previously entered the presence set before they can leave it. - * - * @param data - The payload associated with the presence member. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - leave(data?: any, callback?: errorCallback): void; - /** - * Leaves the presence set for the channel. A client must have previously entered the presence set before they can leave it. - * - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - leave(callback?: errorCallback): void; - /** - * Enters the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to enter into the presence set. - * @param data - The payload associated with the presence member. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - enterClient(clientId: string, data?: any, callback?: errorCallback): void; - /** - * Enters the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to enter into the presence set. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - enterClient(clientId: string, callback?: errorCallback): void; - /** - * Updates the `data` payload for a presence member using a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to update in the presence set. - * @param data - The payload to update for the presence member. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - updateClient(clientId: string, data?: any, callback?: errorCallback): void; - /** - * Leaves the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to leave the presence set for. - * @param data - The payload associated with the presence member. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - leaveClient(clientId: string, data?: any, callback?: errorCallback): void; - /** - * Leaves the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to leave the presence set for. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - leaveClient(clientId: string, callback?: errorCallback): void; - } - /** * Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. */ @@ -2306,7 +1881,7 @@ declare namespace Types { } /** - * The `ChannelBase` class acts as a base class for the {@link ChannelCallbacks} and {@link ChannelPromise} classes. + * The `ChannelBase` class acts as a base class for the {@link ChannelPromise} class. */ class ChannelBase { /** @@ -2315,66 +1890,6 @@ declare namespace Types { name: string; } - /** - * Enables messages to be published and historic messages to be retrieved for a channel. - */ - class ChannelCallbacks extends ChannelBase { - /** - * A {@link PresenceCallbacks} object. - */ - presence: PresenceCallbacks; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link Message} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which messages should be retrieved. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Message} objects. Upon failure, the function will be called with information about the error. - */ - history(params?: RestHistoryParams, callback?: paginatedResultCallback): void; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link Message} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. - * - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Message} objects. Upon failure, the function will be called with information about the error. - */ - history(callback?: paginatedResultCallback): void; - /** - * Publishes a single message to the channel with the given event name and payload. - * - * @param name - The name of the message. - * @param data - The payload of the message. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(name: string, data: any, callback?: errorCallback): void; - /** - * Publishes an array of messages to the channel. - * - * @param messages - An array of {@link Message} objects. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(messages: any[], callback?: errorCallback): void; - /** - * Publishes a message to the channel. - * - * @param message - A {@link Message} object. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(message: any, callback?: errorCallback): void; - /** - * Publishes a single message to the channel with the given event name and payload. - * - * @param name - The name of the message. - * @param data - The payload of the message. - * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(name: string, data: any, options?: PublishOptions, callback?: errorCallback): void; - /** - * Retrieves a {@link ChannelDetails} object for the channel, which includes status and occupancy metrics. - * - * @param callback - A function which, upon success, will be called a {@link ChannelDetails} object. Upon failure, the function will be called with information about the error. - */ - status(callback: StandardCallback): void; - } - /** * Enables messages to be published and historic messages to be retrieved for a channel. */ @@ -2424,7 +1939,7 @@ declare namespace Types { } /** - * The `RealtimeChannelBase` class acts as a base class for the {@link RealtimeChannelCallbacks} and {@link RealtimeChannelPromise} classes. + * The `RealtimeChannelBase` class acts as a base class for the {@link RealtimeChannelPromise} class. */ class RealtimeChannelBase extends EventEmitter { /** @@ -2503,7 +2018,7 @@ declare namespace Types { }; /** - * Contains properties to filter messages with when calling {@link RealtimeChannelCallbacks.subscribe | `RealtimeChannelCallbacks.subscribe()`} or {@link RealtimeChannelPromise.subscribe | `RealtimeChannelPromise.subscribe()`}. + * Contains properties to filter messages with when calling {@link RealtimeChannelPromise.subscribe | `RealtimeChannelPromise.subscribe()`}. */ type MessageFilter = { /** @@ -2528,116 +2043,6 @@ declare namespace Types { clientId: string; }; - /** - * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresenceCallbacks} object of a channel. - */ - class RealtimeChannelCallbacks extends RealtimeChannelBase { - /** - * A {@link RealtimePresenceCallbacks} object. - */ - presence: RealtimePresenceCallbacks; - /** - * Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel are received by any channel listeners registered using {@link RealtimeChannelCallbacks.subscribe | `subscribe()`}. Any resulting channel state change will be emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. As a convenience, `attach()` is called implicitly if {@link RealtimeChannelCallbacks.subscribe | `subscribe()`} for the channel is called, or {@link RealtimePresenceCallbacks.enter | `enter()`} or {@link RealtimePresenceCallbacks.subscribe | `subscribe()`} are called on the {@link RealtimePresenceCallbacks} object for this channel. - * - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - attach(callback?: errorCallback): void; - /** - * Detach from this channel. Any resulting channel state change is emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. Once all clients globally have detached from the channel, the channel will be released in the Ably service within two minutes. - * - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - detach(callback?: errorCallback): void; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link Message} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which presence members should be retrieved. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Message} objects. Upon failure, the function will be called with information about the error. - */ - history(params?: RealtimeHistoryParams, callback?: paginatedResultCallback): void; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link Message} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. - * - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link Message} objects. Upon failure, the function will be called with information about the error. - */ - history(callback?: paginatedResultCallback): void; - /** - * Sets the {@link ChannelOptions} for the channel. - * - * @param options - A {@link ChannelOptions} object. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - setOptions(options: ChannelOptions, callback?: errorCallback): void; - /** - * Registers a listener for messages with a given event name on this channel. The caller supplies a listener function, which is called each time one or more matching messages arrives on the channel. - * - * @param event - The event name. - * @param listener - An event listener function. - * @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - subscribe(event: string, listener?: messageCallback, callbackWhenAttached?: errorCallback): void; - /** - * Registers a listener for messages on this channel for multiple event name values. - * - * @param events - An array of event names. - * @param listener - An event listener function. - * @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - subscribe(events: Array, listener?: messageCallback, callbackWhenAttached?: errorCallback): void; - /** - * Registers a listener for messages on this channel that match the supplied filter. - * - * @param filter - A {@link MessageFilter}. - * @param listener - An event listener function. - * @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - subscribe(filter: MessageFilter, listener?: messageCallback, callbackWhenAttached?: errorCallback): void; - /** - * Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel. - * - * @param listener - An event listener function. - * @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - subscribe(listener: messageCallback, callbackWhenAttached?: errorCallback): void; - /** - * Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. - * - * @param name - The event name. - * @param data - The message payload. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(name: string, data: any, callback?: errorCallback): void; - /** - * Publishes an array of messages to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. - * - * @param messages - An array of {@link Message} objects. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(messages: any[], callback?: errorCallback): void; - /** - * Publish a message to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. - * - * @param message - A {@link Message} object. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(message: any, callback?: errorCallback): void; - /** - * Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. - * - * @param name - The event name. - * @param data - The message payload. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(name: string, data: any, callback?: errorCallback): void; - /** - * Calls the supplied function when the channel reaches the specified {@link ChannelState}. If the channel is already in the specified state, the callback is called immediately. - * - * @param targetState - The state which should be reached. - * @param callback - A function which will be called when the channel has reached the specified {@link ChannelState} with a {@link ChannelStateChange} object as the first argument. - */ - whenState(targetState: ChannelState, callback: channelEventCallback): void; - } - /** * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresencePromise} object of a channel. */ @@ -2976,7 +2381,7 @@ declare namespace Types { } /** - * The `ConnectionBase` class acts as a base class for the {@link ChannelCallbacks} and {@link ChannelPromise} classes. + * The `ConnectionBase` class acts as a base class for the {@link ChannelPromise} class. */ class ConnectionBase extends EventEmitter { /** @@ -3013,25 +2418,6 @@ declare namespace Types { connect(): void; } - /** - * Enables the management of a connection to Ably. - */ - class ConnectionCallbacks extends ConnectionBase { - /** - * When connected, sends a heartbeat ping to the Ably server and executes the callback with any error and the response time in milliseconds when a heartbeat ping request is echoed from the server. This can be useful for measuring true round-trip latency to the connected Ably server. - * - * @param callback - A function which, upon success, will be called with the response time in milliseconds. Upon failure, the function will be called with information about the error. - */ - ping(callback?: Types.StandardCallback): void; - /** - * Calls the supplied function when the connection reaches the specified {@link ConnectionState}. If the connection is already in the specified state, the callback is called immediately. - * - * @param targetState - The state which should be reached. - * @param callback - A function which will be called when the connection has reached the specified {@link ConnectionState} with a {@link ConnectionStateChange} object as the first argument. - */ - whenState(targetState: ConnectionState, callback: connectionEventCallback): void; - } - /** * Enables the management of a connection to Ably. */ @@ -3100,36 +2486,18 @@ declare namespace Types { * Contains the current page of results; for example, an array of {@link Message} or {@link PresenceMessage} objects for a channel history request. */ items: T[]; - /** - * Returns a new `PaginatedResult` for the first page of results. - * - * @param results - A function which, upon success, will be called with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the function will be called with information about the error. - */ - first(results: paginatedResultCallback): void; /** * Returns a new `PaginatedResult` for the first page of results. * * @returns A promise which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ first(): Promise>; - /** - * Returns a new `PaginatedResult` loaded with the next page of results. If there are no further pages, then `null` is returned. - * - * @param results - A function which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the function will be called with information about the error. - */ - next(results: paginatedResultCallback): void; /** * Returns a new `PaginatedResult` loaded with the next page of results. If there are no further pages, then `null` is returned. * * @returns A promise which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ next(): Promise>; - /** - * Returns the `PaginatedResult` for the current page of results. - * - * @param results - A function which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the function will be called with information about the error. - */ - current(results: paginatedResultCallback): void; /** * Returns the `PaginatedResult` for the current page of results. */ @@ -3174,16 +2542,6 @@ declare namespace Types { headers: any; } - /** - * Enables a device to be registered and deregistered from receiving push notifications. - */ - class PushCallbacks { - /** - * A {@link PushAdminCallbacks} object. - */ - admin: PushAdminCallbacks; - } - /** * Enables a device to be registered and deregistered from receiving push notifications. */ @@ -3194,28 +2552,6 @@ declare namespace Types { admin: PushAdminPromise; } - /** - * Enables the management of device registrations and push notification subscriptions. Also enables the publishing of push notifications to devices. - */ - class PushAdminCallbacks { - /** - * A {@link PushDeviceRegistrationsCallbacks} object. - */ - deviceRegistrations: PushDeviceRegistrationsCallbacks; - /** - * A {@link PushChannelSubscriptionsCallbacks} object. - */ - channelSubscriptions: PushChannelSubscriptionsCallbacks; - /** - * Sends a push notification directly to a device, or a group of devices sharing the same `clientId`. - * - * @param recipient - A JSON object containing the recipient details using `clientId`, `deviceId` or the underlying notifications service. - * @param payload - A JSON object containing the push notification payload. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - publish(recipient: any, payload: any, callback?: errorCallback): void; - } - /** * Enables the management of device registrations and push notification subscriptions. Also enables the publishing of push notifications to devices. */ @@ -3238,61 +2574,6 @@ declare namespace Types { publish(recipient: any, payload: any): Promise; } - /** - * Enables the management of push notification registrations with Ably. - */ - class PushDeviceRegistrationsCallbacks { - /** - * Registers or updates a {@link DeviceDetails} object with Ably. Returns the new, or updated {@link DeviceDetails} object. - * - * @param deviceDetails - The {@link DeviceDetails} object to create or update. - * @param callback - A function which, upon success, will be called with a {@link DeviceDetails} object. Upon failure, the function will be called with information about the error. - */ - save(deviceDetails: DeviceDetails, callback?: Types.StandardCallback): void; - /** - * Retrieves the {@link DeviceDetails} of a device registered to receive push notifications using its `deviceId`. - * - * @param deviceId - The unique ID of the device. - * @param callback - A function which, upon success, will be called with a {@link DeviceDetails} object. Upon failure, the function will be called with information about the error. - */ - get(deviceId: string, callback: Types.StandardCallback): void; - /** - * Retrieves the {@link DeviceDetails} of a device registered to receive push notifications using the `id` property of a {@link DeviceDetails} object. - * - * @param deviceDetails - The {@link DeviceDetails} object containing the `id` property of the device. - * @param callback - A function which, upon success, will be called with a {@link DeviceDetails} object. Upon failure, the function will be called with information about the error. - */ - get(deviceDetails: DeviceDetails, callback: Types.StandardCallback): void; - /** - * Retrieves all devices matching the filter `params` provided. Returns a {@link Types.PaginatedResult} object, containing an array of {@link DeviceDetails} objects. - * - * @param params - An object containing key-value pairs to filter devices by. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link DeviceDetails} objects. Upon failure, the function will be called with information about the error. - */ - list(params: DeviceRegistrationParams, callback: paginatedResultCallback): void; - /** - * Removes a device registered to receive push notifications from Ably using its `deviceId`. - * - * @param deviceId - The unique ID of the device. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - remove(deviceId: string, callback?: errorCallback): void; - /** - * Removes a device registered to receive push notifications from Ably using the `id` property of a {@link DeviceDetails} object. - * - * @param deviceDetails - The {@link DeviceDetails} object containing the `id` property of the device. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - remove(deviceDetails: DeviceDetails, callback?: errorCallback): void; - /** - * Removes all devices registered to receive push notifications from Ably matching the filter `params` provided. - * - * @param params - An object containing key-value pairs to filter devices by. This object’s {@link DeviceRegistrationParams.limit} property will be ignored. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - removeWhere(params: DeviceRegistrationParams, callback?: errorCallback): void; - } - /** * Enables the management of push notification registrations with Ably. */ @@ -3348,47 +2629,6 @@ declare namespace Types { removeWhere(params: DeviceRegistrationParams): Promise; } - /** - * Enables device push channel subscriptions. - */ - class PushChannelSubscriptionsCallbacks { - /** - * Subscribes a device, or a group of devices sharing the same `clientId` to push notifications on a channel. Returns a {@link PushChannelSubscription} object. - * - * @param subscription - A {@link PushChannelSubscription} object. - * @param callback - A function which, upon success, will be called with a {@link PushChannelSubscription} object describing the new or updated subscriptions. Upon failure, the function will be called with information about the error. - */ - save(subscription: PushChannelSubscription, callback?: Types.StandardCallback): void; - /** - * Retrieves all push channel subscriptions matching the filter `params` provided. Returns a {@link Types.PaginatedResult} object, containing an array of {@link PushChannelSubscription} objects. - * - * @param params - An object containing key-value pairs to filter subscriptions by. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of {@link PushChannelSubscription} objects. Upon failure, the function will be called with information about the error. - */ - list(params: PushChannelSubscriptionParams, callback: paginatedResultCallback): void; - /** - * Retrieves all channels with at least one device subscribed to push notifications. Returns a {@link Types.PaginatedResult} object, containing an array of channel names. - * - * @param params - An object containing key-value pairs to filter channels by. - * @param callback - A function which, upon success, will be called with a {@link Types.PaginatedResult} object containing an array of channel names. Upon failure, the function will be called with information about the error. - */ - listChannels(params: PushChannelsParams, callback: paginatedResultCallback): void; - /** - * Unsubscribes a device, or a group of devices sharing the same `clientId` from receiving push notifications on a channel. - * - * @param subscription - A {@link PushChannelSubscription} object. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - remove(subscription: PushChannelSubscription, callback?: errorCallback): void; - /** - * Unsubscribes all devices from receiving push notifications on a channel that match the filter `params` provided. - * - * @param params - An object containing key-value pairs to filter subscriptions by. Can contain `channel`, and optionally either `clientId` or `deviceId`. - * @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error. - */ - removeWhere(params: PushChannelSubscriptionParams, callback?: errorCallback): void; - } - /** * Enables device push channel subscriptions. */ @@ -3434,9 +2674,9 @@ declare namespace Types { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare class Rest extends Types.RestCallbacks {} +export declare class Rest extends Types.RestPromise {} /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ -export declare class Realtime extends Types.RealtimeCallbacks {} +export declare class Realtime extends Types.RealtimePromise {} diff --git a/callbacks.d.ts b/callbacks.d.ts deleted file mode 100644 index e0a434a3dd..0000000000 --- a/callbacks.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import Ably = require('./ably'); -export = Ably; diff --git a/callbacks.js b/callbacks.js deleted file mode 100644 index 8bfe2c458f..0000000000 --- a/callbacks.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; -/* When returning promises becomes the default in some future version, this - * file will start modifying the exports as promises.js does today. - * Please note that the file imported below is only generated after running - * the build task. */ -module.exports = require('./build/ably-node'); diff --git a/docs/landing-page.md b/docs/landing-page.md new file mode 100644 index 0000000000..f9a0bb6212 --- /dev/null +++ b/docs/landing-page.md @@ -0,0 +1,7 @@ +# Ably JavaScript Client Library SDK API Reference + +The JavaScript Client Library SDK supports a realtime and a REST interface. The JavaScript API references are generated from the [Ably JavaScript Client Library SDK source code](https://github.com/ably/ably-js/) using [TypeDoc](https://typedoc.org) and structured by classes. + +The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels. The REST interface is stateless and typically implemented server-side. It is used to make requests such as retrieving statistics, token authentication and publishing to a channel. + +View the [Ably docs](https://ably.com/docs/) for conceptual information on using Ably, and for API references featuring all languages. The combined [API references](https://ably.com/docs/api/) are organized by features and split between the [realtime](https://ably.com/docs/api/realtime-sdk) and [REST](https://ably.com/docs/api/rest-sdk) interfaces. diff --git a/docs/landing-pages/choose-library.html b/docs/landing-pages/choose-library.html deleted file mode 100644 index c4243efaf1..0000000000 --- a/docs/landing-pages/choose-library.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - Ably JavaScript Client Library SDK API Reference - - -

Ably JavaScript Client Library SDK API Reference

- -

- The JavaScript Client Library SDK supports a realtime and a REST interface. The JavaScript API references are generated from the Ably JavaScript Client Library SDK source code using TypeDoc and structured by classes. -

- -

- The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels. The REST interface is stateless and typically implemented server-side. It is used to make requests such as retrieving statistics, token authentication and publishing to a channel. -

- -

- There are two API variants of the Ably JavaScript Client Library SDK: - -

-

- -

- View the Ably docs for conceptual information on using Ably, and for API references featuring all languages. The combined API references are organized by features and split between the realtime and REST interfaces. -

- - diff --git a/docs/landing-pages/default.md b/docs/landing-pages/default.md deleted file mode 100644 index 994b06cd18..0000000000 --- a/docs/landing-pages/default.md +++ /dev/null @@ -1,7 +0,0 @@ -# Ably JavaScript Client Library SDK API Reference for Callbacks - -You are currently viewing the callback-based variant of the Ably JavaScript Client Library SDK. View the promise-based variant [here](../promises/index.html). - -The callback-based variant of the API implements the common Node.js error-first callback style. - -To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. diff --git a/docs/landing-pages/promises.md b/docs/landing-pages/promises.md deleted file mode 100644 index c312a44660..0000000000 --- a/docs/landing-pages/promises.md +++ /dev/null @@ -1,7 +0,0 @@ -# Ably JavaScript Client Library SDK API Reference for Promises - -You are currently viewing the promise-based variant of the Ably JavaScript Client Library SDK. View the callback-based variant [here](../default/index.html). - -Passing callbacks to methods when using the promise-based variant of the API is still possible, however, if a method does not receive a callback then it will instead return a promise. - -To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. diff --git a/package.json b/package.json index ce9639b23b..c0b79298eb 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,6 @@ "files": [ "build/**", "ably.d.ts", - "callbacks.d.ts", - "callbacks.js", "promises.d.ts", "promises.js", "resources/**" @@ -101,6 +99,6 @@ "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/cdn_deploy.js", "sourcemap": "source-map-explorer build/ably.min.js", "sourcemap:noencryption": "source-map-explorer build/ably.noencryption.min.js", - "docs": "typedoc --entryPoints ably.d.ts --out docs/generated/default --readme docs/landing-pages/default.md && typedoc --entryPoints promises.d.ts --out docs/generated/promises --name \"ably (Promise-based)\" --readme docs/landing-pages/promises.md && cp docs/landing-pages/choose-library.html docs/generated/index.html" + "docs": "typedoc --entryPoints ably.d.ts --out docs/generated --readme docs/landing-page.md" } } diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index bb4f636d1b..794ea837e1 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -259,9 +259,7 @@ class Auth { _authOptions = authOptions as API.Types.AuthOptions; } if (!callback) { - if (this.client.options.promises) { - return Utils.promisify(this, 'authorize', arguments); - } + return Utils.promisify(this, 'authorize', arguments); } /* RSA10a: authorize() call implies token auth. If a key is passed it, we @@ -418,7 +416,7 @@ class Auth { callback = authOptions; authOptions = null; } - if (!callback && this.client.options.promises) { + if (!callback) { return Utils.promisify(this, 'requestToken', arguments); } @@ -746,7 +744,7 @@ class Auth { callback = authOptions; authOptions = null; } - if (!callback && this.client.options.promises) { + if (!callback) { return Utils.promisify(this, 'createTokenRequest', arguments); } diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index 8937709d5e..efb273351e 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -78,10 +78,7 @@ class Channel extends EventEmitter { callback = params; params = null; } else { - if (this.rest.options.promises) { - return Utils.promisify(this, 'history', arguments); - } - callback = noop; + return Utils.promisify(this, 'history', arguments); } } @@ -115,10 +112,7 @@ class Channel extends EventEmitter { let params: any; if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'publish', arguments); - } - callback = noop; + return Utils.promisify(this, 'publish', arguments); } if (typeof first === 'string' || first === null) { @@ -192,7 +186,7 @@ class Channel extends EventEmitter { } status(callback?: StandardCallback): void | Promise { - if (typeof callback !== 'function' && this.rest.options.promises) { + if (typeof callback !== 'function') { return Utils.promisify(this, 'status', []); } diff --git a/src/common/lib/client/connection.ts b/src/common/lib/client/connection.ts index a1f823710d..be5177ba48 100644 --- a/src/common/lib/client/connection.ts +++ b/src/common/lib/client/connection.ts @@ -8,8 +8,6 @@ import { NormalisedClientOptions } from '../../types/ClientOptions'; import Realtime from './realtime'; import Platform from 'common/platform'; -function noop() {} - class Connection extends EventEmitter { ably: Realtime; connectionManager: ConnectionManager; @@ -58,10 +56,7 @@ class Connection extends EventEmitter { ping(callback: Function): Promise | void { Logger.logAction(Logger.LOG_MINOR, 'Connection.ping()', ''); if (!callback) { - if (this.ably.options.promises) { - return Utils.promisify(this, 'ping', arguments); - } - callback = noop; + return Utils.promisify(this, 'ping', arguments); } this.connectionManager.ping(null, callback); } diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index bf54698b23..416c59fae6 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -195,7 +195,7 @@ export class PaginatedResult { if (relParams) { if ('first' in relParams) { this.first = function (callback: (result?: ErrorInfo | null) => void) { - if (!callback && self.resource.rest.options.promises) { + if (!callback) { return Utils.promisify(self, 'first', []); } self.get(relParams.first, callback); @@ -203,14 +203,14 @@ export class PaginatedResult { } if ('current' in relParams) { this.current = function (callback: (results?: ErrorInfo | null) => void) { - if (!callback && self.resource.rest.options.promises) { + if (!callback) { return Utils.promisify(self, 'current', []); } self.get(relParams.current, callback); }; } this.next = function (callback: (results?: ErrorInfo | null) => void) { - if (!callback && self.resource.rest.options.promises) { + if (!callback) { return Utils.promisify(self, 'next', []); } if ('next' in relParams) { diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index 77210ade55..8d521bdf4c 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -8,8 +8,6 @@ import { PaginatedResultCallback } from '../../types/utils'; import Channel from './channel'; import RealtimeChannel from './realtimechannel'; -function noop() {} - class Presence extends EventEmitter { channel: RealtimeChannel | Channel; basePath: string; @@ -28,10 +26,7 @@ class Presence extends EventEmitter { callback = params; params = null; } else { - if (this.channel.rest.options.promises) { - return Utils.promisify(this, 'get', arguments); - } - callback = noop; + return Utils.promisify(this, 'get', arguments); } } const rest = this.channel.rest, @@ -69,10 +64,7 @@ class Presence extends EventEmitter { callback = params; params = null; } else { - if (this.channel.rest.options.promises) { - return Utils.promisify(this, '_history', [params]); - } - callback = noop; + return Utils.promisify(this, '_history', [params]); } } diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index fb694eaded..b628457f50 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -7,8 +7,6 @@ import PushChannelSubscription from '../types/pushchannelsubscription'; import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; import Rest from './rest'; -const noop = function () {}; - class Push { rest: Rest; admin: Admin; @@ -38,10 +36,7 @@ class Admin { const body = Utils.mixin({ recipient: recipient }, payload); if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'publish', arguments); - } - callback = noop; + return Utils.promisify(this, 'publish', arguments); } Utils.mixin(headers, rest.options.headers); @@ -68,10 +63,7 @@ class DeviceRegistrations { params = {}; if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'save', arguments); - } - callback = noop; + return Utils.promisify(this, 'save', arguments); } Utils.mixin(headers, rest.options.headers); @@ -107,10 +99,7 @@ class DeviceRegistrations { deviceId = deviceIdOrDetails.id || deviceIdOrDetails; if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'get', arguments); - } - callback = noop; + return Utils.promisify(this, 'get', arguments); } if (typeof deviceId !== 'string' || !deviceId.length) { @@ -153,10 +142,7 @@ class DeviceRegistrations { headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'list', arguments); - } - callback = noop; + return Utils.promisify(this, 'list', arguments); } Utils.mixin(headers, rest.options.headers); @@ -178,10 +164,7 @@ class DeviceRegistrations { deviceId = deviceIdOrDetails.id || deviceIdOrDetails; if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'remove', arguments); - } - callback = noop; + return Utils.promisify(this, 'remove', arguments); } if (typeof deviceId !== 'string' || !deviceId.length) { @@ -215,10 +198,7 @@ class DeviceRegistrations { headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'removeWhere', arguments); - } - callback = noop; + return Utils.promisify(this, 'removeWhere', arguments); } Utils.mixin(headers, rest.options.headers); @@ -244,10 +224,7 @@ class ChannelSubscriptions { params = {}; if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'save', arguments); - } - callback = noop; + return Utils.promisify(this, 'save', arguments); } Utils.mixin(headers, rest.options.headers); @@ -278,10 +255,7 @@ class ChannelSubscriptions { headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'list', arguments); - } - callback = noop; + return Utils.promisify(this, 'list', arguments); } Utils.mixin(headers, rest.options.headers); @@ -301,10 +275,7 @@ class ChannelSubscriptions { headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'removeWhere', arguments); - } - callback = noop; + return Utils.promisify(this, 'removeWhere', arguments); } Utils.mixin(headers, rest.options.headers); @@ -324,10 +295,7 @@ class ChannelSubscriptions { headers = Utils.defaultGetHeaders(rest.options, { format }); if (typeof callback !== 'function') { - if (this.rest.options.promises) { - return Utils.promisify(this, 'listChannels', arguments); - } - callback = noop; + return Utils.promisify(this, 'listChannels', arguments); } Utils.mixin(headers, rest.options.headers); diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/realtime.ts index e3b39c7519..58c21a5fcd 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/realtime.ts @@ -4,11 +4,10 @@ import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; import Connection from './connection'; import RealtimeChannel from './realtimechannel'; -import Defaults from '../util/defaults'; import ErrorInfo from '../types/errorinfo'; import ProtocolMessage from '../types/protocolmessage'; import { ChannelOptions } from '../../types/channel'; -import ClientOptions, { InternalClientOptions } from '../../types/ClientOptions'; +import ClientOptions from '../../types/ClientOptions'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; import Platform from 'common/platform'; @@ -36,13 +35,8 @@ class Realtime extends Rest { this.connection.close(); } - static Promise = function (options: InternalClientOptions): Realtime { - options = Defaults.objectifyOptions(options); - options.internal = { ...options.internal, promises: true }; - return new Realtime(options); - }; + static Promise = Realtime; - static Callbacks = Realtime; static Utils = Utils; static ConnectionManager = ConnectionManager; static Platform = Platform; diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 4a04ee5051..0c55e901da 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -136,9 +136,7 @@ class RealtimeChannel extends Channel { setOptions(options?: API.Types.ChannelOptions, callback?: ErrCallback): void | Promise { if (!callback) { - if (this.rest.options.promises) { - return Utils.promisify(this, 'setOptions', arguments); - } + return Utils.promisify(this, 'setOptions', arguments); } const _callback = callback || @@ -194,11 +192,7 @@ class RealtimeChannel extends Channel { let callback = args[argCount - 1]; if (typeof callback !== 'function') { - if (this.realtime.options.promises) { - return Utils.promisify(this, 'publish', arguments); - } - callback = noop; - ++argCount; + return Utils.promisify(this, 'publish', arguments); } if (!this.connectionManager.activeState()) { callback(this.connectionManager.getError()); @@ -274,14 +268,7 @@ class RealtimeChannel extends Channel { attach(callback?: ErrCallback): void | Promise { if (!callback) { - if (this.realtime.options.promises) { - return Utils.promisify(this, 'attach', arguments); - } - callback = function (err?: ErrorInfo | null) { - if (err) { - Logger.logAction(Logger.LOG_MAJOR, 'RealtimeChannel.attach()', 'Channel attach failed: ' + err.toString()); - } - }; + return Utils.promisify(this, 'attach', arguments); } if (this.state === 'attached') { callback(); @@ -357,10 +344,7 @@ class RealtimeChannel extends Channel { detach(callback: ErrCallback): void | Promise { if (!callback) { - if (this.realtime.options.promises) { - return Utils.promisify(this, 'detach', arguments); - } - callback = noop; + return Utils.promisify(this, 'detach', arguments); } const connectionManager = this.connectionManager; if (!connectionManager.activeState()) { @@ -413,7 +397,7 @@ class RealtimeChannel extends Channel { subscribe(...args: unknown[] /* [event], listener, [callback] */): void | Promise { const [event, listener, callback] = RealtimeChannel.processListenerArgs(args); - if (!callback && this.realtime.options.promises) { + if (!callback) { return Utils.promisify(this, 'subscribe', [event, listener]); } @@ -957,10 +941,7 @@ class RealtimeChannel extends Channel { callback = params; params = null; } else { - if (this.rest.options.promises) { - return Utils.promisify(this, 'history', arguments); - } - callback = noop; + return Utils.promisify(this, 'history', arguments); } } diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 39901b2a63..1d2f60be06 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -26,8 +26,6 @@ interface RealtimeHistoryParams { from_serial?: string | null; } -const noop = function () {}; - function getClientId(realtimePresence: RealtimePresence) { return realtimePresence.channel.realtime.auth.clientId; } @@ -134,10 +132,7 @@ class RealtimePresence extends Presence { callback = data as ErrCallback; data = null; } else { - if (this.channel.realtime.options.promises) { - return Utils.promisify(this, '_enterOrUpdateClient', [id, clientId, data, action]); - } - callback = noop; + return Utils.promisify(this, '_enterOrUpdateClient', [id, clientId, data, action]); } } @@ -207,10 +202,7 @@ class RealtimePresence extends Presence { callback = data as ErrCallback; data = null; } else { - if (this.channel.realtime.options.promises) { - return Utils.promisify(this, 'leaveClient', [clientId, data]); - } - callback = noop; + return Utils.promisify(this, 'leaveClient', [clientId, data]); } } @@ -266,10 +258,7 @@ class RealtimePresence extends Presence { const waitForSync = !params || ('waitForSync' in params ? params.waitForSync : true); if (!callback) { - if (this.channel.realtime.options.promises) { - return Utils.promisify(this, 'get', args); - } - callback = noop; + return Utils.promisify(this, 'get', args); } function returnMembers(members: PresenceMap) { @@ -315,10 +304,7 @@ class RealtimePresence extends Presence { callback = params; params = null; } else { - if (this.channel.realtime.options.promises) { - return Utils.promisify(this, 'history', arguments); - } - callback = noop; + return Utils.promisify(this, 'history', arguments); } } @@ -518,10 +504,7 @@ class RealtimePresence extends Presence { const channel = this.channel; if (!callback) { - if (this.channel.realtime.options.promises) { - return Utils.promisify(this, 'subscribe', [event, listener]); - } - callback = noop; + return Utils.promisify(this, 'subscribe', [event, listener]); } if (channel.state === 'failed') { diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 5629f8f391..3c7776135b 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -11,7 +11,7 @@ import HttpMethods from '../../constants/HttpMethods'; import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; import { ErrnoException, IHttp, RequestParams } from '../../types/http'; -import ClientOptions, { InternalClientOptions, NormalisedClientOptions } from '../../types/ClientOptions'; +import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOptions'; import Platform from '../../platform'; import Message from '../types/message'; @@ -92,10 +92,7 @@ class Rest { callback = params; params = null; } else { - if (this.options.promises) { - return Utils.promisify(this, 'stats', [params]) as Promise>; - } - callback = noop; + return Utils.promisify(this, 'stats', [params]) as Promise>; } } const headers = Utils.defaultGetHeaders(this.options), @@ -122,9 +119,7 @@ class Rest { callback = params; params = null; } else { - if (this.options.promises) { - return Utils.promisify(this, 'time', [params]) as Promise; - } + return Utils.promisify(this, 'time', [params]) as Promise; } } @@ -187,12 +182,9 @@ class Rest { : Utils.defaultPostHeaders(this.options, { format, protocolVersion: version }); if (callback === undefined) { - if (this.options.promises) { - return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< - HttpPaginatedResponse - >; - } - callback = noop; + return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< + HttpPaginatedResponse + >; } if (typeof body !== 'string') { @@ -231,13 +223,8 @@ class Rest { Logger.setLog(logOptions.level, logOptions.handler); } - static Promise = function (options: InternalClientOptions): Rest { - options = Defaults.objectifyOptions(options); - options.internal = { ...options.internal, promises: true }; - return new Rest(options); - }; + static Promise = Rest; - static Callbacks = Rest; static Platform = Platform; static Crypto?: typeof Platform.Crypto; static Message = Message; diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 6f8f83123a..1b1fd275fe 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -257,7 +257,6 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie connectivityCheckParams, connectivityCheckUrl, headers, - promises: options.internal?.promises ?? false, }; } diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index ab279cfd65..1eeac34905 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -19,7 +19,6 @@ export type InternalClientOptions = Modify< ClientOptions, { internal?: { - promises?: boolean; maxMessageSize?: number; }; } @@ -36,6 +35,5 @@ export type NormalisedClientOptions = Modify< maxMessageSize: number; connectivityCheckParams: Record | null; headers: Record; - promises: boolean; } >; diff --git a/test/realtime/event_emitter.test.js b/test/realtime/event_emitter.test.js index c20acbb885..1fac46d249 100644 --- a/test/realtime/event_emitter.test.js +++ b/test/realtime/event_emitter.test.js @@ -441,7 +441,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { describe('event_emitter_promise', function () { it('whenState', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); + var realtime = helper.AblyRealtime(); var eventEmitter = realtime.connection; eventEmitter @@ -455,7 +455,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('once', function (done) { - var realtime = helper.AblyRealtime({ internal: { promises: true } }); + var realtime = helper.AblyRealtime(); var eventEmitter = realtime.connection; eventEmitter diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 4b85dd005f..f299dbefa2 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -409,26 +409,5 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { closeAndFinish(done, realtime); }); }); - - it('init_callbacks_promises', function (done) { - try { - var realtime, - keyStr = helper.getTestApp().keys[0].keyStr, - getOptions = function () { - return { key: keyStr, autoConnect: false }; - }; - - realtime = new Ably.Realtime.Promise(getOptions()); - expect(realtime.options.promises, 'Check promises default to true with promise constructor').to.be.ok; - - if (!isBrowser && typeof require == 'function') { - realtime = new require('../../promises').Realtime(getOptions()); - expect(realtime.options.promises, 'Check promises default to true with promise require target').to.be.ok; - } - done(); - } catch (err) { - done(err); - } - }); }); }); diff --git a/test/rest/init.test.js b/test/rest/init.test.js index f4debf54f4..915a7a5a49 100644 --- a/test/rest/init.test.js +++ b/test/rest/init.test.js @@ -62,19 +62,5 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var rest = helper.AblyRest({ clientId: false }); }, 'Check can’t init library with a boolean clientId').to.throw; }); - - it('Init promises', function () { - var rest, - keyStr = helper.getTestApp().keys[0].keyStr; - - rest = new Ably.Rest.Promise(keyStr); - expect(rest.options.promises, 'Check promises default to true with promise constructor').to.be.ok; - - if (!isBrowser && typeof require == 'function') { - var AblyPromises = require('../../promises'); - rest = new AblyPromises.Rest(keyStr); - expect(rest.options.promises, 'Check promises default to true with promise require target').to.be.ok; - } - }); }); }); From 693804c5499691766de7b6580d39ffbe87887676 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 27 Jun 2023 11:42:52 -0300 Subject: [PATCH 127/468] Remove the Promise static property No longer needed now that, as of 2a2ed49, the promises API is the only one we offer. --- ably.d.ts | 8 -------- src/common/lib/client/realtime.ts | 2 -- src/common/lib/client/rest.ts | 2 -- test/common/modules/client_module.js | 4 ++-- test/realtime/api.test.js | 1 - test/realtime/init.test.js | 8 ++++---- test/rest/api.test.js | 1 - test/rest/init.test.js | 4 ++-- 8 files changed, 8 insertions(+), 22 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 852b7ad5ab..c2c5843a14 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1565,10 +1565,6 @@ declare namespace Types { * A client that offers a simple stateless API to interact directly with Ably's REST API. */ class RestPromise extends RestBase { - /** - * A promisified version of the library (use this if you prefer to use Promises or async/await instead of callbacks) - */ - static Promise: typeof Types.RestPromise; /** * An {@link Types.AuthPromise} object. */ @@ -1619,10 +1615,6 @@ declare namespace Types { * A base class used internally for Realtime APIs. */ class RealtimeBase extends RestBase { - /** - * A promisified version of the library (use this if you prefer to use Promises or async/await instead of callbacks) - */ - static Promise: typeof Types.RealtimePromise; /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. */ diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/realtime.ts index 58c21a5fcd..416f5748f6 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/realtime.ts @@ -35,8 +35,6 @@ class Realtime extends Rest { this.connection.close(); } - static Promise = Realtime; - static Utils = Utils; static ConnectionManager = ConnectionManager; static Platform = Platform; diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 3c7776135b..d6e57416e2 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -223,8 +223,6 @@ class Rest { Logger.setLog(logOptions.level, logOptions.handler); } - static Promise = Rest; - static Platform = Platform; static Crypto?: typeof Platform.Crypto; static Message = Message; diff --git a/test/common/modules/client_module.js b/test/common/modules/client_module.js index d0806d6d4b..7f2c990ea0 100644 --- a/test/common/modules/client_module.js +++ b/test/common/modules/client_module.js @@ -23,11 +23,11 @@ define(['ably', 'globals', 'test/common/modules/testapp_module'], function (Ably } function ablyRest(options) { - return new Ably.Rest.Promise(ablyClientOptions(options)); + return new Ably.Rest(ablyClientOptions(options)); } function ablyRealtime(options) { - return new Ably.Realtime.Promise(ablyClientOptions(options)); + return new Ably.Realtime(ablyClientOptions(options)); } return (module.exports = { diff --git a/test/realtime/api.test.js b/test/realtime/api.test.js index 36f3dd080b..951c0d0669 100644 --- a/test/realtime/api.test.js +++ b/test/realtime/api.test.js @@ -6,7 +6,6 @@ define(['ably', 'chai'], function (Ably, chai) { describe('realtime/api', function () { it('Client constructors', function () { expect(typeof Ably.Realtime).to.equal('function'); - expect(typeof Ably.Realtime.Promise).to.equal('function'); }); it('Crypto', function () { diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index f299dbefa2..f617628641 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -54,7 +54,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime; try { var keyStr = helper.getTestApp().keys[0].keyStr; - realtime = new helper.Ably.Realtime.Promise(keyStr); + realtime = new helper.Ably.Realtime(keyStr); try { expect(realtime.options.key).to.equal(keyStr); @@ -83,7 +83,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { } var tokenStr = tokenDetails.token, - realtime = new helper.Ably.Realtime.Promise(tokenStr); + realtime = new helper.Ably.Realtime(tokenStr); try { expect(realtime.options.token).to.equal(tokenStr); @@ -208,7 +208,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { try { var keyStr = helper.getTestApp().keys[0].keyStr; expect(function () { - realtime = new helper.Ably.Realtime.Promise({ key: keyStr, useTokenAuth: false, clientId: 'foo' }); + realtime = new helper.Ably.Realtime({ key: keyStr, useTokenAuth: false, clientId: 'foo' }); }).to.throw; done(); } catch (err) { @@ -222,7 +222,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { /* want to check the default host when no custom environment or custom * host set, so not using helpers.realtime this time, which will use a * test env */ - var realtime = new Ably.Realtime.Promise({ key: 'not_a.real:key', autoConnect: false }); + var realtime = new Ably.Realtime({ key: 'not_a.real:key', autoConnect: false }); var defaultHost = realtime.connection.connectionManager.httpHosts[0]; expect(defaultHost).to.equal('rest.ably.io', 'Verify correct default rest host chosen'); realtime.close(); diff --git a/test/rest/api.test.js b/test/rest/api.test.js index 02950ca883..fcc19aa3df 100644 --- a/test/rest/api.test.js +++ b/test/rest/api.test.js @@ -6,7 +6,6 @@ define(['ably', 'chai'], function (Ably, chai) { describe('rest/api', function () { it('Client constructors', function () { expect(typeof Ably.Rest).to.equal('function'); - expect(typeof Ably.Rest.Promise).to.equal('function'); }); it('Crypto', function () { diff --git a/test/rest/init.test.js b/test/rest/init.test.js index 915a7a5a49..2d4b316dde 100644 --- a/test/rest/init.test.js +++ b/test/rest/init.test.js @@ -18,7 +18,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('Init with key string', function () { var keyStr = helper.getTestApp().keys[0].keyStr; - var rest = new helper.Ably.Rest.Promise(keyStr); + var rest = new helper.Ably.Rest(keyStr); expect(rest.options.key).to.equal(keyStr); }); @@ -30,7 +30,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var tokenDetails = await rest.auth.requestToken(null, testKeyOpts); var tokenStr = tokenDetails.token, - rest = new helper.Ably.Rest.Promise(tokenStr); + rest = new helper.Ably.Rest(tokenStr); expect(rest.options.token).to.equal(tokenStr); }); From 6cf248fbc18434629def4fa7a37b006a396afd76 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 27 Jun 2023 09:11:02 -0300 Subject: [PATCH 128/468] Remove promises.js / .d.ts Motivation as in 693804c. --- package.json | 2 -- promises.d.ts | 14 -------------- promises.js | 32 -------------------------------- 3 files changed, 48 deletions(-) delete mode 100644 promises.d.ts delete mode 100644 promises.js diff --git a/package.json b/package.json index c0b79298eb..1c0a2d9914 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,6 @@ "files": [ "build/**", "ably.d.ts", - "promises.d.ts", - "promises.js", "resources/**" ], "dependencies": { diff --git a/promises.d.ts b/promises.d.ts deleted file mode 100644 index 0c79c9e897..0000000000 --- a/promises.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import Ably = require('./ably'); - -/** - * The `Rest` object offers a simple stateless API to interact directly with Ably's REST API. - */ -export declare class Rest extends Ably.Rest.Promise {} -/** - * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. - */ -export declare class Realtime extends Ably.Realtime.Promise {} - -// Re-export the Types namespace. -import Types = Ably.Types; -export { Types }; diff --git a/promises.js b/promises.js deleted file mode 100644 index c40986d4a4..0000000000 --- a/promises.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; -function promisifyOptions(options) { - if (typeof options == 'string') { - options = options.indexOf(':') == -1 ? { token: options } : { key: options }; - } - if (options.internal === undefined) { - options.internal = { promises: true } - } else { - options.internal.promises = true - } - return options; -} - -/* Please note that the file imported below is only generated after running - * the build task. */ -// eslint-disable-next-line @typescript-eslint/no-var-requires -var Ably = require('./build/ably-node'); - -var RestPromise = function (options) { - return new Ably.Rest(promisifyOptions(options)); -}; -Object.assign(RestPromise, Ably.Rest); - -var RealtimePromise = function (options) { - return new Ably.Realtime(promisifyOptions(options)); -}; -Object.assign(RealtimePromise, Ably.Realtime); - -module.exports = { - Rest: RestPromise, - Realtime: RealtimePromise, -}; From 8c3afa4915513ffe125cba9e211201a1193b566f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 26 Jun 2023 17:27:07 -0300 Subject: [PATCH 129/468] Move RealtimeChannelPromise declaration directly below RealtimeChannelBase Preparation for merging the *Base and *Promise classes. --- ably.d.ts | 72 +++++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index c2c5843a14..be583fa897 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1999,42 +1999,6 @@ declare namespace Types { unsubscribe(): void; } - /** - * Optional parameters for message publishing. - */ - type PublishOptions = { - /** - * See [here](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes). - */ - quickAck?: boolean; - }; - - /** - * Contains properties to filter messages with when calling {@link RealtimeChannelPromise.subscribe | `RealtimeChannelPromise.subscribe()`}. - */ - type MessageFilter = { - /** - * Filters messages by a specific message `name`. - */ - name?: string; - /** - * Filters messages by a specific `extras.ref.timeserial` value. - */ - refTimeserial?: string; - /** - * Filters messages by a specific `extras.ref.type` value. - */ - refType?: string; - /** - * Filters messages based on whether they contain an `extras.ref`. - */ - isRef?: boolean; - /** - * Filters messages by a specific message `clientId`. - */ - clientId: string; - }; - /** * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresencePromise} object of a channel. */ @@ -2130,6 +2094,42 @@ declare namespace Types { whenState(targetState: ChannelState): Promise; } + /** + * Optional parameters for message publishing. + */ + type PublishOptions = { + /** + * See [here](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes). + */ + quickAck?: boolean; + }; + + /** + * Contains properties to filter messages with when calling {@link RealtimeChannelPromise.subscribe | `RealtimeChannelPromise.subscribe()`}. + */ + type MessageFilter = { + /** + * Filters messages by a specific message `name`. + */ + name?: string; + /** + * Filters messages by a specific `extras.ref.timeserial` value. + */ + refTimeserial?: string; + /** + * Filters messages by a specific `extras.ref.type` value. + */ + refType?: string; + /** + * Filters messages based on whether they contain an `extras.ref`. + */ + isRef?: boolean; + /** + * Filters messages by a specific message `clientId`. + */ + clientId: string; + }; + /** * Creates and destroys {@link ChannelBase} and {@link RealtimeChannelBase} objects. */ From 69c35f14fd82e8b33f9d88ad78180db3d3acc888 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 27 Jun 2023 15:49:24 -0300 Subject: [PATCH 130/468] Remove Realtime < Rest inheritance in public API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I want to merge the following classes together: - RestBase and RestPromise into a class named Rest - RealtimeBase and RealtimePromise into a class named Realtime However, I need to decide what to do about the inheritance of RealtimeBase from RestBase. The obvious thing to do would be to make Realtime inherit from Rest, but this won’t work because RealtimeChannel’s type declaration does not include a `status()` function and hence Realtime.channels cannot satisfy the type of Rest.channels. So, since the IDL does not mention any inheritance relation between the REST and Realtime classes, I’m going to sever this connection in the type declarations. (We can consider the fact that _internally_ there is inheritance to be an implementation detail.) --- ably.d.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ably.d.ts b/ably.d.ts index be583fa897..4567547545 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1614,7 +1614,31 @@ declare namespace Types { /** * A base class used internally for Realtime APIs. */ - class RealtimeBase extends RestBase { + class RealtimeBase { + /** + * Construct a client object using an Ably {@link Types.ClientOptions} object. + * + * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + */ + constructor(options: Types.ClientOptions); + /** + * Constructs a client object using an Ably API key or token string. + * + * @param keyOrToken - The Ably API key or token string used to validate the client. + */ + constructor(keyOrToken: string); + /** + * The cryptographic functions available in the library. + */ + static Crypto: Types.Crypto; + /** + * Static utilities related to messages. + */ + static Message: Types.MessageStatic; + /** + * Static utilities related to presence messages. + */ + static PresenceMessage: Types.PresenceMessageStatic; /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. */ From 6cb717990d5a993ebd6e0430b2b2629660c27cf8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 22 Jun 2023 16:59:45 -0300 Subject: [PATCH 131/468] Merge *Base and *Promise classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The *Base classes are no longer needed now that we’ve removed the *Callbacks classes. --- ably.d.ts | 233 +++++++++++++++++++++++------------------------------- 1 file changed, 99 insertions(+), 134 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 4567547545..a276fade46 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -41,7 +41,7 @@ declare namespace Types { type FAILED = 'failed'; } /** - * Describes the possible states of a {@link ChannelBase} or {@link RealtimeChannelBase} object. + * Describes the possible states of a {@link Channel} or {@link RealtimeChannel} object. */ type ChannelState = | ChannelState.FAILED @@ -90,7 +90,7 @@ declare namespace Types { type UPDATE = 'update'; } /** - * Describes the events emitted by a {@link ChannelBase} or {@link RealtimeChannelBase} object. An event is either an `UPDATE` or a {@link ChannelState}. + * Describes the events emitted by a {@link Channel} or {@link RealtimeChannel} object. An event is either an `UPDATE` or a {@link ChannelState}. */ type ChannelEvent = | ChannelEvent.FAILED @@ -123,7 +123,7 @@ declare namespace Types { */ type DISCONNECTED = 'disconnected'; /** - * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link ConnectionBase.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). + * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link Connection.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). */ type SUSPENDED = 'suspended'; /** @@ -131,16 +131,16 @@ declare namespace Types { */ type CLOSING = 'closing'; /** - * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link ConnectionBase.connect | `connect()`}, which results in a new connection. + * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}, which results in a new connection. */ type CLOSED = 'closed'; /** - * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link ConnectionBase.connect | `connect()`}. + * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}. */ type FAILED = 'failed'; } /** - * Describes the realtime {@link ConnectionBase} object states. + * Describes the realtime {@link Connection} object states. */ type ConnectionState = | ConnectionState.INITIALIZED @@ -173,7 +173,7 @@ declare namespace Types { */ type DISCONNECTED = 'disconnected'; /** - * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link ConnectionBase.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). + * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link Connection.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). */ type SUSPENDED = 'suspended'; /** @@ -181,11 +181,11 @@ declare namespace Types { */ type CLOSING = 'closing'; /** - * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link ConnectionBase.connect | `connect()`}, which results in a new connection. + * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}, which results in a new connection. */ type CLOSED = 'closed'; /** - * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link ConnectionBase.connect | `connect()`}. + * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}. */ type FAILED = 'failed'; /** @@ -194,7 +194,7 @@ declare namespace Types { type UPDATE = 'update'; } /** - * Describes the events emitted by a {@link ConnectionBase} object. An event is either an `UPDATE` or a {@link ConnectionState}. + * Describes the events emitted by a {@link Connection} object. An event is either an `UPDATE` or a {@link ConnectionState}. */ type ConnectionEvent = | ConnectionEvent.INITIALIZED @@ -296,7 +296,7 @@ declare namespace Types { type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'comet'; /** - * Contains the details of a {@link ChannelBase} or {@link RealtimeChannelBase} object such as its ID and {@link ChannelStatus}. + * Contains the details of a {@link Channel} or {@link RealtimeChannel} object such as its ID and {@link ChannelStatus}. */ interface ChannelDetails { /** @@ -310,7 +310,7 @@ declare namespace Types { } /** - * Contains the status of a {@link ChannelBase} or {@link RealtimeChannelBase} object such as whether it is active and its {@link ChannelOccupancy}. + * Contains the status of a {@link Channel} or {@link RealtimeChannel} object such as whether it is active and its {@link ChannelOccupancy}. */ interface ChannelStatus { /** @@ -324,7 +324,7 @@ declare namespace Types { } /** - * Contains the metrics of a {@link ChannelBase} or {@link RealtimeChannelBase} object. + * Contains the metrics of a {@link Channel} or {@link RealtimeChannel} object. */ interface ChannelOccupancy { /** @@ -334,7 +334,7 @@ declare namespace Types { } /** - * Contains the metrics associated with a {@link ChannelBase} or {@link RealtimeChannelBase}, such as the number of publishers, subscribers and connections it has. + * Contains the metrics associated with a {@link Channel} or {@link RealtimeChannel}, such as the number of publishers, subscribers and connections it has. */ interface ChannelMetrics { /** @@ -364,11 +364,11 @@ declare namespace Types { } /** - * Passes additional client-specific properties to the REST {@link RestBase.constructor | `constructor()`} or the Realtime {@link RealtimeBase.constructor | `constructor()`}. + * Passes additional client-specific properties to the REST {@link Rest.constructor | `constructor()`} or the Realtime {@link Realtime.constructor | `constructor()`}. */ interface ClientOptions extends AuthOptions { /** - * When `true`, the client connects to Ably as soon as it is instantiated. You can set this to `false` and explicitly connect to Ably using the {@link ConnectionBase.connect | `connect()`} method. The default is `true`. + * When `true`, the client connects to Ably as soon as it is instantiated. You can set this to `false` and explicitly connect to Ably using the {@link Connection.connect | `connect()`} method. The default is `true`. * * @defaultValue `true` */ @@ -704,7 +704,7 @@ declare namespace Types { } /** - * Sets the properties to configure encryption for a {@link ChannelBase} or {@link RealtimeChannelBase} object. + * Sets the properties to configure encryption for a {@link Channel} or {@link RealtimeChannel} object. */ interface CipherParams { /** @@ -890,7 +890,7 @@ declare namespace Types { } /** - * Contains the properties of a request for a token to Ably. Tokens are generated using {@link AuthPromise.requestToken}. + * Contains the properties of a request for a token to Ably. Tokens are generated using {@link Auth.requestToken}. */ interface TokenRequest { /** @@ -971,7 +971,7 @@ declare namespace Types { type ChannelModes = Array; /** - * Passes additional properties to a {@link ChannelBase} or {@link RealtimeChannelBase} object, such as encryption, {@link ChannelMode} and channel parameters. + * Passes additional properties to a {@link Channel} or {@link RealtimeChannel} object, such as encryption, {@link ChannelMode} and channel parameters. */ interface ChannelOptions { /** @@ -989,7 +989,7 @@ declare namespace Types { } /** - * Passes additional properties to a {@link RealtimeChannelBase} name to produce a new derived channel + * Passes additional properties to a {@link RealtimeChannel} name to produce a new derived channel */ interface DeriveOptions { /** @@ -1001,8 +1001,8 @@ declare namespace Types { /** * The `RestHistoryParams` interface describes the parameters accepted by the following methods: * - * - {@link PresencePromise.history} - * - {@link ChannelPromise.history} + * - {@link Presence.history} + * - {@link Channel.history} */ interface RestHistoryParams { /** @@ -1030,7 +1030,7 @@ declare namespace Types { } /** - * The `RestPresenceParams` interface describes the parameters accepted by {@link PresencePromise.get}. + * The `RestPresenceParams` interface describes the parameters accepted by {@link Presence.get}. */ interface RestPresenceParams { /** @@ -1050,7 +1050,7 @@ declare namespace Types { } /** - * The `RealtimePresenceParams` interface describes the parameters accepted by {@link RealtimePresencePromise.get}. + * The `RealtimePresenceParams` interface describes the parameters accepted by {@link RealtimePresence.get}. */ interface RealtimePresenceParams { /** @@ -1072,8 +1072,8 @@ declare namespace Types { /** * The `RealtimeHistoryParams` interface describes the parameters accepted by the following methods: * - * - {@link RealtimePresencePromise.history} - * - {@link RealtimeChannelPromise.history} + * - {@link RealtimePresence.history} + * - {@link RealtimeChannel.history} */ interface RealtimeHistoryParams { /** @@ -1105,7 +1105,7 @@ declare namespace Types { } /** - * Contains state change information emitted by {@link ChannelBase} and {@link RealtimeChannelBase} objects. + * Contains state change information emitted by {@link Channel} and {@link RealtimeChannel} objects. */ interface ChannelStateChange { /** @@ -1127,7 +1127,7 @@ declare namespace Types { } /** - * Contains {@link ConnectionState} change information emitted by the {@link ConnectionBase} object. + * Contains {@link ConnectionState} change information emitted by the {@link Connection} object. */ interface ConnectionStateChange { /** @@ -1300,8 +1300,8 @@ declare namespace Types { /** * The `DeviceRegistrationParams` interface describes the parameters accepted by the following methods: * - * - {@link PushDeviceRegistrationsPromise.list} - * - {@link PushDeviceRegistrationsPromise.removeWhere} + * - {@link PushDeviceRegistrations.list} + * - {@link PushDeviceRegistrations.removeWhere} */ interface DeviceRegistrationParams { /** @@ -1325,8 +1325,8 @@ declare namespace Types { /** * The `PushChannelSubscriptionParams` interface describes the parameters accepted by the following methods: * - * - {@link PushChannelSubscriptionsPromise.list} - * - {@link PushChannelSubscriptionsPromise.removeWhere} + * - {@link PushChannelSubscriptions.list} + * - {@link PushChannelSubscriptions.removeWhere} */ interface PushChannelSubscriptionParams { /** @@ -1348,7 +1348,7 @@ declare namespace Types { } /** - * The `PushChannelsParams` interface describes the parameters accepted by {@link PushChannelSubscriptionsPromise.listChannels}. + * The `PushChannelsParams` interface describes the parameters accepted by {@link PushChannelSubscriptions.listChannels}. */ interface PushChannelsParams { /** @@ -1360,8 +1360,8 @@ declare namespace Types { /** * The `StatsParams` interface describes the parameters accepted by the following methods: * - * - {@link RestPromise.stats} - * - {@link RealtimePromise.stats} + * - {@link Rest.stats} + * - {@link Realtime.stats} */ interface StatsParams { /** @@ -1398,19 +1398,19 @@ declare namespace Types { // Common Listeners /** - * A callback which returns only a single argument, used for {@link RealtimeChannelBase} subscriptions. + * A callback which returns only a single argument, used for {@link RealtimeChannel} subscriptions. * * @param message - The message which triggered the callback. */ type messageCallback = (message: T) => void; /** - * The callback used for the events emitted by {@link RealtimeChannelBase}. + * The callback used for the events emitted by {@link RealtimeChannel}. * * @param changeStateChange - The state change that occurred. */ type channelEventCallback = (changeStateChange: ChannelStateChange) => void; /** - * The callback used for the events emitted by {@link ConnectionBase}. + * The callback used for the events emitted by {@link Connection}. * * @param connectionStateChange - The state change that occurred. */ @@ -1456,7 +1456,7 @@ declare namespace Types { // that returns a Promise if desired, EventEmitter uses method overloading to // present both methods /** - * A generic interface for event registration and delivery used in a number of the types in the Realtime client library. For example, the {@link ConnectionBase} object emits events for connection state using the `EventEmitter` pattern. + * A generic interface for event registration and delivery used in a number of the types in the Realtime client library. For example, the {@link Connection} object emits events for connection state using the `EventEmitter` pattern. */ class EventEmitter { /** @@ -1532,9 +1532,9 @@ declare namespace Types { // Classes /** - * The `RestBase` class acts as a base class for the {@link RestPromise} class. + * A client that offers a simple stateless API to interact directly with Ably's REST API. */ - class RestBase { + class Rest { /** * Construct a client object using an Ably {@link Types.ClientOptions} object. * @@ -1559,20 +1559,15 @@ declare namespace Types { * Static utilities related to presence messages. */ static PresenceMessage: Types.PresenceMessageStatic; - } - /** - * A client that offers a simple stateless API to interact directly with Ably's REST API. - */ - class RestPromise extends RestBase { /** - * An {@link Types.AuthPromise} object. + * An {@link Types.Auth} object. */ - auth: Types.AuthPromise; + auth: Types.Auth; /** * A {@link Types.Channels} object. */ - channels: Types.Channels; + channels: Types.Channels; /** * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. * @@ -1606,15 +1601,15 @@ declare namespace Types { */ time(): Promise; /** - * A {@link Types.PushPromise} object. + * A {@link Types.Push} object. */ - push: Types.PushPromise; + push: Types.Push; } /** - * A base class used internally for Realtime APIs. + * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ - class RealtimeBase { + class Realtime { /** * Construct a client object using an Ably {@link Types.ClientOptions} object. * @@ -1644,31 +1639,26 @@ declare namespace Types { */ clientId: string; /** - * Calls {@link Types.ConnectionBase.close | `connection.close()`} and causes the connection to close, entering the closing state. Once closed, the library will not attempt to re-establish the connection without an explicit call to {@link Types.ConnectionBase.connect | `connect()`}. + * Calls {@link Types.Connection.close | `connection.close()`} and causes the connection to close, entering the closing state. Once closed, the library will not attempt to re-establish the connection without an explicit call to {@link Types.Connection.connect | `connect()`}. */ close(): void; /** - * Calls {@link Types.ConnectionBase.connect | `connection.connect()`} and causes the connection to open, entering the connecting state. Explicitly calling `connect()` is unnecessary unless the {@link Types.ClientOptions.autoConnect} property is disabled. + * Calls {@link Types.Connection.connect | `connection.connect()`} and causes the connection to open, entering the connecting state. Explicitly calling `connect()` is unnecessary unless the {@link Types.ClientOptions.autoConnect} property is disabled. */ connect(): void; - } - /** - * A client that extends the functionality of {@link RestPromise} and provides additional realtime-specific features. - */ - class RealtimePromise extends RealtimeBase { /** - * An {@link Types.AuthPromise} object. + * An {@link Types.Auth} object. */ - auth: Types.AuthPromise; + auth: Types.Auth; /** * A {@link Types.Channels} object. */ - channels: Types.Channels; + channels: Types.Channels; /** - * A {@link Types.ConnectionPromise} object. + * A {@link Types.Connection} object. */ - connection: Types.ConnectionPromise; + connection: Types.Connection; /** * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. * @@ -1702,25 +1692,20 @@ declare namespace Types { */ time(): Promise; /** - * A {@link Types.PushPromise} object. + * A {@link Types.Push} object. */ - push: Types.PushPromise; + push: Types.Push; } /** - * The `AuthBase` class acts as a base class for the {@link AuthPromise} class. + * Creates Ably {@link TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients. */ - class AuthBase { + class Auth { /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error is raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [identified clients](https://ably.com/docs/core-features/authentication#identified-clients). */ clientId: string; - } - /** - * Creates Ably {@link TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients. - */ - class AuthPromise extends AuthBase { /** * Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token has been obtained. Also stores any {@link TokenParams} and {@link AuthOptions} passed in as the new defaults, to be used for all subsequent implicit or explicit token requests. Any {@link TokenParams} and {@link AuthOptions} objects passed in entirely replace, as opposed to being merged with, the current client library saved values. * @@ -1750,7 +1735,7 @@ declare namespace Types { /** * Enables the retrieval of the current and historic presence set for a channel. */ - class PresencePromise { + class Presence { /** * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns a {@link Types.PaginatedResult} object, containing an array of {@link PresenceMessage} objects. * @@ -1768,9 +1753,9 @@ declare namespace Types { } /** - * The `RealtimePresenceBase` class acts as a base class for the {@link RealtimePresencePromise} class. + * Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. */ - class RealtimePresenceBase { + class RealtimePresence { /** * Indicates whether the presence set synchronization between Ably and the clients on the channel has been completed. Set to `true` when the sync is complete. */ @@ -1811,12 +1796,7 @@ declare namespace Types { * Deregisters all listeners currently receiving {@link PresenceMessage} for the channel. */ unsubscribe(): void; - } - /** - * Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. - */ - class RealtimePresencePromise extends RealtimePresenceBase { /** * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns an array of {@link PresenceMessage} objects. * @@ -1836,7 +1816,7 @@ declare namespace Types { * * @param action - A {@link PresenceAction} or an array of {@link PresenceAction | `PresenceAction`s} to register the listener for. * @param listener - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. */ subscribe( action: PresenceAction | Array, @@ -1846,7 +1826,7 @@ declare namespace Types { * Registers a listener that is called each time a {@link PresenceMessage} is received on the channel, such as a new member entering the presence set. * * @param listener - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. */ subscribe(listener?: messageCallback): Promise; /** @@ -1897,23 +1877,18 @@ declare namespace Types { } /** - * The `ChannelBase` class acts as a base class for the {@link ChannelPromise} class. + * Enables messages to be published and historic messages to be retrieved for a channel. */ - class ChannelBase { + class Channel { /** * The channel name. */ name: string; - } - /** - * Enables messages to be published and historic messages to be retrieved for a channel. - */ - class ChannelPromise extends ChannelBase { /** - * A {@link PresencePromise} object. + * A {@link Presence} object. */ - presence: PresencePromise; + presence: Presence; /** * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link Message} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. * @@ -1955,9 +1930,9 @@ declare namespace Types { } /** - * The `RealtimeChannelBase` class acts as a base class for the {@link RealtimeChannelPromise} class. + * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresence} object of a channel. */ - class RealtimeChannelBase extends EventEmitter { + class RealtimeChannel extends EventEmitter { /** * The channel name. */ @@ -2021,18 +1996,13 @@ declare namespace Types { * Deregisters all listeners to messages on this channel. This removes all earlier subscriptions. */ unsubscribe(): void; - } - /** - * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresencePromise} object of a channel. - */ - class RealtimeChannelPromise extends RealtimeChannelBase { /** - * A {@link RealtimePresencePromise} object. + * A {@link RealtimePresence} object. */ - presence: RealtimePresencePromise; + presence: RealtimePresence; /** - * Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel are received by any channel listeners registered using {@link RealtimeChannelPromise.subscribe | `subscribe()`}. Any resulting channel state change will be emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. As a convenience, `attach()` is called implicitly if {@link RealtimeChannelPromise.subscribe | `subscribe()`} for the channel is called, or {@link RealtimePresencePromise.enter | `enter()`} or {@link RealtimePresencePromise.subscribe | `subscribe()`} are called on the {@link RealtimePresencePromise} object for this channel. + * Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel are received by any channel listeners registered using {@link RealtimeChannel.subscribe | `subscribe()`}. Any resulting channel state change will be emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. As a convenience, `attach()` is called implicitly if {@link RealtimeChannel.subscribe | `subscribe()`} for the channel is called, or {@link RealtimePresence.enter | `enter()`} or {@link RealtimePresence.subscribe | `subscribe()`} are called on the {@link RealtimePresence} object for this channel. * * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. */ @@ -2062,7 +2032,7 @@ declare namespace Types { * * @param event - The event name. * @param listener - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. */ subscribe(event: string, listener?: messageCallback): Promise; /** @@ -2070,7 +2040,7 @@ declare namespace Types { * * @param events - An array of event names. * @param listener - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. */ subscribe(events: Array, listener?: messageCallback): Promise; /** @@ -2078,14 +2048,14 @@ declare namespace Types { * * @param filter - A {@link MessageFilter}. * @param listener - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. */ subscribe(filter: MessageFilter, listener?: messageCallback): Promise; /** * Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel. * * @param callback - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. */ subscribe(callback: messageCallback): Promise; /** @@ -2129,7 +2099,7 @@ declare namespace Types { }; /** - * Contains properties to filter messages with when calling {@link RealtimeChannelPromise.subscribe | `RealtimeChannelPromise.subscribe()`}. + * Contains properties to filter messages with when calling {@link RealtimeChannel.subscribe | `RealtimeChannel.subscribe()`}. */ type MessageFilter = { /** @@ -2155,15 +2125,15 @@ declare namespace Types { }; /** - * Creates and destroys {@link ChannelBase} and {@link RealtimeChannelBase} objects. + * Creates and destroys {@link Channel} and {@link RealtimeChannel} objects. */ class Channels { /** - * Creates a new {@link ChannelBase} or {@link RealtimeChannelBase} object, with the specified {@link ChannelOptions}, or returns the existing channel object. + * Creates a new {@link Channel} or {@link RealtimeChannel} object, with the specified {@link ChannelOptions}, or returns the existing channel object. * * @param name - The channel name. * @param channelOptions - A {@link ChannelOptions} object. - * @returns A {@link ChannelBase} or {@link RealtimeChannelBase} object. + * @returns A {@link Channel} or {@link RealtimeChannel} object. */ get(name: string, channelOptions?: ChannelOptions): T; /** @@ -2172,17 +2142,17 @@ declare namespace Types { * to receive only part of the data from the channel. * See the [announcement post](https://pages.ably.com/subscription-filters-preview) for more information. * - * Creates a new {@link ChannelBase} or {@link RealtimeChannelBase} object, with the specified channel {@link DeriveOptions} + * Creates a new {@link Channel} or {@link RealtimeChannel} object, with the specified channel {@link DeriveOptions} * and {@link ChannelOptions}, or returns the existing channel object. * * @param name - The channel name. * @param deriveOptions - A {@link DeriveOptions} object. * @param channelOptions - A {@link ChannelOptions} object. - * @returns A {@link RealtimeChannelBase} object. + * @returns A {@link RealtimeChannel} object. */ getDerived(name: string, deriveOptions: DeriveOptions, channelOptions?: ChannelOptions): T; /** - * Releases a {@link ChannelBase} or {@link RealtimeChannelBase} object, deleting it, and enabling it to be garbage collected. It also removes any listeners associated with the channel. To release a channel, the {@link ChannelState} must be `INITIALIZED`, `DETACHED`, or `FAILED`. + * Releases a {@link Channel} or {@link RealtimeChannel} object, deleting it, and enabling it to be garbage collected. It also removes any listeners associated with the channel. To release a channel, the {@link ChannelState} must be `INITIALIZED`, `DETACHED`, or `FAILED`. * * @param name - The channel name. */ @@ -2397,9 +2367,9 @@ declare namespace Types { } /** - * The `ConnectionBase` class acts as a base class for the {@link ChannelPromise} class. + * Enables the management of a connection to Ably. */ - class ConnectionBase extends EventEmitter { + class Connection extends EventEmitter { /** * An {@link ErrorInfo} object describing the last error received if a connection failure occurs. */ @@ -2425,19 +2395,14 @@ declare namespace Types { */ readonly state: ConnectionState; /** - * Causes the connection to close, entering the {@link ConnectionState.CLOSING} state. Once closed, the library does not attempt to re-establish the connection without an explicit call to {@link ConnectionBase.connect | `connect()`}. + * Causes the connection to close, entering the {@link ConnectionState.CLOSING} state. Once closed, the library does not attempt to re-establish the connection without an explicit call to {@link Connection.connect | `connect()`}. */ close(): void; /** * Explicitly calling `connect()` is unnecessary unless the `autoConnect` attribute of the {@link ClientOptions} object is `false`. Unless already connected or connecting, this method causes the connection to open, entering the {@link ConnectionState.CONNECTING} state. */ connect(): void; - } - /** - * Enables the management of a connection to Ably. - */ - class ConnectionPromise extends ConnectionBase { /** * When connected, sends a heartbeat ping to the Ably server and executes the callback with any error and the response time in milliseconds when a heartbeat ping request is echoed from the server. This can be useful for measuring true round-trip latency to the connected Ably server. * @@ -2561,25 +2526,25 @@ declare namespace Types { /** * Enables a device to be registered and deregistered from receiving push notifications. */ - class PushPromise { + class Push { /** - * A {@link PushAdminPromise | `PushAdmin`} object. + * A {@link PushAdmin} object. */ - admin: PushAdminPromise; + admin: PushAdmin; } /** * Enables the management of device registrations and push notification subscriptions. Also enables the publishing of push notifications to devices. */ - class PushAdminPromise { + class PushAdmin { /** - * A {@link PushDeviceRegistrationsPromise} object. + * A {@link PushDeviceRegistrations} object. */ - deviceRegistrations: PushDeviceRegistrationsPromise; + deviceRegistrations: PushDeviceRegistrations; /** - * A {@link PushChannelSubscriptionsPromise} object. + * A {@link PushChannelSubscriptions} object. */ - channelSubscriptions: PushChannelSubscriptionsPromise; + channelSubscriptions: PushChannelSubscriptions; /** * Sends a push notification directly to a device, or a group of devices sharing the same `clientId`. * @@ -2593,7 +2558,7 @@ declare namespace Types { /** * Enables the management of push notification registrations with Ably. */ - class PushDeviceRegistrationsPromise { + class PushDeviceRegistrations { /** * Registers or updates a {@link DeviceDetails} object with Ably. Returns the new, or updated {@link DeviceDetails} object. * @@ -2648,7 +2613,7 @@ declare namespace Types { /** * Enables device push channel subscriptions. */ - class PushChannelSubscriptionsPromise { + class PushChannelSubscriptions { /** * Subscribes a device, or a group of devices sharing the same `clientId` to push notifications on a channel. Returns a {@link PushChannelSubscription} object. * @@ -2690,9 +2655,9 @@ declare namespace Types { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare class Rest extends Types.RestPromise {} +export declare class Rest extends Types.Rest {} /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ -export declare class Realtime extends Types.RealtimePromise {} +export declare class Realtime extends Types.Realtime {} From dbc3dbeb22e3ad4bd18dcef1c03397951c4a6b0a Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 7 Jul 2023 15:56:06 +0100 Subject: [PATCH 132/468] chore: change xhr transport file extensions to .ts these modules won't compile yet, but the extension change is in a separate commit from the code changes otherwise git won't track that these files have been moved (as opposed to being deleted) --- .../transport/{xhrpollingtransport.js => xhrpollingtransport.ts} | 0 .../{xhrstreamingtransport.js => xhrstreamingtransport.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/platform/web/lib/transport/{xhrpollingtransport.js => xhrpollingtransport.ts} (100%) rename src/platform/web/lib/transport/{xhrstreamingtransport.js => xhrstreamingtransport.ts} (100%) diff --git a/src/platform/web/lib/transport/xhrpollingtransport.js b/src/platform/web/lib/transport/xhrpollingtransport.ts similarity index 100% rename from src/platform/web/lib/transport/xhrpollingtransport.js rename to src/platform/web/lib/transport/xhrpollingtransport.ts diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.js b/src/platform/web/lib/transport/xhrstreamingtransport.ts similarity index 100% rename from src/platform/web/lib/transport/xhrstreamingtransport.js rename to src/platform/web/lib/transport/xhrstreamingtransport.ts From 2fd7cdb5f2918585c2cf75df6c83b65daaec9974 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 7 Jul 2023 15:57:56 +0100 Subject: [PATCH 133/468] refactor: convert xhrpollingtransport.ts to typescript --- .../web/lib/transport/xhrpollingtransport.ts | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index af75d43abb..212af4682f 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -1,35 +1,42 @@ -import * as Utils from '../../../../common/lib/util/utils'; import Platform from '../../../../common/platform'; import CometTransport from '../../../../common/lib/transport/comettransport'; import XHRRequest from './xhrrequest'; - -var XHRPollingTransport = function (connectionManager) { - var shortName = 'xhr_polling'; - - function XHRPollingTransport(connectionManager, auth, params) { +import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; +import Auth from 'common/lib/client/auth'; +import { RequestParams } from 'common/types/http'; + +var shortName = 'xhr_polling'; +class XHRPollingTransport extends CometTransport { + shortName = shortName; + constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { + super(connectionManager, auth, params); params.stream = false; - CometTransport.call(this, connectionManager, auth, params); this.shortName = shortName; } - Utils.inherits(XHRPollingTransport, CometTransport); - XHRPollingTransport.isAvailable = function () { + static isAvailable() { return Platform.Config.xhrSupported && Platform.Config.allowComet; - }; + } - XHRPollingTransport.prototype.toString = function () { + toString() { return 'XHRPollingTransport; uri=' + this.baseUri + '; isConnected=' + this.isConnected; - }; + } - XHRPollingTransport.prototype.createRequest = function (uri, headers, params, body, requestMode) { + createRequest( + uri: string, + headers: Record, + params: RequestParams, + body: unknown, + requestMode: number + ) { return XHRRequest.createRequest(uri, headers, params, body, requestMode, this.timeouts); - }; - - if (typeof connectionManager !== 'undefined' && XHRPollingTransport.isAvailable()) { - connectionManager.supportedTransports[shortName] = XHRPollingTransport; } +} + +function initialiseTransport(connectionManager: any): typeof XHRPollingTransport { + if (XHRPollingTransport.isAvailable()) connectionManager.supportedTransports[shortName] = XHRPollingTransport; return XHRPollingTransport; -}; +} -export default XHRPollingTransport; +export default initialiseTransport; From 52f1f4e848f7936d85552fc633974b04ffd76348 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 7 Jul 2023 15:58:12 +0100 Subject: [PATCH 134/468] refactor: convert xhrstreamingtransport to typescript --- .../lib/transport/xhrstreamingtransport.ts | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index faf40f15ff..d2b816a650 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -1,35 +1,40 @@ -import * as Utils from '../../../../common/lib/util/utils'; import CometTransport from '../../../../common/lib/transport/comettransport'; import Platform from '../../../../common/platform'; import XHRRequest from './xhrrequest'; - -var XHRStreamingTransport = function (connectionManager) { - var shortName = 'xhr_streaming'; - - /* public constructor */ - function XHRStreamingTransport(connectionManager, auth, params) { - CometTransport.call(this, connectionManager, auth, params); - this.shortName = shortName; +import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; +import Auth from 'common/lib/client/auth'; +import { RequestParams } from 'common/types/http'; + +const shortName = 'xhr_streaming'; +class XHRStreamingTransport extends CometTransport { + shortName = shortName; + constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { + super(connectionManager, auth, params); } - Utils.inherits(XHRStreamingTransport, CometTransport); - XHRStreamingTransport.isAvailable = function () { + static isAvailable() { return Platform.Config.xhrSupported && Platform.Config.streamingSupported && Platform.Config.allowComet; - }; + } - XHRStreamingTransport.prototype.toString = function () { + toString() { return 'XHRStreamingTransport; uri=' + this.baseUri + '; isConnected=' + this.isConnected; - }; + } - XHRStreamingTransport.prototype.createRequest = function (uri, headers, params, body, requestMode) { + createRequest( + uri: string, + headers: Record, + params: RequestParams, + body: unknown, + requestMode: number + ) { return XHRRequest.createRequest(uri, headers, params, body, requestMode, this.timeouts); - }; - - if (typeof connectionManager !== 'undefined' && XHRStreamingTransport.isAvailable()) { - connectionManager.supportedTransports[shortName] = XHRStreamingTransport; } +} + +function initialiseTransport(connectionManager: any): typeof XHRStreamingTransport { + if (XHRStreamingTransport.isAvailable()) connectionManager.supportedTransports[shortName] = XHRStreamingTransport; return XHRStreamingTransport; -}; +} -export default XHRStreamingTransport; +export default initialiseTransport; From 3bb7623ade684588b4002bc4a02684414f150006 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 30 Jun 2023 14:59:29 +0100 Subject: [PATCH 135/468] deps: add esbuild dependency --- package-lock.json | 574 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 575 insertions(+) diff --git a/package-lock.json b/package-lock.json index ee1341051c..5d47210e39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "chai": "^4.2.0", "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", + "esbuild": "^0.18.10", "eslint": "^7.13.0", "eslint-plugin-jsdoc": "^40.0.0", "eslint-plugin-security": "^1.4.0", @@ -136,6 +137,358 @@ "node": "^14 || ^16 || ^17 || ^18 || ^19" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.10.tgz", + "integrity": "sha512-3KClmVNd+Fku82uZJz5C4Rx8m1PPmWUFz5Zkw8jkpZPOmsq+EG1TTOtw1OXkHuX3WczOFQigrtf60B1ijKwNsg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.10.tgz", + "integrity": "sha512-ynm4naLbNbK0ajf9LUWtQB+6Vfg1Z/AplArqr4tGebC00Z6m9Y91OVIcjDa461wGcZwcaHYaZAab4yJxfhisTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.10.tgz", + "integrity": "sha512-vFfXj8P9Yfjh54yqUDEHKzqzYuEfPyAOl3z7R9hjkwt+NCvbn9VMxX+IILnAfdImRBfYVItgSUsqGKhJFnBwZw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.10.tgz", + "integrity": "sha512-k2OJQ7ZxE6sVc91+MQeZH9gFeDAH2uIYALPAwTjTCvcPy9Dzrf7V7gFUQPYkn09zloWhQ+nvxWHia2x2ZLR0sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.10.tgz", + "integrity": "sha512-tnz/mdZk1L1Z3WpGjin/L2bKTe8/AKZpI8fcCLtH+gq8WXWsCNJSxlesAObV4qbtTl6pG5vmqFXfWUQ5hV8PAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.10.tgz", + "integrity": "sha512-QJluV0LwBrbHnYYwSKC+K8RGz0g/EyhpQH1IxdoFT0nM7PfgjE+aS8wxq/KFEsU0JkL7U/EEKd3O8xVBxXb2aA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.10.tgz", + "integrity": "sha512-Hi/ycUkS6KTw+U9G5PK5NoK7CZboicaKUSVs0FSiPNtuCTzK6HNM4DIgniH7hFaeuszDS9T4dhAHWiLSt/Y5Ng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.10.tgz", + "integrity": "sha512-HfFoxY172tVHPIvJy+FHxzB4l8xU7e5cxmNS11cQ2jt4JWAukn/7LXaPdZid41UyTweqa4P/1zs201gRGCTwHw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.10.tgz", + "integrity": "sha512-Nz6XcfRBOO7jSrVpKAyEyFOPGhySPNlgumSDhWAspdQQ11ub/7/NZDMhWDFReE9QH/SsCOCLQbdj0atAk/HMOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.10.tgz", + "integrity": "sha512-otMdmSmkMe+pmiP/bZBjfphyAsTsngyT9RCYwoFzqrveAbux9nYitDTpdgToG0Z0U55+PnH654gCH2GQ1aB6Yw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.10.tgz", + "integrity": "sha512-t8tjFuON1koxskzQ4VFoh0T5UDUMiLYjwf9Wktd0tx8AoK6xgU+5ubKOpWpcnhEQ2tESS5u0v6QuN8PX/ftwcQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.10.tgz", + "integrity": "sha512-+dUkcVzcfEJHz3HEnVpIJu8z8Wdn2n/nWMWdl6FVPFGJAVySO4g3+XPzNKFytVFwf8hPVDwYXzVcu8GMFqsqZw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.10.tgz", + "integrity": "sha512-sO3PjjxEGy+PY2qkGe2gwJbXdZN9wAYpVBZWFD0AwAoKuXRkWK0/zaMQ5ekUFJDRDCRm8x5U0Axaub7ynH/wVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.10.tgz", + "integrity": "sha512-JDtdbJg3yjDeXLv4lZYE1kiTnxv73/8cbPHY9T/dUKi8rYOM/k5b3W4UJLMUksuQ6nTm5c89W1nADsql6FW75A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.10.tgz", + "integrity": "sha512-NLuSKcp8WckjD2a7z5kzLiCywFwBTMlIxDNuud1AUGVuwBBJSkuubp6cNjJ0p5c6CZaA3QqUGwjHJBiG1SoOFw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.10.tgz", + "integrity": "sha512-wj2KRsCsFusli+6yFgNO/zmmLslislAWryJnodteRmGej7ZzinIbMdsyp13rVGde88zxJd5vercNYK9kuvlZaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.10.tgz", + "integrity": "sha512-pQ9QqxEPI3cVRZyUtCoZxhZK3If+7RzR8L2yz2+TDzdygofIPOJFaAPkEJ5rYIbUO101RaiYxfdOBahYexLk5A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.10.tgz", + "integrity": "sha512-k8GTIIW9I8pEEfoOUm32TpPMgSg06JhL5DO+ql66aLTkOQUs0TxCA67Wi7pv6z8iF8STCGcNbm3UWFHLuci+ag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.10.tgz", + "integrity": "sha512-vIGYJIdEI6d4JBucAx8py792G8J0GP40qSH+EvSt80A4zvGd6jph+5t1g+eEXcS2aRpgZw6CrssNCFZxTdEsxw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.10.tgz", + "integrity": "sha512-kRhNcMZFGMW+ZHCarAM1ypr8OZs0k688ViUCetVCef9p3enFxzWeBg9h/575Y0nsFu0ZItluCVF5gMR2pwOEpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.10.tgz", + "integrity": "sha512-AR9PX1whYaYh9p0EOaKna0h48F/A101Mt/ag72+kMkkBZXPQ7cjbz2syXI/HI3OlBdUytSdHneljfjvUoqwqiQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.10.tgz", + "integrity": "sha512-5sTkYhAGHNRr6bVf4RM0PsscqVr6/DBYdrlMh168oph3usid3lKHcHEEHmr34iZ9GHeeg2juFOxtpl6XyC3tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2075,6 +2428,43 @@ "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", "dev": true }, + "node_modules/esbuild": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.10.tgz", + "integrity": "sha512-33WKo67auOXzZHBY/9DTJRo7kIvfU12S+D4sp2wIz39N88MDIaCGyCwbW01RR70pK6Iya0I74lHEpyLfFqOHPA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.10", + "@esbuild/android-arm64": "0.18.10", + "@esbuild/android-x64": "0.18.10", + "@esbuild/darwin-arm64": "0.18.10", + "@esbuild/darwin-x64": "0.18.10", + "@esbuild/freebsd-arm64": "0.18.10", + "@esbuild/freebsd-x64": "0.18.10", + "@esbuild/linux-arm": "0.18.10", + "@esbuild/linux-arm64": "0.18.10", + "@esbuild/linux-ia32": "0.18.10", + "@esbuild/linux-loong64": "0.18.10", + "@esbuild/linux-mips64el": "0.18.10", + "@esbuild/linux-ppc64": "0.18.10", + "@esbuild/linux-riscv64": "0.18.10", + "@esbuild/linux-s390x": "0.18.10", + "@esbuild/linux-x64": "0.18.10", + "@esbuild/netbsd-x64": "0.18.10", + "@esbuild/openbsd-x64": "0.18.10", + "@esbuild/sunos-x64": "0.18.10", + "@esbuild/win32-arm64": "0.18.10", + "@esbuild/win32-ia32": "0.18.10", + "@esbuild/win32-x64": "0.18.10" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -7518,6 +7908,160 @@ "jsdoc-type-pratt-parser": "~3.1.0" } }, + "@esbuild/android-arm": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.10.tgz", + "integrity": "sha512-3KClmVNd+Fku82uZJz5C4Rx8m1PPmWUFz5Zkw8jkpZPOmsq+EG1TTOtw1OXkHuX3WczOFQigrtf60B1ijKwNsg==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.10.tgz", + "integrity": "sha512-ynm4naLbNbK0ajf9LUWtQB+6Vfg1Z/AplArqr4tGebC00Z6m9Y91OVIcjDa461wGcZwcaHYaZAab4yJxfhisTQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.10.tgz", + "integrity": "sha512-vFfXj8P9Yfjh54yqUDEHKzqzYuEfPyAOl3z7R9hjkwt+NCvbn9VMxX+IILnAfdImRBfYVItgSUsqGKhJFnBwZw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.10.tgz", + "integrity": "sha512-k2OJQ7ZxE6sVc91+MQeZH9gFeDAH2uIYALPAwTjTCvcPy9Dzrf7V7gFUQPYkn09zloWhQ+nvxWHia2x2ZLR0sQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.10.tgz", + "integrity": "sha512-tnz/mdZk1L1Z3WpGjin/L2bKTe8/AKZpI8fcCLtH+gq8WXWsCNJSxlesAObV4qbtTl6pG5vmqFXfWUQ5hV8PAQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.10.tgz", + "integrity": "sha512-QJluV0LwBrbHnYYwSKC+K8RGz0g/EyhpQH1IxdoFT0nM7PfgjE+aS8wxq/KFEsU0JkL7U/EEKd3O8xVBxXb2aA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.10.tgz", + "integrity": "sha512-Hi/ycUkS6KTw+U9G5PK5NoK7CZboicaKUSVs0FSiPNtuCTzK6HNM4DIgniH7hFaeuszDS9T4dhAHWiLSt/Y5Ng==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.10.tgz", + "integrity": "sha512-HfFoxY172tVHPIvJy+FHxzB4l8xU7e5cxmNS11cQ2jt4JWAukn/7LXaPdZid41UyTweqa4P/1zs201gRGCTwHw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.10.tgz", + "integrity": "sha512-Nz6XcfRBOO7jSrVpKAyEyFOPGhySPNlgumSDhWAspdQQ11ub/7/NZDMhWDFReE9QH/SsCOCLQbdj0atAk/HMOQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.10.tgz", + "integrity": "sha512-otMdmSmkMe+pmiP/bZBjfphyAsTsngyT9RCYwoFzqrveAbux9nYitDTpdgToG0Z0U55+PnH654gCH2GQ1aB6Yw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.10.tgz", + "integrity": "sha512-t8tjFuON1koxskzQ4VFoh0T5UDUMiLYjwf9Wktd0tx8AoK6xgU+5ubKOpWpcnhEQ2tESS5u0v6QuN8PX/ftwcQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.10.tgz", + "integrity": "sha512-+dUkcVzcfEJHz3HEnVpIJu8z8Wdn2n/nWMWdl6FVPFGJAVySO4g3+XPzNKFytVFwf8hPVDwYXzVcu8GMFqsqZw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.10.tgz", + "integrity": "sha512-sO3PjjxEGy+PY2qkGe2gwJbXdZN9wAYpVBZWFD0AwAoKuXRkWK0/zaMQ5ekUFJDRDCRm8x5U0Axaub7ynH/wVg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.10.tgz", + "integrity": "sha512-JDtdbJg3yjDeXLv4lZYE1kiTnxv73/8cbPHY9T/dUKi8rYOM/k5b3W4UJLMUksuQ6nTm5c89W1nADsql6FW75A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.10.tgz", + "integrity": "sha512-NLuSKcp8WckjD2a7z5kzLiCywFwBTMlIxDNuud1AUGVuwBBJSkuubp6cNjJ0p5c6CZaA3QqUGwjHJBiG1SoOFw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.10.tgz", + "integrity": "sha512-wj2KRsCsFusli+6yFgNO/zmmLslislAWryJnodteRmGej7ZzinIbMdsyp13rVGde88zxJd5vercNYK9kuvlZaQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.10.tgz", + "integrity": "sha512-pQ9QqxEPI3cVRZyUtCoZxhZK3If+7RzR8L2yz2+TDzdygofIPOJFaAPkEJ5rYIbUO101RaiYxfdOBahYexLk5A==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.10.tgz", + "integrity": "sha512-k8GTIIW9I8pEEfoOUm32TpPMgSg06JhL5DO+ql66aLTkOQUs0TxCA67Wi7pv6z8iF8STCGcNbm3UWFHLuci+ag==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.10.tgz", + "integrity": "sha512-vIGYJIdEI6d4JBucAx8py792G8J0GP40qSH+EvSt80A4zvGd6jph+5t1g+eEXcS2aRpgZw6CrssNCFZxTdEsxw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.10.tgz", + "integrity": "sha512-kRhNcMZFGMW+ZHCarAM1ypr8OZs0k688ViUCetVCef9p3enFxzWeBg9h/575Y0nsFu0ZItluCVF5gMR2pwOEpA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.10.tgz", + "integrity": "sha512-AR9PX1whYaYh9p0EOaKna0h48F/A101Mt/ag72+kMkkBZXPQ7cjbz2syXI/HI3OlBdUytSdHneljfjvUoqwqiQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.10.tgz", + "integrity": "sha512-5sTkYhAGHNRr6bVf4RM0PsscqVr6/DBYdrlMh168oph3usid3lKHcHEEHmr34iZ9GHeeg2juFOxtpl6XyC3tpw==", + "dev": true, + "optional": true + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -9001,6 +9545,36 @@ "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", "dev": true }, + "esbuild": { + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.10.tgz", + "integrity": "sha512-33WKo67auOXzZHBY/9DTJRo7kIvfU12S+D4sp2wIz39N88MDIaCGyCwbW01RR70pK6Iya0I74lHEpyLfFqOHPA==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.18.10", + "@esbuild/android-arm64": "0.18.10", + "@esbuild/android-x64": "0.18.10", + "@esbuild/darwin-arm64": "0.18.10", + "@esbuild/darwin-x64": "0.18.10", + "@esbuild/freebsd-arm64": "0.18.10", + "@esbuild/freebsd-x64": "0.18.10", + "@esbuild/linux-arm": "0.18.10", + "@esbuild/linux-arm64": "0.18.10", + "@esbuild/linux-ia32": "0.18.10", + "@esbuild/linux-loong64": "0.18.10", + "@esbuild/linux-mips64el": "0.18.10", + "@esbuild/linux-ppc64": "0.18.10", + "@esbuild/linux-riscv64": "0.18.10", + "@esbuild/linux-s390x": "0.18.10", + "@esbuild/linux-x64": "0.18.10", + "@esbuild/netbsd-x64": "0.18.10", + "@esbuild/openbsd-x64": "0.18.10", + "@esbuild/sunos-x64": "0.18.10", + "@esbuild/win32-arm64": "0.18.10", + "@esbuild/win32-ia32": "0.18.10", + "@esbuild/win32-x64": "0.18.10" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", diff --git a/package.json b/package.json index 1c0a2d9914..6e36d9be39 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "chai": "^4.2.0", "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", + "esbuild": "^0.18.10", "eslint": "^7.13.0", "eslint-plugin-jsdoc": "^40.0.0", "eslint-plugin-security": "^1.4.0", From 27d065361cbe4b1b9f745f31915c9095e9817f4c Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 7 Jul 2023 16:14:57 +0100 Subject: [PATCH 136/468] deps: add esbuild-plugin-umd-wrapper dependency this is needed for legacy platform support - esbuild only supports iife, esm, and commonjs out of the box --- package-lock.json | 13 +++++++++++++ package.json | 1 + 2 files changed, 14 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5d47210e39..8a6f6b3d33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", "esbuild": "^0.18.10", + "esbuild-plugin-umd-wrapper": "^1.0.7", "eslint": "^7.13.0", "eslint-plugin-jsdoc": "^40.0.0", "eslint-plugin-security": "^1.4.0", @@ -2465,6 +2466,12 @@ "@esbuild/win32-x64": "0.18.10" } }, + "node_modules/esbuild-plugin-umd-wrapper": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/esbuild-plugin-umd-wrapper/-/esbuild-plugin-umd-wrapper-1.0.7.tgz", + "integrity": "sha512-Jo1S3rczXupUzPGt4eXF643FF/J7nDkwwwHH2PS6ic1l0ye7kTS0plAJQoD9u349ybLyt4wyG9fFa/RCuI2dXw==", + "dev": true + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -9575,6 +9582,12 @@ "@esbuild/win32-x64": "0.18.10" } }, + "esbuild-plugin-umd-wrapper": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/esbuild-plugin-umd-wrapper/-/esbuild-plugin-umd-wrapper-1.0.7.tgz", + "integrity": "sha512-Jo1S3rczXupUzPGt4eXF643FF/J7nDkwwwHH2PS6ic1l0ye7kTS0plAJQoD9u349ybLyt4wyG9fFa/RCuI2dXw==", + "dev": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", diff --git a/package.json b/package.json index 6e36d9be39..f147f333f2 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "copy-webpack-plugin": "^11.0.0", "cors": "~2.7", "esbuild": "^0.18.10", + "esbuild-plugin-umd-wrapper": "^1.0.7", "eslint": "^7.13.0", "eslint-plugin-jsdoc": "^40.0.0", "eslint-plugin-security": "^1.4.0", From 9212a9100f664f5419d76dd41d4a7a13b1533b67 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 7 Jul 2023 16:17:49 +0100 Subject: [PATCH 137/468] refactor: use named exports for web entrypoint keeping the default export too since webpack still uses this entry point for webworkers --- src/platform/web/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index 378e987d25..b98d70f7f0 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -46,6 +46,8 @@ if (Platform.Config.noUpgrade) { Platform.Defaults.upgradeTransports = []; } +export { Rest, Realtime, msgpack }; + export default { Rest, Realtime, From 102c7334e14f3ae647ca2a77451f74cbc1658580 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 7 Jul 2023 16:18:04 +0100 Subject: [PATCH 138/468] refactor: use instanceof for isErrorInfoOrPartialErrorInfo --- src/common/lib/util/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 52a5e5f6e0..6d0c839494 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -418,7 +418,7 @@ export function isErrorInfoOrPartialErrorInfo(err: unknown): err is ErrorInfo | return ( typeof err == 'object' && err !== null && - (err.constructor.name == 'ErrorInfo' || err.constructor.name == 'PartialErrorInfo') + (err instanceof ErrorInfo || err instanceof PartialErrorInfo) ); } From d9b0d3624e353b17f2238b59e362780e651a0072 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 7 Jul 2023 16:18:38 +0100 Subject: [PATCH 139/468] refactor: avoid unguarded `global` access --- src/common/lib/transport/connectionmanager.ts | 4 +++- src/common/lib/util/logger.ts | 2 +- src/common/lib/util/utils.ts | 8 ++------ src/platform/web/lib/util/webstorage.ts | 12 +++++++----- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index a3b98c9a53..92c87d1a0c 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -21,6 +21,8 @@ import HttpStatusCodes from 'common/constants/HttpStatusCodes'; type Realtime = any; type ClientOptions = any; +let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self; + const haveWebStorage = () => typeof Platform.WebStorage !== 'undefined' && Platform.WebStorage?.localSupported; const haveSessionStorage = () => typeof Platform.WebStorage !== 'undefined' && Platform.WebStorage?.sessionSupported; const actions = ProtocolMessage.Action; @@ -1136,7 +1138,7 @@ class ConnectionManager extends EventEmitter { setSessionRecoverData({ recoveryKey: recoveryKey, disconnectedAt: Utils.now(), - location: global.location, + location: globalObject.location, clientId: this.realtime.auth.clientId, }); } diff --git a/src/common/lib/util/logger.ts b/src/common/lib/util/logger.ts index 8ce22c8b88..bfafe2c500 100644 --- a/src/common/lib/util/logger.ts +++ b/src/common/lib/util/logger.ts @@ -8,7 +8,7 @@ type LoggerFunction = (...args: string[]) => void; // Workaround for salesforce lightning locker compatibility // This is a shorthand version of Utils.getGlobalObject (which we can't use here without creating a circular import) -let globalObject = global || (typeof window !== 'undefined' ? window : self); +let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self; enum LogLevels { None = 0, diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 6d0c839494..303d961d13 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -415,11 +415,7 @@ export const now = }; export function isErrorInfoOrPartialErrorInfo(err: unknown): err is ErrorInfo | PartialErrorInfo { - return ( - typeof err == 'object' && - err !== null && - (err instanceof ErrorInfo || err instanceof PartialErrorInfo) - ); + return typeof err == 'object' && err !== null && (err instanceof ErrorInfo || err instanceof PartialErrorInfo); } export function inspectError(err: unknown): string { @@ -535,7 +531,7 @@ export function getJitterCoefficient() { } export function getGlobalObject() { - if (global) { + if (typeof global !== 'undefined') { return global; } diff --git a/src/platform/web/lib/util/webstorage.ts b/src/platform/web/lib/util/webstorage.ts index d1ac7e4ec2..010bff419b 100644 --- a/src/platform/web/lib/util/webstorage.ts +++ b/src/platform/web/lib/util/webstorage.ts @@ -3,6 +3,8 @@ import IWebStorage from 'common/types/IWebStorage'; const test = 'ablyjs-storage-test'; +let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self; + class Webstorage implements IWebStorage { sessionSupported: boolean; localSupported: boolean; @@ -14,16 +16,16 @@ class Webstorage implements IWebStorage { * somewhat roundabout way. (If unsupported or no global object, * will throw on accessing a property of undefined) */ try { - global.sessionStorage.setItem(test, test); - global.sessionStorage.removeItem(test); + globalObject.sessionStorage.setItem(test, test); + globalObject.sessionStorage.removeItem(test); this.sessionSupported = true; } catch (e) { this.sessionSupported = false; } try { - global.localStorage.setItem(test, test); - global.localStorage.removeItem(test); + globalObject.localStorage.setItem(test, test); + globalObject.localStorage.removeItem(test); this.localSupported = true; } catch (e) { this.localSupported = false; @@ -80,7 +82,7 @@ class Webstorage implements IWebStorage { } private storageInterface(session?: boolean) { - return session ? global.sessionStorage : global.localStorage; + return session ? globalObject.sessionStorage : globalObject.localStorage; } } From ed9be585cd51ba73529789714f398749a34b1413 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Mon, 10 Jul 2023 16:42:56 +0100 Subject: [PATCH 140/468] ci: remove closure compiler check not needed since we are no longer targeting lower ES levels --- .github/workflows/check.yml | 1 - CONTRIBUTING.md | 5 ++--- Gruntfile.js | 24 +----------------------- package.json | 3 --- 4 files changed, 3 insertions(+), 30 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8247cb59b4..675343c945 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -21,6 +21,5 @@ jobs: - run: npm ci - run: npm run lint - run: npm run format:check - - run: npm run check-closure-compiler - run: npx tsc --noEmit ably.d.ts build/ably-webworker.min.d.ts - run: npm audit --production diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4faa7a424a..2c040df3e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,9 +8,8 @@ 4. Commit your changes (`git commit -am 'Add some feature'`) 5. Ensure you have added suitable tests and the test suite is passing(`npm test`) 6. Ensure the [type definitions](https://github.com/ably/ably-js/blob/main/ably.d.ts) have been updated if the public API has changed -7. Ensure you stick to the version of JS used by the library (currently ES5). (The minfication task (`npm run grunt -- closureCompiler:ably.js`) will enforce that you stick to ES5 syntax, but will not enforce that you don't use, for example, new methods) -8. Push the branch (`git push origin my-new-feature`) -9. Create a new Pull Request +7. Push the branch (`git push origin my-new-feature`) +8. Create a new Pull Request ## Release Process diff --git a/Gruntfile.js b/Gruntfile.js index bf5ddfcc45..bf159a0ee9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,7 +6,6 @@ var webpackConfig = require('./webpack.config'); module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-closure-tools'); grunt.loadNpmTasks('grunt-bump'); grunt.loadNpmTasks('grunt-webpack'); @@ -16,7 +15,6 @@ module.exports = function (grunt) { fragments: 'src/platform/web/fragments', static: 'build', dest: 'build', - tools_compiler: __dirname + '/node_modules/google-closure-compiler/compiler.jar', }; function compilerSpec(src, dest) { @@ -51,24 +49,6 @@ module.exports = function (grunt) { }, }; - gruntConfig['closureCompiler'] = { - options: { - compilerFile: dirs.tools_compiler, - compilerOpts: { - compilation_level: 'SIMPLE_OPTIMIZATIONS', - /* By default, the compiler assumes you're using es6 and transpiles to - * es5, adding various (unnecessary and undesired) polyfills. Specify - * both in and out to es5 to avoid transpilation */ - language_in: 'ECMASCRIPT5', - language_out: 'ECMASCRIPT5', - strict_mode_input: true, - checks_only: true, - warning_level: 'QUIET', - }, - }, - 'ably.js': compilerSpec('<%= dirs.static %>/ably.js'), - }; - gruntConfig.bump = { options: { files: ['package.json', 'README.md'], @@ -112,9 +92,7 @@ module.exports = function (grunt) { grunt.registerTask('build:browser', ['checkGitSubmodules', 'webpack:browser']); - grunt.registerTask('check-closure-compiler', ['build', 'closureCompiler:ably.js']); - - grunt.registerTask('all', ['build', 'check-closure-compiler', 'requirejs']); + grunt.registerTask('all', ['build', 'requirejs']); grunt.loadTasks('test/tasks'); diff --git a/package.json b/package.json index f147f333f2..4c62961d7d 100644 --- a/package.json +++ b/package.json @@ -44,11 +44,9 @@ "eslint-plugin-security": "^1.4.0", "express": "^4.17.1", "glob": "~4.4", - "google-closure-compiler": "^20180610.0.1", "grunt": "^1.4.1", "grunt-bump": "^0.3.1", "grunt-cli": "~1.2.0", - "grunt-closure-tools": "^1.0.0", "grunt-contrib-concat": "~0.5", "grunt-shell": "~1.1", "grunt-webpack": "^5.0.0", @@ -93,7 +91,6 @@ "requirejs": "grunt requirejs", "lint": "eslint .", "lint:fix": "eslint --fix .", - "check-closure-compiler": "grunt check-closure-compiler", "prepare": "npm run build", "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/cdn_deploy.js docs/chrome-mv3.md", "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/cdn_deploy.js", From 4d22ee275b572fe8d24e91e693ba763b3a0cbd1c Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 30 Jun 2023 14:59:38 +0100 Subject: [PATCH 141/468] build: use esbuild for browser bundles --- Gruntfile.js | 44 +++++++++++++++++++++++++++++++++++++++++++- webpack.config.js | 42 ------------------------------------------ 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index bf159a0ee9..ca1f636f9a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,6 +3,9 @@ var fs = require('fs'); var path = require('path'); var webpackConfig = require('./webpack.config'); +var esbuild = require('esbuild'); +var umdWrapper = require('esbuild-plugin-umd-wrapper'); +var banner = require('./src/fragments/license'); module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-concat'); @@ -86,7 +89,7 @@ module.exports = function (grunt) { }); }); - grunt.registerTask('build', ['checkGitSubmodules', 'webpack:all']); + grunt.registerTask('build', ['checkGitSubmodules', 'webpack:all', 'build:browser']); grunt.registerTask('build:node', ['checkGitSubmodules', 'webpack:node']); @@ -94,6 +97,45 @@ module.exports = function (grunt) { grunt.registerTask('all', ['build', 'requirejs']); + grunt.registerTask('build:browser', function () { + var done = this.async(); + + var baseConfig = { + entryPoints: ['src/platform/web/index.ts'], + outfile: 'build/ably.js', + bundle: true, + sourcemap: true, + format: 'umd', + banner: { js: '/*' + banner + '*/' }, + plugins: [umdWrapper.default()], + target: 'es6', + }; + + Promise.all([ + esbuild.build(baseConfig), + esbuild.build({ + ...baseConfig, + outfile: 'build/ably.min.js', + minify: true, + }), + esbuild.build({ + ...baseConfig, + entryPoints: ['src/platform/web-noencryption/index.ts'], + outfile: 'build/ably.noencryption.js', + }), + + esbuild.build({ + ...baseConfig, + entryPoints: ['src/platform/web-noencryption/index.ts'], + outfile: 'build/ably.noencryption.min.js', + minify: true, + }), + ]).then(() => { + console.log('esbuild succeeded'); + done(true); + }); + }); + grunt.loadTasks('test/tasks'); grunt.registerTask('test', ['test:node']); diff --git a/webpack.config.js b/webpack.config.js index 2c0c981ca7..c569d64272 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -137,21 +137,6 @@ const reactNativeConfig = { }, }; -const browserMinConfig = { - ...browserConfig, - output: { - ...baseConfig.output, - filename: 'ably.min.js', - }, - optimization: { - minimize: true, - }, - performance: { - hints: 'warning', - }, - devtool: 'source-map', -}; - const webworkerConfig = { target: ['webworker', 'es5'], ...browserConfig, @@ -181,36 +166,9 @@ const webworkerConfig = { ], }; -const noEncryptionConfig = { - ...browserConfig, - entry: { - index: platformPath('web-noencryption'), - }, - output: { - ...baseConfig.output, - filename: 'ably.noencryption.js', - }, -}; - -const noEncryptionMinConfig = { - ...browserMinConfig, - entry: { - index: platformPath('web-noencryption'), - }, - output: { - ...baseConfig.output, - filename: 'ably.noencryption.min.js', - }, - devtool: 'source-map', -}; - module.exports = { node: nodeConfig, - browser: browserConfig, - browserMin: browserMinConfig, webworker: webworkerConfig, nativeScript: nativeScriptConfig, reactNative: reactNativeConfig, - noEncryption: noEncryptionConfig, - noEncryptionMin: noEncryptionMinConfig, }; From 6d25bd2ff3d0486f3d0a688222c7080e3ae5bbd5 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 2 Aug 2023 13:44:33 -0300 Subject: [PATCH 142/468] Update class hierarchy in preparation for tree-shakability We rename the Rest class to BaseClient, introduce a new class BaseRest, and add the prefix "Base" to the name of the Realtime class. The BaseRest and BaseRealtime classes will be exported by the upcoming tree-shakable variant of the library. The Default* classes become the exports of the current (i.e. non tree-shakable) variant. As we make the library tree-shakable, we will extract functionality from the BaseClient and BaseRealtime classes and move them into modules exported by the tree-shakable variant of the library. Whatever functionality we remove from the Base* classes will be reintroduced in their Default* counterparts. The purpose of the BaseRest class is probably not immediately obvious, since it adds no functionality on top of BaseClient. However, upon introducing the tree-shakable version of the library, which will split the REST functionality into a separate module that Realtime can optionally use, we will use the BaseRest class to ensure that even in the tree-shakable version of the library, the BaseRest class always includes REST functionality. Resolves #1415. Co-authored-by: Owen Pearson --- src/common/lib/client/auth.ts | 20 ++++++++-------- .../lib/client/{rest.ts => baseclient.ts} | 24 ++++++++++++------- .../client/{realtime.ts => baserealtime.ts} | 13 ++++++---- src/common/lib/client/baserest.ts | 6 +++++ src/common/lib/client/channel.ts | 8 +++---- src/common/lib/client/connection.ts | 6 ++--- src/common/lib/client/defaultrealtime.ts | 6 +++++ src/common/lib/client/defaultrest.ts | 6 +++++ src/common/lib/client/paginatedresource.ts | 6 ++--- src/common/lib/client/push.ts | 18 +++++++------- src/common/lib/client/realtimechannel.ts | 6 ++--- src/common/lib/client/resource.ts | 16 ++++++------- src/common/types/http.d.ts | 10 ++++---- src/platform/nativescript/index.ts | 12 +++++----- src/platform/nodejs/index.ts | 12 +++++----- src/platform/nodejs/lib/util/http.ts | 14 +++++------ src/platform/react-native/index.ts | 12 +++++----- src/platform/web-noencryption/index.ts | 12 +++++----- src/platform/web/index.ts | 14 +++++------ .../web/lib/transport/fetchrequest.ts | 4 ++-- src/platform/web/lib/util/http.ts | 16 ++++++------- 21 files changed, 135 insertions(+), 106 deletions(-) rename src/common/lib/client/{rest.ts => baseclient.ts} (94%) rename src/common/lib/client/{realtime.ts => baserealtime.ts} (93%) create mode 100644 src/common/lib/client/baserest.ts create mode 100644 src/common/lib/client/defaultrealtime.ts create mode 100644 src/common/lib/client/defaultrest.ts diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 3425547636..156991f317 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -5,8 +5,8 @@ import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; import { ErrnoException, RequestCallback, RequestParams } from '../../types/http'; import * as API from '../../../../ably'; import { StandardCallback } from '../../types/utils'; -import Rest from './rest'; -import Realtime from './realtime'; +import BaseClient from './baseclient'; +import BaseRealtime from './baserealtime'; import ClientOptions from '../../types/ClientOptions'; import HttpMethods from '../../constants/HttpMethods'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; @@ -26,8 +26,8 @@ function random() { return ('000000' + Math.floor(Math.random() * 1e16)).slice(-16); } -function isRealtime(client: Rest | Realtime): client is Realtime { - return !!(client as Realtime).connection; +function isRealtime(client: BaseClient | BaseRealtime): client is BaseRealtime { + return !!(client as BaseRealtime).connection; } /* A client auth callback may give errors in any number of formats; normalise to an ErrorInfo or PartialErrorInfo */ @@ -113,7 +113,7 @@ function getTokenRequestId() { } class Auth { - client: Rest | Realtime; + client: BaseClient | BaseRealtime; tokenParams: API.Types.TokenParams; currentTokenRequestId: number | null; waitingForTokenRequest: ReturnType | null; @@ -125,7 +125,7 @@ class Auth { basicKey?: string; clientId?: string | null; - constructor(client: Rest | Realtime, options: ClientOptions) { + constructor(client: BaseClient | BaseRealtime, options: ClientOptions) { this.client = client; this.tokenParams = options.defaultTokenParams || {}; /* The id of the current token request if one is in progress, else null */ @@ -282,11 +282,11 @@ class Auth { _authOptions, (err: ErrorInfo, tokenDetails: API.Types.TokenDetails) => { if (err) { - if ((this.client as Realtime).connection && err.statusCode === HttpStatusCodes.Forbidden) { + if ((this.client as BaseRealtime).connection && err.statusCode === HttpStatusCodes.Forbidden) { /* Per RSA4d & RSA4d1, if the auth server explicitly repudiates our right to * stay connecticed by returning a 403, we actively disconnect the connection * even though we may well still have time left in the old token. */ - (this.client as Realtime).connection.connectionManager.actOnErrorFromAuthorize(err); + (this.client as BaseRealtime).connection.connectionManager.actOnErrorFromAuthorize(err); } callback?.(err); return; @@ -295,8 +295,8 @@ class Auth { /* RTC8 * - When authorize called by an end user and have a realtime connection, * don't call back till new token has taken effect. - * - Use this.client.connection as a proxy for (this.client instanceof Realtime), - * which doesn't work in node as Realtime isn't part of the vm context for Rest clients */ + * - Use this.client.connection as a proxy for (this.client instanceof BaseRealtime), + * which doesn't work in node as BaseRealtime isn't part of the vm context for Rest clients */ if (isRealtime(this.client)) { this.client.connection.connectionManager.onAuthUpdated(tokenDetails, callback || noop); } else { diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/baseclient.ts similarity index 94% rename from src/common/lib/client/rest.ts rename to src/common/lib/client/baseclient.ts index f70d9ef8fa..fb7a0d9322 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/baseclient.ts @@ -29,7 +29,11 @@ type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; type BatchPresenceResult = BatchResult; const noop = function () {}; -class Rest { + +/** + `BaseClient` acts as the base class for all of the client classes exported by the SDK. It is an implementation detail and this class is not advertised publicly. + */ +class BaseClient { options: NormalisedClientOptions; baseUri: (host: string) => string; authority: (host: string) => string; @@ -46,13 +50,17 @@ class Rest { constructor(options: ClientOptions | string) { if (!options) { const msg = 'no options provided'; - Logger.logAction(Logger.LOG_ERROR, 'Rest()', msg); + Logger.logAction(Logger.LOG_ERROR, 'BaseClient()', msg); throw new Error(msg); } const optionsObj = Defaults.objectifyOptions(options); Logger.setLog(optionsObj.logLevel, optionsObj.logHandler); - Logger.logAction(Logger.LOG_MICRO, 'Rest()', 'initialized with clientOptions ' + Platform.Config.inspect(options)); + Logger.logAction( + Logger.LOG_MICRO, + 'BaseClient()', + 'initialized with clientOptions ' + Platform.Config.inspect(options) + ); const normalOptions = (this.options = Defaults.normaliseOptions(optionsObj)); @@ -61,7 +69,7 @@ class Rest { const keyMatch = normalOptions.key.match(/^([^:\s]+):([^:.\s]+)$/); if (!keyMatch) { const msg = 'invalid key parameter'; - Logger.logAction(Logger.LOG_ERROR, 'Rest()', msg); + Logger.logAction(Logger.LOG_ERROR, 'BaseClient()', msg); throw new ErrorInfo(msg, 40400, 404); } normalOptions.keyName = keyMatch[1]; @@ -79,7 +87,7 @@ class Rest { ); } - Logger.logAction(Logger.LOG_MINOR, 'Rest()', 'started; version = ' + Defaults.version); + Logger.logAction(Logger.LOG_MINOR, 'BaseClient()', 'started; version = ' + Defaults.version); this.baseUri = this.authority = function (host) { return Defaults.getHttpScheme(normalOptions) + host + ':' + Defaults.getPort(normalOptions, false); @@ -328,10 +336,10 @@ class Rest { } class Channels { - rest: Rest; + rest: BaseClient; all: Record; - constructor(rest: Rest) { + constructor(rest: BaseClient) { this.rest = rest; this.all = Object.create(null); } @@ -355,4 +363,4 @@ class Channels { } } -export default Rest; +export default BaseClient; diff --git a/src/common/lib/client/realtime.ts b/src/common/lib/client/baserealtime.ts similarity index 93% rename from src/common/lib/client/realtime.ts rename to src/common/lib/client/baserealtime.ts index 416f5748f6..56e34cf532 100644 --- a/src/common/lib/client/realtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -1,5 +1,5 @@ import * as Utils from '../util/utils'; -import Rest from './rest'; +import BaseClient from './baseclient'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; import Connection from './connection'; @@ -13,7 +13,10 @@ import ConnectionManager from '../transport/connectionmanager'; import Platform from 'common/platform'; import Message from '../types/message'; -class Realtime extends Rest { +/** + `BaseRealtime` acts as the base class for the `DefaultRealtime` class exported by the SDK. It is currently an implementation detail, but will become an export of the forthcoming tree-shakable version of the SDK. + */ +class BaseRealtime extends BaseClient { channels: any; connection: Connection; @@ -44,10 +47,10 @@ class Realtime extends Rest { } class Channels extends EventEmitter { - realtime: Realtime; + realtime: BaseRealtime; all: Record; - constructor(realtime: Realtime) { + constructor(realtime: BaseRealtime) { super(); this.realtime = realtime; this.all = Object.create(null); @@ -179,4 +182,4 @@ class Channels extends EventEmitter { } } -export default Realtime; +export default BaseRealtime; diff --git a/src/common/lib/client/baserest.ts b/src/common/lib/client/baserest.ts new file mode 100644 index 0000000000..872c3f6b8f --- /dev/null +++ b/src/common/lib/client/baserest.ts @@ -0,0 +1,6 @@ +import BaseClient from './baseclient'; + +/** + `BaseRest` acts as the base class for the `DefaultRest` class exported by the SDK. It is currently an implementation detail, but will become an export of the forthcoming tree-shakable version of the SDK. + */ +export class BaseRest extends BaseClient {} diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index efb273351e..b97ed490a4 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -8,8 +8,8 @@ import PaginatedResource, { PaginatedResult } from './paginatedresource'; import Resource, { ResourceCallback } from './resource'; import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import Rest from './rest'; -import Realtime from './realtime'; +import BaseClient from './baseclient'; +import BaseRealtime from './baserealtime'; import * as API from '../../../../ably'; import Platform from 'common/platform'; @@ -47,13 +47,13 @@ function normaliseChannelOptions(options?: ChannelOptions) { } class Channel extends EventEmitter { - rest: Rest | Realtime; + rest: BaseClient | BaseRealtime; name: string; basePath: string; presence: Presence; channelOptions: ChannelOptions; - constructor(rest: Rest | Realtime, name: string, channelOptions?: ChannelOptions) { + constructor(rest: BaseClient | BaseRealtime, name: string, channelOptions?: ChannelOptions) { super(); Logger.logAction(Logger.LOG_MINOR, 'Channel()', 'started; name = ' + name); this.rest = rest; diff --git a/src/common/lib/client/connection.ts b/src/common/lib/client/connection.ts index be5177ba48..47170b9430 100644 --- a/src/common/lib/client/connection.ts +++ b/src/common/lib/client/connection.ts @@ -5,18 +5,18 @@ import Logger from '../util/logger'; import ConnectionStateChange from './connectionstatechange'; import ErrorInfo from '../types/errorinfo'; import { NormalisedClientOptions } from '../../types/ClientOptions'; -import Realtime from './realtime'; +import BaseRealtime from './baserealtime'; import Platform from 'common/platform'; class Connection extends EventEmitter { - ably: Realtime; + ably: BaseRealtime; connectionManager: ConnectionManager; state: string; key?: string; id?: string; errorReason: ErrorInfo | null; - constructor(ably: Realtime, options: NormalisedClientOptions) { + constructor(ably: BaseRealtime, options: NormalisedClientOptions) { super(); this.ably = ably; this.connectionManager = new ConnectionManager(ably, options); diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts new file mode 100644 index 0000000000..a80f045742 --- /dev/null +++ b/src/common/lib/client/defaultrealtime.ts @@ -0,0 +1,6 @@ +import BaseRealtime from './baserealtime'; + +/** + `DefaultRealtime` is the class that the SDK exports as `Realtime`. This is currently the only Realtime class exported by the SDK. When we introduce the forthcoming tree-shakable version of the SDK, which will export `BaseRealtime`, `DefaultRealtime` will remain an export of the non-tree-shakable version. + */ +export class DefaultRealtime extends BaseRealtime {} diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts new file mode 100644 index 0000000000..41c65b762a --- /dev/null +++ b/src/common/lib/client/defaultrest.ts @@ -0,0 +1,6 @@ +import { BaseRest } from './baserest'; + +/** + `DefaultRest` is the class that the SDK exports as `Rest`. This is currently the only REST class exported by the SDK. When we introduce the forthcoming tree-shakable version of the SDK, which will export `BaseRest`, `DefaultRest` will remain an export of the non-tree-shakable version. + */ +export class DefaultRest extends BaseRest {} diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 0deff1dace..8f5d8bbc7c 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -3,7 +3,7 @@ import Logger from '../util/logger'; import Resource from './resource'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; import { PaginatedResultCallback } from '../../types/utils'; -import Rest from './rest'; +import BaseClient from './baseclient'; export type BodyHandler = (body: unknown, headers: Record, unpacked?: boolean) => Promise; @@ -35,7 +35,7 @@ function returnErrOnly(err: IPartialErrorInfo, body: unknown, useHPR?: boolean) } class PaginatedResource { - rest: Rest; + rest: BaseClient; path: string; headers: Record; envelope: Utils.Format | null; @@ -43,7 +43,7 @@ class PaginatedResource { useHttpPaginatedResponse: boolean; constructor( - rest: Rest, + rest: BaseClient, path: string, headers: Record, envelope: Utils.Format | undefined, diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index b628457f50..737debe81a 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -5,24 +5,24 @@ import PaginatedResource from './paginatedresource'; import ErrorInfo from '../types/errorinfo'; import PushChannelSubscription from '../types/pushchannelsubscription'; import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import Rest from './rest'; +import BaseClient from './baseclient'; class Push { - rest: Rest; + rest: BaseClient; admin: Admin; - constructor(rest: Rest) { + constructor(rest: BaseClient) { this.rest = rest; this.admin = new Admin(rest); } } class Admin { - rest: Rest; + rest: BaseClient; deviceRegistrations: DeviceRegistrations; channelSubscriptions: ChannelSubscriptions; - constructor(rest: Rest) { + constructor(rest: BaseClient) { this.rest = rest; this.deviceRegistrations = new DeviceRegistrations(rest); this.channelSubscriptions = new ChannelSubscriptions(rest); @@ -49,9 +49,9 @@ class Admin { } class DeviceRegistrations { - rest: Rest; + rest: BaseClient; - constructor(rest: Rest) { + constructor(rest: BaseClient) { this.rest = rest; } @@ -210,9 +210,9 @@ class DeviceRegistrations { } class ChannelSubscriptions { - rest: Rest; + rest: BaseClient; - constructor(rest: Rest) { + constructor(rest: BaseClient) { this.rest = rest; } diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index dec7d5f4af..335d86c83a 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -13,7 +13,7 @@ import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; import ConnectionStateChange from './connectionstatechange'; import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import Realtime from './realtime'; +import BaseRealtime from './baserealtime'; interface RealtimeHistoryParams { start?: number; @@ -49,7 +49,7 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) { } class RealtimeChannel extends Channel { - realtime: Realtime; + realtime: BaseRealtime; presence: RealtimePresence; connectionManager: ConnectionManager; state: API.Types.ChannelState; @@ -80,7 +80,7 @@ class RealtimeChannel extends Channel { retryTimer?: number | NodeJS.Timeout | null; retryCount: number = 0; - constructor(realtime: Realtime, name: string, options?: API.Types.ChannelOptions) { + constructor(realtime: BaseRealtime, name: string, options?: API.Types.ChannelOptions) { super(realtime, name, options); Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel()', 'started; name = ' + name); this.realtime = realtime; diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 6c1e3b9abd..0614e37e1a 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -4,11 +4,11 @@ import Logger from '../util/logger'; import Auth from './auth'; import HttpMethods from '../../constants/HttpMethods'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; -import Rest from './rest'; +import BaseClient from './baseclient'; import { ErrnoException } from '../../types/http'; function withAuthDetails( - rest: Rest, + rest: BaseClient, headers: Record, params: Record, errCallback: Function, @@ -132,7 +132,7 @@ export type ResourceCallback = ( class Resource { static get( - rest: Rest, + rest: BaseClient, path: string, headers: Record, params: Record, @@ -143,7 +143,7 @@ class Resource { } static delete( - rest: Rest, + rest: BaseClient, path: string, headers: Record, params: Record, @@ -154,7 +154,7 @@ class Resource { } static post( - rest: Rest, + rest: BaseClient, path: string, body: unknown, headers: Record, @@ -166,7 +166,7 @@ class Resource { } static patch( - rest: Rest, + rest: BaseClient, path: string, body: unknown, headers: Record, @@ -178,7 +178,7 @@ class Resource { } static put( - rest: Rest, + rest: BaseClient, path: string, body: unknown, headers: Record, @@ -191,7 +191,7 @@ class Resource { static do( method: HttpMethods, - rest: Rest, + rest: BaseClient, path: string, body: unknown, headers: Record, diff --git a/src/common/types/http.d.ts b/src/common/types/http.d.ts index c3f454d475..706ad18697 100644 --- a/src/common/types/http.d.ts +++ b/src/common/types/http.d.ts @@ -1,5 +1,5 @@ import HttpMethods from '../constants/HttpMethods'; -import Rest from '../lib/client/rest'; +import BaseClient from '../lib/client/baseclient'; import ErrorInfo from '../lib/types/errorinfo'; import { Agents } from 'got'; @@ -25,17 +25,17 @@ export declare class IHttp { Request?: ( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, body: unknown, callback: RequestCallback ) => void; - _getHosts: (client: Rest | Realtime) => string[]; + _getHosts: (client: BaseClient | Realtime) => string[]; do( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, path: PathParameter, headers: Record | null, body: unknown, @@ -44,7 +44,7 @@ export declare class IHttp { ): void; doUri( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, uri: string, headers: Record | null, body: unknown, diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index fb4cfea6d8..5f8fee9253 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -1,6 +1,6 @@ // Common -import Rest from '../../common/lib/client/rest'; -import Realtime from '../../common/lib/client/realtime'; +import { DefaultRest } from '../../common/lib/client/defaultrest'; +import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; @@ -29,8 +29,8 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -Rest.Crypto = Crypto; -Realtime.Crypto = Crypto; +DefaultRest.Crypto = Crypto; +DefaultRealtime.Crypto = Crypto; Logger.initLogHandlers(); @@ -43,7 +43,7 @@ if (Platform.Config.agent) { export default { ErrorInfo, - Rest, - Realtime, + Rest: DefaultRest, + Realtime: DefaultRealtime, msgpack, }; diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index aa1b3403f4..ca7e40e0d8 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -1,6 +1,6 @@ // Common -import Rest from '../../common/lib/client/rest'; -import Realtime from '../../common/lib/client/realtime'; +import { DefaultRest } from '../../common/lib/client/defaultrest'; +import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; @@ -25,8 +25,8 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = null; -Rest.Crypto = Crypto; -Realtime.Crypto = Crypto; +DefaultRest.Crypto = Crypto; +DefaultRealtime.Crypto = Crypto; Logger.initLogHandlers(); @@ -39,7 +39,7 @@ if (Platform.Config.agent) { export default { ErrorInfo, - Rest, - Realtime, + Rest: DefaultRest, + Realtime: DefaultRealtime, msgpack: null, }; diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index a6e757f37e..5a6c1dcfe6 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -6,8 +6,8 @@ import HttpMethods from '../../../../common/constants/HttpMethods'; import got, { Response, Options, CancelableRequest, Agents } from 'got'; import http from 'http'; import https from 'https'; -import Rest from 'common/lib/client/rest'; -import Realtime from 'common/lib/client/realtime'; +import BaseClient from 'common/lib/client/baseclient'; +import BaseRealtime from 'common/lib/client/baserealtime'; import { NormalisedClientOptions, RestAgentOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; import { shallowEquals } from 'common/lib/util/utils'; @@ -74,11 +74,11 @@ function shouldFallback(err: ErrnoException) { ); } -function getHosts(client: Rest | Realtime): string[] { +function getHosts(client: BaseClient | BaseRealtime): string[] { /* If we're a connected realtime client, try the endpoint we're connected * to first -- but still have fallbacks, being connected is not an absolute * guarantee that a datacenter has free capacity to service REST requests. */ - const connection = (client as Realtime).connection; + const connection = (client as BaseRealtime).connection; const connectionHost = connection && connection.connectionManager.host; if (connectionHost) { @@ -105,7 +105,7 @@ const Http: typeof IHttp = class { /* Unlike for doUri, the 'rest' param here is mandatory, as it's used to generate the hosts */ do( method: HttpMethods, - rest: Rest, + rest: BaseClient, path: PathParameter, headers: Record | null, body: unknown, @@ -185,7 +185,7 @@ const Http: typeof IHttp = class { doUri( method: HttpMethods, - rest: Rest, + rest: BaseClient, uri: string, headers: Record | null, body: unknown, @@ -275,7 +275,7 @@ const Http: typeof IHttp = class { Request?: ( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index d59af64fe2..c7bf713567 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -1,6 +1,6 @@ // Common -import Rest from '../../common/lib/client/rest'; -import Realtime from '../../common/lib/client/realtime'; +import { DefaultRest } from '../../common/lib/client/defaultrest'; +import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; @@ -29,8 +29,8 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -Rest.Crypto = Crypto; -Realtime.Crypto = Crypto; +DefaultRest.Crypto = Crypto; +DefaultRealtime.Crypto = Crypto; Logger.initLogHandlers(); @@ -43,7 +43,7 @@ if (Platform.Config.agent) { export default { ErrorInfo, - Rest, - Realtime, + Rest: DefaultRest, + Realtime: DefaultRealtime, msgpack, }; diff --git a/src/platform/web-noencryption/index.ts b/src/platform/web-noencryption/index.ts index 94723e9d9b..5c813474b8 100644 --- a/src/platform/web-noencryption/index.ts +++ b/src/platform/web-noencryption/index.ts @@ -1,6 +1,6 @@ // Common -import Rest from '../../common/lib/client/rest'; -import Realtime from '../../common/lib/client/realtime'; +import { DefaultRest } from '../../common/lib/client/defaultrest'; +import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; @@ -24,8 +24,8 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -Rest.Crypto = null; -Realtime.Crypto = null; +DefaultRest.Crypto = null; +DefaultRealtime.Crypto = null; Logger.initLogHandlers(); @@ -38,7 +38,7 @@ if (Platform.Config.agent) { export default { ErrorInfo, - Rest, - Realtime, + Rest: DefaultRest, + Realtime: DefaultRealtime, msgpack, }; diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index be345548b4..7c984e304e 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -1,6 +1,6 @@ // Common -import Rest from '../../common/lib/client/rest'; -import Realtime from '../../common/lib/client/realtime'; +import { DefaultRest } from '../../common/lib/client/defaultrest'; +import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; @@ -27,8 +27,8 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -Rest.Crypto = Crypto; -Realtime.Crypto = Crypto; +DefaultRest.Crypto = Crypto; +DefaultRealtime.Crypto = Crypto; Logger.initLogHandlers(); @@ -47,11 +47,11 @@ if (Platform.Config.noUpgrade) { Platform.Defaults.upgradeTransports = []; } -export { Rest, Realtime, msgpack }; +export { DefaultRest as Rest, DefaultRealtime as Realtime, msgpack }; export default { ErrorInfo, - Rest, - Realtime, + Rest: DefaultRest, + Realtime: DefaultRealtime, msgpack, }; diff --git a/src/platform/web/lib/transport/fetchrequest.ts b/src/platform/web/lib/transport/fetchrequest.ts index 9af6592628..607674fd58 100644 --- a/src/platform/web/lib/transport/fetchrequest.ts +++ b/src/platform/web/lib/transport/fetchrequest.ts @@ -1,5 +1,5 @@ import HttpMethods from 'common/constants/HttpMethods'; -import Rest from 'common/lib/client/rest'; +import BaseClient from 'common/lib/client/baseclient'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; import { RequestCallback, RequestParams } from 'common/types/http'; import Platform from 'common/platform'; @@ -19,7 +19,7 @@ function getAblyError(responseBody: unknown, headers: Headers) { export default function fetchRequest( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, diff --git a/src/platform/web/lib/util/http.ts b/src/platform/web/lib/util/http.ts index cad0a06185..37630adf45 100644 --- a/src/platform/web/lib/util/http.ts +++ b/src/platform/web/lib/util/http.ts @@ -4,8 +4,8 @@ import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; import { ErrnoException, IHttp, RequestCallback, RequestParams } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; -import Rest from 'common/lib/client/rest'; -import Realtime from 'common/lib/client/realtime'; +import BaseClient from 'common/lib/client/baseclient'; +import BaseRealtime from 'common/lib/client/baserealtime'; import XHRRequest from '../transport/xhrrequest'; import XHRStates from 'common/constants/XHRStates'; import Logger from 'common/lib/util/logger'; @@ -26,11 +26,11 @@ function shouldFallback(errorInfo: ErrorInfo) { ); } -function getHosts(client: Rest | Realtime): string[] { +function getHosts(client: BaseClient | BaseRealtime): string[] { /* If we're a connected realtime client, try the endpoint we're connected * to first -- but still have fallbacks, being connected is not an absolute * guarantee that a datacenter has free capacity to service REST requests. */ - const connection = (client as Realtime).connection, + const connection = (client as BaseRealtime).connection, connectionHost = connection && connection.connectionManager.host; if (connectionHost) { @@ -57,7 +57,7 @@ const Http: typeof IHttp = class { this.supportsAuthHeaders = true; this.Request = function ( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, @@ -143,7 +143,7 @@ const Http: typeof IHttp = class { /* Unlike for doUri, the 'rest' param here is mandatory, as it's used to generate the hosts */ do( method: HttpMethods, - rest: Rest, + rest: BaseClient, path: string, headers: Record | null, body: unknown, @@ -230,7 +230,7 @@ const Http: typeof IHttp = class { doUri( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, uri: string, headers: Record | null, body: unknown, @@ -246,7 +246,7 @@ const Http: typeof IHttp = class { Request?: ( method: HttpMethods, - rest: Rest | null, + rest: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, From 21fcf137f585591dfcbc1937caacf1ad76af57ee Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 2 Aug 2023 14:22:38 -0300 Subject: [PATCH 143/468] Distribute an ECMAScript module variant of the library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This forms the beginning of the tree-shakable variant of the library mentioned in 6d25bd2. It currently exports the BaseRest and BaseRealtime classes. It’s available to import from 'ably/modules'. Co-authored-by: Owen Pearson --- Gruntfile.js | 11 ++++++ src/common/lib/client/baserealtime.ts | 2 +- src/common/lib/client/baserest.ts | 2 +- src/common/lib/client/defaultrealtime.ts | 2 +- src/common/lib/client/defaultrest.ts | 2 +- src/platform/web/modules.ts | 50 ++++++++++++++++++++++++ test/browser/modules.test.js | 20 ++++++++++ test/common/modules/client_module.js | 1 + test/common/modules/shared_helper.js | 11 +++++- test/mocha.html | 1 + test/playwright.html | 1 + 11 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/platform/web/modules.ts create mode 100644 test/browser/modules.test.js diff --git a/Gruntfile.js b/Gruntfile.js index ca1f636f9a..fc5d605ea2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -111,6 +111,17 @@ module.exports = function (grunt) { target: 'es6', }; + var modulesConfig = { + ...baseConfig, + entryPoints: ['src/platform/web/modules.ts'], + outfile: 'build/modules/index.js', + format: 'esm', + plugins: [], + }; + + // For reasons I don't understand this build fails when run asynchronously + esbuild.buildSync(modulesConfig); + Promise.all([ esbuild.build(baseConfig), esbuild.build({ diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 56e34cf532..dfc3339933 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -14,7 +14,7 @@ import Platform from 'common/platform'; import Message from '../types/message'; /** - `BaseRealtime` acts as the base class for the `DefaultRealtime` class exported by the SDK. It is currently an implementation detail, but will become an export of the forthcoming tree-shakable version of the SDK. + `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. */ class BaseRealtime extends BaseClient { channels: any; diff --git a/src/common/lib/client/baserest.ts b/src/common/lib/client/baserest.ts index 872c3f6b8f..6073aafd00 100644 --- a/src/common/lib/client/baserest.ts +++ b/src/common/lib/client/baserest.ts @@ -1,6 +1,6 @@ import BaseClient from './baseclient'; /** - `BaseRest` acts as the base class for the `DefaultRest` class exported by the SDK. It is currently an implementation detail, but will become an export of the forthcoming tree-shakable version of the SDK. + `BaseRest` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRest` class exported by the non tree-shakable version. */ export class BaseRest extends BaseClient {} diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index a80f045742..f23e14a648 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -1,6 +1,6 @@ import BaseRealtime from './baserealtime'; /** - `DefaultRealtime` is the class that the SDK exports as `Realtime`. This is currently the only Realtime class exported by the SDK. When we introduce the forthcoming tree-shakable version of the SDK, which will export `BaseRealtime`, `DefaultRealtime` will remain an export of the non-tree-shakable version. + `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. */ export class DefaultRealtime extends BaseRealtime {} diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index 41c65b762a..0e4cf44c18 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -1,6 +1,6 @@ import { BaseRest } from './baserest'; /** - `DefaultRest` is the class that the SDK exports as `Rest`. This is currently the only REST class exported by the SDK. When we introduce the forthcoming tree-shakable version of the SDK, which will export `BaseRest`, `DefaultRest` will remain an export of the non-tree-shakable version. + `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. */ export class DefaultRest extends BaseRest {} diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts new file mode 100644 index 0000000000..e1031a9c17 --- /dev/null +++ b/src/platform/web/modules.ts @@ -0,0 +1,50 @@ +// Common +import BaseClient from 'common/lib/client/baseclient'; +import { BaseRest } from '../../common/lib/client/baserest'; +import BaseRealtime from '../../common/lib/client/baserealtime'; +import Platform from '../../common/platform'; +import ErrorInfo from '../../common/lib/types/errorinfo'; + +// Platform Specific +import BufferUtils from './lib/util/bufferutils'; +// @ts-ignore +import CryptoFactory from './lib/util/crypto'; +import Http from './lib/util/http'; +import Config from './config'; +// @ts-ignore +import Transports from './lib/transport'; +import Logger from '../../common/lib/util/logger'; +import { getDefaults } from '../../common/lib/util/defaults'; +import WebStorage from './lib/util/webstorage'; +import PlatformDefaults from './lib/util/defaults'; + +const Crypto = CryptoFactory(Config, BufferUtils); + +Platform.Crypto = Crypto; +Platform.BufferUtils = BufferUtils; +Platform.Http = Http; +Platform.Config = Config; +Platform.Transports = Transports; +Platform.WebStorage = WebStorage; + +BaseClient.Crypto = Crypto; +BaseRealtime.Crypto = Crypto; + +Logger.initLogHandlers(); + +Platform.Defaults = getDefaults(PlatformDefaults); + +if (Platform.Config.agent) { + // @ts-ignore + Platform.Defaults.agent += ' ' + Platform.Config.agent; +} + +/* If using IE8, don't attempt to upgrade from xhr_polling to xhr_streaming - + * while it can do streaming, the low max http-connections-per-host limit means + * that the polling transport is crippled during the upgrade process. So just + * leave it at the base transport */ +if (Platform.Config.noUpgrade) { + Platform.Defaults.upgradeTransports = []; +} + +export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js new file mode 100644 index 0000000000..7455059b27 --- /dev/null +++ b/test/browser/modules.test.js @@ -0,0 +1,20 @@ +import { BaseRest, BaseRealtime } from '../../build/modules/index.js'; + +describe('browser/modules', function () { + this.timeout(10 * 1000); + const expect = chai.expect; + let ablyClientOptions; + + before((done) => { + ablyClientOptions = window.ablyHelpers.ablyClientOptions; + window.ablyHelpers.setupApp(done); + }); + + for (const clientClass of [BaseRest, BaseRealtime]) { + it(clientClass.name, async () => { + const client = new clientClass(ablyClientOptions()); + const time = await client.time(); + expect(time).to.be.a('number'); + }); + } +}); diff --git a/test/common/modules/client_module.js b/test/common/modules/client_module.js index 7f2c990ea0..88458b2212 100644 --- a/test/common/modules/client_module.js +++ b/test/common/modules/client_module.js @@ -34,5 +34,6 @@ define(['ably', 'globals', 'test/common/modules/testapp_module'], function (Ably Ably: Ably, AblyRest: ablyRest, AblyRealtime: ablyRealtime, + ablyClientOptions, }); }); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index fdf2a82587..c3265b1b26 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -222,7 +222,7 @@ define([ return Math.random().toString().slice(2); } - return (module.exports = { + var exports = { setupApp: testAppModule.setup, tearDownApp: testAppModule.tearDown, createStats: testAppModule.createStatsFixtureData, @@ -231,6 +231,7 @@ define([ Ably: clientModule.Ably, AblyRest: clientModule.AblyRest, AblyRealtime: clientModule.AblyRealtime, + ablyClientOptions: clientModule.ablyClientOptions, Utils: utils, loadTestData: testAppManager.loadJsonData, @@ -254,5 +255,11 @@ define([ arrFilter: arrFilter, whenPromiseSettles: whenPromiseSettles, randomString: randomString, - }); + }; + + if (typeof window !== 'undefined') { + window.ablyHelpers = exports; + } + + return (module.exports = exports); }); diff --git a/test/mocha.html b/test/mocha.html index 376e39478e..48746f80ea 100644 --- a/test/mocha.html +++ b/test/mocha.html @@ -22,6 +22,7 @@ + diff --git a/test/playwright.html b/test/playwright.html index a902fa5ba1..7843225db1 100644 --- a/test/playwright.html +++ b/test/playwright.html @@ -20,6 +20,7 @@ + From c48a7881a717931af6528c82f498a27eaa08b4ef Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 2 Aug 2023 14:39:10 -0300 Subject: [PATCH 144/468] Add a mechanism for passing modules to Base* clients The Base* classes now take a map of encapsulated modules which can be provided through the constructor. These modules work like plugins, adding features to the client. Co-authored-by: Owen Pearson --- src/common/lib/client/baseclient.ts | 7 ++++++- src/common/lib/client/baserealtime.ts | 5 +++-- src/common/lib/client/defaultrealtime.ts | 10 ++++++++-- src/common/lib/client/defaultrest.ts | 10 ++++++++-- src/common/lib/client/modulesmap.ts | 3 +++ test/browser/modules.test.js | 16 +++++++++------- 6 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 src/common/lib/client/modulesmap.ts diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index fb7a0d9322..fe1a22f77c 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -18,6 +18,7 @@ import Platform from '../../platform'; import Message from '../types/message'; import PresenceMessage from '../types/presencemessage'; import Resource from './resource'; +import { ModulesMap } from './modulesmap'; type BatchResult = API.Types.BatchResult; type BatchPublishSpec = API.Types.BatchPublishSpec; @@ -47,7 +48,11 @@ class BaseClient { channels: Channels; push: Push; - constructor(options: ClientOptions | string) { + constructor( + options: ClientOptions | string, + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars + modules: ModulesMap + ) { if (!options) { const msg = 'no options provided'; Logger.logAction(Logger.LOG_ERROR, 'BaseClient()', msg); diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index dfc3339933..74057b9a58 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -12,6 +12,7 @@ import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; import Platform from 'common/platform'; import Message from '../types/message'; +import { ModulesMap } from './modulesmap'; /** `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. @@ -20,8 +21,8 @@ class BaseRealtime extends BaseClient { channels: any; connection: Connection; - constructor(options: ClientOptions) { - super(options); + constructor(options: ClientOptions, modules: ModulesMap) { + super(options, modules); Logger.logAction(Logger.LOG_MINOR, 'Realtime()', ''); this.connection = new Connection(this, this.options); this.channels = new Channels(this); diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index f23e14a648..b52c280070 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -1,6 +1,12 @@ import BaseRealtime from './baserealtime'; +import ClientOptions from '../../types/ClientOptions'; +import { allCommonModules } from './modulesmap'; /** - `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. + `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. */ -export class DefaultRealtime extends BaseRealtime {} +export class DefaultRealtime extends BaseRealtime { + constructor(options: ClientOptions) { + super(options, allCommonModules); + } +} diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index 0e4cf44c18..431e4547d8 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -1,6 +1,12 @@ import { BaseRest } from './baserest'; +import ClientOptions from '../../types/ClientOptions'; +import { allCommonModules } from './modulesmap'; /** - `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. + `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. */ -export class DefaultRest extends BaseRest {} +export class DefaultRest extends BaseRest { + constructor(options: ClientOptions | string) { + super(options, allCommonModules); + } +} diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts new file mode 100644 index 0000000000..13b207d8c5 --- /dev/null +++ b/src/common/lib/client/modulesmap.ts @@ -0,0 +1,3 @@ +export interface ModulesMap {} + +export const allCommonModules: ModulesMap = {}; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 7455059b27..755d70063f 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -10,11 +10,13 @@ describe('browser/modules', function () { window.ablyHelpers.setupApp(done); }); - for (const clientClass of [BaseRest, BaseRealtime]) { - it(clientClass.name, async () => { - const client = new clientClass(ablyClientOptions()); - const time = await client.time(); - expect(time).to.be.a('number'); - }); - } + describe('without any modules', () => { + for (const clientClass of [BaseRest, BaseRealtime]) { + it(clientClass.name, async () => { + const client = new clientClass(ablyClientOptions()); + const time = await client.time(); + expect(time).to.be.a('number'); + }); + } + }); }); From 6389157251f2ffb88263f517436946cff87e2973 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 19 Jul 2023 10:58:21 +0100 Subject: [PATCH 145/468] ci: display info about sizes of tree-shakable modules Co-authored-by: Lawrence Forooghian --- .github/workflows/bundle-report.yml | 1 + package.json | 5 ++-- scripts/moduleReport.js | 38 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 scripts/moduleReport.js diff --git a/.github/workflows/bundle-report.yml b/.github/workflows/bundle-report.yml index 7866bb9dc3..39dda65d14 100644 --- a/.github/workflows/bundle-report.yml +++ b/.github/workflows/bundle-report.yml @@ -37,3 +37,4 @@ jobs: sourcePath: bundle-reports githubToken: ${{ secrets.GITHUB_TOKEN }} artifactName: bundle-report + - run: npm run modulereport diff --git a/package.json b/package.json index d8ee738b8d..8c95387aa4 100644 --- a/package.json +++ b/package.json @@ -122,10 +122,11 @@ "lint": "eslint .", "lint:fix": "eslint --fix .", "prepare": "npm run build", - "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/cdn_deploy.js docs/chrome-mv3.md", - "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/cdn_deploy.js", + "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/*.js docs/chrome-mv3.md", + "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/*.js", "sourcemap": "source-map-explorer build/ably.min.js", "sourcemap:noencryption": "source-map-explorer build/ably.noencryption.min.js", + "modulereport": "node scripts/moduleReport.js", "docs": "typedoc --entryPoints ably.d.ts --out docs/generated --readme docs/landing-page.md" } } diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js new file mode 100644 index 0000000000..f9a9ce65e8 --- /dev/null +++ b/scripts/moduleReport.js @@ -0,0 +1,38 @@ +const esbuild = require('esbuild'); + +// List of all modules accepted in ModulesMap +const moduleNames = []; + +function formatBytes(bytes) { + const kibibytes = bytes / 1024; + const formatted = kibibytes.toFixed(2); + return `${formatted} KiB`; +} + +// Gets the bundled size in bytes of an array of named exports from 'ably/modules' +function getImportSize(modules) { + const outfile = modules.join(''); + const result = esbuild.buildSync({ + stdin: { + contents: `export { ${modules.join(', ')} } from './build/modules'`, + resolveDir: '.', + }, + metafile: true, + minify: true, + bundle: true, + outfile, + write: false, + }); + + return result.metafile.outputs[outfile].bytes; +} + +['BaseRest', 'BaseRealtime'].forEach((baseClient) => { + // First display the size of the base client + console.log(`${baseClient}: ${formatBytes(getImportSize([baseClient]))}`); + + // Then display the size of each module together with the base client + moduleNames.forEach((moduleName) => { + console.log(`${baseClient} + ${moduleName}: ${formatBytes(getImportSize([baseClient, moduleName]))}`); + }); +}); From 1600ffda13e3477ccd7cfc9f8c91665956f8d71a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 17 Aug 2023 10:56:47 -0300 Subject: [PATCH 146/468] CI: Perform a check of whether tree-shaking is working Emit an error if adding a tree-shakable module does not increase the bundle size (this means that the module is not being tree-shaken correctly). --- .github/workflows/bundle-report.yml | 1 + scripts/moduleReport.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bundle-report.yml b/.github/workflows/bundle-report.yml index 39dda65d14..6c91f72b25 100644 --- a/.github/workflows/bundle-report.yml +++ b/.github/workflows/bundle-report.yml @@ -37,4 +37,5 @@ jobs: sourcePath: bundle-reports githubToken: ${{ secrets.GITHUB_TOKEN }} artifactName: bundle-report + # This step performs some validation and may fail, so it should come after the steps that we want to always execute. - run: npm run modulereport diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index f9a9ce65e8..19d1071672 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -27,12 +27,30 @@ function getImportSize(modules) { return result.metafile.outputs[outfile].bytes; } +const errors = []; + ['BaseRest', 'BaseRealtime'].forEach((baseClient) => { + const baseClientSize = getImportSize([baseClient]); + // First display the size of the base client - console.log(`${baseClient}: ${formatBytes(getImportSize([baseClient]))}`); + console.log(`${baseClient}: ${formatBytes(baseClientSize)}`); // Then display the size of each module together with the base client moduleNames.forEach((moduleName) => { - console.log(`${baseClient} + ${moduleName}: ${formatBytes(getImportSize([baseClient, moduleName]))}`); + const size = getImportSize([baseClient, moduleName]); + console.log(`${baseClient} + ${moduleName}: ${formatBytes(size)}`); + + if (!(baseClientSize < size)) { + // Emit an error if adding the module does not increase the bundle size + // (this means that the module is not being tree-shaken correctly). + errors.push(new Error(`Adding ${moduleName} to ${baseClient} does not increase the bundle size.`)); + } }); }); + +if (errors.length > 0) { + for (const error of errors) { + console.log(error.message); + } + process.exit(1); +} From 1cf95d432de6df05194aa1fbde59922143ef496f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 2 Aug 2023 15:54:18 -0300 Subject: [PATCH 147/468] Remove unnecessary BaseClient.authority property --- src/common/lib/client/baseclient.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index fe1a22f77c..d5a2aa96de 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -36,8 +36,6 @@ const noop = function () {}; */ class BaseClient { options: NormalisedClientOptions; - baseUri: (host: string) => string; - authority: (host: string) => string; _currentFallback: null | { host: string; validUntil: number; @@ -94,9 +92,6 @@ class BaseClient { Logger.logAction(Logger.LOG_MINOR, 'BaseClient()', 'started; version = ' + Defaults.version); - this.baseUri = this.authority = function (host) { - return Defaults.getHttpScheme(normalOptions) + host + ':' + Defaults.getPort(normalOptions, false); - }; this._currentFallback = null; this.serverTimeOffset = null; @@ -106,6 +101,10 @@ class BaseClient { this.push = new Push(this); } + baseUri(host: string) { + return Defaults.getHttpScheme(this.options) + host + ':' + Defaults.getPort(this.options, false); + } + stats( params: RequestParams, callback: StandardCallback> @@ -152,7 +151,7 @@ class BaseClient { const headers = Utils.defaultGetHeaders(this.options); if (this.options.headers) Utils.mixin(headers, this.options.headers); const timeUri = (host: string) => { - return this.authority(host) + '/time'; + return this.baseUri(host) + '/time'; }; this.http.do( HttpMethods.Get, From 1cc16286d5e39055795c6bb257a4ff5584a54d67 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 2 Aug 2023 16:23:01 -0300 Subject: [PATCH 148/468] Rename all `rest` variables to `client` This will avoid confusion when we introduce the tree-shakable module named `Rest`. Co-authored-by: Owen Pearson --- src/common/lib/client/baseclient.ts | 8 +- src/common/lib/client/channel.ts | 34 ++-- src/common/lib/client/paginatedresource.ts | 18 +-- src/common/lib/client/presence.ts | 24 +-- src/common/lib/client/push.ts | 150 +++++++++--------- src/common/lib/client/resource.ts | 40 ++--- src/common/types/http.d.ts | 6 +- src/platform/nodejs/lib/util/http.ts | 34 ++-- .../web/lib/transport/fetchrequest.ts | 4 +- src/platform/web/lib/util/http.ts | 38 ++--- 10 files changed, 178 insertions(+), 178 deletions(-) diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index d5a2aa96de..f81d69f4e2 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -340,11 +340,11 @@ class BaseClient { } class Channels { - rest: BaseClient; + client: BaseClient; all: Record; - constructor(rest: BaseClient) { - this.rest = rest; + constructor(client: BaseClient) { + this.client = client; this.all = Object.create(null); } @@ -352,7 +352,7 @@ class Channels { name = String(name); let channel = this.all[name]; if (!channel) { - this.all[name] = channel = new Channel(this.rest, name, channelOptions); + this.all[name] = channel = new Channel(this.client, name, channelOptions); } else if (channelOptions) { channel.setOptions(channelOptions); } diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index b97ed490a4..42d2545500 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -47,16 +47,16 @@ function normaliseChannelOptions(options?: ChannelOptions) { } class Channel extends EventEmitter { - rest: BaseClient | BaseRealtime; + client: BaseClient | BaseRealtime; name: string; basePath: string; presence: Presence; channelOptions: ChannelOptions; - constructor(rest: BaseClient | BaseRealtime, name: string, channelOptions?: ChannelOptions) { + constructor(client: BaseClient | BaseRealtime, name: string, channelOptions?: ChannelOptions) { super(); Logger.logAction(Logger.LOG_MINOR, 'Channel()', 'started; name = ' + name); - this.rest = rest; + this.client = client; this.name = name; this.basePath = '/channels/' + encodeURIComponent(name); this.presence = new Presence(this); @@ -86,15 +86,15 @@ class Channel extends EventEmitter { } _history(params: RestHistoryParams | null, callback: PaginatedResultCallback): void { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format, + headers = Utils.defaultGetHeaders(client.options, { format }); - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); const options = this.channelOptions; - new PaginatedResource(rest, this.basePath + '/messages', headers, envelope, async function ( + new PaginatedResource(client, this.basePath + '/messages', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean @@ -138,11 +138,11 @@ class Channel extends EventEmitter { params = {}; } - const rest = this.rest, - options = rest.options, + const client = this.client, + options = client.options, format = options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - idempotentRestPublishing = rest.options.idempotentRestPublishing, - headers = Utils.defaultPostHeaders(rest.options, { format }); + idempotentRestPublishing = client.options.idempotentRestPublishing, + headers = Utils.defaultPostHeaders(client.options, { format }); Utils.mixin(headers, options.headers); @@ -182,7 +182,7 @@ class Channel extends EventEmitter { } _publish(requestBody: unknown, headers: Record, params: any, callback: ResourceCallback): void { - Resource.post(this.rest, this.basePath + '/messages', requestBody, headers, params, null, callback); + Resource.post(this.client, this.basePath + '/messages', requestBody, headers, params, null, callback); } status(callback?: StandardCallback): void | Promise { @@ -190,10 +190,10 @@ class Channel extends EventEmitter { return Utils.promisify(this, 'status', []); } - const format = this.rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; - const headers = Utils.defaultPostHeaders(this.rest.options, { format }); + const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; + const headers = Utils.defaultPostHeaders(this.client.options, { format }); - Resource.get(this.rest, this.basePath, headers, {}, format, callback || noop); + Resource.get(this.client, this.basePath, headers, {}, format, callback || noop); } } diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 8f5d8bbc7c..fd899c3e14 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -35,7 +35,7 @@ function returnErrOnly(err: IPartialErrorInfo, body: unknown, useHPR?: boolean) } class PaginatedResource { - rest: BaseClient; + client: BaseClient; path: string; headers: Record; envelope: Utils.Format | null; @@ -43,14 +43,14 @@ class PaginatedResource { useHttpPaginatedResponse: boolean; constructor( - rest: BaseClient, + client: BaseClient, path: string, headers: Record, envelope: Utils.Format | undefined, bodyHandler: BodyHandler, useHttpPaginatedResponse?: boolean ) { - this.rest = rest; + this.client = client; this.path = path; this.headers = headers; this.envelope = envelope ?? null; @@ -60,7 +60,7 @@ class PaginatedResource { get(params: Record, callback: PaginatedResultCallback): void { Resource.get( - this.rest, + this.client, this.path, this.headers, params, @@ -73,7 +73,7 @@ class PaginatedResource { delete(params: Record, callback: PaginatedResultCallback): void { Resource.delete( - this.rest, + this.client, this.path, this.headers, params, @@ -86,7 +86,7 @@ class PaginatedResource { post(params: Record, body: unknown, callback: PaginatedResultCallback): void { Resource.post( - this.rest, + this.client, this.path, body, this.headers, @@ -102,7 +102,7 @@ class PaginatedResource { put(params: Record, body: unknown, callback: PaginatedResultCallback): void { Resource.put( - this.rest, + this.client, this.path, body, this.headers, @@ -118,7 +118,7 @@ class PaginatedResource { patch(params: Record, body: unknown, callback: PaginatedResultCallback): void { Resource.patch( - this.rest, + this.client, this.path, body, this.headers, @@ -234,7 +234,7 @@ export class PaginatedResult { get(params: any, callback: PaginatedResultCallback): void { const res = this.resource; Resource.get( - res.rest, + res.client, res.path, res.headers, params, diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index 8d521bdf4c..18cb5a2b97 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -29,15 +29,15 @@ class Presence extends EventEmitter { return Utils.promisify(this, 'get', arguments); } } - const rest = this.channel.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.channel.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.channel.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.channel.client.http.supportsLinkHeaders ? undefined : format, + headers = Utils.defaultGetHeaders(client.options, { format }); - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); const options = this.channel.channelOptions; - new PaginatedResource(rest, this.basePath, headers, envelope, async function ( + new PaginatedResource(client, this.basePath, headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean @@ -68,15 +68,15 @@ class Presence extends EventEmitter { } } - const rest = this.channel.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.channel.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.channel.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.channel.client.http.supportsLinkHeaders ? undefined : format, + headers = Utils.defaultGetHeaders(client.options, { format }); - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); const options = this.channel.channelOptions; - new PaginatedResource(rest, this.basePath + '/history', headers, envelope, async function ( + new PaginatedResource(client, this.basePath + '/history', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 737debe81a..0a60822cbd 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -8,30 +8,30 @@ import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../ty import BaseClient from './baseclient'; class Push { - rest: BaseClient; + client: BaseClient; admin: Admin; - constructor(rest: BaseClient) { - this.rest = rest; - this.admin = new Admin(rest); + constructor(client: BaseClient) { + this.client = client; + this.admin = new Admin(client); } } class Admin { - rest: BaseClient; + client: BaseClient; deviceRegistrations: DeviceRegistrations; channelSubscriptions: ChannelSubscriptions; - constructor(rest: BaseClient) { - this.rest = rest; - this.deviceRegistrations = new DeviceRegistrations(rest); - this.channelSubscriptions = new ChannelSubscriptions(rest); + constructor(client: BaseClient) { + this.client = client; + this.deviceRegistrations = new DeviceRegistrations(client); + this.channelSubscriptions = new ChannelSubscriptions(client); } publish(recipient: any, payload: any, callback: ErrCallback) { - const rest = this.rest; - const format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(rest.options, { format }), + const client = this.client; + const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Utils.defaultPostHeaders(client.options, { format }), params = {}; const body = Utils.mixin({ recipient: recipient }, payload); @@ -39,40 +39,40 @@ class Admin { return Utils.promisify(this, 'publish', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); + if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, format); - Resource.post(rest, '/push/publish', requestBody, headers, params, null, (err) => callback(err)); + Resource.post(client, '/push/publish', requestBody, headers, params, null, (err) => callback(err)); } } class DeviceRegistrations { - rest: BaseClient; + client: BaseClient; - constructor(rest: BaseClient) { - this.rest = rest; + constructor(client: BaseClient) { + this.client = client; } save(device: any, callback: StandardCallback) { - const rest = this.rest; + const client = this.client; const body = DeviceDetails.fromValues(device); - const format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(rest.options, { format }), + const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Utils.defaultPostHeaders(client.options, { format }), params = {}; if (typeof callback !== 'function') { return Utils.promisify(this, 'save', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); + if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, format); Resource.put( - rest, + client, '/push/deviceRegistrations/' + encodeURIComponent(device.id), requestBody, headers, @@ -93,9 +93,9 @@ class DeviceRegistrations { } get(deviceIdOrDetails: any, callback: StandardCallback) { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, { format }), + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Utils.defaultGetHeaders(client.options, { format }), deviceId = deviceIdOrDetails.id || deviceIdOrDetails; if (typeof callback !== 'function') { @@ -113,10 +113,10 @@ class DeviceRegistrations { return; } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); Resource.get( - rest, + client, '/push/deviceRegistrations/' + encodeURIComponent(deviceId), headers, {}, @@ -136,18 +136,18 @@ class DeviceRegistrations { } list(params: any, callback: PaginatedResultCallback) { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format, + headers = Utils.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'list', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - new PaginatedResource(rest, '/push/deviceRegistrations', headers, envelope, async function ( + new PaginatedResource(client, '/push/deviceRegistrations', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean @@ -157,9 +157,9 @@ class DeviceRegistrations { } remove(deviceIdOrDetails: any, callback: ErrCallback) { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, { format }), + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Utils.defaultGetHeaders(client.options, { format }), params = {}, deviceId = deviceIdOrDetails.id || deviceIdOrDetails; @@ -178,12 +178,12 @@ class DeviceRegistrations { return; } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); + if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); Resource['delete']( - rest, + client, '/push/deviceRegistrations/' + encodeURIComponent(deviceId), headers, params, @@ -193,47 +193,47 @@ class DeviceRegistrations { } removeWhere(params: any, callback: ErrCallback) { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Utils.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'removeWhere', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); + if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - Resource['delete'](rest, '/push/deviceRegistrations', headers, params, null, (err) => callback(err)); + Resource['delete'](client, '/push/deviceRegistrations', headers, params, null, (err) => callback(err)); } } class ChannelSubscriptions { - rest: BaseClient; + client: BaseClient; - constructor(rest: BaseClient) { - this.rest = rest; + constructor(client: BaseClient) { + this.client = client; } save(subscription: Record, callback: PaginatedResultCallback) { - const rest = this.rest; + const client = this.client; const body = PushChannelSubscription.fromValues(subscription); - const format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(rest.options, { format }), + const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Utils.defaultPostHeaders(client.options, { format }), params = {}; if (typeof callback !== 'function') { return Utils.promisify(this, 'save', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); + if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, format); Resource.post( - rest, + client, '/push/channelSubscriptions', requestBody, headers, @@ -249,18 +249,18 @@ class ChannelSubscriptions { } list(params: any, callback: PaginatedResultCallback) { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format, + headers = Utils.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'list', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - new PaginatedResource(rest, '/push/channelSubscriptions', headers, envelope, async function ( + new PaginatedResource(client, '/push/channelSubscriptions', headers, envelope, async function ( body: any, headers: Record, unpacked?: boolean @@ -270,39 +270,39 @@ class ChannelSubscriptions { } removeWhere(params: any, callback: PaginatedResultCallback) { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Utils.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'removeWhere', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); + if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - Resource['delete'](rest, '/push/channelSubscriptions', headers, params, null, (err) => callback(err)); + Resource['delete'](client, '/push/channelSubscriptions', headers, params, null, (err) => callback(err)); } /* ChannelSubscriptions have no unique id; removing one is equivalent to removeWhere by its properties */ remove = ChannelSubscriptions.prototype.removeWhere; listChannels(params: any, callback: PaginatedResultCallback) { - const rest = this.rest, - format = rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.rest.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(rest.options, { format }); + const client = this.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format, + headers = Utils.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'listChannels', arguments); } - Utils.mixin(headers, rest.options.headers); + Utils.mixin(headers, client.options.headers); - if (rest.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); + if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - new PaginatedResource(rest, '/push/channels', headers, envelope, async function ( + new PaginatedResource(client, '/push/channels', headers, envelope, async function ( body: unknown, headers: Record, unpacked?: boolean diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 0614e37e1a..125069f214 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -8,19 +8,19 @@ import BaseClient from './baseclient'; import { ErrnoException } from '../../types/http'; function withAuthDetails( - rest: BaseClient, + client: BaseClient, headers: Record, params: Record, errCallback: Function, opCallback: Function ) { - if (rest.http.supportsAuthHeaders) { - rest.auth.getAuthHeaders(function (err: Error, authHeaders: Record) { + if (client.http.supportsAuthHeaders) { + client.auth.getAuthHeaders(function (err: Error, authHeaders: Record) { if (err) errCallback(err); else opCallback(Utils.mixin(authHeaders, headers), params); }); } else { - rest.auth.getAuthParams(function (err: Error, authParams: Record) { + client.auth.getAuthParams(function (err: Error, authParams: Record) { if (err) errCallback(err); else opCallback(headers, Utils.mixin(authParams, params)); }); @@ -132,29 +132,29 @@ export type ResourceCallback = ( class Resource { static get( - rest: BaseClient, + client: BaseClient, path: string, headers: Record, params: Record, envelope: Utils.Format | null, callback: ResourceCallback ): void { - Resource.do(HttpMethods.Get, rest, path, null, headers, params, envelope, callback); + Resource.do(HttpMethods.Get, client, path, null, headers, params, envelope, callback); } static delete( - rest: BaseClient, + client: BaseClient, path: string, headers: Record, params: Record, envelope: Utils.Format | null, callback: ResourceCallback ): void { - Resource.do(HttpMethods.Delete, rest, path, null, headers, params, envelope, callback); + Resource.do(HttpMethods.Delete, client, path, null, headers, params, envelope, callback); } static post( - rest: BaseClient, + client: BaseClient, path: string, body: unknown, headers: Record, @@ -162,11 +162,11 @@ class Resource { envelope: Utils.Format | null, callback: ResourceCallback ): void { - Resource.do(HttpMethods.Post, rest, path, body, headers, params, envelope, callback); + Resource.do(HttpMethods.Post, client, path, body, headers, params, envelope, callback); } static patch( - rest: BaseClient, + client: BaseClient, path: string, body: unknown, headers: Record, @@ -174,11 +174,11 @@ class Resource { envelope: Utils.Format | null, callback: ResourceCallback ): void { - Resource.do(HttpMethods.Patch, rest, path, body, headers, params, envelope, callback); + Resource.do(HttpMethods.Patch, client, path, body, headers, params, envelope, callback); } static put( - rest: BaseClient, + client: BaseClient, path: string, body: unknown, headers: Record, @@ -186,12 +186,12 @@ class Resource { envelope: Utils.Format | null, callback: ResourceCallback ): void { - Resource.do(HttpMethods.Put, rest, path, body, headers, params, envelope, callback); + Resource.do(HttpMethods.Put, client, path, body, headers, params, envelope, callback); } static do( method: HttpMethods, - rest: BaseClient, + client: BaseClient, path: string, body: unknown, headers: Record, @@ -237,9 +237,9 @@ class Resource { ); } - rest.http.do( + client.http.do( method, - rest, + client, path, headers, body, @@ -253,13 +253,13 @@ class Resource { ) { if (err && Auth.isTokenErr(err as ErrorInfo)) { /* token has expired, so get a new one */ - rest.auth.authorize(null, null, function (err: ErrorInfo) { + client.auth.authorize(null, null, function (err: ErrorInfo) { if (err) { callback(err); return; } /* retry ... */ - withAuthDetails(rest, headers, params, callback, doRequest); + withAuthDetails(client, headers, params, callback, doRequest); }); return; } @@ -268,7 +268,7 @@ class Resource { ); } - withAuthDetails(rest, headers, params, callback, doRequest); + withAuthDetails(client, headers, params, callback, doRequest); } } diff --git a/src/common/types/http.d.ts b/src/common/types/http.d.ts index 706ad18697..16be6249cd 100644 --- a/src/common/types/http.d.ts +++ b/src/common/types/http.d.ts @@ -25,7 +25,7 @@ export declare class IHttp { Request?: ( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, @@ -35,7 +35,7 @@ export declare class IHttp { _getHosts: (client: BaseClient | Realtime) => string[]; do( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, path: PathParameter, headers: Record | null, body: unknown, @@ -44,7 +44,7 @@ export declare class IHttp { ): void; doUri( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, uri: string, headers: Record | null, body: unknown, diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 5a6c1dcfe6..08a5e2d6b7 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -102,10 +102,10 @@ const Http: typeof IHttp = class { this.options = options || {}; } - /* Unlike for doUri, the 'rest' param here is mandatory, as it's used to generate the hosts */ + /* Unlike for doUri, the 'client' param here is mandatory, as it's used to generate the hosts */ do( method: HttpMethods, - rest: BaseClient, + client: BaseClient, path: PathParameter, headers: Record | null, body: unknown, @@ -116,16 +116,16 @@ const Http: typeof IHttp = class { typeof path === 'function' ? path : function (host: string) { - return rest.baseUri(host) + path; + return client.baseUri(host) + path; }; - const currentFallback = rest._currentFallback; + const currentFallback = client._currentFallback; if (currentFallback) { if (currentFallback.validUntil > Date.now()) { /* Use stored fallback */ this.doUri( method, - rest, + client, uriFromHost(currentFallback.host), headers, body, @@ -133,8 +133,8 @@ const Http: typeof IHttp = class { (err?: ErrnoException | ErrorInfo | null, ...args: unknown[]) => { if (err && shouldFallback(err as ErrnoException)) { /* unstore the fallback and start from the top with the default sequence */ - rest._currentFallback = null; - this.do(method, rest, path, headers, body, params, callback); + client._currentFallback = null; + this.do(method, client, path, headers, body, params, callback); return; } callback(err, ...args); @@ -143,15 +143,15 @@ const Http: typeof IHttp = class { return; } else { /* Fallback expired; remove it and fallthrough to normal sequence */ - rest._currentFallback = null; + client._currentFallback = null; } } - const hosts = getHosts(rest); + const hosts = getHosts(client); /* see if we have one or more than one host */ if (hosts.length === 1) { - this.doUri(method, rest, uriFromHost(hosts[0]), headers, body, params, callback); + this.doUri(method, client, uriFromHost(hosts[0]), headers, body, params, callback); return; } @@ -159,7 +159,7 @@ const Http: typeof IHttp = class { const host = candidateHosts.shift(); this.doUri( method, - rest, + client, uriFromHost(host as string), headers, body, @@ -171,9 +171,9 @@ const Http: typeof IHttp = class { } if (persistOnSuccess) { /* RSC15f */ - rest._currentFallback = { + client._currentFallback = { host: host as string, - validUntil: Date.now() + rest.options.timeouts.fallbackRetryTimeout, + validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, }; } callback(err, ...args); @@ -185,7 +185,7 @@ const Http: typeof IHttp = class { doUri( method: HttpMethods, - rest: BaseClient, + client: BaseClient, uri: string, headers: Record | null, body: unknown, @@ -195,7 +195,7 @@ const Http: typeof IHttp = class { /* Will generally be making requests to one or two servers exclusively * (Ably and perhaps an auth server), so for efficiency, use the * foreverAgent to keep the TCP stream alive between requests where possible */ - const agentOptions = (rest && rest.options.restAgentOptions) || (Defaults.restAgentOptions as RestAgentOptions); + const agentOptions = (client && client.options.restAgentOptions) || (Defaults.restAgentOptions as RestAgentOptions); const doOptions: Options = { headers: headers || undefined, responseType: 'buffer' }; if (!this.agent) { @@ -222,7 +222,7 @@ const Http: typeof IHttp = class { doOptions.agent = this.agent; doOptions.url = uri; - doOptions.timeout = { request: ((rest && rest.options.timeouts) || Defaults.TIMEOUTS).httpRequestTimeout }; + doOptions.timeout = { request: ((client && client.options.timeouts) || Defaults.TIMEOUTS).httpRequestTimeout }; // We have our own logic that retries appropriate statuscodes to fallback endpoints, // with timeouts constructed appropriately. Don't want `got` doing its own retries to // the same endpoint, inappropriately retrying 429s, etc @@ -275,7 +275,7 @@ const Http: typeof IHttp = class { Request?: ( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, diff --git a/src/platform/web/lib/transport/fetchrequest.ts b/src/platform/web/lib/transport/fetchrequest.ts index 607674fd58..7203ff62a6 100644 --- a/src/platform/web/lib/transport/fetchrequest.ts +++ b/src/platform/web/lib/transport/fetchrequest.ts @@ -19,7 +19,7 @@ function getAblyError(responseBody: unknown, headers: Headers) { export default function fetchRequest( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, @@ -36,7 +36,7 @@ export default function fetchRequest( controller.abort(); callback(new PartialErrorInfo('Request timed out', null, 408)); }, - rest ? rest.options.timeouts.httpRequestTimeout : Defaults.TIMEOUTS.httpRequestTimeout + client ? client.options.timeouts.httpRequestTimeout : Defaults.TIMEOUTS.httpRequestTimeout ); const requestInit: RequestInit = { diff --git a/src/platform/web/lib/util/http.ts b/src/platform/web/lib/util/http.ts index 37630adf45..0decb2cc53 100644 --- a/src/platform/web/lib/util/http.ts +++ b/src/platform/web/lib/util/http.ts @@ -57,7 +57,7 @@ const Http: typeof IHttp = class { this.supportsAuthHeaders = true; this.Request = function ( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, @@ -70,7 +70,7 @@ const Http: typeof IHttp = class { params, body, XHRStates.REQ_SEND, - rest && rest.options.timeouts, + client && client.options.timeouts, method ); req.once('complete', callback); @@ -134,16 +134,16 @@ const Http: typeof IHttp = class { ); }; } else { - this.Request = (method, rest, uri, headers, params, body, callback) => { + this.Request = (method, client, uri, headers, params, body, callback) => { callback(new PartialErrorInfo('no supported HTTP transports available', null, 400), null); }; } } - /* Unlike for doUri, the 'rest' param here is mandatory, as it's used to generate the hosts */ + /* Unlike for doUri, the 'client' param here is mandatory, as it's used to generate the hosts */ do( method: HttpMethods, - rest: BaseClient, + client: BaseClient, path: string, headers: Record | null, body: unknown, @@ -154,10 +154,10 @@ const Http: typeof IHttp = class { typeof path == 'function' ? path : function (host: string) { - return rest.baseUri(host) + path; + return client.baseUri(host) + path; }; - const currentFallback = rest._currentFallback; + const currentFallback = client._currentFallback; if (currentFallback) { if (currentFallback.validUntil > Utils.now()) { /* Use stored fallback */ @@ -167,7 +167,7 @@ const Http: typeof IHttp = class { } this.Request( method, - rest, + client, uriFromHost(currentFallback.host), headers, params, @@ -176,8 +176,8 @@ const Http: typeof IHttp = class { // This typecast is safe because ErrnoExceptions are only thrown in NodeJS if (err && shouldFallback(err as ErrorInfo)) { /* unstore the fallback and start from the top with the default sequence */ - rest._currentFallback = null; - this.do(method, rest, path, headers, body, params, callback); + client._currentFallback = null; + this.do(method, client, path, headers, body, params, callback); return; } callback?.(err, ...args); @@ -186,15 +186,15 @@ const Http: typeof IHttp = class { return; } else { /* Fallback expired; remove it and fallthrough to normal sequence */ - rest._currentFallback = null; + client._currentFallback = null; } } - const hosts = getHosts(rest); + const hosts = getHosts(client); /* if there is only one host do it */ if (hosts.length === 1) { - this.doUri(method, rest, uriFromHost(hosts[0]), headers, body, params, callback as RequestCallback); + this.doUri(method, client, uriFromHost(hosts[0]), headers, body, params, callback as RequestCallback); return; } @@ -203,7 +203,7 @@ const Http: typeof IHttp = class { const host = candidateHosts.shift(); this.doUri( method, - rest, + client, uriFromHost(host as string), headers, body, @@ -216,9 +216,9 @@ const Http: typeof IHttp = class { } if (persistOnSuccess) { /* RSC15f */ - rest._currentFallback = { + client._currentFallback = { host: host as string, - validUntil: Utils.now() + rest.options.timeouts.fallbackRetryTimeout, + validUntil: Utils.now() + client.options.timeouts.fallbackRetryTimeout, }; } callback?.(err, ...args); @@ -230,7 +230,7 @@ const Http: typeof IHttp = class { doUri( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, uri: string, headers: Record | null, body: unknown, @@ -241,12 +241,12 @@ const Http: typeof IHttp = class { callback(new PartialErrorInfo('Request invoked before assigned to', null, 500)); return; } - this.Request(method, rest, uri, headers, params, body, callback); + this.Request(method, client, uri, headers, params, body, callback); } Request?: ( method: HttpMethods, - rest: BaseClient | null, + client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, From ab9ee53b32f0b628a5b34b028a91166dcd18684f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 14 Aug 2023 11:51:37 -0300 Subject: [PATCH 149/468] Remove redundant `| BaseRealtime` in `BaseClient | BaseRealtime` BaseRealtime inherits from BaseClient. --- src/common/lib/client/auth.ts | 6 +++--- src/common/lib/client/channel.ts | 5 ++--- src/platform/nodejs/lib/util/http.ts | 2 +- src/platform/web/lib/util/http.ts | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 156991f317..721d23e394 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -26,7 +26,7 @@ function random() { return ('000000' + Math.floor(Math.random() * 1e16)).slice(-16); } -function isRealtime(client: BaseClient | BaseRealtime): client is BaseRealtime { +function isRealtime(client: BaseClient): client is BaseRealtime { return !!(client as BaseRealtime).connection; } @@ -113,7 +113,7 @@ function getTokenRequestId() { } class Auth { - client: BaseClient | BaseRealtime; + client: BaseClient; tokenParams: API.Types.TokenParams; currentTokenRequestId: number | null; waitingForTokenRequest: ReturnType | null; @@ -125,7 +125,7 @@ class Auth { basicKey?: string; clientId?: string | null; - constructor(client: BaseClient | BaseRealtime, options: ClientOptions) { + constructor(client: BaseClient, options: ClientOptions) { this.client = client; this.tokenParams = options.defaultTokenParams || {}; /* The id of the current token request if one is in progress, else null */ diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index 42d2545500..b195ecf370 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -9,7 +9,6 @@ import Resource, { ResourceCallback } from './resource'; import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseClient from './baseclient'; -import BaseRealtime from './baserealtime'; import * as API from '../../../../ably'; import Platform from 'common/platform'; @@ -47,13 +46,13 @@ function normaliseChannelOptions(options?: ChannelOptions) { } class Channel extends EventEmitter { - client: BaseClient | BaseRealtime; + client: BaseClient; name: string; basePath: string; presence: Presence; channelOptions: ChannelOptions; - constructor(client: BaseClient | BaseRealtime, name: string, channelOptions?: ChannelOptions) { + constructor(client: BaseClient, name: string, channelOptions?: ChannelOptions) { super(); Logger.logAction(Logger.LOG_MINOR, 'Channel()', 'started; name = ' + name); this.client = client; diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 08a5e2d6b7..322f2b6b80 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -74,7 +74,7 @@ function shouldFallback(err: ErrnoException) { ); } -function getHosts(client: BaseClient | BaseRealtime): string[] { +function getHosts(client: BaseClient): string[] { /* If we're a connected realtime client, try the endpoint we're connected * to first -- but still have fallbacks, being connected is not an absolute * guarantee that a datacenter has free capacity to service REST requests. */ diff --git a/src/platform/web/lib/util/http.ts b/src/platform/web/lib/util/http.ts index 0decb2cc53..a417d5f83c 100644 --- a/src/platform/web/lib/util/http.ts +++ b/src/platform/web/lib/util/http.ts @@ -26,7 +26,7 @@ function shouldFallback(errorInfo: ErrorInfo) { ); } -function getHosts(client: BaseClient | BaseRealtime): string[] { +function getHosts(client: BaseClient): string[] { /* If we're a connected realtime client, try the endpoint we're connected * to first -- but still have fallbacks, being connected is not an absolute * guarantee that a datacenter has free capacity to service REST requests. */ From 6b7eb2f3cdc4c1f26ed6b1e8e4aa58812d7b6915 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Wed, 19 Jul 2023 10:58:09 +0100 Subject: [PATCH 150/468] refactor: move some defaults helpers out of utils tl;dr: there's a circular import here between utils.ts and defaults.ts. in the following commits this results in a build error, so i'm making utils.ts no longer depend on defaults.ts. --- src/common/lib/client/auth.ts | 5 +-- src/common/lib/client/baseclient.ts | 12 +++---- src/common/lib/client/channel.ts | 7 ++-- src/common/lib/client/presence.ts | 5 +-- src/common/lib/client/push.ts | 21 +++++------ src/common/lib/util/defaults.ts | 54 +++++++++++++++++++++++++++++ src/common/lib/util/utils.ts | 52 --------------------------- 7 files changed, 81 insertions(+), 75 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 721d23e394..1a47291c08 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -12,6 +12,7 @@ import HttpMethods from '../../constants/HttpMethods'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; import Platform from '../../platform'; import Resource from './resource'; +import Defaults from '../util/defaults'; type BatchResult = API.Types.BatchResult; type TokenRevocationTargetSpecifier = API.Types.TokenRevocationTargetSpecifier; @@ -584,7 +585,7 @@ class Auth { return client.baseUri(host) + path; }; - const requestHeaders = Utils.defaultPostHeaders(this.client.options); + const requestHeaders = Defaults.defaultPostHeaders(this.client.options); if (authOptions.requestHeaders) Utils.mixin(requestHeaders, authOptions.requestHeaders); Logger.logAction( Logger.LOG_MICRO, @@ -1073,7 +1074,7 @@ class Auth { }; const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(this.client.options, { format }); + headers = Defaults.defaultPostHeaders(this.client.options, { format }); if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index f81d69f4e2..1486df07b5 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -118,7 +118,7 @@ class BaseClient { return Utils.promisify(this, 'stats', [params]) as Promise>; } } - const headers = Utils.defaultGetHeaders(this.options), + const headers = Defaults.defaultGetHeaders(this.options), format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.http.supportsLinkHeaders ? undefined : format; @@ -148,7 +148,7 @@ class BaseClient { const _callback = callback || noop; - const headers = Utils.defaultGetHeaders(this.options); + const headers = Defaults.defaultGetHeaders(this.options); if (this.options.headers) Utils.mixin(headers, this.options.headers); const timeUri = (host: string) => { return this.baseUri(host) + '/time'; @@ -201,8 +201,8 @@ class BaseClient { const _method = method.toLowerCase() as HttpMethods; const headers = _method == 'get' - ? Utils.defaultGetHeaders(this.options, { format, protocolVersion: version }) - : Utils.defaultPostHeaders(this.options, { format, protocolVersion: version }); + ? Defaults.defaultGetHeaders(this.options, { format, protocolVersion: version }) + : Defaults.defaultPostHeaders(this.options, { format, protocolVersion: version }); if (callback === undefined) { return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< @@ -264,7 +264,7 @@ class BaseClient { } const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(this.options, { format }); + headers = Defaults.defaultPostHeaders(this.options, { format }); if (this.options.headers) Utils.mixin(headers, this.options.headers); @@ -304,7 +304,7 @@ class BaseClient { } const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(this.options, { format }); + headers = Defaults.defaultPostHeaders(this.options, { format }); if (this.options.headers) Utils.mixin(headers, this.options.headers); diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index b195ecf370..d14f4c9752 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -11,6 +11,7 @@ import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseClient from './baseclient'; import * as API from '../../../../ably'; import Platform from 'common/platform'; +import Defaults from '../util/defaults'; interface RestHistoryParams { start?: number; @@ -88,7 +89,7 @@ class Channel extends EventEmitter { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); Utils.mixin(headers, client.options.headers); @@ -141,7 +142,7 @@ class Channel extends EventEmitter { options = client.options, format = options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, idempotentRestPublishing = client.options.idempotentRestPublishing, - headers = Utils.defaultPostHeaders(client.options, { format }); + headers = Defaults.defaultPostHeaders(client.options, { format }); Utils.mixin(headers, options.headers); @@ -190,7 +191,7 @@ class Channel extends EventEmitter { } const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; - const headers = Utils.defaultPostHeaders(this.client.options, { format }); + const headers = Defaults.defaultPostHeaders(this.client.options, { format }); Resource.get(this.client, this.basePath, headers, {}, format, callback || noop); } diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index 18cb5a2b97..ea64ad3cd7 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -7,6 +7,7 @@ import { CipherOptions } from '../types/message'; import { PaginatedResultCallback } from '../../types/utils'; import Channel from './channel'; import RealtimeChannel from './realtimechannel'; +import Defaults from '../util/defaults'; class Presence extends EventEmitter { channel: RealtimeChannel | Channel; @@ -32,7 +33,7 @@ class Presence extends EventEmitter { const client = this.channel.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.channel.client.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); Utils.mixin(headers, client.options.headers); @@ -71,7 +72,7 @@ class Presence extends EventEmitter { const client = this.channel.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.channel.client.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); Utils.mixin(headers, client.options.headers); diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 0a60822cbd..2cf3e01d19 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -6,6 +6,7 @@ import ErrorInfo from '../types/errorinfo'; import PushChannelSubscription from '../types/pushchannelsubscription'; import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseClient from './baseclient'; +import Defaults from '../util/defaults'; class Push { client: BaseClient; @@ -31,7 +32,7 @@ class Admin { publish(recipient: any, payload: any, callback: ErrCallback) { const client = this.client; const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(client.options, { format }), + headers = Defaults.defaultPostHeaders(client.options, { format }), params = {}; const body = Utils.mixin({ recipient: recipient }, payload); @@ -59,7 +60,7 @@ class DeviceRegistrations { const client = this.client; const body = DeviceDetails.fromValues(device); const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(client.options, { format }), + headers = Defaults.defaultPostHeaders(client.options, { format }), params = {}; if (typeof callback !== 'function') { @@ -95,7 +96,7 @@ class DeviceRegistrations { get(deviceIdOrDetails: any, callback: StandardCallback) { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(client.options, { format }), + headers = Defaults.defaultGetHeaders(client.options, { format }), deviceId = deviceIdOrDetails.id || deviceIdOrDetails; if (typeof callback !== 'function') { @@ -139,7 +140,7 @@ class DeviceRegistrations { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'list', arguments); @@ -159,7 +160,7 @@ class DeviceRegistrations { remove(deviceIdOrDetails: any, callback: ErrCallback) { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(client.options, { format }), + headers = Defaults.defaultGetHeaders(client.options, { format }), params = {}, deviceId = deviceIdOrDetails.id || deviceIdOrDetails; @@ -195,7 +196,7 @@ class DeviceRegistrations { removeWhere(params: any, callback: ErrCallback) { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'removeWhere', arguments); @@ -220,7 +221,7 @@ class ChannelSubscriptions { const client = this.client; const body = PushChannelSubscription.fromValues(subscription); const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultPostHeaders(client.options, { format }), + headers = Defaults.defaultPostHeaders(client.options, { format }), params = {}; if (typeof callback !== 'function') { @@ -252,7 +253,7 @@ class ChannelSubscriptions { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'list', arguments); @@ -272,7 +273,7 @@ class ChannelSubscriptions { removeWhere(params: any, callback: PaginatedResultCallback) { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'removeWhere', arguments); @@ -292,7 +293,7 @@ class ChannelSubscriptions { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format, - headers = Utils.defaultGetHeaders(client.options, { format }); + headers = Defaults.defaultGetHeaders(client.options, { format }); if (typeof callback !== 'function') { return Utils.promisify(this, 'listChannels', arguments); diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 1b1fd275fe..23d73aee98 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -42,6 +42,8 @@ type CompleteDefaults = IDefaults & { getRealtimeHost(options: ClientOptions, production: boolean, environment: string): string; objectifyOptions(options: ClientOptions | string): ClientOptions; normaliseOptions(options: InternalClientOptions): NormalisedClientOptions; + defaultGetHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record; + defaultPostHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record; }; const Defaults = { @@ -87,6 +89,8 @@ const Defaults = { checkHost, objectifyOptions, normaliseOptions, + defaultGetHeaders, + defaultPostHeaders, }; export function getHost(options: ClientOptions, host?: string | null, ws?: boolean): string { @@ -260,6 +264,56 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie }; } +const contentTypes = { + json: 'application/json', + xml: 'application/xml', + html: 'text/html', + msgpack: 'application/x-msgpack', +}; + +export interface HeadersOptions { + format?: Utils.Format; + protocolVersion?: number; +} + +const defaultHeadersOptions: Required = { + format: Utils.Format.json, + protocolVersion: Defaults.protocolVersion, +}; + +export function defaultGetHeaders( + options: NormalisedClientOptions, + { + format = defaultHeadersOptions.format, + protocolVersion = defaultHeadersOptions.protocolVersion, + }: HeadersOptions = {} +): Record { + const accept = contentTypes[format]; + return { + accept: accept, + 'X-Ably-Version': protocolVersion.toString(), + 'Ably-Agent': getAgentString(options), + }; +} + +export function defaultPostHeaders( + options: NormalisedClientOptions, + { + format = defaultHeadersOptions.format, + protocolVersion = defaultHeadersOptions.protocolVersion, + }: HeadersOptions = {} +): Record { + let contentType; + const accept = (contentType = contentTypes[format]); + + return { + accept: accept, + 'content-type': contentType, + 'X-Ably-Version': protocolVersion.toString(), + 'Ably-Agent': getAgentString(options), + }; +} + export default Defaults as CompleteDefaults; export function getDefaults(platformDefaults: IDefaults) { diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 8d8477a2ee..03d546b0a8 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -1,7 +1,5 @@ import Platform from 'common/platform'; -import Defaults, { getAgentString } from './defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { NormalisedClientOptions } from 'common/types/ClientOptions'; function randomPosn(arrOrStr: Array | string) { return Math.floor(Math.random() * arrOrStr.length); @@ -330,61 +328,11 @@ export function allSame(arr: Array>, prop: string): bool }); } -const contentTypes = { - json: 'application/json', - xml: 'application/xml', - html: 'text/html', - msgpack: 'application/x-msgpack', -}; - export enum Format { msgpack = 'msgpack', json = 'json', } -export interface HeadersOptions { - format?: Format; - protocolVersion?: number; -} - -const defaultHeadersOptions: Required = { - format: Format.json, - protocolVersion: Defaults.protocolVersion, -}; - -export function defaultGetHeaders( - options: NormalisedClientOptions, - { - format = defaultHeadersOptions.format, - protocolVersion = defaultHeadersOptions.protocolVersion, - }: HeadersOptions = {} -): Record { - const accept = contentTypes[format]; - return { - accept: accept, - 'X-Ably-Version': protocolVersion.toString(), - 'Ably-Agent': getAgentString(options), - }; -} - -export function defaultPostHeaders( - options: NormalisedClientOptions, - { - format = defaultHeadersOptions.format, - protocolVersion = defaultHeadersOptions.protocolVersion, - }: HeadersOptions = {} -): Record { - let contentType; - const accept = (contentType = contentTypes[format]); - - return { - accept: accept, - 'content-type': contentType, - 'X-Ably-Version': protocolVersion.toString(), - 'Ably-Agent': getAgentString(options), - }; -} - export function arrPopRandomElement(arr: Array): T { return arr.splice(randomPosn(arr), 1)[0]; } From 89c0761215baa67d2304932964581da6a0d63994 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 2 Aug 2023 14:48:58 -0300 Subject: [PATCH 151/468] Split REST functionality into a tree-shakable module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This moves the following functionality from BaseClient into a new module named Rest: - methods that wrap REST endpoints (e.g. `stats`) - the `request` method - all functionality accessed via `BaseRest.channels` and `BaseRest.push` This allows us to now construct a BaseRealtime instance that doesn’t have REST functionality. Note that the BaseRest class always includes the Rest module. This comes after discussions in which we decided that it would be quite useless without it. Resolves #1374. Co-authored-by: Owen Pearson --- scripts/moduleReport.js | 4 +- src/common/lib/client/baseclient.ts | 277 +++--------------------- src/common/lib/client/baserealtime.ts | 8 +- src/common/lib/client/baserest.ts | 11 +- src/common/lib/client/modulesmap.ts | 8 +- src/common/lib/client/rest.ts | 295 ++++++++++++++++++++++++++ src/common/types/http.d.ts | 2 +- src/platform/web/modules.ts | 1 + test/browser/modules.test.js | 33 ++- 9 files changed, 381 insertions(+), 258 deletions(-) create mode 100644 src/common/lib/client/rest.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 19d1071672..61bdff99e3 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -1,7 +1,7 @@ const esbuild = require('esbuild'); // List of all modules accepted in ModulesMap -const moduleNames = []; +const moduleNames = ['Rest']; function formatBytes(bytes) { const kibibytes = bytes / 1024; @@ -40,7 +40,7 @@ const errors = []; const size = getImportSize([baseClient, moduleName]); console.log(`${baseClient} + ${moduleName}: ${formatBytes(size)}`); - if (!(baseClientSize < size)) { + if (!(baseClientSize < size) && !(baseClient === 'BaseRest' && moduleName === 'Rest')) { // Emit an error if adding the module does not increase the bundle size // (this means that the module is not being tree-shaken correctly). errors.push(new Error(`Adding ${moduleName} to ${baseClient} does not increase the bundle size.`)); diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 1486df07b5..aeac034d62 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -1,24 +1,19 @@ -import * as Utils from '../util/utils'; import Logger, { LoggerOptions } from '../util/logger'; import Defaults from '../util/defaults'; import Auth from './auth'; -import Push from './push'; -import PaginatedResource, { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; -import Channel from './channel'; +import { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; import ErrorInfo from '../types/errorinfo'; import Stats from '../types/stats'; -import HttpMethods from '../../constants/HttpMethods'; -import { ChannelOptions } from '../../types/channel'; -import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import { ErrnoException, IHttp, RequestParams } from '../../types/http'; +import { StandardCallback } from '../../types/utils'; +import { IHttp, RequestParams } from '../../types/http'; import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOptions'; import * as API from '../../../../ably'; import Platform from '../../platform'; import Message from '../types/message'; import PresenceMessage from '../types/presencemessage'; -import Resource from './resource'; import { ModulesMap } from './modulesmap'; +import { Rest } from './rest'; type BatchResult = API.Types.BatchResult; type BatchPublishSpec = API.Types.BatchPublishSpec; @@ -29,8 +24,6 @@ type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult; type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; type BatchPresenceResult = BatchResult; -const noop = function () {}; - /** `BaseClient` acts as the base class for all of the client classes exported by the SDK. It is an implementation detail and this class is not advertised publicly. */ @@ -43,14 +36,10 @@ class BaseClient { serverTimeOffset: number | null; http: IHttp; auth: Auth; - channels: Channels; - push: Push; - constructor( - options: ClientOptions | string, - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - modules: ModulesMap - ) { + private readonly _rest: Rest | null; + + constructor(options: ClientOptions | string, modules: ModulesMap) { if (!options) { const msg = 'no options provided'; Logger.logAction(Logger.LOG_ERROR, 'BaseClient()', msg); @@ -97,8 +86,23 @@ class BaseClient { this.serverTimeOffset = null; this.http = new Platform.Http(normalOptions); this.auth = new Auth(this, normalOptions); - this.channels = new Channels(this); - this.push = new Push(this); + + this._rest = modules.Rest ? new modules.Rest(this) : null; + } + + private get rest(): Rest { + if (!this._rest) { + throw new ErrorInfo('Rest module not provided', 400, 40000); + } + return this._rest; + } + + get channels() { + return this.rest.channels; + } + + get push() { + return this.rest.push; } baseUri(host: string) { @@ -109,78 +113,11 @@ class BaseClient { params: RequestParams, callback: StandardCallback> ): Promise> | void { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'stats', [params]) as Promise>; - } - } - const headers = Defaults.defaultGetHeaders(this.options), - format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.http.supportsLinkHeaders ? undefined : format; - - Utils.mixin(headers, this.options.headers); - - new PaginatedResource(this, '/stats', headers, envelope, function ( - body: unknown, - headers: Record, - unpacked?: boolean - ) { - const statsValues = unpacked ? body : JSON.parse(body as string); - for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); - return statsValues; - }).get(params as Record, callback); + return this.rest.stats(params, callback); } time(params?: RequestParams | StandardCallback, callback?: StandardCallback): Promise | void { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'time', [params]) as Promise; - } - } - - const _callback = callback || noop; - - const headers = Defaults.defaultGetHeaders(this.options); - if (this.options.headers) Utils.mixin(headers, this.options.headers); - const timeUri = (host: string) => { - return this.baseUri(host) + '/time'; - }; - this.http.do( - HttpMethods.Get, - this, - timeUri, - headers, - null, - params as RequestParams, - ( - err?: ErrorInfo | ErrnoException | null, - res?: unknown, - headers?: Record, - unpacked?: boolean - ) => { - if (err) { - _callback(err); - return; - } - if (!unpacked) res = JSON.parse(res as string); - const time = (res as number[])[0]; - if (!time) { - _callback(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); - return; - } - /* calculate time offset only once for this device by adding to the prototype */ - this.serverTimeOffset = time - Utils.now(); - _callback(null, time); - } - ); + return this.rest.time(params, callback); } request( @@ -192,141 +129,17 @@ class BaseClient { customHeaders: Record, callback: StandardCallback> ): Promise> | void { - const useBinary = this.options.useBinaryProtocol, - encoder = useBinary ? Platform.Config.msgpack.encode : JSON.stringify, - decoder = useBinary ? Platform.Config.msgpack.decode : JSON.parse, - format = useBinary ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.http.supportsLinkHeaders ? undefined : format; - params = params || {}; - const _method = method.toLowerCase() as HttpMethods; - const headers = - _method == 'get' - ? Defaults.defaultGetHeaders(this.options, { format, protocolVersion: version }) - : Defaults.defaultPostHeaders(this.options, { format, protocolVersion: version }); - - if (callback === undefined) { - return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< - HttpPaginatedResponse - >; - } - - if (typeof body !== 'string') { - body = encoder(body); - } - Utils.mixin(headers, this.options.headers); - if (customHeaders) { - Utils.mixin(headers, customHeaders); - } - const paginatedResource = new PaginatedResource( - this, - path, - headers, - envelope, - async function (resbody: unknown, headers: Record, unpacked?: boolean) { - return Utils.ensureArray(unpacked ? resbody : decoder(resbody as string & Buffer)); - }, - /* useHttpPaginatedResponse: */ true - ); - - if (!Utils.arrIn(Platform.Http.methods, _method)) { - throw new ErrorInfo('Unsupported method ' + _method, 40500, 405); - } - - if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { - paginatedResource[_method as HttpMethods.Post](params, body, callback as PaginatedResultCallback); - } else { - paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete]( - params, - callback as PaginatedResultCallback - ); - } + return this.rest.request(method, path, version, params, body, customHeaders, callback); } batchPublish( specOrSpecs: T - ): Promise; - batchPublish( - specOrSpecs: T, - callback?: StandardCallback - ): void | Promise { - if (callback === undefined) { - return Utils.promisify(this, 'batchPublish', [specOrSpecs]); - } - - let requestBodyDTO: BatchPublishSpec[]; - let singleSpecMode: boolean; - if (Utils.isArray(specOrSpecs)) { - requestBodyDTO = specOrSpecs; - singleSpecMode = false; - } else { - requestBodyDTO = [specOrSpecs]; - singleSpecMode = true; - } - - const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Defaults.defaultPostHeaders(this.options, { format }); - - if (this.options.headers) Utils.mixin(headers, this.options.headers); - - const requestBody = Utils.encodeBody(requestBodyDTO, format); - Resource.post( - this, - '/messages', - requestBody, - headers, - { newBatchResponse: 'true' }, - null, - (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } - - const batchResults = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPublishResult[]; - - // I don't love the below type assertions for `callback` but not sure how to avoid them - if (singleSpecMode) { - (callback as StandardCallback)(null, batchResults[0]); - } else { - (callback as StandardCallback)(null, batchResults); - } - } - ); + ): Promise { + return this.rest.batchPublish(specOrSpecs); } - batchPresence(channels: string[]): Promise; - batchPresence( - channels: string[], - callback?: StandardCallback - ): void | Promise { - if (callback === undefined) { - return Utils.promisify(this, 'batchPresence', [channels]); - } - - const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Defaults.defaultPostHeaders(this.options, { format }); - - if (this.options.headers) Utils.mixin(headers, this.options.headers); - - const channelsParam = channels.join(','); - - Resource.get( - this, - '/presence', - headers, - { newBatchResponse: 'true', channels: channelsParam }, - null, - (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } - - const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPresenceResult; - - callback(null, batchResult); - } - ); + batchPresence(channels: string[]): Promise { + return this.rest.batchPresence(channels); } setLog(logOptions: LoggerOptions): void { @@ -339,32 +152,4 @@ class BaseClient { static PresenceMessage = PresenceMessage; } -class Channels { - client: BaseClient; - all: Record; - - constructor(client: BaseClient) { - this.client = client; - this.all = Object.create(null); - } - - get(name: string, channelOptions?: ChannelOptions) { - name = String(name); - let channel = this.all[name]; - if (!channel) { - this.all[name] = channel = new Channel(this.client, name, channelOptions); - } else if (channelOptions) { - channel.setOptions(channelOptions); - } - - return channel; - } - - /* Included to support certain niche use-cases; most users should ignore this. - * Please do not use this unless you know what you're doing */ - release(name: string) { - delete this.all[String(name)]; - } -} - export default BaseClient; diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 74057b9a58..fb7e888bb0 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -18,17 +18,21 @@ import { ModulesMap } from './modulesmap'; `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. */ class BaseRealtime extends BaseClient { - channels: any; + _channels: any; connection: Connection; constructor(options: ClientOptions, modules: ModulesMap) { super(options, modules); Logger.logAction(Logger.LOG_MINOR, 'Realtime()', ''); this.connection = new Connection(this, this.options); - this.channels = new Channels(this); + this._channels = new Channels(this); if (options.autoConnect !== false) this.connect(); } + get channels() { + return this._channels; + } + connect(): void { Logger.logAction(Logger.LOG_MINOR, 'Realtime.connect()', ''); this.connection.connect(); diff --git a/src/common/lib/client/baserest.ts b/src/common/lib/client/baserest.ts index 6073aafd00..247145f989 100644 --- a/src/common/lib/client/baserest.ts +++ b/src/common/lib/client/baserest.ts @@ -1,6 +1,15 @@ import BaseClient from './baseclient'; +import ClientOptions from '../../types/ClientOptions'; +import { ModulesMap } from './modulesmap'; +import { Rest } from './rest'; /** `BaseRest` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRest` class exported by the non tree-shakable version. + + It always includes the `Rest` module. */ -export class BaseRest extends BaseClient {} +export class BaseRest extends BaseClient { + constructor(options: ClientOptions | string, modules: ModulesMap) { + super(options, { Rest, ...modules }); + } +} diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index 13b207d8c5..c56c2d98e7 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -1,3 +1,7 @@ -export interface ModulesMap {} +import { Rest } from './rest'; -export const allCommonModules: ModulesMap = {}; +export interface ModulesMap { + Rest?: typeof Rest; +} + +export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts new file mode 100644 index 0000000000..a9d11b3c3b --- /dev/null +++ b/src/common/lib/client/rest.ts @@ -0,0 +1,295 @@ +import * as Utils from '../util/utils'; +import Logger, { LoggerOptions } from '../util/logger'; +import Defaults from '../util/defaults'; +import Push from './push'; +import PaginatedResource, { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; +import Channel from './channel'; +import ErrorInfo from '../types/errorinfo'; +import Stats from '../types/stats'; +import HttpMethods from '../../constants/HttpMethods'; +import { ChannelOptions } from '../../types/channel'; +import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; +import { ErrnoException, RequestParams } from '../../types/http'; +import * as API from '../../../../ably'; +import Resource from './resource'; + +import Platform from '../../platform'; +import BaseClient from './baseclient'; + +type BatchResult = API.Types.BatchResult; +type BatchPublishSpec = API.Types.BatchPublishSpec; +type BatchPublishSuccessResult = API.Types.BatchPublishSuccessResult; +type BatchPublishFailureResult = API.Types.BatchPublishFailureResult; +type BatchPublishResult = BatchResult; +type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult; +type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; +type BatchPresenceResult = BatchResult; + +const noop = function () {}; +export class Rest { + private readonly client: BaseClient; + readonly channels: Channels; + readonly push: Push; + + constructor(client: BaseClient) { + this.client = client; + this.channels = new Channels(this.client); + this.push = new Push(this.client); + } + + stats( + params: RequestParams, + callback: StandardCallback> + ): Promise> | void { + /* params and callback are optional; see if params contains the callback */ + if (callback === undefined) { + if (typeof params == 'function') { + callback = params; + params = null; + } else { + return Utils.promisify(this, 'stats', [params]) as Promise>; + } + } + const headers = Defaults.defaultGetHeaders(this.client.options), + format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format; + + Utils.mixin(headers, this.client.options.headers); + + new PaginatedResource(this.client, '/stats', headers, envelope, function ( + body: unknown, + headers: Record, + unpacked?: boolean + ) { + const statsValues = unpacked ? body : JSON.parse(body as string); + for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); + return statsValues; + }).get(params as Record, callback); + } + + time(params?: RequestParams | StandardCallback, callback?: StandardCallback): Promise | void { + /* params and callback are optional; see if params contains the callback */ + if (callback === undefined) { + if (typeof params == 'function') { + callback = params; + params = null; + } else { + return Utils.promisify(this, 'time', [params]) as Promise; + } + } + + const _callback = callback || noop; + + const headers = Defaults.defaultGetHeaders(this.client.options); + if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); + const timeUri = (host: string) => { + return this.client.baseUri(host) + '/time'; + }; + this.client.http.do( + HttpMethods.Get, + this.client, + timeUri, + headers, + null, + params as RequestParams, + ( + err?: ErrorInfo | ErrnoException | null, + res?: unknown, + headers?: Record, + unpacked?: boolean + ) => { + if (err) { + _callback(err); + return; + } + if (!unpacked) res = JSON.parse(res as string); + const time = (res as number[])[0]; + if (!time) { + _callback(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); + return; + } + /* calculate time offset only once for this device by adding to the prototype */ + this.client.serverTimeOffset = time - Utils.now(); + _callback(null, time); + } + ); + } + + request( + method: string, + path: string, + version: number, + params: RequestParams, + body: unknown, + customHeaders: Record, + callback: StandardCallback> + ): Promise> | void { + const useBinary = this.client.options.useBinaryProtocol, + encoder = useBinary ? Platform.Config.msgpack.encode : JSON.stringify, + decoder = useBinary ? Platform.Config.msgpack.decode : JSON.parse, + format = useBinary ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format; + params = params || {}; + const _method = method.toLowerCase() as HttpMethods; + const headers = + _method == 'get' + ? Defaults.defaultGetHeaders(this.client.options, { format, protocolVersion: version }) + : Defaults.defaultPostHeaders(this.client.options, { format, protocolVersion: version }); + + if (callback === undefined) { + return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< + HttpPaginatedResponse + >; + } + + if (typeof body !== 'string') { + body = encoder(body); + } + Utils.mixin(headers, this.client.options.headers); + if (customHeaders) { + Utils.mixin(headers, customHeaders); + } + const paginatedResource = new PaginatedResource( + this.client, + path, + headers, + envelope, + async function (resbody: unknown, headers: Record, unpacked?: boolean) { + return Utils.ensureArray(unpacked ? resbody : decoder(resbody as string & Buffer)); + }, + /* useHttpPaginatedResponse: */ true + ); + + if (!Utils.arrIn(Platform.Http.methods, _method)) { + throw new ErrorInfo('Unsupported method ' + _method, 40500, 405); + } + + if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { + paginatedResource[_method as HttpMethods.Post](params, body, callback as PaginatedResultCallback); + } else { + paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete]( + params, + callback as PaginatedResultCallback + ); + } + } + + batchPublish( + specOrSpecs: T + ): Promise; + batchPublish( + specOrSpecs: T, + callback?: StandardCallback + ): void | Promise { + if (callback === undefined) { + return Utils.promisify(this, 'batchPublish', [specOrSpecs]); + } + + let requestBodyDTO: BatchPublishSpec[]; + let singleSpecMode: boolean; + if (Utils.isArray(specOrSpecs)) { + requestBodyDTO = specOrSpecs; + singleSpecMode = false; + } else { + requestBodyDTO = [specOrSpecs]; + singleSpecMode = true; + } + + const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Defaults.defaultPostHeaders(this.client.options, { format }); + + if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); + + const requestBody = Utils.encodeBody(requestBodyDTO, format); + Resource.post( + this.client, + '/messages', + requestBody, + headers, + { newBatchResponse: 'true' }, + null, + (err, body, headers, unpacked) => { + if (err) { + callback(err); + return; + } + + const batchResults = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPublishResult[]; + + // I don't love the below type assertions for `callback` but not sure how to avoid them + if (singleSpecMode) { + (callback as StandardCallback)(null, batchResults[0]); + } else { + (callback as StandardCallback)(null, batchResults); + } + } + ); + } + + batchPresence(channels: string[]): Promise; + batchPresence( + channels: string[], + callback?: StandardCallback + ): void | Promise { + if (callback === undefined) { + return Utils.promisify(this, 'batchPresence', [channels]); + } + + const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Defaults.defaultPostHeaders(this.client.options, { format }); + + if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); + + const channelsParam = channels.join(','); + + Resource.get( + this.client, + '/presence', + headers, + { newBatchResponse: 'true', channels: channelsParam }, + null, + (err, body, headers, unpacked) => { + if (err) { + callback(err); + return; + } + + const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPresenceResult; + + callback(null, batchResult); + } + ); + } + + setLog(logOptions: LoggerOptions): void { + Logger.setLog(logOptions.level, logOptions.handler); + } +} + +class Channels { + client: BaseClient; + all: Record; + + constructor(client: BaseClient) { + this.client = client; + this.all = Object.create(null); + } + + get(name: string, channelOptions?: ChannelOptions) { + name = String(name); + let channel = this.all[name]; + if (!channel) { + this.all[name] = channel = new Channel(this.client, name, channelOptions); + } else if (channelOptions) { + channel.setOptions(channelOptions); + } + + return channel; + } + + /* Included to support certain niche use-cases; most users should ignore this. + * Please do not use this unless you know what you're doing */ + release(name: string) { + delete this.all[String(name)]; + } +} diff --git a/src/common/types/http.d.ts b/src/common/types/http.d.ts index 16be6249cd..0727978b52 100644 --- a/src/common/types/http.d.ts +++ b/src/common/types/http.d.ts @@ -1,5 +1,5 @@ import HttpMethods from '../constants/HttpMethods'; -import BaseClient from '../lib/client/baseclient'; +import { BaseClient } from '../lib/client/baseclient'; import ErrorInfo from '../lib/types/errorinfo'; import { Agents } from 'got'; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index e1031a9c17..8054981daf 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -47,4 +47,5 @@ if (Platform.Config.noUpgrade) { Platform.Defaults.upgradeTransports = []; } +export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 755d70063f..6ccbf64bbc 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -1,4 +1,4 @@ -import { BaseRest, BaseRealtime } from '../../build/modules/index.js'; +import { BaseRest, BaseRealtime, Rest } from '../../build/modules/index.js'; describe('browser/modules', function () { this.timeout(10 * 1000); @@ -12,11 +12,36 @@ describe('browser/modules', function () { describe('without any modules', () => { for (const clientClass of [BaseRest, BaseRealtime]) { - it(clientClass.name, async () => { - const client = new clientClass(ablyClientOptions()); + describe(clientClass.name, () => { + it('can be constructed', async () => { + expect(() => new clientClass(ablyClientOptions(), {})).not.to.throw(); + }); + }); + } + }); + + describe('Rest', () => { + describe('BaseRest without explicit Rest', () => { + it('offers REST functionality', async () => { + const client = new BaseRest(ablyClientOptions(), {}); const time = await client.time(); expect(time).to.be.a('number'); }); - } + }); + + describe('BaseRealtime with Rest', () => { + it('offers REST functionality', async () => { + const client = new BaseRealtime(ablyClientOptions(), { Rest }); + const time = await client.time(); + expect(time).to.be.a('number'); + }); + }); + + describe('BaseRealtime without Rest', () => { + it('throws an error when attempting to use REST functionality', async () => { + const client = new BaseRealtime(ablyClientOptions(), {}); + expect(() => client.time()).to.throw('Rest module not provided'); + }); + }); }); }); From 96f4cf8bd3af9a8525cab7a68d5f081947c7467d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 3 Aug 2023 05:30:37 -0300 Subject: [PATCH 152/468] Remove redundant static properties from BaseRealtime These already exist in its superclass BaseClient. --- src/common/lib/client/baserealtime.ts | 5 ----- src/platform/nativescript/index.ts | 4 ++-- src/platform/nodejs/index.ts | 4 ++-- src/platform/react-native/index.ts | 4 ++-- src/platform/web-noencryption/index.ts | 4 ++-- src/platform/web/index.ts | 4 ++-- src/platform/web/modules.ts | 1 - 7 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index fb7e888bb0..7164c04b7d 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -10,8 +10,6 @@ import { ChannelOptions } from '../../types/channel'; import ClientOptions from '../../types/ClientOptions'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; -import Platform from 'common/platform'; -import Message from '../types/message'; import { ModulesMap } from './modulesmap'; /** @@ -45,10 +43,7 @@ class BaseRealtime extends BaseClient { static Utils = Utils; static ConnectionManager = ConnectionManager; - static Platform = Platform; static ProtocolMessage = ProtocolMessage; - static Message = Message; - static Crypto?: typeof Platform.Crypto; } class Channels extends EventEmitter { diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index 5f8fee9253..be9941dc66 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -1,4 +1,5 @@ // Common +import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -29,8 +30,7 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -DefaultRest.Crypto = Crypto; -DefaultRealtime.Crypto = Crypto; +BaseClient.Crypto = Crypto; Logger.initLogHandlers(); diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index ca7e40e0d8..eb67752641 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -1,4 +1,5 @@ // Common +import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -25,8 +26,7 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = null; -DefaultRest.Crypto = Crypto; -DefaultRealtime.Crypto = Crypto; +BaseClient.Crypto = Crypto; Logger.initLogHandlers(); diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index c7bf713567..88dae850da 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -1,4 +1,5 @@ // Common +import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -29,8 +30,7 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -DefaultRest.Crypto = Crypto; -DefaultRealtime.Crypto = Crypto; +BaseClient.Crypto = Crypto; Logger.initLogHandlers(); diff --git a/src/platform/web-noencryption/index.ts b/src/platform/web-noencryption/index.ts index 5c813474b8..e10ae425c2 100644 --- a/src/platform/web-noencryption/index.ts +++ b/src/platform/web-noencryption/index.ts @@ -1,4 +1,5 @@ // Common +import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -24,8 +25,7 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -DefaultRest.Crypto = null; -DefaultRealtime.Crypto = null; +BaseClient.Crypto = null; Logger.initLogHandlers(); diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index 7c984e304e..ea6fb1b53e 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -1,4 +1,5 @@ // Common +import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -27,8 +28,7 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -DefaultRest.Crypto = Crypto; -DefaultRealtime.Crypto = Crypto; +BaseClient.Crypto = Crypto; Logger.initLogHandlers(); diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 8054981daf..d924eab04e 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -28,7 +28,6 @@ Platform.Transports = Transports; Platform.WebStorage = WebStorage; BaseClient.Crypto = Crypto; -BaseRealtime.Crypto = Crypto; Logger.initLogHandlers(); From 805304f4e3520945a68806e3e2dc0f850f944f1e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 3 Aug 2023 05:33:29 -0300 Subject: [PATCH 153/468] Move remaining static properties from BaseRealtime to DefaultRealtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These properties are only used by the tests and are not part of the public API. Let’s move them out of BaseRealtime to make it more tree-shakable. --- src/common/lib/client/baserealtime.ts | 5 ----- src/common/lib/client/defaultrealtime.ts | 7 +++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 7164c04b7d..971ec09038 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -9,7 +9,6 @@ import ProtocolMessage from '../types/protocolmessage'; import { ChannelOptions } from '../../types/channel'; import ClientOptions from '../../types/ClientOptions'; import * as API from '../../../../ably'; -import ConnectionManager from '../transport/connectionmanager'; import { ModulesMap } from './modulesmap'; /** @@ -40,10 +39,6 @@ class BaseRealtime extends BaseClient { Logger.logAction(Logger.LOG_MINOR, 'Realtime.close()', ''); this.connection.close(); } - - static Utils = Utils; - static ConnectionManager = ConnectionManager; - static ProtocolMessage = ProtocolMessage; } class Channels extends EventEmitter { diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index b52c280070..4b75d9bd94 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -1,6 +1,9 @@ import BaseRealtime from './baserealtime'; import ClientOptions from '../../types/ClientOptions'; import { allCommonModules } from './modulesmap'; +import * as Utils from '../util/utils'; +import ConnectionManager from '../transport/connectionmanager'; +import ProtocolMessage from '../types/protocolmessage'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -9,4 +12,8 @@ export class DefaultRealtime extends BaseRealtime { constructor(options: ClientOptions) { super(options, allCommonModules); } + + static Utils = Utils; + static ConnectionManager = ConnectionManager; + static ProtocolMessage = ProtocolMessage; } From abec79c6f0d6e1fa5ef3ce817d957454c754d8cf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 3 Aug 2023 05:43:43 -0300 Subject: [PATCH 154/468] Move storage of supportedTransports out of ConnectionManager class This allows this class to be tree-shaken. --- src/common/lib/transport/connectionmanager.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 3ec2f39bb4..992fe239b6 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -99,6 +99,8 @@ function decodeRecoveryKey(recoveryKey: string): RecoveryContext | null { } } +const supportedTransports: Record = {}; + export class TransportParams { options: ClientOptions; host: string | null; @@ -402,7 +404,9 @@ class ConnectionManager extends EventEmitter { * transport management *********************/ - static supportedTransports: Record = {}; + static get supportedTransports() { + return supportedTransports; + } static initTransports() { WebSocketTransport(ConnectionManager); From bfebd5efdab25a8dcb90123ed82b5fd399272329 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 1 Aug 2023 16:59:59 -0300 Subject: [PATCH 155/468] =?UTF-8?q?Make=20Platform.Crypto=E2=80=99s=20type?= =?UTF-8?q?=20a=20bit=20stronger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/platform.ts | 10 +++++----- src/common/types/ICryptoStatic.ts | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/common/platform.ts b/src/common/platform.ts index 41992c3bc5..b55e625a26 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -7,6 +7,7 @@ import IBufferUtils from './types/IBufferUtils'; import Transport from './lib/transport/transport'; import * as WebBufferUtils from '../platform/web/lib/util/bufferutils'; import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils'; +import { IUntypedCryptoStatic } from '../common/types/ICryptoStatic'; type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; @@ -23,12 +24,11 @@ export default class Platform { */ static BufferUtils: IBufferUtils; /* - This should be a class whose static methods implement the ICryptoStatic - interface, but (for the same reasons as described in the BufferUtils - comment above) Platform doesn’t currently allow us to express the - generic parameters, hence keeping the type as `any`. + We’d like this to be ICryptoStatic with the correct generic arguments, + but Platform doesn’t currently allow that, as described in the BufferUtils + comment above. */ - static Crypto: any; + static Crypto: IUntypedCryptoStatic | null; static Http: typeof IHttp; static Transports: Array<(connectionManager: typeof ConnectionManager) => Transport>; static Defaults: IDefaults; diff --git a/src/common/types/ICryptoStatic.ts b/src/common/types/ICryptoStatic.ts index 97dac8fcce..4115c5d248 100644 --- a/src/common/types/ICryptoStatic.ts +++ b/src/common/types/ICryptoStatic.ts @@ -13,3 +13,11 @@ export default interface ICryptoStatic ): IGetCipherReturnValue>; } + +/* + A less strongly typed version of ICryptoStatic to use until we + can make Platform a generic type (see comment there). + */ +export interface IUntypedCryptoStatic extends API.Types.Crypto { + getCipher(params: any): any; +} From 72c895d250a9a3e5993ed8d90f2f9624fdd0d649 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 3 Aug 2023 09:25:18 -0300 Subject: [PATCH 156/468] Make static BaseClient.Crypto consistent with public type The type of this property in ably.d.ts does not permit it to be absent or null. So, instead, throw an error if the user attempts to access it when not set (for example, in the noencryption variant of the library). --- src/common/lib/client/baseclient.ts | 14 +++++++++++++- src/platform/web-noencryption/index.ts | 3 --- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index aeac034d62..39b88edbf6 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -147,7 +147,19 @@ class BaseClient { } static Platform = Platform; - static Crypto?: typeof Platform.Crypto; + + private static _Crypto: typeof Platform.Crypto = null; + static get Crypto() { + if (this._Crypto === null) { + throw new Error('Encryption not enabled; use ably.encryption.js instead'); + } + + return this._Crypto; + } + static set Crypto(newValue: typeof Platform.Crypto) { + this._Crypto = newValue; + } + static Message = Message; static PresenceMessage = PresenceMessage; } diff --git a/src/platform/web-noencryption/index.ts b/src/platform/web-noencryption/index.ts index e10ae425c2..80748e6b9e 100644 --- a/src/platform/web-noencryption/index.ts +++ b/src/platform/web-noencryption/index.ts @@ -1,5 +1,4 @@ // Common -import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -25,8 +24,6 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -BaseClient.Crypto = null; - Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); From 86d623dfe704d08e6871763fa7c69730af8baf45 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 1 Aug 2023 10:48:16 -0300 Subject: [PATCH 157/468] Remove all usage of Platform.Crypto in top-level functions Preparation for #1396 (making crypto functionality tree-shakable). --- src/common/lib/client/channel.ts | 11 ++++++----- src/common/lib/types/message.ts | 12 ++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index d14f4c9752..67d824a6fc 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -12,6 +12,7 @@ import BaseClient from './baseclient'; import * as API from '../../../../ably'; import Platform from 'common/platform'; import Defaults from '../util/defaults'; +import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; interface RestHistoryParams { start?: number; @@ -30,11 +31,11 @@ function allEmptyIds(messages: Array) { }); } -function normaliseChannelOptions(options?: ChannelOptions) { +function normaliseChannelOptions(Crypto: IUntypedCryptoStatic | null, options?: ChannelOptions) { const channelOptions = options || {}; if (channelOptions.cipher) { - if (!Platform.Crypto) throw new Error('Encryption not enabled; use ably.encryption.js instead'); - const cipher = Platform.Crypto.getCipher(channelOptions.cipher); + if (!Crypto) throw new Error('Encryption not enabled; use ably.encryption.js instead'); + const cipher = Crypto.getCipher(channelOptions.cipher); channelOptions.cipher = cipher.cipherParams; channelOptions.channelCipher = cipher.cipher; } else if ('cipher' in channelOptions) { @@ -60,11 +61,11 @@ class Channel extends EventEmitter { this.name = name; this.basePath = '/channels/' + encodeURIComponent(name); this.presence = new Presence(this); - this.channelOptions = normaliseChannelOptions(channelOptions); + this.channelOptions = normaliseChannelOptions(Platform.Crypto, channelOptions); } setOptions(options?: ChannelOptions): void { - this.channelOptions = normaliseChannelOptions(options); + this.channelOptions = normaliseChannelOptions(Platform.Crypto, options); } history( diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 052df0b178..b04678f661 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -6,6 +6,7 @@ import PresenceMessage from './presencemessage'; import * as Utils from '../util/utils'; import { Bufferlike as BrowserBufferlike } from '../../../platform/web/lib/util/bufferutils'; import * as API from '../../../../ably'; +import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; export type CipherOptions = { channelCipher: { @@ -42,10 +43,13 @@ function normaliseContext(context: CipherOptions | EncodingDecodingContext | Cha return context as EncodingDecodingContext; } -function normalizeCipherOptions(options: API.Types.ChannelOptions | null): ChannelOptions { +function normalizeCipherOptions( + Crypto: IUntypedCryptoStatic | null, + options: API.Types.ChannelOptions | null +): ChannelOptions { if (options && options.cipher) { - if (!Platform.Crypto) throw new Error('Encryption not enabled; use ably.encryption.js instead'); - const cipher = Platform.Crypto.getCipher(options.cipher); + if (!Crypto) throw new Error('Encryption not enabled; use ably.encryption.js instead'); + const cipher = Crypto.getCipher(options.cipher); return { cipher: cipher.cipherParams, channelCipher: cipher.cipher, @@ -332,7 +336,7 @@ class Message { static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { const msg = Message.fromValues(encoded); - const options = normalizeCipherOptions(inputOptions ?? null); + const options = normalizeCipherOptions(Platform.Crypto, inputOptions ?? null); /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ try { From 342490d85564ac7ff9fe32ee999b4c27231db7e2 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 1 Aug 2023 11:32:26 -0300 Subject: [PATCH 158/468] Pull Message.fromEncoded* into standalone functions --- src/common/lib/types/message.ts | 46 ++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index b04678f661..c78c0c401f 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -75,6 +75,35 @@ function getMessageSize(msg: Message) { return size; } +export async function fromEncoded( + Crypto: IUntypedCryptoStatic | null, + encoded: unknown, + inputOptions?: API.Types.ChannelOptions +): Promise { + const msg = Message.fromValues(encoded); + const options = normalizeCipherOptions(Crypto, inputOptions ?? null); + /* if decoding fails at any point, catch and return the message decoded to + * the fullest extent possible */ + try { + await Message.decode(msg, options); + } catch (e) { + Logger.logAction(Logger.LOG_ERROR, 'Message.fromEncoded()', (e as Error).toString()); + } + return msg; +} + +export async function fromEncodedArray( + Crypto: IUntypedCryptoStatic | null, + encodedArray: Array, + options?: API.Types.ChannelOptions +): Promise { + return Promise.all( + encodedArray.map(function (encoded) { + return fromEncoded(Crypto, encoded, options); + }) + ); +} + class Message { name?: string; id?: string; @@ -335,24 +364,11 @@ class Message { } static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { - const msg = Message.fromValues(encoded); - const options = normalizeCipherOptions(Platform.Crypto, inputOptions ?? null); - /* if decoding fails at any point, catch and return the message decoded to - * the fullest extent possible */ - try { - await Message.decode(msg, options); - } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'Message.fromEncoded()', (e as Error).toString()); - } - return msg; + return fromEncoded(Platform.Crypto, encoded, inputOptions); } static async fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Promise { - return Promise.all( - encodedArray.map(function (encoded) { - return Message.fromEncoded(encoded, options); - }) - ); + return fromEncodedArray(Platform.Crypto, encodedArray, options); } /* This should be called on encode()d (and encrypt()d) Messages (as it From 02232d8231209f71dc373ad88466ebc8ad1dba64 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 1 Aug 2023 15:59:43 -0300 Subject: [PATCH 159/468] Rename CryptoFactory to createCryptoClass A bit clearer. Also use a named instead of default export. --- src/platform/nativescript/index.ts | 4 ++-- src/platform/nodejs/index.ts | 4 ++-- src/platform/nodejs/lib/util/crypto.ts | 4 ++-- src/platform/react-native/index.ts | 4 ++-- src/platform/web/index.ts | 4 ++-- src/platform/web/lib/util/crypto.ts | 4 ++-- src/platform/web/modules.ts | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index be9941dc66..53733753c2 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; // @ts-ignore -import CryptoFactory from '../web/lib/util/crypto'; +import { createCryptoClass } from '../web/lib/util/crypto'; import Http from '../web/lib/util/http'; // @ts-ignore import Config from './config'; @@ -21,7 +21,7 @@ import WebStorage from './lib/util/webstorage'; import PlatformDefaults from '../web/lib/util/defaults'; import msgpack from '../web/lib/util/msgpack'; -const Crypto = CryptoFactory(Config, BufferUtils); +const Crypto = createCryptoClass(Config, BufferUtils); Platform.Crypto = Crypto; Platform.BufferUtils = BufferUtils; diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index eb67752641..3c93395f4a 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; // @ts-ignore -import CryptoFactory from './lib/util/crypto'; +import { createCryptoClass } from './lib/util/crypto'; import Http from './lib/util/http'; import Config from './config'; // @ts-ignore @@ -17,7 +17,7 @@ import Logger from '../../common/lib/util/logger'; import { getDefaults } from '../../common/lib/util/defaults'; import PlatformDefaults from './lib/util/defaults'; -const Crypto = CryptoFactory(BufferUtils); +const Crypto = createCryptoClass(BufferUtils); Platform.Crypto = Crypto; Platform.BufferUtils = BufferUtils as typeof Platform.BufferUtils; diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index 1d99b060af..62fe8303b8 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -18,7 +18,7 @@ type OutputCiphertext = Buffer; type InputCiphertext = CryptoDataTypes.InputCiphertext; type OutputPlaintext = Buffer; -var CryptoFactory = function (bufferUtils: typeof BufferUtils) { +var createCryptoClass = function (bufferUtils: typeof BufferUtils) { var DEFAULT_ALGORITHM = 'aes'; var DEFAULT_KEYLENGTH = 256; // bits var DEFAULT_MODE = 'cbc'; @@ -276,4 +276,4 @@ var CryptoFactory = function (bufferUtils: typeof BufferUtils) { return Crypto; }; -export default CryptoFactory; +export { createCryptoClass }; diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index 88dae850da..ae99b67ab8 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; // @ts-ignore -import CryptoFactory from '../web/lib/util/crypto'; +import { createCryptoClass } from '../web/lib/util/crypto'; import Http from '../web/lib/util/http'; import configFactory from './config'; // @ts-ignore @@ -21,7 +21,7 @@ import msgpack from '../web/lib/util/msgpack'; const Config = configFactory(BufferUtils); -const Crypto = CryptoFactory(Config, BufferUtils); +const Crypto = createCryptoClass(Config, BufferUtils); Platform.Crypto = Crypto; Platform.BufferUtils = BufferUtils; diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index ea6fb1b53e..b340425c51 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; // @ts-ignore -import CryptoFactory from './lib/util/crypto'; +import { createCryptoClass } from './lib/util/crypto'; import Http from './lib/util/http'; import Config from './config'; // @ts-ignore @@ -19,7 +19,7 @@ import WebStorage from './lib/util/webstorage'; import PlatformDefaults from './lib/util/defaults'; import msgpack from './lib/util/msgpack'; -const Crypto = CryptoFactory(Config, BufferUtils); +const Crypto = createCryptoClass(Config, BufferUtils); Platform.Crypto = Crypto; Platform.BufferUtils = BufferUtils; diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 23c572d9dd..eff091f094 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -16,7 +16,7 @@ type OutputCiphertext = ArrayBuffer; type InputCiphertext = CryptoDataTypes.InputCiphertext; type OutputPlaintext = ArrayBuffer; -var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof BufferUtils) { +var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof BufferUtils) { var DEFAULT_ALGORITHM = 'aes'; var DEFAULT_KEYLENGTH = 256; // bits var DEFAULT_MODE = 'cbc'; @@ -318,4 +318,4 @@ var CryptoFactory = function (config: IPlatformConfig, bufferUtils: typeof Buffe return Crypto; }; -export default CryptoFactory; +export { createCryptoClass }; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index d924eab04e..ecb4564ff3 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; // @ts-ignore -import CryptoFactory from './lib/util/crypto'; +import { createCryptoClass } from './lib/util/crypto'; import Http from './lib/util/http'; import Config from './config'; // @ts-ignore @@ -18,7 +18,7 @@ import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from './lib/util/webstorage'; import PlatformDefaults from './lib/util/defaults'; -const Crypto = CryptoFactory(Config, BufferUtils); +const Crypto = createCryptoClass(Config, BufferUtils); Platform.Crypto = Crypto; Platform.BufferUtils = BufferUtils; From c859b90a5dd7818339c170ec0b3147f0261659ee Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 3 Aug 2023 10:14:28 -0300 Subject: [PATCH 160/468] Remove static Crypto property from BaseClient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have decided that we don’t want the tree-shakable version of the library to make use of static properties or methods, as they are not amenable to tree-shaking. There’s also no obvious way to set a static property in the API that we’ve chosen for tree-shaking (since we pass dependencies into the class’s constructor). Instead, the tree-shakable version of the library now exports standalone functions which replace the removed static methods. --- scripts/moduleReport.js | 15 +++++++++------ src/common/lib/client/baseclient.ts | 13 ------------- src/common/lib/client/defaultrealtime.ts | 13 +++++++++++++ src/common/lib/client/defaultrest.ts | 13 +++++++++++++ src/platform/nativescript/index.ts | 5 +++-- src/platform/nodejs/index.ts | 5 +++-- src/platform/react-native/index.ts | 5 +++-- src/platform/web/index.ts | 5 +++-- src/platform/web/modules.ts | 4 +--- src/platform/web/modules/crypto.ts | 10 ++++++++++ test/browser/modules.test.js | 15 ++++++++++++++- 11 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 src/platform/web/modules/crypto.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 61bdff99e3..8c71c3a406 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -3,6 +3,9 @@ const esbuild = require('esbuild'); // List of all modules accepted in ModulesMap const moduleNames = ['Rest']; +// List of all free-standing functions exported by the library +const functionNames = ['generateRandomKey', 'getDefaultCryptoParams']; + function formatBytes(bytes) { const kibibytes = bytes / 1024; const formatted = kibibytes.toFixed(2); @@ -35,15 +38,15 @@ const errors = []; // First display the size of the base client console.log(`${baseClient}: ${formatBytes(baseClientSize)}`); - // Then display the size of each module together with the base client - moduleNames.forEach((moduleName) => { - const size = getImportSize([baseClient, moduleName]); - console.log(`${baseClient} + ${moduleName}: ${formatBytes(size)}`); + // Then display the size of each export together with the base client + [...moduleNames, ...functionNames].forEach((exportName) => { + const size = getImportSize([baseClient, exportName]); + console.log(`${baseClient} + ${exportName}: ${formatBytes(size)}`); - if (!(baseClientSize < size) && !(baseClient === 'BaseRest' && moduleName === 'Rest')) { + if (!(baseClientSize < size) && !(baseClient === 'BaseRest' && exportName === 'Rest')) { // Emit an error if adding the module does not increase the bundle size // (this means that the module is not being tree-shaken correctly). - errors.push(new Error(`Adding ${moduleName} to ${baseClient} does not increase the bundle size.`)); + errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); } }); }); diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 39b88edbf6..be62f91cd0 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -147,19 +147,6 @@ class BaseClient { } static Platform = Platform; - - private static _Crypto: typeof Platform.Crypto = null; - static get Crypto() { - if (this._Crypto === null) { - throw new Error('Encryption not enabled; use ably.encryption.js instead'); - } - - return this._Crypto; - } - static set Crypto(newValue: typeof Platform.Crypto) { - this._Crypto = newValue; - } - static Message = Message; static PresenceMessage = PresenceMessage; } diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 4b75d9bd94..8c58501b04 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -4,6 +4,7 @@ import { allCommonModules } from './modulesmap'; import * as Utils from '../util/utils'; import ConnectionManager from '../transport/connectionmanager'; import ProtocolMessage from '../types/protocolmessage'; +import Platform from 'common/platform'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -16,4 +17,16 @@ export class DefaultRealtime extends BaseRealtime { static Utils = Utils; static ConnectionManager = ConnectionManager; static ProtocolMessage = ProtocolMessage; + + private static _Crypto: typeof Platform.Crypto = null; + static get Crypto() { + if (this._Crypto === null) { + throw new Error('Encryption not enabled; use ably.encryption.js instead'); + } + + return this._Crypto; + } + static set Crypto(newValue: typeof Platform.Crypto) { + this._Crypto = newValue; + } } diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index 431e4547d8..4e46527a63 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -1,6 +1,7 @@ import { BaseRest } from './baserest'; import ClientOptions from '../../types/ClientOptions'; import { allCommonModules } from './modulesmap'; +import Platform from 'common/platform'; /** `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -9,4 +10,16 @@ export class DefaultRest extends BaseRest { constructor(options: ClientOptions | string) { super(options, allCommonModules); } + + private static _Crypto: typeof Platform.Crypto = null; + static get Crypto() { + if (this._Crypto === null) { + throw new Error('Encryption not enabled; use ably.encryption.js instead'); + } + + return this._Crypto; + } + static set Crypto(newValue: typeof Platform.Crypto) { + this._Crypto = newValue; + } } diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index 53733753c2..bdb55c8a34 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -1,5 +1,4 @@ // Common -import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -30,7 +29,9 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -BaseClient.Crypto = Crypto; +for (const clientClass of [DefaultRest, DefaultRealtime]) { + clientClass.Crypto = Crypto; +} Logger.initLogHandlers(); diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index 3c93395f4a..ba683f9bad 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -1,5 +1,4 @@ // Common -import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -26,7 +25,9 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = null; -BaseClient.Crypto = Crypto; +for (const clientClass of [DefaultRest, DefaultRealtime]) { + clientClass.Crypto = Crypto; +} Logger.initLogHandlers(); diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index ae99b67ab8..478ee9664d 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -1,5 +1,4 @@ // Common -import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -30,7 +29,9 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -BaseClient.Crypto = Crypto; +for (const clientClass of [DefaultRest, DefaultRealtime]) { + clientClass.Crypto = Crypto; +} Logger.initLogHandlers(); diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index b340425c51..9fa12989e4 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -1,5 +1,4 @@ // Common -import BaseClient from '../../common/lib/client/baseclient'; import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; @@ -28,7 +27,9 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -BaseClient.Crypto = Crypto; +for (const clientClass of [DefaultRest, DefaultRealtime]) { + clientClass.Crypto = Crypto; +} Logger.initLogHandlers(); diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index ecb4564ff3..39fe3e6319 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -1,5 +1,4 @@ // Common -import BaseClient from 'common/lib/client/baseclient'; import { BaseRest } from '../../common/lib/client/baserest'; import BaseRealtime from '../../common/lib/client/baserealtime'; import Platform from '../../common/platform'; @@ -27,8 +26,6 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; -BaseClient.Crypto = Crypto; - Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); @@ -46,5 +43,6 @@ if (Platform.Config.noUpgrade) { Platform.Defaults.upgradeTransports = []; } +export * from './modules/crypto'; export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/src/platform/web/modules/crypto.ts b/src/platform/web/modules/crypto.ts new file mode 100644 index 0000000000..7c9935b05a --- /dev/null +++ b/src/platform/web/modules/crypto.ts @@ -0,0 +1,10 @@ +import * as API from '../../../../ably'; +import Platform from 'common/platform'; + +export const generateRandomKey: API.Types.Crypto['generateRandomKey'] = (keyLength) => { + return Platform.Crypto!.generateRandomKey(keyLength); +}; + +export const getDefaultCryptoParams: API.Types.Crypto['getDefaultParams'] = (params) => { + return Platform.Crypto!.getDefaultParams(params); +}; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 6ccbf64bbc..c21f5201ac 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -1,4 +1,4 @@ -import { BaseRest, BaseRealtime, Rest } from '../../build/modules/index.js'; +import { BaseRest, BaseRealtime, Rest, generateRandomKey, getDefaultCryptoParams } from '../../build/modules/index.js'; describe('browser/modules', function () { this.timeout(10 * 1000); @@ -44,4 +44,17 @@ describe('browser/modules', function () { }); }); }); + + describe('Crypto standalone functions', () => { + it('generateRandomKey', async () => { + const key = await generateRandomKey(); + expect(key).to.be.an('ArrayBuffer'); + }); + + it('getDefaultCryptoParams', async () => { + const key = await generateRandomKey(); + const params = getDefaultCryptoParams({ key }); + expect(params).to.be.an('object'); + }); + }); }); From 5cc2c13f7e5dd8eee1fc23ca64d9c4539336732e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 15 Aug 2023 17:06:21 -0300 Subject: [PATCH 161/468] Extract testMessageEquality to helper I want to use it in a second test file. --- test/common/modules/shared_helper.js | 30 +++++++++++++++++++++++++++- test/realtime/crypto.test.js | 22 +------------------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index c3265b1b26..4d8581ab22 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -8,9 +8,12 @@ define([ 'test/common/modules/client_module', 'test/common/modules/testapp_manager', 'async', -], function (testAppModule, clientModule, testAppManager, async) { + 'chai', +], function (testAppModule, clientModule, testAppManager, async, chai) { var utils = clientModule.Ably.Realtime.Utils; var platform = clientModule.Ably.Realtime.Platform; + var BufferUtils = platform.BufferUtils; + var expect = chai.expect; clientModule.Ably.Realtime.ConnectionManager.initTransports(); var availableTransports = utils.keysArray(clientModule.Ably.Realtime.ConnectionManager.supportedTransports), bestTransport = availableTransports[0], @@ -222,6 +225,30 @@ define([ return Math.random().toString().slice(2); } + function testMessageEquality(one, two) { + // treat `null` same as `undefined` (using ==, rather than ===) + expect(one.encoding == two.encoding, "Encoding mismatch ('" + one.encoding + "' != '" + two.encoding + "').").to.be + .ok; + + if (typeof one.data === 'string' && typeof two.data === 'string') { + expect(one.data === two.data, 'String data contents mismatch.').to.be.ok; + return; + } + + if (BufferUtils.isBuffer(one.data) && BufferUtils.isBuffer(two.data)) { + expect(BufferUtils.areBuffersEqual(one.data, two.data), 'Buffer data contents mismatch.').to.equal(true); + return; + } + + var json1 = JSON.stringify(one.data); + var json2 = JSON.stringify(two.data); + if (null === json1 || undefined === json1 || null === json2 || undefined === json2) { + expect(false, 'JSON stringify failed.').to.be.ok; + return; + } + expect(json1 === json2, 'JSON data contents mismatch.').to.be.ok; + } + var exports = { setupApp: testAppModule.setup, tearDownApp: testAppModule.tearDown, @@ -255,6 +282,7 @@ define([ arrFilter: arrFilter, whenPromiseSettles: whenPromiseSettles, randomString: randomString, + testMessageEquality: testMessageEquality, }; if (typeof window !== 'undefined') { diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 8952bcf71e..407dbc6e25 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -25,27 +25,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function testMessageEquality(done, one, two) { try { - // treat `null` same as `undefined` (using ==, rather than ===) - expect(one.encoding == two.encoding, "Encoding mismatch ('" + one.encoding + "' != '" + two.encoding + "').").to - .be.ok; - - if (typeof one.data === 'string' && typeof two.data === 'string') { - expect(one.data === two.data, 'String data contents mismatch.').to.be.ok; - return; - } - - if (BufferUtils.isBuffer(one.data) && BufferUtils.isBuffer(two.data)) { - expect(BufferUtils.areBuffersEqual(one.data, two.data), 'Buffer data contents mismatch.').to.equal(true); - return; - } - - var json1 = JSON.stringify(one.data); - var json2 = JSON.stringify(two.data); - if (null === json1 || undefined === json1 || null === json2 || undefined === json2) { - expect(false, 'JSON stringify failed.').to.be.ok; - return; - } - expect(json1 === json2, 'JSON data contents mismatch.').to.be.ok; + helper.testMessageEquality(one, two); } catch (err) { done(err); } From 601b46b85196a175e71bea904c55ff54fc846a51 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 15 Aug 2023 11:45:07 -0300 Subject: [PATCH 162/468] Remove Message-related static things in tree-shakable library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes the static BaseClient.Message property and the static Message.{fromEncoded, fromEncodedArray} methods. The motivation, and the approach taken for the tree-shakable and non tree-shakable versions of the library, are as in c859b90. Note that instead of fromEncoded and fromEncodedArray, the standalone functions are named decodeMessage and decodeMessages — after some discussion we decided that this naming was more consistent with the kind of naming used for standalone functions in JavaScript libraries. --- scripts/moduleReport.js | 2 +- src/common/lib/client/baseclient.ts | 2 - src/common/lib/client/defaultrealtime.ts | 3 + src/common/lib/client/defaultrest.ts | 3 + src/common/lib/types/defaultmessage.ts | 16 +++++ src/common/lib/types/message.ts | 8 --- src/platform/web/modules.ts | 1 + src/platform/web/modules/message.ts | 13 ++++ test/browser/modules.test.js | 83 +++++++++++++++++++++++- 9 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 src/common/lib/types/defaultmessage.ts create mode 100644 src/platform/web/modules/message.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 8c71c3a406..54c50af390 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -4,7 +4,7 @@ const esbuild = require('esbuild'); const moduleNames = ['Rest']; // List of all free-standing functions exported by the library -const functionNames = ['generateRandomKey', 'getDefaultCryptoParams']; +const functionNames = ['generateRandomKey', 'getDefaultCryptoParams', 'decodeMessage', 'decodeMessages']; function formatBytes(bytes) { const kibibytes = bytes / 1024; diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index be62f91cd0..1fe6aeb7b5 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -10,7 +10,6 @@ import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOption import * as API from '../../../../ably'; import Platform from '../../platform'; -import Message from '../types/message'; import PresenceMessage from '../types/presencemessage'; import { ModulesMap } from './modulesmap'; import { Rest } from './rest'; @@ -147,7 +146,6 @@ class BaseClient { } static Platform = Platform; - static Message = Message; static PresenceMessage = PresenceMessage; } diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 8c58501b04..1cfbab3ab7 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -5,6 +5,7 @@ import * as Utils from '../util/utils'; import ConnectionManager from '../transport/connectionmanager'; import ProtocolMessage from '../types/protocolmessage'; import Platform from 'common/platform'; +import { DefaultMessage } from '../types/defaultmessage'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -29,4 +30,6 @@ export class DefaultRealtime extends BaseRealtime { static set Crypto(newValue: typeof Platform.Crypto) { this._Crypto = newValue; } + + static Message = DefaultMessage; } diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index 4e46527a63..324f6d8756 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -2,6 +2,7 @@ import { BaseRest } from './baserest'; import ClientOptions from '../../types/ClientOptions'; import { allCommonModules } from './modulesmap'; import Platform from 'common/platform'; +import { DefaultMessage } from '../types/defaultmessage'; /** `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -22,4 +23,6 @@ export class DefaultRest extends BaseRest { static set Crypto(newValue: typeof Platform.Crypto) { this._Crypto = newValue; } + + static Message = DefaultMessage; } diff --git a/src/common/lib/types/defaultmessage.ts b/src/common/lib/types/defaultmessage.ts new file mode 100644 index 0000000000..3ef7dab056 --- /dev/null +++ b/src/common/lib/types/defaultmessage.ts @@ -0,0 +1,16 @@ +import Message, { fromEncoded, fromEncodedArray } from './message'; +import * as API from '../../../../ably'; +import Platform from 'common/platform'; + +/** + `DefaultMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `Message` static property. It introduces the static methods described in the `MessageStatic` interface of the public API of the non tree-shakable version of the library. + */ +export class DefaultMessage extends Message { + static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { + return fromEncoded(Platform.Crypto, encoded, inputOptions); + } + + static async fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Promise { + return fromEncodedArray(Platform.Crypto, encodedArray, options); + } +} diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index c78c0c401f..35697038b9 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -363,14 +363,6 @@ class Message { return result; } - static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { - return fromEncoded(Platform.Crypto, encoded, inputOptions); - } - - static async fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Promise { - return fromEncodedArray(Platform.Crypto, encodedArray, options); - } - /* This should be called on encode()d (and encrypt()d) Messages (as it * assumes the data is a string or buffer) */ static getMessagesSize(messages: Message[]): number { diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 39fe3e6319..ed8456d789 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -44,5 +44,6 @@ if (Platform.Config.noUpgrade) { } export * from './modules/crypto'; +export * from './modules/message'; export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/src/platform/web/modules/message.ts b/src/platform/web/modules/message.ts new file mode 100644 index 0000000000..b908dce825 --- /dev/null +++ b/src/platform/web/modules/message.ts @@ -0,0 +1,13 @@ +import * as API from '../../../../ably'; +import Platform from 'common/platform'; +import { fromEncoded, fromEncodedArray } from '../../../common/lib/types/message'; + +// The type assertions for the decode* functions below are due to https://github.com/ably/ably-js/issues/1421 + +export const decodeMessage = ((obj, options) => { + return fromEncoded(Platform.Crypto, obj, options); +}) as API.Types.MessageStatic['fromEncoded']; + +export const decodeMessages = ((obj, options) => { + return fromEncodedArray(Platform.Crypto, obj, options); +}) as API.Types.MessageStatic['fromEncodedArray']; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index c21f5201ac..8d13564e74 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -1,12 +1,33 @@ -import { BaseRest, BaseRealtime, Rest, generateRandomKey, getDefaultCryptoParams } from '../../build/modules/index.js'; +import { + BaseRest, + BaseRealtime, + Rest, + generateRandomKey, + getDefaultCryptoParams, + decodeMessage, + decodeMessages, +} from '../../build/modules/index.js'; describe('browser/modules', function () { this.timeout(10 * 1000); const expect = chai.expect; + const BufferUtils = BaseRest.Platform.BufferUtils; let ablyClientOptions; + let testResourcesPath; + let loadTestData; + let testMessageEquality; before((done) => { ablyClientOptions = window.ablyHelpers.ablyClientOptions; + testResourcesPath = window.ablyHelpers.testResourcesPath; + testMessageEquality = window.ablyHelpers.testMessageEquality; + + loadTestData = async (dataPath) => { + return new Promise((resolve, reject) => { + window.ablyHelpers.loadTestData(dataPath, (err, testData) => (err ? reject(err) : resolve(testData))); + }); + }; + window.ablyHelpers.setupApp(done); }); @@ -57,4 +78,64 @@ describe('browser/modules', function () { expect(params).to.be.an('object'); }); }); + + describe('Message standalone functions', () => { + describe('decodeMessage', () => { + it('decodes a message’s data', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + + const item = testData.items[1]; + const decoded = await decodeMessage(item.encoded); + + expect(decoded.data).to.be.an('ArrayBuffer'); + }); + + it('decrypts a message', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); + + for (const item of testData.items) { + const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ + decodeMessage(item.encoded), + decodeMessage(item.encrypted, { cipher: { key, iv } }), + ]); + + testMessageEquality(decodedFromEncoded, decodedFromEncrypted); + } + }); + }); + + describe('decodeMessages', () => { + it('decodes messages’ data', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + + const items = [testData.items[1], testData.items[3]]; + const decoded = await decodeMessages(items.map((item) => item.encoded)); + + expect(decoded[0].data).to.be.an('ArrayBuffer'); + expect(decoded[1].data).to.be.an('array'); + }); + + it('decrypts messages', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); + + const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ + decodeMessages(testData.items.map((item) => item.encoded)), + decodeMessages( + testData.items.map((item) => item.encrypted), + { cipher: { key, iv } } + ), + ]); + + for (let i = 0; i < decodedFromEncoded.length; i++) { + testMessageEquality(decodedFromEncoded[i], decodedFromEncrypted[i]); + } + }); + }); + }); }); From 63f5524e85807221e6d702e0fa2d150d8cc5ddc4 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 1 Aug 2023 14:58:32 -0300 Subject: [PATCH 163/468] Make crypto functionality tree-shakable We move the crypto functionality into a tree-shakable Crypto module. Then, we split the decodeMessage* functions introduced in 601b46b in two; we introduce new decodeEncryptedMessage* variants which import the Crypto module and are hence able to decrypt encrypted messages, and we change the existing functions to not import the Crypto module and to fail if they are given cipher options. Resolves #1396. --- scripts/moduleReport.js | 42 +++++++- src/common/lib/client/baseclient.ts | 6 +- src/common/lib/client/channel.ts | 7 +- src/common/lib/client/defaultrealtime.ts | 2 +- src/common/lib/client/defaultrest.ts | 2 +- src/common/lib/client/modulesmap.ts | 2 + src/common/lib/types/message.ts | 2 +- src/common/lib/util/utils.ts | 5 + src/platform/web/modules.ts | 4 - src/platform/web/modules/crypto.ts | 10 +- src/platform/web/modules/message.ts | 14 ++- test/browser/modules.test.js | 126 +++++++++++++++++++++-- 12 files changed, 191 insertions(+), 31 deletions(-) diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 54c50af390..1937afc34d 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -1,10 +1,18 @@ const esbuild = require('esbuild'); // List of all modules accepted in ModulesMap -const moduleNames = ['Rest']; +const moduleNames = ['Rest', 'Crypto']; -// List of all free-standing functions exported by the library -const functionNames = ['generateRandomKey', 'getDefaultCryptoParams', 'decodeMessage', 'decodeMessages']; +// List of all free-standing functions exported by the library along with the +// ModulesMap entries that we expect them to transitively import +const functions = [ + { name: 'generateRandomKey', transitiveImports: ['Crypto'] }, + { name: 'getDefaultCryptoParams', transitiveImports: ['Crypto'] }, + { name: 'decodeMessage', transitiveImports: [] }, + { name: 'decodeEncryptedMessage', transitiveImports: ['Crypto'] }, + { name: 'decodeMessages', transitiveImports: [] }, + { name: 'decodeEncryptedMessages', transitiveImports: ['Crypto'] }, +]; function formatBytes(bytes) { const kibibytes = bytes / 1024; @@ -39,7 +47,7 @@ const errors = []; console.log(`${baseClient}: ${formatBytes(baseClientSize)}`); // Then display the size of each export together with the base client - [...moduleNames, ...functionNames].forEach((exportName) => { + [...moduleNames, ...Object.values(functions).map((functionData) => functionData.name)].forEach((exportName) => { const size = getImportSize([baseClient, exportName]); console.log(`${baseClient} + ${exportName}: ${formatBytes(size)}`); @@ -51,6 +59,32 @@ const errors = []; }); }); +for (const functionData of functions) { + const { name: functionName, transitiveImports } = functionData; + + // First display the size of the function + const standaloneSize = getImportSize([functionName]); + console.log(`${functionName}: ${formatBytes(standaloneSize)}`); + + // Then display the size of the function together with the modules we expect + // it to transitively import + if (transitiveImports.length > 0) { + const withTransitiveImportsSize = getImportSize([functionName, ...transitiveImports]); + console.log(`${functionName} + ${transitiveImports.join(' + ')}: ${formatBytes(withTransitiveImportsSize)}`); + + if (withTransitiveImportsSize > standaloneSize) { + // Emit an error if the bundle size is increased by adding the modules + // that we expect this function to have transitively imported anyway. + // This seemed like a useful sense check, but it might need tweaking in + // the future if we make future optimisations that mean that the + // standalone functions don’t necessarily import the whole module. + errors.push( + new Error(`Adding ${transitiveImports.join(' + ')} to ${functionName} unexpectedly increases the bundle size.`) + ); + } + } +} + if (errors.length > 0) { for (const error of errors) { console.log(error.message); diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 1fe6aeb7b5..da5ee730e3 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -13,6 +13,8 @@ import Platform from '../../platform'; import PresenceMessage from '../types/presencemessage'; import { ModulesMap } from './modulesmap'; import { Rest } from './rest'; +import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; +import { throwMissingModuleError } from '../util/utils'; type BatchResult = API.Types.BatchResult; type BatchPublishSpec = API.Types.BatchPublishSpec; @@ -37,6 +39,7 @@ class BaseClient { auth: Auth; private readonly _rest: Rest | null; + readonly _Crypto: IUntypedCryptoStatic | null; constructor(options: ClientOptions | string, modules: ModulesMap) { if (!options) { @@ -87,11 +90,12 @@ class BaseClient { this.auth = new Auth(this, normalOptions); this._rest = modules.Rest ? new modules.Rest(this) : null; + this._Crypto = modules.Crypto ?? null; } private get rest(): Rest { if (!this._rest) { - throw new ErrorInfo('Rest module not provided', 400, 40000); + throwMissingModuleError('Rest'); } return this._rest; } diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index 67d824a6fc..370c32993e 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -10,7 +10,6 @@ import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseClient from './baseclient'; import * as API from '../../../../ably'; -import Platform from 'common/platform'; import Defaults from '../util/defaults'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; @@ -34,7 +33,7 @@ function allEmptyIds(messages: Array) { function normaliseChannelOptions(Crypto: IUntypedCryptoStatic | null, options?: ChannelOptions) { const channelOptions = options || {}; if (channelOptions.cipher) { - if (!Crypto) throw new Error('Encryption not enabled; use ably.encryption.js instead'); + if (!Crypto) Utils.throwMissingModuleError('Crypto'); const cipher = Crypto.getCipher(channelOptions.cipher); channelOptions.cipher = cipher.cipherParams; channelOptions.channelCipher = cipher.cipher; @@ -61,11 +60,11 @@ class Channel extends EventEmitter { this.name = name; this.basePath = '/channels/' + encodeURIComponent(name); this.presence = new Presence(this); - this.channelOptions = normaliseChannelOptions(Platform.Crypto, channelOptions); + this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, channelOptions); } setOptions(options?: ChannelOptions): void { - this.channelOptions = normaliseChannelOptions(Platform.Crypto, options); + this.channelOptions = normaliseChannelOptions(this.client._Crypto ?? null, options); } history( diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 1cfbab3ab7..e8b66f878e 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -12,7 +12,7 @@ import { DefaultMessage } from '../types/defaultmessage'; */ export class DefaultRealtime extends BaseRealtime { constructor(options: ClientOptions) { - super(options, allCommonModules); + super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined }); } static Utils = Utils; diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index 324f6d8756..1b7df607a6 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -9,7 +9,7 @@ import { DefaultMessage } from '../types/defaultmessage'; */ export class DefaultRest extends BaseRest { constructor(options: ClientOptions | string) { - super(options, allCommonModules); + super(options, { ...allCommonModules, Crypto: DefaultRest.Crypto ?? undefined }); } private static _Crypto: typeof Platform.Crypto = null; diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index c56c2d98e7..4133bcc3a3 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -1,7 +1,9 @@ import { Rest } from './rest'; +import { IUntypedCryptoStatic } from '../../types/ICryptoStatic'; export interface ModulesMap { Rest?: typeof Rest; + Crypto?: IUntypedCryptoStatic; } export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 35697038b9..692c5e069f 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -48,7 +48,7 @@ function normalizeCipherOptions( options: API.Types.ChannelOptions | null ): ChannelOptions { if (options && options.cipher) { - if (!Crypto) throw new Error('Encryption not enabled; use ably.encryption.js instead'); + if (!Crypto) Utils.throwMissingModuleError('Crypto'); const cipher = Crypto.getCipher(options.cipher); return { cipher: cipher.cipherParams, diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 03d546b0a8..788f54d502 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -1,5 +1,6 @@ import Platform from 'common/platform'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; +import { ModulesMap } from '../client/modulesmap'; function randomPosn(arrOrStr: Array | string) { return Math.floor(Math.random() * arrOrStr.length); @@ -551,3 +552,7 @@ export function arrEquals(a: any[], b: any[]) { }) ); } + +export function throwMissingModuleError(moduleName: keyof ModulesMap): never { + throw new ErrorInfo(`${moduleName} module not provided`, 400, 40000); +} diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index ed8456d789..ed91109bfe 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -7,7 +7,6 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; // @ts-ignore -import { createCryptoClass } from './lib/util/crypto'; import Http from './lib/util/http'; import Config from './config'; // @ts-ignore @@ -17,9 +16,6 @@ import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from './lib/util/webstorage'; import PlatformDefaults from './lib/util/defaults'; -const Crypto = createCryptoClass(Config, BufferUtils); - -Platform.Crypto = Crypto; Platform.BufferUtils = BufferUtils; Platform.Http = Http; Platform.Config = Config; diff --git a/src/platform/web/modules/crypto.ts b/src/platform/web/modules/crypto.ts index 7c9935b05a..2c65d23eb1 100644 --- a/src/platform/web/modules/crypto.ts +++ b/src/platform/web/modules/crypto.ts @@ -1,10 +1,14 @@ +import BufferUtils from '../lib/util/bufferutils'; +import { createCryptoClass } from '../lib/util/crypto'; +import Config from '../config'; import * as API from '../../../../ably'; -import Platform from 'common/platform'; + +export const Crypto = /* @__PURE__@ */ createCryptoClass(Config, BufferUtils); export const generateRandomKey: API.Types.Crypto['generateRandomKey'] = (keyLength) => { - return Platform.Crypto!.generateRandomKey(keyLength); + return Crypto.generateRandomKey(keyLength); }; export const getDefaultCryptoParams: API.Types.Crypto['getDefaultParams'] = (params) => { - return Platform.Crypto!.getDefaultParams(params); + return Crypto.getDefaultParams(params); }; diff --git a/src/platform/web/modules/message.ts b/src/platform/web/modules/message.ts index b908dce825..de8b2ab4f9 100644 --- a/src/platform/web/modules/message.ts +++ b/src/platform/web/modules/message.ts @@ -1,13 +1,21 @@ import * as API from '../../../../ably'; -import Platform from 'common/platform'; +import { Crypto } from './crypto'; import { fromEncoded, fromEncodedArray } from '../../../common/lib/types/message'; // The type assertions for the decode* functions below are due to https://github.com/ably/ably-js/issues/1421 export const decodeMessage = ((obj, options) => { - return fromEncoded(Platform.Crypto, obj, options); + return fromEncoded(null, obj, options); +}) as API.Types.MessageStatic['fromEncoded']; + +export const decodeEncryptedMessage = ((obj, options) => { + return fromEncoded(Crypto, obj, options); }) as API.Types.MessageStatic['fromEncoded']; export const decodeMessages = ((obj, options) => { - return fromEncodedArray(Platform.Crypto, obj, options); + return fromEncodedArray(null, obj, options); +}) as API.Types.MessageStatic['fromEncodedArray']; + +export const decodeEncryptedMessages = ((obj, options) => { + return fromEncodedArray(Crypto, obj, options); }) as API.Types.MessageStatic['fromEncodedArray']; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 8d13564e74..72aabe2c8e 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -5,7 +5,10 @@ import { generateRandomKey, getDefaultCryptoParams, decodeMessage, + decodeEncryptedMessage, decodeMessages, + decodeEncryptedMessages, + Crypto, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -80,14 +83,40 @@ describe('browser/modules', function () { }); describe('Message standalone functions', () => { + async function testDecodesMessageData(functionUnderTest) { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + + const item = testData.items[1]; + const decoded = await functionUnderTest(item.encoded); + + expect(decoded.data).to.be.an('ArrayBuffer'); + } + describe('decodeMessage', () => { it('decodes a message’s data', async () => { + testDecodesMessageData(decodeMessage); + }); + + it('throws an error when given channel options with a cipher', async () => { const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); - const item = testData.items[1]; - const decoded = await decodeMessage(item.encoded); + let thrownError = null; + try { + await decodeMessage(testData.items[0].encrypted, { cipher: { key, iv } }); + } catch (error) { + thrownError = error; + } + + expect(thrownError).not.to.be.null; + expect(thrownError.message).to.equal('Crypto module not provided'); + }); + }); - expect(decoded.data).to.be.an('ArrayBuffer'); + describe('decodeEncryptedMessage', async () => { + it('decodes a message’s data', async () => { + testDecodesMessageData(decodeEncryptedMessage); }); it('decrypts a message', async () => { @@ -99,7 +128,7 @@ describe('browser/modules', function () { for (const item of testData.items) { const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ decodeMessage(item.encoded), - decodeMessage(item.encrypted, { cipher: { key, iv } }), + decodeEncryptedMessage(item.encrypted, { cipher: { key, iv } }), ]); testMessageEquality(decodedFromEncoded, decodedFromEncrypted); @@ -107,15 +136,44 @@ describe('browser/modules', function () { }); }); + async function testDecodesMessagesData(functionUnderTest) { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + + const items = [testData.items[1], testData.items[3]]; + const decoded = await functionUnderTest(items.map((item) => item.encoded)); + + expect(decoded[0].data).to.be.an('ArrayBuffer'); + expect(decoded[1].data).to.be.an('array'); + } + describe('decodeMessages', () => { it('decodes messages’ data', async () => { + testDecodesMessagesData(decodeMessages); + }); + + it('throws an error when given channel options with a cipher', async () => { const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); + + let thrownError = null; + try { + await decodeMessages( + testData.items.map((item) => item.encrypted), + { cipher: { key, iv } } + ); + } catch (error) { + thrownError = error; + } - const items = [testData.items[1], testData.items[3]]; - const decoded = await decodeMessages(items.map((item) => item.encoded)); + expect(thrownError).not.to.be.null; + expect(thrownError.message).to.equal('Crypto module not provided'); + }); + }); - expect(decoded[0].data).to.be.an('ArrayBuffer'); - expect(decoded[1].data).to.be.an('array'); + describe('decodeEncryptedMessages', () => { + it('decodes messages’ data', async () => { + testDecodesMessagesData(decodeEncryptedMessages); }); it('decrypts messages', async () => { @@ -126,7 +184,7 @@ describe('browser/modules', function () { const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ decodeMessages(testData.items.map((item) => item.encoded)), - decodeMessages( + decodeEncryptedMessages( testData.items.map((item) => item.encrypted), { cipher: { key, iv } } ), @@ -138,4 +196,54 @@ describe('browser/modules', function () { }); }); }); + + describe('Crypto', () => { + describe('without Crypto', () => { + for (const clientClass of [BaseRest, BaseRealtime]) { + describe(clientClass.name, () => { + it('throws an error when given channel options with a cipher', async () => { + const client = new clientClass(ablyClientOptions(), {}); + const key = await generateRandomKey(); + expect(() => client.channels.get('channel', { cipher: { key } })).to.throw('Crypto module not provided'); + }); + }); + } + }); + + describe('with Crypto', () => { + for (const clientClass of [BaseRest, BaseRealtime]) { + describe(clientClass.name, () => { + it('is able to publish encrypted messages', async () => { + const clientOptions = ablyClientOptions(); + + const key = await generateRandomKey(); + + // Publish the message on a channel configured to use encryption, and receive it on one not configured to use encryption + + const rxClient = new BaseRealtime(clientOptions, {}); + const rxChannel = rxClient.channels.get('channel'); + await rxChannel.attach(); + + const rxMessagePromise = new Promise((resolve, _) => rxChannel.subscribe((message) => resolve(message))); + + const encryptionChannelOptions = { cipher: { key } }; + + const txMessage = { name: 'message', data: 'data' }; + const txClient = new clientClass(clientOptions, { Crypto }); + const txChannel = txClient.channels.get('channel', encryptionChannelOptions); + await txChannel.publish(txMessage); + + const rxMessage = await rxMessagePromise; + + // Verify that the message was published with encryption + expect(rxMessage.encoding).to.equal('utf-8/cipher+aes-256-cbc'); + + // Verify that the message was correctly encrypted + const rxMessageDecrypted = await decodeEncryptedMessage(rxMessage, encryptionChannelOptions); + testMessageEquality(rxMessageDecrypted, txMessage); + }); + }); + } + }); + }); }); From 4ac8eb5914c554b8d956721491656731f0a53c6b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 21 Aug 2023 09:21:59 -0300 Subject: [PATCH 164/468] Remove redundant setting of useBinaryProtocol in options normalisation The property is already manipulated earlier in this method. Duplication introduced in 4f66064; seems unintentional. --- src/common/lib/util/defaults.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 23d73aee98..4f0ba8ee53 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -250,10 +250,6 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie return { ...options, - useBinaryProtocol: - 'useBinaryProtocol' in options - ? Platform.Config.supportsBinary && options.useBinaryProtocol - : Platform.Config.preferBinary, realtimeHost, restHost, maxMessageSize: options.internal?.maxMessageSize || Defaults.maxMessageSize, From ae1c4de2938cf5bf252d9a22ed5f50590808b3cf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 21 Aug 2023 10:05:45 -0300 Subject: [PATCH 165/468] Move MsgPack interface into its own file Preparation for #1375 (making MessagePack functionality tree-shakable). --- src/common/types/IPlatformConfig.d.ts | 5 +---- src/common/types/msgpack.ts | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 src/common/types/msgpack.ts diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index ecb0e4868d..4a83e20a39 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -1,7 +1,4 @@ -interface MsgPack { - encode(value: any, sparse?: boolean): Buffer | ArrayBuffer | undefined; - decode(buffer: Buffer): any; -} +import { MsgPack } from './msgpack'; export interface IPlatformConfig { agent: string; diff --git a/src/common/types/msgpack.ts b/src/common/types/msgpack.ts new file mode 100644 index 0000000000..0667def08a --- /dev/null +++ b/src/common/types/msgpack.ts @@ -0,0 +1,4 @@ +export interface MsgPack { + encode(value: any, sparse?: boolean): Buffer | ArrayBuffer | undefined; + decode(buffer: Buffer): any; +} From 5230f056fb007ffa6acbe96dc8cc3712ee9d77d0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 17 Aug 2023 15:09:43 -0300 Subject: [PATCH 166/468] Add a _MsgPack property to BaseClient The _MsgPack assignment becomes the only place that Platform.Config.msgpack gets accessed. All other uses now instead access _MsgPack. Preparation for #1375 (making MessagePack functionality tree-shakable). --- src/common/lib/client/auth.ts | 6 +++-- src/common/lib/client/baseclient.ts | 1 + src/common/lib/client/channel.ts | 4 ++-- src/common/lib/client/presence.ts | 14 +++++++++-- src/common/lib/client/push.ts | 23 +++++++++++++------ src/common/lib/client/resource.ts | 13 +++++++---- src/common/lib/client/rest.ts | 14 +++++++---- .../lib/transport/websockettransport.ts | 6 +++-- src/common/lib/types/devicedetails.ts | 4 +++- src/common/lib/types/message.ts | 4 +++- src/common/lib/types/presencemessage.ts | 4 +++- src/common/lib/types/protocolmessage.ts | 5 ++-- .../lib/types/pushchannelsubscription.ts | 4 +++- src/common/lib/util/utils.ts | 9 ++++---- src/platform/nodejs/lib/util/http.ts | 13 +++++++---- 15 files changed, 85 insertions(+), 39 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 1a47291c08..3ed509ed6d 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -1078,7 +1078,7 @@ class Auth { if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); - const requestBody = Utils.encodeBody(requestBodyDTO, format); + const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); Resource.post( this.client, `/keys/${keyName}/revokeTokens`, @@ -1092,7 +1092,9 @@ class Auth { return; } - const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as TokenRevocationResult; + const batchResult = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as TokenRevocationResult; callback(null, batchResult); } diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index da5ee730e3..e48ea76fb5 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -40,6 +40,7 @@ class BaseClient { private readonly _rest: Rest | null; readonly _Crypto: IUntypedCryptoStatic | null; + readonly _MsgPack = Platform.Config.msgpack; constructor(options: ClientOptions | string, modules: ModulesMap) { if (!options) { diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index 370c32993e..ad4be4b767 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -99,7 +99,7 @@ class Channel extends EventEmitter { headers: Record, unpacked?: boolean ) { - return await Message.fromResponseBody(body, options, unpacked ? undefined : format); + return await Message.fromResponseBody(body, options, client._MsgPack, unpacked ? undefined : format); }).get(params as Record, callback); } @@ -177,7 +177,7 @@ class Channel extends EventEmitter { return; } - this._publish(Message.serialize(messages, format), headers, params, callback); + this._publish(Message.serialize(messages, client._MsgPack, format), headers, params, callback); }); } diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index ea64ad3cd7..ac756ef9b8 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -43,7 +43,12 @@ class Presence extends EventEmitter { headers: Record, unpacked?: boolean ) { - return await PresenceMessage.fromResponseBody(body, options as CipherOptions, unpacked ? undefined : format); + return await PresenceMessage.fromResponseBody( + body, + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); }).get(params, callback); } @@ -82,7 +87,12 @@ class Presence extends EventEmitter { headers: Record, unpacked?: boolean ) { - return await PresenceMessage.fromResponseBody(body, options as CipherOptions, unpacked ? undefined : format); + return await PresenceMessage.fromResponseBody( + body, + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); }).get(params, callback); } } diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 2cf3e01d19..33054fbd0c 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -44,7 +44,7 @@ class Admin { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - const requestBody = Utils.encodeBody(body, format); + const requestBody = Utils.encodeBody(body, client._MsgPack, format); Resource.post(client, '/push/publish', requestBody, headers, params, null, (err) => callback(err)); } } @@ -71,7 +71,7 @@ class DeviceRegistrations { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - const requestBody = Utils.encodeBody(body, format); + const requestBody = Utils.encodeBody(body, client._MsgPack, format); Resource.put( client, '/push/deviceRegistrations/' + encodeURIComponent(device.id), @@ -85,6 +85,7 @@ class DeviceRegistrations { !err ? (DeviceDetails.fromResponseBody( body as Record, + client._MsgPack, unpacked ? undefined : format ) as DeviceDetails) : undefined @@ -128,6 +129,7 @@ class DeviceRegistrations { !err ? (DeviceDetails.fromResponseBody( body as Record, + client._MsgPack, unpacked ? undefined : format ) as DeviceDetails) : undefined @@ -153,7 +155,7 @@ class DeviceRegistrations { headers: Record, unpacked?: boolean ) { - return DeviceDetails.fromResponseBody(body, unpacked ? undefined : format); + return DeviceDetails.fromResponseBody(body, client._MsgPack, unpacked ? undefined : format); }).get(params, callback); } @@ -232,7 +234,7 @@ class ChannelSubscriptions { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - const requestBody = Utils.encodeBody(body, format); + const requestBody = Utils.encodeBody(body, client._MsgPack, format); Resource.post( client, '/push/channelSubscriptions', @@ -243,7 +245,12 @@ class ChannelSubscriptions { function (err, body, headers, unpacked) { callback( err, - !err && PushChannelSubscription.fromResponseBody(body as Record, unpacked ? undefined : format) + !err && + PushChannelSubscription.fromResponseBody( + body as Record, + client._MsgPack, + unpacked ? undefined : format + ) ); } ); @@ -266,7 +273,7 @@ class ChannelSubscriptions { headers: Record, unpacked?: boolean ) { - return PushChannelSubscription.fromResponseBody(body, unpacked ? undefined : format); + return PushChannelSubscription.fromResponseBody(body, client._MsgPack, unpacked ? undefined : format); }).get(params, callback); } @@ -308,7 +315,9 @@ class ChannelSubscriptions { headers: Record, unpacked?: boolean ) { - const parsedBody = (!unpacked && format ? Utils.decodeBody(body, format) : body) as Array; + const parsedBody = ( + !unpacked && format ? Utils.decodeBody(body, client._MsgPack, format) : body + ) as Array; for (let i = 0; i < parsedBody.length; i++) { parsedBody[i] = String(parsedBody[i]); diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 125069f214..8a1b855369 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -6,6 +6,7 @@ import HttpMethods from '../../constants/HttpMethods'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import BaseClient from './baseclient'; import { ErrnoException } from '../../types/http'; +import { MsgPack } from 'common/types/msgpack'; function withAuthDetails( client: BaseClient, @@ -27,7 +28,11 @@ function withAuthDetails( } } -function unenvelope(callback: ResourceCallback, format: Utils.Format | null): ResourceCallback { +function unenvelope( + callback: ResourceCallback, + MsgPack: MsgPack, + format: Utils.Format | null +): ResourceCallback { return (err, body, outerHeaders, unpacked, outerStatusCode) => { if (err && !body) { callback(err); @@ -36,7 +41,7 @@ function unenvelope(callback: ResourceCallback, format: Utils.Format | nul if (!unpacked) { try { - body = Utils.decodeBody(body, format); + body = Utils.decodeBody(body, MsgPack, format); } catch (e) { if (Utils.isErrorInfoOrPartialErrorInfo(e)) { callback(e); @@ -204,7 +209,7 @@ class Resource { } if (envelope) { - callback = callback && unenvelope(callback, envelope); + callback = callback && unenvelope(callback, client._MsgPack, envelope); (params = params || {})['envelope'] = envelope; } @@ -221,7 +226,7 @@ class Resource { let decodedBody = body; if (headers['content-type']?.indexOf('msgpack') > 0) { try { - decodedBody = Platform.Config.msgpack.decode(body as Buffer); + decodedBody = client._MsgPack.decode(body as Buffer); } catch (decodeErr) { Logger.logAction( Logger.LOG_MICRO, diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index a9d11b3c3b..2dd412ed84 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -125,8 +125,8 @@ export class Rest { callback: StandardCallback> ): Promise> | void { const useBinary = this.client.options.useBinaryProtocol, - encoder = useBinary ? Platform.Config.msgpack.encode : JSON.stringify, - decoder = useBinary ? Platform.Config.msgpack.decode : JSON.parse, + encoder = useBinary ? this.client._MsgPack.encode : JSON.stringify, + decoder = useBinary ? this.client._MsgPack.decode : JSON.parse, format = useBinary ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format; params = params || {}; @@ -200,7 +200,7 @@ export class Rest { if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); - const requestBody = Utils.encodeBody(requestBodyDTO, format); + const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); Resource.post( this.client, '/messages', @@ -214,7 +214,9 @@ export class Rest { return; } - const batchResults = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPublishResult[]; + const batchResults = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as BatchPublishResult[]; // I don't love the below type assertions for `callback` but not sure how to avoid them if (singleSpecMode) { @@ -254,7 +256,9 @@ export class Rest { return; } - const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPresenceResult; + const batchResult = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as BatchPresenceResult; callback(null, batchResult); } diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index ed805197cc..624c23dff5 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -102,7 +102,9 @@ class WebSocketTransport extends Transport { return; } try { - (wsConnection as NodeWebSocket).send(ProtocolMessage.serialize(message, this.params.format)); + (wsConnection as NodeWebSocket).send( + ProtocolMessage.serialize(message, this.connectionManager.realtime._MsgPack, this.params.format) + ); } catch (e) { const msg = 'Exception from ws connection when trying to send: ' + Utils.inspectError(e); Logger.logAction(Logger.LOG_ERROR, 'WebSocketTransport.send()', msg); @@ -119,7 +121,7 @@ class WebSocketTransport extends Transport { 'data received; length = ' + data.length + '; type = ' + typeof data ); try { - this.onProtocolMessage(ProtocolMessage.deserialize(data, this.format)); + this.onProtocolMessage(ProtocolMessage.deserialize(data, this.connectionManager.realtime._MsgPack, this.format)); } catch (e) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/lib/types/devicedetails.ts b/src/common/lib/types/devicedetails.ts index 59cc80b514..a8ba6c953c 100644 --- a/src/common/lib/types/devicedetails.ts +++ b/src/common/lib/types/devicedetails.ts @@ -1,3 +1,4 @@ +import { MsgPack } from 'common/types/msgpack'; import * as Utils from '../util/utils'; import ErrorInfo, { IConvertibleToErrorInfo } from './errorinfo'; @@ -74,10 +75,11 @@ class DeviceDetails { static fromResponseBody( body: Array> | Record, + MsgPack: MsgPack, format?: Utils.Format ): DeviceDetails | DeviceDetails[] { if (format) { - body = Utils.decodeBody(body, format); + body = Utils.decodeBody(body, MsgPack, format); } if (Utils.isArray(body)) { diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 692c5e069f..7a994d867d 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -7,6 +7,7 @@ import * as Utils from '../util/utils'; import { Bufferlike as BrowserBufferlike } from '../../../platform/web/lib/util/bufferutils'; import * as API from '../../../../ably'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; +import { MsgPack } from 'common/types/msgpack'; export type CipherOptions = { channelCipher: { @@ -335,10 +336,11 @@ class Message { static async fromResponseBody( body: Array, options: ChannelOptions | EncodingDecodingContext, + MsgPack: MsgPack, format?: Utils.Format ): Promise { if (format) { - body = Utils.decodeBody(body, format); + body = Utils.decodeBody(body, MsgPack, format); } for (let i = 0; i < body.length; i++) { diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index 2b9c04e766..606b922e2c 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -3,6 +3,7 @@ import Platform from 'common/platform'; import Message, { CipherOptions } from './message'; import * as Utils from '../util/utils'; import * as API from '../../../../ably'; +import { MsgPack } from 'common/types/msgpack'; function toActionValue(actionString: string) { return PresenceMessage.Actions.indexOf(actionString); @@ -111,11 +112,12 @@ class PresenceMessage { static async fromResponseBody( body: Record[], options: CipherOptions, + MsgPack: MsgPack, format?: Utils.Format ): Promise { const messages: PresenceMessage[] = []; if (format) { - body = Utils.decodeBody(body, format); + body = Utils.decodeBody(body, MsgPack, format); } for (let i = 0; i < body.length; i++) { diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index 20acbdf380..46641b5c1c 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -1,3 +1,4 @@ +import { MsgPack } from 'common/types/msgpack'; import { Types } from '../../../../ably'; import * as Utils from '../util/utils'; import ErrorInfo from './errorinfo'; @@ -109,8 +110,8 @@ class ProtocolMessage { static serialize = Utils.encodeBody; - static deserialize = function (serialized: unknown, format?: Utils.Format): ProtocolMessage { - const deserialized = Utils.decodeBody>(serialized, format); + static deserialize = function (serialized: unknown, MsgPack: MsgPack, format?: Utils.Format): ProtocolMessage { + const deserialized = Utils.decodeBody>(serialized, MsgPack, format); return ProtocolMessage.fromDeserialized(deserialized); }; diff --git a/src/common/lib/types/pushchannelsubscription.ts b/src/common/lib/types/pushchannelsubscription.ts index b4056c006c..16a150b1d6 100644 --- a/src/common/lib/types/pushchannelsubscription.ts +++ b/src/common/lib/types/pushchannelsubscription.ts @@ -1,3 +1,4 @@ +import { MsgPack } from 'common/types/msgpack'; import * as Utils from '../util/utils'; type PushChannelSubscriptionObject = { @@ -36,10 +37,11 @@ class PushChannelSubscription { static fromResponseBody( body: Array> | Record, + MsgPack: MsgPack, format?: Utils.Format ): PushChannelSubscription | PushChannelSubscription[] { if (format) { - body = Utils.decodeBody(body, format) as Record; + body = Utils.decodeBody(body, MsgPack, format) as Record; } if (Utils.isArray(body)) { diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 788f54d502..42a9db1d2d 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -1,6 +1,7 @@ import Platform from 'common/platform'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; import { ModulesMap } from '../client/modulesmap'; +import { MsgPack } from 'common/types/msgpack'; function randomPosn(arrOrStr: Array | string) { return Math.floor(Math.random() * arrOrStr.length); @@ -451,12 +452,12 @@ export function promisify(ob: Record, fnName: string, args: IArg }); } -export function decodeBody(body: unknown, format?: Format | null): T { - return format == 'msgpack' ? Platform.Config.msgpack.decode(body as Buffer) : JSON.parse(String(body)); +export function decodeBody(body: unknown, MsgPack: MsgPack, format?: Format | null): T { + return format == 'msgpack' ? MsgPack.decode(body as Buffer) : JSON.parse(String(body)); } -export function encodeBody(body: unknown, format?: Format): string | Buffer { - return format == 'msgpack' ? (Platform.Config.msgpack.encode(body, true) as Buffer) : JSON.stringify(body); +export function encodeBody(body: unknown, MsgPack: MsgPack, format?: Format): string | Buffer { + return format == 'msgpack' ? (MsgPack.encode(body, true) as Buffer) : JSON.stringify(body); } export function allToLowerCase(arr: Array): Array { diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 322f2b6b80..cec946a235 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -28,7 +28,7 @@ import { shallowEquals } from 'common/lib/util/utils'; const globalAgentPool: Array<{ options: RestAgentOptions; agents: Agents }> = []; -const handler = function (uri: string, params: unknown, callback?: RequestCallback) { +const handler = function (uri: string, params: unknown, client: BaseClient | null, callback?: RequestCallback) { return function (err: ErrnoException | null, response?: Response, body?: unknown) { if (err) { callback?.(err); @@ -42,7 +42,10 @@ const handler = function (uri: string, params: unknown, callback?: RequestCallba body = JSON.parse(body as string); break; case 'application/x-msgpack': - body = Platform.Config.msgpack.decode(body as Buffer); + if (!client) { + throw new ErrorInfo('Cannot use MessagePack without a client', 400, 40000); + } + body = client._MsgPack.decode(body as Buffer); } const error = (body as { error: ErrorInfo }).error ? ErrorInfo.fromValues((body as { error: ErrorInfo }).error) @@ -230,14 +233,14 @@ const Http: typeof IHttp = class { (got[method](doOptions) as CancelableRequest) .then((res: Response) => { - handler(uri, params, callback)(null, res, res.body); + handler(uri, params, client, callback)(null, res, res.body); }) .catch((err: ErrnoException) => { if (err instanceof got.HTTPError) { - handler(uri, params, callback)(null, err.response, err.response.body); + handler(uri, params, client, callback)(null, err.response, err.response.body); return; } - handler(uri, params, callback)(err); + handler(uri, params, client, callback)(err); }); } From f2e425a075625df0de440d134adc5d8707b7004b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 17 Aug 2023 16:45:08 -0300 Subject: [PATCH 167/468] Make MessagePack functionality tree-shakable We move the MessagePack functionality into a tree-shakable MsgPack module. Resolves #1375. --- scripts/moduleReport.js | 2 +- src/common/lib/client/baseclient.ts | 6 +- src/common/lib/client/defaultrealtime.ts | 10 ++- src/common/lib/client/defaultrest.ts | 14 +++- src/common/lib/client/modulesmap.ts | 2 + src/common/lib/client/resource.ts | 5 +- src/common/lib/client/rest.ts | 16 +++-- src/common/lib/types/devicedetails.ts | 2 +- src/common/lib/types/message.ts | 2 +- src/common/lib/types/presencemessage.ts | 2 +- .../lib/types/pushchannelsubscription.ts | 2 +- src/common/lib/util/defaults.ts | 15 ++-- src/common/lib/util/utils.ts | 22 ++++-- src/common/types/IPlatformConfig.d.ts | 3 - src/platform/nativescript/config.js | 2 - src/platform/nativescript/index.ts | 1 + src/platform/nodejs/config.ts | 1 - src/platform/nodejs/index.ts | 2 + src/platform/nodejs/lib/util/http.ts | 6 +- src/platform/react-native/config.ts | 2 - src/platform/react-native/index.ts | 1 + src/platform/web-noencryption/index.ts | 4 ++ src/platform/web/config.ts | 2 - src/platform/web/index.ts | 1 + src/platform/web/modules.ts | 1 + src/platform/web/modules/msgpack.ts | 1 + test/browser/modules.test.js | 71 +++++++++++++++++++ test/rest/defaults.test.js | 22 ++++++ 28 files changed, 183 insertions(+), 37 deletions(-) create mode 100644 src/platform/web/modules/msgpack.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 1937afc34d..577371359f 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -1,7 +1,7 @@ const esbuild = require('esbuild'); // List of all modules accepted in ModulesMap -const moduleNames = ['Rest', 'Crypto']; +const moduleNames = ['Rest', 'Crypto', 'MsgPack']; // List of all free-standing functions exported by the library along with the // ModulesMap entries that we expect them to transitively import diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index e48ea76fb5..2c7dc089a0 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -15,6 +15,7 @@ import { ModulesMap } from './modulesmap'; import { Rest } from './rest'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; import { throwMissingModuleError } from '../util/utils'; +import { MsgPack } from 'common/types/msgpack'; type BatchResult = API.Types.BatchResult; type BatchPublishSpec = API.Types.BatchPublishSpec; @@ -40,7 +41,7 @@ class BaseClient { private readonly _rest: Rest | null; readonly _Crypto: IUntypedCryptoStatic | null; - readonly _MsgPack = Platform.Config.msgpack; + readonly _MsgPack: MsgPack | null; constructor(options: ClientOptions | string, modules: ModulesMap) { if (!options) { @@ -57,7 +58,8 @@ class BaseClient { 'initialized with clientOptions ' + Platform.Config.inspect(options) ); - const normalOptions = (this.options = Defaults.normaliseOptions(optionsObj)); + this._MsgPack = modules.MsgPack ?? null; + const normalOptions = (this.options = Defaults.normaliseOptions(optionsObj, this._MsgPack)); /* process options */ if (normalOptions.key) { diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index e8b66f878e..8a905b449d 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -6,13 +6,19 @@ import ConnectionManager from '../transport/connectionmanager'; import ProtocolMessage from '../types/protocolmessage'; import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; +import { MsgPack } from 'common/types/msgpack'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. */ export class DefaultRealtime extends BaseRealtime { constructor(options: ClientOptions) { - super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined }); + const MsgPack = DefaultRealtime._MsgPack; + if (!MsgPack) { + throw new Error('Expected DefaultRealtime._MsgPack to have been set'); + } + + super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined, MsgPack }); } static Utils = Utils; @@ -32,4 +38,6 @@ export class DefaultRealtime extends BaseRealtime { } static Message = DefaultMessage; + + static _MsgPack: MsgPack | null = null; } diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index 1b7df607a6..e70a9eecf6 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -3,13 +3,23 @@ import ClientOptions from '../../types/ClientOptions'; import { allCommonModules } from './modulesmap'; import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; +import { MsgPack } from 'common/types/msgpack'; /** `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. */ export class DefaultRest extends BaseRest { constructor(options: ClientOptions | string) { - super(options, { ...allCommonModules, Crypto: DefaultRest.Crypto ?? undefined }); + const MsgPack = DefaultRest._MsgPack; + if (!MsgPack) { + throw new Error('Expected DefaultRest._MsgPack to have been set'); + } + + super(options, { + ...allCommonModules, + Crypto: DefaultRest.Crypto ?? undefined, + MsgPack: DefaultRest._MsgPack ?? undefined, + }); } private static _Crypto: typeof Platform.Crypto = null; @@ -25,4 +35,6 @@ export class DefaultRest extends BaseRest { } static Message = DefaultMessage; + + static _MsgPack: MsgPack | null = null; } diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index 4133bcc3a3..12ac9bc7f6 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -1,9 +1,11 @@ import { Rest } from './rest'; import { IUntypedCryptoStatic } from '../../types/ICryptoStatic'; +import { MsgPack } from 'common/types/msgpack'; export interface ModulesMap { Rest?: typeof Rest; Crypto?: IUntypedCryptoStatic; + MsgPack?: MsgPack; } export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 8a1b855369..7d506d369e 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -30,7 +30,7 @@ function withAuthDetails( function unenvelope( callback: ResourceCallback, - MsgPack: MsgPack, + MsgPack: MsgPack | null, format: Utils.Format | null ): ResourceCallback { return (err, body, outerHeaders, unpacked, outerStatusCode) => { @@ -226,6 +226,9 @@ class Resource { let decodedBody = body; if (headers['content-type']?.indexOf('msgpack') > 0) { try { + if (!client._MsgPack) { + Utils.throwMissingModuleError('MsgPack'); + } decodedBody = client._MsgPack.decode(body as Buffer); } catch (decodeErr) { Logger.logAction( diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 2dd412ed84..f58ef1b493 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -124,11 +124,17 @@ export class Rest { customHeaders: Record, callback: StandardCallback> ): Promise> | void { - const useBinary = this.client.options.useBinaryProtocol, - encoder = useBinary ? this.client._MsgPack.encode : JSON.stringify, - decoder = useBinary ? this.client._MsgPack.decode : JSON.parse, - format = useBinary ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.client.http.supportsLinkHeaders ? undefined : format; + const [encoder, decoder, format] = (() => { + if (this.client.options.useBinaryProtocol) { + if (!this.client._MsgPack) { + Utils.throwMissingModuleError('MsgPack'); + } + return [this.client._MsgPack.encode, this.client._MsgPack.decode, Utils.Format.msgpack]; + } else { + return [JSON.stringify, JSON.parse, Utils.Format.json]; + } + })(); + const envelope = this.client.http.supportsLinkHeaders ? undefined : format; params = params || {}; const _method = method.toLowerCase() as HttpMethods; const headers = diff --git a/src/common/lib/types/devicedetails.ts b/src/common/lib/types/devicedetails.ts index a8ba6c953c..8ad7e59a0b 100644 --- a/src/common/lib/types/devicedetails.ts +++ b/src/common/lib/types/devicedetails.ts @@ -75,7 +75,7 @@ class DeviceDetails { static fromResponseBody( body: Array> | Record, - MsgPack: MsgPack, + MsgPack: MsgPack | null, format?: Utils.Format ): DeviceDetails | DeviceDetails[] { if (format) { diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 7a994d867d..48285adae3 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -336,7 +336,7 @@ class Message { static async fromResponseBody( body: Array, options: ChannelOptions | EncodingDecodingContext, - MsgPack: MsgPack, + MsgPack: MsgPack | null, format?: Utils.Format ): Promise { if (format) { diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index 606b922e2c..da6f3cc5c5 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -112,7 +112,7 @@ class PresenceMessage { static async fromResponseBody( body: Record[], options: CipherOptions, - MsgPack: MsgPack, + MsgPack: MsgPack | null, format?: Utils.Format ): Promise { const messages: PresenceMessage[] = []; diff --git a/src/common/lib/types/pushchannelsubscription.ts b/src/common/lib/types/pushchannelsubscription.ts index 16a150b1d6..48190f25af 100644 --- a/src/common/lib/types/pushchannelsubscription.ts +++ b/src/common/lib/types/pushchannelsubscription.ts @@ -37,7 +37,7 @@ class PushChannelSubscription { static fromResponseBody( body: Array> | Record, - MsgPack: MsgPack, + MsgPack: MsgPack | null, format?: Utils.Format ): PushChannelSubscription | PushChannelSubscription[] { if (format) { diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 4f0ba8ee53..9939c975c7 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -5,6 +5,7 @@ import ErrorInfo from 'common/lib/types/errorinfo'; import { version } from '../../../../package.json'; import ClientOptions, { InternalClientOptions, NormalisedClientOptions } from 'common/types/ClientOptions'; import IDefaults from '../../types/IDefaults'; +import { MsgPack } from 'common/types/msgpack'; let agent = 'ably-js/' + version; @@ -41,7 +42,7 @@ type CompleteDefaults = IDefaults & { checkHost(host: string): void; getRealtimeHost(options: ClientOptions, production: boolean, environment: string): string; objectifyOptions(options: ClientOptions | string): ClientOptions; - normaliseOptions(options: InternalClientOptions): NormalisedClientOptions; + normaliseOptions(options: InternalClientOptions, MsgPack: MsgPack | null): NormalisedClientOptions; defaultGetHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record; defaultPostHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record; }; @@ -185,7 +186,7 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions return options; } -export function normaliseOptions(options: InternalClientOptions): NormalisedClientOptions { +export function normaliseOptions(options: InternalClientOptions, MsgPack: MsgPack | null): NormalisedClientOptions { if (typeof options.recover === 'function' && options.closeOnUnload === true) { Logger.logAction( Logger.LOG_ERROR, @@ -222,10 +223,14 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie const timeouts = getTimeouts(options); - if ('useBinaryProtocol' in options) { - options.useBinaryProtocol = Platform.Config.supportsBinary && options.useBinaryProtocol; + if (MsgPack) { + if ('useBinaryProtocol' in options) { + options.useBinaryProtocol = Platform.Config.supportsBinary && options.useBinaryProtocol; + } else { + options.useBinaryProtocol = Platform.Config.preferBinary; + } } else { - options.useBinaryProtocol = Platform.Config.preferBinary; + options.useBinaryProtocol = false; } const headers: Record = {}; diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 42a9db1d2d..ba8fa6f370 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -452,12 +452,26 @@ export function promisify(ob: Record, fnName: string, args: IArg }); } -export function decodeBody(body: unknown, MsgPack: MsgPack, format?: Format | null): T { - return format == 'msgpack' ? MsgPack.decode(body as Buffer) : JSON.parse(String(body)); +export function decodeBody(body: unknown, MsgPack: MsgPack | null, format?: Format | null): T { + if (format == 'msgpack') { + if (!MsgPack) { + throwMissingModuleError('MsgPack'); + } + return MsgPack.decode(body as Buffer); + } + + return JSON.parse(String(body)); } -export function encodeBody(body: unknown, MsgPack: MsgPack, format?: Format): string | Buffer { - return format == 'msgpack' ? (MsgPack.encode(body, true) as Buffer) : JSON.stringify(body); +export function encodeBody(body: unknown, MsgPack: MsgPack | null, format?: Format): string | Buffer { + if (format == 'msgpack') { + if (!MsgPack) { + throwMissingModuleError('MsgPack'); + } + return MsgPack.encode(body, true) as Buffer; + } + + return JSON.stringify(body); } export function allToLowerCase(arr: Array): Array { diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 4a83e20a39..9d678f23bb 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -1,12 +1,9 @@ -import { MsgPack } from './msgpack'; - export interface IPlatformConfig { agent: string; logTimestamps: boolean; binaryType: BinaryType; WebSocket: typeof WebSocket | typeof import('ws'); useProtocolHeartbeats: boolean; - msgpack: MsgPack; supportsBinary: boolean; preferBinary: boolean; nextTick: process.nextTick; diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index 31090e0240..ccdece4dd5 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -1,5 +1,4 @@ /* eslint-disable no-undef */ -import msgpack from '../web/lib/util/msgpack'; require('nativescript-websockets'); var randomBytes; @@ -28,7 +27,6 @@ var Config = { allowComet: true, streamingSupported: false, useProtocolHeartbeats: true, - msgpack: msgpack, supportsBinary: typeof TextDecoder !== 'undefined' && TextDecoder, preferBinary: false, ArrayBuffer: ArrayBuffer, diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index bdb55c8a34..f1ea2e7285 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -31,6 +31,7 @@ Platform.WebStorage = WebStorage; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; + clientClass._MsgPack = msgpack; } Logger.initLogHandlers(); diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 716be3b12a..4947994239 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -10,7 +10,6 @@ const Config: IPlatformConfig = { binaryType: 'nodebuffer' as BinaryType, WebSocket, useProtocolHeartbeats: false, - msgpack: require('@ably/msgpack-js'), supportsBinary: true, preferBinary: true, nextTick: process.nextTick, diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index ba683f9bad..b066225b6b 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -15,6 +15,7 @@ import Transports from './lib/transport'; import Logger from '../../common/lib/util/logger'; import { getDefaults } from '../../common/lib/util/defaults'; import PlatformDefaults from './lib/util/defaults'; +import msgpack = require('@ably/msgpack-js'); const Crypto = createCryptoClass(BufferUtils); @@ -27,6 +28,7 @@ Platform.WebStorage = null; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; + clientClass._MsgPack = msgpack; } Logger.initLogHandlers(); diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index cec946a235..7f53e13638 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -10,7 +10,7 @@ import BaseClient from 'common/lib/client/baseclient'; import BaseRealtime from 'common/lib/client/baserealtime'; import { NormalisedClientOptions, RestAgentOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; -import { shallowEquals } from 'common/lib/util/utils'; +import { shallowEquals, throwMissingModuleError } from 'common/lib/util/utils'; /*************************************************** * @@ -42,8 +42,8 @@ const handler = function (uri: string, params: unknown, client: BaseClient | nul body = JSON.parse(body as string); break; case 'application/x-msgpack': - if (!client) { - throw new ErrorInfo('Cannot use MessagePack without a client', 400, 40000); + if (!client?._MsgPack) { + throwMissingModuleError('MsgPack'); } body = client._MsgPack.decode(body as Buffer); } diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index f6e6f29a2e..6a2f0107b8 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -1,4 +1,3 @@ -import msgpack from '../web/lib/util/msgpack'; import { IPlatformConfig } from '../../common/types/IPlatformConfig'; import BufferUtils from '../web/lib/util/bufferutils'; @@ -13,7 +12,6 @@ export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { allowComet: true, streamingSupported: true, useProtocolHeartbeats: true, - msgpack: msgpack, supportsBinary: !!(typeof TextDecoder !== 'undefined' && TextDecoder), preferBinary: false, ArrayBuffer: typeof ArrayBuffer !== 'undefined' && ArrayBuffer, diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index 478ee9664d..3b7d6debeb 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -31,6 +31,7 @@ Platform.WebStorage = WebStorage; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; + clientClass._MsgPack = msgpack; } Logger.initLogHandlers(); diff --git a/src/platform/web-noencryption/index.ts b/src/platform/web-noencryption/index.ts index 80748e6b9e..4b8d3ed9de 100644 --- a/src/platform/web-noencryption/index.ts +++ b/src/platform/web-noencryption/index.ts @@ -24,6 +24,10 @@ Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; +for (const clientClass of [DefaultRest, DefaultRealtime]) { + clientClass._MsgPack = msgpack; +} + Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 87536361ad..f3aac12c93 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -1,4 +1,3 @@ -import msgpack from './lib/util/msgpack'; import { IPlatformConfig } from '../../common/types/IPlatformConfig'; import * as Utils from 'common/lib/util/utils'; @@ -37,7 +36,6 @@ const Config: IPlatformConfig = { allowComet: allowComet(), streamingSupported: true, useProtocolHeartbeats: true, - msgpack: msgpack, supportsBinary: !!globalObject.TextDecoder, preferBinary: false, ArrayBuffer: globalObject.ArrayBuffer, diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index 9fa12989e4..d4566814d5 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -29,6 +29,7 @@ Platform.WebStorage = WebStorage; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; + clientClass._MsgPack = msgpack; } Logger.initLogHandlers(); diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index ed91109bfe..516e220c84 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -41,5 +41,6 @@ if (Platform.Config.noUpgrade) { export * from './modules/crypto'; export * from './modules/message'; +export * from './modules/msgpack'; export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/src/platform/web/modules/msgpack.ts b/src/platform/web/modules/msgpack.ts new file mode 100644 index 0000000000..698b09e34c --- /dev/null +++ b/src/platform/web/modules/msgpack.ts @@ -0,0 +1 @@ +export { default as MsgPack } from '../lib/util/msgpack'; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 72aabe2c8e..6536f25f6e 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -9,6 +9,7 @@ import { decodeMessages, decodeEncryptedMessages, Crypto, + MsgPack, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -246,4 +247,74 @@ describe('browser/modules', function () { } }); }); + + describe('MsgPack', () => { + async function testRestUsesContentType(rest, expectedContentType) { + const channelName = 'channel'; + const channel = rest.channels.get(channelName); + const contentTypeUsedForPublishPromise = new Promise((resolve, reject) => { + rest.http.do = (method, client, path, headers, body, params, callback) => { + if (!(method == 'post' && path == `/channels/${channelName}/messages`)) { + return; + } + resolve(headers['content-type']); + callback(null); + }; + }); + + await channel.publish('message', 'body'); + + const contentTypeUsedForPublish = await contentTypeUsedForPublishPromise; + expect(contentTypeUsedForPublish).to.equal(expectedContentType); + } + + async function testRealtimeUsesFormat(realtime, expectedFormat) { + const formatUsedForConnectionPromise = new Promise((resolve, reject) => { + realtime.connection.connectionManager.connectImpl = (transportParams) => { + resolve(transportParams.format); + }; + }); + realtime.connect(); + + const formatUsedForConnection = await formatUsedForConnectionPromise; + expect(formatUsedForConnection).to.equal(expectedFormat); + } + + // TODO once https://github.com/ably/ably-js/issues/1424 is fixed, this should also test the case where the useBinaryProtocol option is not specified + describe('with useBinaryProtocol client option', () => { + describe('without MsgPack', () => { + describe('BaseRest', () => { + it('uses JSON', async () => { + const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), {}); + await testRestUsesContentType(client, 'application/json'); + }); + }); + + describe('BaseRealtime', () => { + it('uses JSON', async () => { + const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), {}); + await testRealtimeUsesFormat(client, 'json'); + }); + }); + }); + + describe('with MsgPack', () => { + describe('BaseRest', () => { + it('uses MessagePack', async () => { + const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { MsgPack }); + await testRestUsesContentType(client, 'application/x-msgpack'); + }); + }); + + describe('BaseRealtime', () => { + it('uses MessagePack', async () => { + const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { + MsgPack, + }); + await testRealtimeUsesFormat(client, 'msgpack'); + }); + }); + }); + }); + }); }); diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index 40be2e7213..173dfd684e 100644 --- a/test/rest/defaults.test.js +++ b/test/rest/defaults.test.js @@ -135,6 +135,28 @@ define(['ably', 'chai'], function (Ably, chai) { Defaults.ENVIRONMENT = ''; }); + // TODO once https://github.com/ably/ably-js/issues/1424 is fixed, this should also test the case where the useBinaryProtocol option is not specified + describe('normaliseOptions with useBinaryProtocol == true', () => { + if (Ably.Realtime.Platform.Config.supportsBinary) { + describe('given MsgPack implementation', () => { + it('maintains useBinaryProtocol as true', () => { + const StubMsgPack = {}; + var normalisedOptions = Defaults.normaliseOptions({ useBinaryProtocol: true }, StubMsgPack); + + expect(normalisedOptions.useBinaryProtocol).to.be.true; + }); + }); + } + + describe('given no MsgPack implementation', () => { + it('changes useBinaryProtocol to false', () => { + var normalisedOptions = Defaults.normaliseOptions({ useBinaryProtocol: true }, null); + + expect(normalisedOptions.useBinaryProtocol).to.be.false; + }); + }); + }); + it('closeOnUnload', function () { var options; From 89edf0466f9e3ed3bc581b9506756ecb6a83ab02 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 1 Nov 2023 16:45:20 -0300 Subject: [PATCH 168/468] =?UTF-8?q?Do=20e013cc0=E2=80=99s=20typescript-esl?= =?UTF-8?q?int=20version=20bumps=20in=20package.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So that they don’t get lost next time we need to do something that may cause information in package-lock.json to get discarded, for example resolving a merge conflict. I asked whether my approach in e013cc0 was incorrect, and Owen said he thought it was: "i'm pretty sure what you're supposed to do is put anything you want to control the version of into package.json", so let’s do that. --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f80863b27d..fdfc9dbb39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,8 @@ "@types/node": "^18.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", "@vitejs/plugin-react": "^1.3.2", "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", diff --git a/package.json b/package.json index 8c95387aa4..534b07c644 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,8 @@ "@types/node": "^18.0.0", "@types/request": "^2.48.7", "@types/ws": "^8.2.0", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", "@vitejs/plugin-react": "^1.3.2", "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", From e8c2a041e4a138b1d39db3fc0ddfd6fa6106590d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 9 Nov 2023 14:30:13 -0300 Subject: [PATCH 169/468] =?UTF-8?q?Don=E2=80=99t=20populate=20DeviceDetail?= =?UTF-8?q?s.toRequestBody=20by=20assignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It causes the DeviceDetails class to not get tree-shaken. --- src/common/lib/types/devicedetails.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/lib/types/devicedetails.ts b/src/common/lib/types/devicedetails.ts index 8ad7e59a0b..4b93cac560 100644 --- a/src/common/lib/types/devicedetails.ts +++ b/src/common/lib/types/devicedetails.ts @@ -71,7 +71,9 @@ class DeviceDetails { return result; } - static toRequestBody = Utils.encodeBody; + static toRequestBody(body: unknown, MsgPack: MsgPack | null, format?: Utils.Format) { + return Utils.encodeBody(body, MsgPack, format); + } static fromResponseBody( body: Array> | Record, From 7cbd6fc9b35b3da9dca3c4ecea35b4e82510ae62 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 22 Aug 2023 09:24:47 -0300 Subject: [PATCH 170/468] Make realtime presence functionality tree-shakable We move it into a tree-shakable RealtimePresence module. Resolves #1375. --- scripts/moduleReport.js | 2 +- src/common/lib/client/baserealtime.ts | 3 ++ src/common/lib/client/channel.ts | 7 ++-- src/common/lib/client/defaultrealtime.ts | 3 +- src/common/lib/client/modulesmap.ts | 2 ++ src/common/lib/client/realtimechannel.ts | 22 ++++++++++--- src/platform/web/modules.ts | 1 + src/platform/web/modules/realtimepresence.ts | 1 + test/browser/modules.test.js | 34 ++++++++++++++++++++ 9 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 src/platform/web/modules/realtimepresence.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 577371359f..c346615d36 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -1,7 +1,7 @@ const esbuild = require('esbuild'); // List of all modules accepted in ModulesMap -const moduleNames = ['Rest', 'Crypto', 'MsgPack']; +const moduleNames = ['Rest', 'Crypto', 'MsgPack', 'RealtimePresence']; // List of all free-standing functions exported by the library along with the // ModulesMap entries that we expect them to transitively import diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 971ec09038..ff51a2a773 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -10,17 +10,20 @@ import { ChannelOptions } from '../../types/channel'; import ClientOptions from '../../types/ClientOptions'; import * as API from '../../../../ably'; import { ModulesMap } from './modulesmap'; +import RealtimePresence from './realtimepresence'; /** `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. */ class BaseRealtime extends BaseClient { + readonly _RealtimePresence: typeof RealtimePresence | null; _channels: any; connection: Connection; constructor(options: ClientOptions, modules: ModulesMap) { super(options, modules); Logger.logAction(Logger.LOG_MINOR, 'Realtime()', ''); + this._RealtimePresence = modules.RealtimePresence ?? null; this.connection = new Connection(this, this.options); this._channels = new Channels(this); if (options.autoConnect !== false) this.connect(); diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index ad4be4b767..ac7efe4d94 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -50,7 +50,10 @@ class Channel extends EventEmitter { client: BaseClient; name: string; basePath: string; - presence: Presence; + private _presence: Presence; + get presence(): Presence { + return this._presence; + } channelOptions: ChannelOptions; constructor(client: BaseClient, name: string, channelOptions?: ChannelOptions) { @@ -59,7 +62,7 @@ class Channel extends EventEmitter { this.client = client; this.name = name; this.basePath = '/channels/' + encodeURIComponent(name); - this.presence = new Presence(this); + this._presence = new Presence(this); this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, channelOptions); } diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 8a905b449d..68073accf0 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -7,6 +7,7 @@ import ProtocolMessage from '../types/protocolmessage'; import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; +import RealtimePresence from './realtimepresence'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -18,7 +19,7 @@ export class DefaultRealtime extends BaseRealtime { throw new Error('Expected DefaultRealtime._MsgPack to have been set'); } - super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined, MsgPack }); + super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined, MsgPack, RealtimePresence }); } static Utils = Utils; diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index 12ac9bc7f6..a4de0a0d51 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -1,11 +1,13 @@ import { Rest } from './rest'; import { IUntypedCryptoStatic } from '../../types/ICryptoStatic'; import { MsgPack } from 'common/types/msgpack'; +import RealtimePresence from './realtimepresence'; export interface ModulesMap { Rest?: typeof Rest; Crypto?: IUntypedCryptoStatic; MsgPack?: MsgPack; + RealtimePresence?: typeof RealtimePresence; } export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 335d86c83a..3d6d7e4269 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -50,7 +50,13 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) { class RealtimeChannel extends Channel { realtime: BaseRealtime; - presence: RealtimePresence; + private _realtimePresence: RealtimePresence | null; + get presence(): RealtimePresence { + if (!this._realtimePresence) { + Utils.throwMissingModuleError('RealtimePresence'); + } + return this._realtimePresence; + } connectionManager: ConnectionManager; state: API.Types.ChannelState; subscriptions: EventEmitter; @@ -84,7 +90,7 @@ class RealtimeChannel extends Channel { super(realtime, name, options); Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel()', 'started; name = ' + name); this.realtime = realtime; - this.presence = new RealtimePresence(this); + this._realtimePresence = realtime._RealtimePresence ? new realtime._RealtimePresence(this) : null; this.connectionManager = realtime.connection.connectionManager; this.state = 'initialized'; this.subscriptions = new EventEmitter(); @@ -616,7 +622,9 @@ class RealtimeChannel extends Channel { if (this.state === 'attached') { if (!resumed) { /* On a loss of continuity, the presence set needs to be re-synced */ - this.presence.onAttached(hasPresence); + if (this._realtimePresence) { + this._realtimePresence.onAttached(hasPresence); + } } const change = new ChannelStateChange(this.state, this.state, resumed, hasBacklog, message.error); this._allChannelChanges.emit('update', change); @@ -674,7 +682,9 @@ class RealtimeChannel extends Channel { Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString()); } } - this.presence.setPresence(presence, isSync, syncChannelSerial as any); + if (this._realtimePresence) { + this._realtimePresence.setPresence(presence, isSync, syncChannelSerial as any); + } break; } case actions.MESSAGE: { @@ -810,7 +820,9 @@ class RealtimeChannel extends Channel { if (state === this.state) { return; } - this.presence.actOnChannelState(state, hasPresence, reason); + if (this._realtimePresence) { + this._realtimePresence.actOnChannelState(state, hasPresence, reason); + } if (state === 'suspended' && this.connectionManager.state.sendEvents) { this.startRetryTimer(); } else { diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 516e220c84..5370247778 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -42,5 +42,6 @@ if (Platform.Config.noUpgrade) { export * from './modules/crypto'; export * from './modules/message'; export * from './modules/msgpack'; +export * from './modules/realtimepresence'; export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/src/platform/web/modules/realtimepresence.ts b/src/platform/web/modules/realtimepresence.ts new file mode 100644 index 0000000000..425677272c --- /dev/null +++ b/src/platform/web/modules/realtimepresence.ts @@ -0,0 +1 @@ +export { default as RealtimePresence } from '../../../common/lib/client/realtimepresence'; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 6536f25f6e..e3d483b684 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -10,6 +10,7 @@ import { decodeEncryptedMessages, Crypto, MsgPack, + RealtimePresence, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -20,11 +21,13 @@ describe('browser/modules', function () { let testResourcesPath; let loadTestData; let testMessageEquality; + let randomString; before((done) => { ablyClientOptions = window.ablyHelpers.ablyClientOptions; testResourcesPath = window.ablyHelpers.testResourcesPath; testMessageEquality = window.ablyHelpers.testMessageEquality; + randomString = window.ablyHelpers.randomString; loadTestData = async (dataPath) => { return new Promise((resolve, reject) => { @@ -317,4 +320,35 @@ describe('browser/modules', function () { }); }); }); + + describe('RealtimePresence', () => { + describe('BaseRealtime without RealtimePresence', () => { + it('throws an error when attempting to access the `presence` property', () => { + const client = new BaseRealtime(ablyClientOptions(), {}); + const channel = client.channels.get('channel'); + + expect(() => channel.presence).to.throw('RealtimePresence module not provided'); + }); + }); + + describe('BaseRealtime with RealtimePresence', () => { + it('offers realtime presence functionality', async () => { + const rxChannel = new BaseRealtime(ablyClientOptions(), { RealtimePresence }).channels.get('channel'); + const txClientId = randomString(); + const txChannel = new BaseRealtime(ablyClientOptions({ clientId: txClientId }), { + RealtimePresence, + }).channels.get('channel'); + + let resolveRxPresenceMessagePromise; + const rxPresenceMessagePromise = new Promise((resolve, reject) => { + resolveRxPresenceMessagePromise = resolve; + }); + await rxChannel.presence.subscribe('enter', resolveRxPresenceMessagePromise); + await txChannel.presence.enter(); + + const rxPresenceMessage = await rxPresenceMessagePromise; + expect(rxPresenceMessage.clientId).to.equal(txClientId); + }); + }); + }); }); From 0e722b9c198aa136fee785d9c9de311c7f772c24 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 23 Aug 2023 09:39:46 -0300 Subject: [PATCH 171/468] Remove PresenceMessage-related static things in tree-shakable library As in 601b46b. Resolves #1427. --- scripts/moduleReport.js | 3 + src/common/lib/client/baseclient.ts | 2 - src/common/lib/client/defaultrealtime.ts | 2 + src/common/lib/client/defaultrest.ts | 2 + src/common/lib/client/realtimechannel.ts | 4 +- src/common/lib/client/realtimepresence.ts | 10 +-- .../lib/types/defaultpresencemessage.ts | 22 ++++++ src/common/lib/types/presencemessage.ts | 69 ++++++++++--------- src/common/lib/types/protocolmessage.ts | 4 +- src/platform/web/modules.ts | 1 + src/platform/web/modules/presencemessage.ts | 8 +++ test/browser/modules.test.js | 46 +++++++++++++ 12 files changed, 129 insertions(+), 44 deletions(-) create mode 100644 src/common/lib/types/defaultpresencemessage.ts create mode 100644 src/platform/web/modules/presencemessage.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index c346615d36..84d6bac99b 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -12,6 +12,9 @@ const functions = [ { name: 'decodeEncryptedMessage', transitiveImports: ['Crypto'] }, { name: 'decodeMessages', transitiveImports: [] }, { name: 'decodeEncryptedMessages', transitiveImports: ['Crypto'] }, + { name: 'decodePresenceMessage', transitiveImports: [] }, + { name: 'decodePresenceMessages', transitiveImports: [] }, + { name: 'constructPresenceMessage', transitiveImports: [] }, ]; function formatBytes(bytes) { diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 2c7dc089a0..939257edb7 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -10,7 +10,6 @@ import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOption import * as API from '../../../../ably'; import Platform from '../../platform'; -import PresenceMessage from '../types/presencemessage'; import { ModulesMap } from './modulesmap'; import { Rest } from './rest'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; @@ -153,7 +152,6 @@ class BaseClient { } static Platform = Platform; - static PresenceMessage = PresenceMessage; } export default BaseClient; diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 68073accf0..242e612288 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -8,6 +8,7 @@ import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; +import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -39,6 +40,7 @@ export class DefaultRealtime extends BaseRealtime { } static Message = DefaultMessage; + static PresenceMessage = DefaultPresenceMessage; static _MsgPack: MsgPack | null = null; } diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index e70a9eecf6..c03849af37 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -4,6 +4,7 @@ import { allCommonModules } from './modulesmap'; import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; +import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; /** `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -35,6 +36,7 @@ export class DefaultRest extends BaseRest { } static Message = DefaultMessage; + static PresenceMessage = DefaultPresenceMessage; static _MsgPack: MsgPack | null = null; } diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 3d6d7e4269..ee403f7bc5 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -7,7 +7,7 @@ import RealtimePresence from './realtimepresence'; import Message, { CipherOptions } from '../types/message'; import ChannelStateChange from './channelstatechange'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; -import PresenceMessage from '../types/presencemessage'; +import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; import ConnectionErrors from '../transport/connectionerrors'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; @@ -591,7 +591,7 @@ class RealtimeChannel extends Channel { channel: this.name, presence: Utils.isArray(presence) ? PresenceMessage.fromValuesArray(presence) - : [PresenceMessage.fromValues(presence)], + : [presenceMessageFromValues(presence)], }); this.sendMessage(msg, callback); } diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index f76c65021a..5d5e84c346 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -2,7 +2,7 @@ import * as Utils from '../util/utils'; import Presence from './presence'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; -import PresenceMessage from '../types/presencemessage'; +import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import RealtimeChannel from './realtimechannel'; import Multicaster from '../util/multicaster'; @@ -342,7 +342,7 @@ class RealtimePresence extends Presence { } for (let i = 0; i < presenceSet.length; i++) { - const presence = PresenceMessage.fromValues(presenceSet[i]); + const presence = presenceMessageFromValues(presenceSet[i]); switch (presence.action) { case 'leave': if (members.remove(presence)) { @@ -480,7 +480,7 @@ class RealtimePresence extends Presence { _synthesizeLeaves(items: PresenceMessage[]): void { const subscriptions = this.subscriptions; Utils.arrForEach(items, function (item) { - const presence = PresenceMessage.fromValues({ + const presence = presenceMessageFromValues({ action: 'leave', connectionId: item.connectionId, clientId: item.clientId, @@ -568,7 +568,7 @@ class PresenceMap extends EventEmitter { put(item: PresenceMessage) { if (item.action === 'enter' || item.action === 'update') { - item = PresenceMessage.fromValues(item); + item = presenceMessageFromValues(item); item.action = 'present'; } const map = this.map, @@ -606,7 +606,7 @@ class PresenceMap extends EventEmitter { /* RTP2f */ if (this.syncInProgress) { - item = PresenceMessage.fromValues(item); + item = presenceMessageFromValues(item); item.action = 'absent'; map[key] = item; } else { diff --git a/src/common/lib/types/defaultpresencemessage.ts b/src/common/lib/types/defaultpresencemessage.ts new file mode 100644 index 0000000000..5f1ceb6575 --- /dev/null +++ b/src/common/lib/types/defaultpresencemessage.ts @@ -0,0 +1,22 @@ +import * as API from '../../../../ably'; +import PresenceMessage, { fromEncoded, fromEncodedArray, fromValues } from './presencemessage'; + +/** + `DefaultPresenceMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `PresenceMessage` static property. It introduces the static methods described in the `PresenceMessageStatic` interface of the public API of the non tree-shakable version of the library. + */ +export class DefaultPresenceMessage extends PresenceMessage { + static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { + return fromEncoded(encoded, inputOptions); + } + + static async fromEncodedArray( + encodedArray: Array, + options?: API.Types.ChannelOptions + ): Promise { + return fromEncodedArray(encodedArray, options); + } + + static fromValues(values: PresenceMessage | Record, stringifyAction?: boolean): PresenceMessage { + return fromValues(values, stringifyAction); + } +} diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index da6f3cc5c5..c1fa4db450 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -9,6 +9,39 @@ function toActionValue(actionString: string) { return PresenceMessage.Actions.indexOf(actionString); } +export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { + const msg = fromValues(encoded as PresenceMessage | Record, true); + /* if decoding fails at any point, catch and return the message decoded to + * the fullest extent possible */ + try { + await PresenceMessage.decode(msg, options ?? {}); + } catch (e) { + Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromEncoded()', (e as Error).toString()); + } + return msg; +} + +export async function fromEncodedArray( + encodedArray: unknown[], + options?: API.Types.ChannelOptions +): Promise { + return Promise.all( + encodedArray.map(function (encoded) { + return fromEncoded(encoded, options); + }) + ); +} + +export function fromValues( + values: PresenceMessage | Record, + stringifyAction?: boolean +): PresenceMessage { + if (stringifyAction) { + values.action = PresenceMessage.Actions[values.action as number]; + } + return Object.assign(new PresenceMessage(), values); +} + class PresenceMessage { action?: string | number; id?: string; @@ -121,7 +154,7 @@ class PresenceMessage { } for (let i = 0; i < body.length; i++) { - const msg = (messages[i] = PresenceMessage.fromValues(body[i], true)); + const msg = (messages[i] = fromValues(body[i], true)); try { await PresenceMessage.decode(msg, options); } catch (e) { @@ -131,48 +164,18 @@ class PresenceMessage { return messages; } - static fromValues(values: PresenceMessage | Record, stringifyAction?: boolean): PresenceMessage { - if (stringifyAction) { - values.action = PresenceMessage.Actions[values.action as number]; - } - return Object.assign(new PresenceMessage(), values); - } - static fromValuesArray(values: unknown[]): PresenceMessage[] { const count = values.length, result = new Array(count); - for (let i = 0; i < count; i++) result[i] = PresenceMessage.fromValues(values[i] as Record); + for (let i = 0; i < count; i++) result[i] = fromValues(values[i] as Record); return result; } - static async fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { - const msg = PresenceMessage.fromValues(encoded as PresenceMessage | Record, true); - /* if decoding fails at any point, catch and return the message decoded to - * the fullest extent possible */ - try { - await PresenceMessage.decode(msg, options ?? {}); - } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromEncoded()', (e as Error).toString()); - } - return msg; - } - - static async fromEncodedArray( - encodedArray: unknown[], - options?: API.Types.ChannelOptions - ): Promise { - return Promise.all( - encodedArray.map(function (encoded) { - return PresenceMessage.fromEncoded(encoded, options); - }) - ); - } - static fromData(data: unknown): PresenceMessage { if (data instanceof PresenceMessage) { return data; } - return PresenceMessage.fromValues({ + return fromValues({ data, }); } diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index 46641b5c1c..fde1978f9a 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -3,7 +3,7 @@ import { Types } from '../../../../ably'; import * as Utils from '../util/utils'; import ErrorInfo from './errorinfo'; import Message from './message'; -import PresenceMessage from './presencemessage'; +import PresenceMessage, { fromValues as presenceMessageFromValues } from './presencemessage'; const actions = { HEARTBEAT: 0, @@ -121,7 +121,7 @@ class ProtocolMessage { const messages = deserialized.messages as Message[]; if (messages) for (let i = 0; i < messages.length; i++) messages[i] = Message.fromValues(messages[i]); const presence = deserialized.presence as PresenceMessage[]; - if (presence) for (let i = 0; i < presence.length; i++) presence[i] = PresenceMessage.fromValues(presence[i], true); + if (presence) for (let i = 0; i < presence.length; i++) presence[i] = presenceMessageFromValues(presence[i], true); return Object.assign(new ProtocolMessage(), deserialized); }; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 5370247778..0488f6bdf3 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -41,6 +41,7 @@ if (Platform.Config.noUpgrade) { export * from './modules/crypto'; export * from './modules/message'; +export * from './modules/presencemessage'; export * from './modules/msgpack'; export * from './modules/realtimepresence'; export { Rest } from '../../common/lib/client/rest'; diff --git a/src/platform/web/modules/presencemessage.ts b/src/platform/web/modules/presencemessage.ts new file mode 100644 index 0000000000..d90d2b42b5 --- /dev/null +++ b/src/platform/web/modules/presencemessage.ts @@ -0,0 +1,8 @@ +import * as API from '../../../../ably'; +import { fromEncoded, fromEncodedArray, fromValues } from '../../../common/lib/types/presencemessage'; + +// The type assertions for the functions below are due to https://github.com/ably/ably-js/issues/1421 + +export const decodePresenceMessage = fromEncoded as API.Types.PresenceMessageStatic['fromEncoded']; +export const decodePresenceMessages = fromEncodedArray as API.Types.PresenceMessageStatic['fromEncodedArray']; +export const constructPresenceMessage = fromValues as API.Types.PresenceMessageStatic['fromValues']; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index e3d483b684..0e0acd38a7 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -11,6 +11,9 @@ import { Crypto, MsgPack, RealtimePresence, + decodePresenceMessage, + decodePresenceMessages, + constructPresenceMessage, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -351,4 +354,47 @@ describe('browser/modules', function () { }); }); }); + + describe('PresenceMessage standalone functions', () => { + describe('decodePresenceMessage', () => { + it('decodes a presence message’s data', async () => { + const buffer = BufferUtils.utf8Encode('foo'); + const encodedMessage = { data: BufferUtils.base64Encode(buffer), encoding: 'base64' }; + + const decodedMessage = await decodePresenceMessage(encodedMessage); + + expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffer)).to.be.true; + expect(decodedMessage.encoding).to.be.null; + }); + }); + + describe('decodeMessages', () => { + it('decodes presence messages’ data', async () => { + const buffers = ['foo', 'bar'].map((data) => BufferUtils.utf8Encode(data)); + const encodedMessages = buffers.map((buffer) => ({ + data: BufferUtils.base64Encode(buffer), + encoding: 'base64', + })); + + const decodedMessages = await decodePresenceMessages(encodedMessages); + + for (let i = 0; i < decodedMessages.length; i++) { + const decodedMessage = decodedMessages[i]; + + expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffers[i])).to.be.true; + expect(decodedMessage.encoding).to.be.null; + } + }); + }); + + describe('constructPresenceMessage', () => { + it('creates a PresenceMessage instance', async () => { + const extras = { foo: 'bar' }; + const presenceMessage = constructPresenceMessage({ extras }); + + expect(presenceMessage.constructor.name).to.contain('PresenceMessage'); + expect(presenceMessage.extras).to.equal(extras); + }); + }); + }); }); From 50a934a428fcbf61ad1df275b10cb09d0e1a024d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 24 Aug 2023 09:26:28 -0300 Subject: [PATCH 172/468] Properly type realtime instance in ConnectionManager --- src/common/lib/transport/connectionmanager.ts | 9 +++++---- src/common/lib/types/protocolmessage.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 992fe239b6..0c9c3f15b0 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -17,8 +17,8 @@ import Transport, { TransportCtor } from './transport'; import * as API from '../../../../ably'; import { ErrCallback } from 'common/types/utils'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; +import BaseRealtime from '../client/baserealtime'; -type Realtime = any; type ClientOptions = any; let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self; @@ -190,7 +190,7 @@ type ConnectionState = { }; class ConnectionManager extends EventEmitter { - realtime: Realtime; + realtime: BaseRealtime; options: ClientOptions; states: Record; state: ConnectionState; @@ -226,7 +226,7 @@ class ConnectionManager extends EventEmitter { queue: { message: ProtocolMessage; transport: Transport }[]; } = { isProcessing: false, queue: [] }; - constructor(realtime: Realtime, options: ClientOptions) { + constructor(realtime: BaseRealtime, options: ClientOptions) { super(); ConnectionManager.initTransports(); this.realtime = realtime; @@ -1191,7 +1191,8 @@ class ConnectionManager extends EventEmitter { const newState = (this.state = this.states[stateChange.current as string]); if (stateChange.reason) { this.errorReason = stateChange.reason; - this.realtime.connection.errorReason = stateChange.reason; + // TODO remove this type assertion after fixing https://github.com/ably/ably-js/issues/1405 + this.realtime.connection.errorReason = stateChange.reason as ErrorInfo; } if (newState.terminal || newState.state === 'suspended') { /* suspended is nonterminal, but once in the suspended state, realtime diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index fde1978f9a..b721fe9e10 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -110,7 +110,7 @@ class ProtocolMessage { static serialize = Utils.encodeBody; - static deserialize = function (serialized: unknown, MsgPack: MsgPack, format?: Utils.Format): ProtocolMessage { + static deserialize = function (serialized: unknown, MsgPack: MsgPack | null, format?: Utils.Format): ProtocolMessage { const deserialized = Utils.decodeBody>(serialized, MsgPack, format); return ProtocolMessage.fromDeserialized(deserialized); }; From b818c279588c2d0ddb7da5336b2fd47a0be89c1e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 23 Aug 2023 14:07:16 -0300 Subject: [PATCH 173/468] Fix type of IPlatform.Transports --- src/common/platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/platform.ts b/src/common/platform.ts index b55e625a26..b276c1cbd9 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -30,7 +30,7 @@ export default class Platform { */ static Crypto: IUntypedCryptoStatic | null; static Http: typeof IHttp; - static Transports: Array<(connectionManager: typeof ConnectionManager) => Transport>; + static Transports: Array<(connectionManager: typeof ConnectionManager) => typeof Transport>; static Defaults: IDefaults; static WebStorage: IWebStorage | null; } From ebbb35425a621e425732141a6663e13b35ed88b8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 23 Aug 2023 14:38:02 -0300 Subject: [PATCH 174/468] Extract type of IPlatform.Transports Preparation for #1394 (making transports tree-shakable). --- src/common/lib/transport/connectionmanager.ts | 2 ++ src/common/platform.ts | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 0c9c3f15b0..0a7fc38c72 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -2167,3 +2167,5 @@ class ConnectionManager extends EventEmitter { } export default ConnectionManager; + +export type TransportInitialiser = (connectionManager: typeof ConnectionManager) => typeof Transport; diff --git a/src/common/platform.ts b/src/common/platform.ts index b276c1cbd9..aa8e133019 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -1,10 +1,9 @@ import { IPlatformConfig } from './types/IPlatformConfig'; import { IHttp } from './types/http'; -import ConnectionManager from './lib/transport/connectionmanager'; +import { TransportInitialiser } from './lib/transport/connectionmanager'; import IDefaults from './types/IDefaults'; import IWebStorage from './types/IWebStorage'; import IBufferUtils from './types/IBufferUtils'; -import Transport from './lib/transport/transport'; import * as WebBufferUtils from '../platform/web/lib/util/bufferutils'; import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils'; import { IUntypedCryptoStatic } from '../common/types/ICryptoStatic'; @@ -30,7 +29,7 @@ export default class Platform { */ static Crypto: IUntypedCryptoStatic | null; static Http: typeof IHttp; - static Transports: Array<(connectionManager: typeof ConnectionManager) => typeof Transport>; + static Transports: TransportInitialiser[]; static Defaults: IDefaults; static WebStorage: IWebStorage | null; } From 5d0a71ccbc4d64ee36f6564a03b06f888823f9e7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 23 Aug 2023 14:01:05 -0300 Subject: [PATCH 175/468] Convert platform/**/lib/transport/index.ts to TypeScript --- src/platform/nodejs/lib/transport/index.js | 3 --- src/platform/nodejs/lib/transport/index.ts | 3 +++ src/platform/nodejs/lib/transport/nodecomettransport.d.ts | 5 +++++ src/platform/web/lib/transport/index.js | 4 ---- src/platform/web/lib/transport/index.ts | 4 ++++ 5 files changed, 12 insertions(+), 7 deletions(-) delete mode 100644 src/platform/nodejs/lib/transport/index.js create mode 100644 src/platform/nodejs/lib/transport/index.ts create mode 100644 src/platform/nodejs/lib/transport/nodecomettransport.d.ts delete mode 100644 src/platform/web/lib/transport/index.js create mode 100644 src/platform/web/lib/transport/index.ts diff --git a/src/platform/nodejs/lib/transport/index.js b/src/platform/nodejs/lib/transport/index.js deleted file mode 100644 index 2d9be41828..0000000000 --- a/src/platform/nodejs/lib/transport/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import NodeCometTransport from './nodecomettransport'; - -export default [NodeCometTransport]; diff --git a/src/platform/nodejs/lib/transport/index.ts b/src/platform/nodejs/lib/transport/index.ts new file mode 100644 index 0000000000..1c0163c500 --- /dev/null +++ b/src/platform/nodejs/lib/transport/index.ts @@ -0,0 +1,3 @@ +import initialiseNodeCometTransport from './nodecomettransport'; + +export default [initialiseNodeCometTransport]; diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.d.ts b/src/platform/nodejs/lib/transport/nodecomettransport.d.ts new file mode 100644 index 0000000000..9067155ab9 --- /dev/null +++ b/src/platform/nodejs/lib/transport/nodecomettransport.d.ts @@ -0,0 +1,5 @@ +import ConnectionManager from '../../../../common/lib/transport/connectionmanager'; +import Transport from '../../../../common/lib/transport/transport'; + +declare function initialiseNodeCometTransport(connectionManager: typeof ConnectionManager): typeof Transport; +export default initialiseNodeCometTransport; diff --git a/src/platform/web/lib/transport/index.js b/src/platform/web/lib/transport/index.js deleted file mode 100644 index 8fd7afb26a..0000000000 --- a/src/platform/web/lib/transport/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import XHRPollingTransport from './xhrpollingtransport'; -import XHRStreamingTransport from './xhrstreamingtransport'; - -export default [XHRPollingTransport, XHRStreamingTransport]; diff --git a/src/platform/web/lib/transport/index.ts b/src/platform/web/lib/transport/index.ts new file mode 100644 index 0000000000..8d97bccaa7 --- /dev/null +++ b/src/platform/web/lib/transport/index.ts @@ -0,0 +1,4 @@ +import initialiseXHRPollingTransport from './xhrpollingtransport'; +import initialiseXHRStreamingTransport from './xhrstreamingtransport'; + +export default [initialiseXHRPollingTransport, initialiseXHRStreamingTransport]; From 17fce5575ba52f8ef658a28d95e62ba0451a89de Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 24 Aug 2023 15:20:40 -0300 Subject: [PATCH 176/468] Fix inflection of TransportNames enum name --- .../constants/{TransportNames.ts => TransportName.ts} | 4 ++-- src/common/types/IDefaults.d.ts | 10 +++++----- src/platform/nodejs/lib/util/defaults.ts | 10 +++++----- src/platform/web/lib/util/defaults.ts | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) rename src/common/constants/{TransportNames.ts => TransportName.ts} (68%) diff --git a/src/common/constants/TransportNames.ts b/src/common/constants/TransportName.ts similarity index 68% rename from src/common/constants/TransportNames.ts rename to src/common/constants/TransportName.ts index 9cda74c614..8c49a76cee 100644 --- a/src/common/constants/TransportNames.ts +++ b/src/common/constants/TransportName.ts @@ -1,8 +1,8 @@ -enum TransportNames { +enum TransportName { WebSocket = 'web_socket', Comet = 'comet', XhrStreaming = 'xhr_streaming', XhrPolling = 'xhr_polling', } -export default TransportNames; +export default TransportName; diff --git a/src/common/types/IDefaults.d.ts b/src/common/types/IDefaults.d.ts index 3fb342cbc8..1fd02bf5da 100644 --- a/src/common/types/IDefaults.d.ts +++ b/src/common/types/IDefaults.d.ts @@ -1,11 +1,11 @@ -import TransportNames from '../constants/TransportNames'; +import TransportName from '../constants/TransportName'; import { RestAgentOptions } from './ClientOptions'; export default interface IDefaults { connectivityCheckUrl: string; - defaultTransports: TransportNames[]; - baseTransportOrder: TransportNames[]; - transportPreferenceOrder: TransportNames[]; - upgradeTransports: TransportNames[]; + defaultTransports: TransportName[]; + baseTransportOrder: TransportName[]; + transportPreferenceOrder: TransportName[]; + upgradeTransports: TransportName[]; restAgentOptions?: RestAgentOptions; } diff --git a/src/platform/nodejs/lib/util/defaults.ts b/src/platform/nodejs/lib/util/defaults.ts index adaf8a5083..e6b55f18f8 100644 --- a/src/platform/nodejs/lib/util/defaults.ts +++ b/src/platform/nodejs/lib/util/defaults.ts @@ -1,14 +1,14 @@ import IDefaults from '../../../../common/types/IDefaults'; -import TransportNames from '../../../../common/constants/TransportNames'; +import TransportName from '../../../../common/constants/TransportName'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', /* Note: order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's supported. */ - defaultTransports: [TransportNames.WebSocket], - baseTransportOrder: [TransportNames.Comet, TransportNames.WebSocket], - transportPreferenceOrder: [TransportNames.Comet, TransportNames.WebSocket], - upgradeTransports: [TransportNames.WebSocket], + defaultTransports: [TransportName.WebSocket], + baseTransportOrder: [TransportName.Comet, TransportName.WebSocket], + transportPreferenceOrder: [TransportName.Comet, TransportName.WebSocket], + upgradeTransports: [TransportName.WebSocket], restAgentOptions: { maxSockets: 40, keepAlive: true }, }; diff --git a/src/platform/web/lib/util/defaults.ts b/src/platform/web/lib/util/defaults.ts index 7366ec75a4..b182008c18 100644 --- a/src/platform/web/lib/util/defaults.ts +++ b/src/platform/web/lib/util/defaults.ts @@ -1,15 +1,15 @@ import IDefaults from 'common/types/IDefaults'; -import TransportNames from 'common/constants/TransportNames'; +import TransportName from 'common/constants/TransportName'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', /* Order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's * supported. */ - defaultTransports: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], - baseTransportOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], - transportPreferenceOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], - upgradeTransports: [TransportNames.XhrStreaming, TransportNames.WebSocket], + defaultTransports: [TransportName.XhrPolling, TransportName.XhrStreaming, TransportName.WebSocket], + baseTransportOrder: [TransportName.XhrPolling, TransportName.XhrStreaming, TransportName.WebSocket], + transportPreferenceOrder: [TransportName.XhrPolling, TransportName.XhrStreaming, TransportName.WebSocket], + upgradeTransports: [TransportName.XhrStreaming, TransportName.WebSocket], }; export default Defaults; From a0df5f58bb030ab10453104f7fe98833b57dba3c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 24 Aug 2023 15:30:13 -0300 Subject: [PATCH 177/468] =?UTF-8?q?Use=20TransportName=20values=20when=20s?= =?UTF-8?q?etting=20transports=E2=80=99=20shortName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for #1394 (making transports tree-shakable). --- src/common/lib/transport/websockettransport.ts | 3 ++- src/platform/nodejs/lib/transport/nodecomettransport.js | 3 ++- src/platform/web/lib/transport/xhrpollingtransport.ts | 3 ++- src/platform/web/lib/transport/xhrstreamingtransport.ts | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 624c23dff5..3daafd2f8f 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -8,8 +8,9 @@ import ErrorInfo from '../types/errorinfo'; import NodeWebSocket from 'ws'; import ConnectionManager, { TransportParams } from './connectionmanager'; import Auth from '../client/auth'; +import TransportName from 'common/constants/TransportName'; -const shortName = 'web_socket'; +const shortName = TransportName.WebSocket; function isNodeWebSocket(ws: WebSocket | NodeWebSocket): ws is NodeWebSocket { return !!(ws as NodeWebSocket).on; diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.js b/src/platform/nodejs/lib/transport/nodecomettransport.js index 7ba730eb10..f215c97a74 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.js +++ b/src/platform/nodejs/lib/transport/nodecomettransport.js @@ -10,10 +10,11 @@ import http from 'http'; import https from 'https'; import url from 'url'; import util from 'util'; +import TransportName from '../../../../common/constants/TransportName'; var NodeCometTransport = function (connectionManager) { var noop = function () {}; - var shortName = 'comet'; + var shortName = TransportName.Comet; /* * A transport to use with nodejs diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index 212af4682f..a0dd833a02 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -4,8 +4,9 @@ import XHRRequest from './xhrrequest'; import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; +import TransportName from 'common/constants/TransportName'; -var shortName = 'xhr_polling'; +var shortName = TransportName.XhrPolling; class XHRPollingTransport extends CometTransport { shortName = shortName; constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index d2b816a650..c7c5c4fa2b 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -4,8 +4,9 @@ import XHRRequest from './xhrrequest'; import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; +import TransportName from 'common/constants/TransportName'; -const shortName = 'xhr_streaming'; +const shortName = TransportName.XhrStreaming; class XHRStreamingTransport extends CometTransport { shortName = shortName; constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { From 8d4e4779999f1afac93fb701308d1839f071a77e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 24 Aug 2023 15:54:39 -0300 Subject: [PATCH 178/468] =?UTF-8?q?Decouple=20Platform.Transport=E2=80=99s?= =?UTF-8?q?=20transport=20order=20from=20implementations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for #1394 (making transports tree-shakable). --- src/common/lib/transport/connectionmanager.ts | 3 ++- src/common/platform.ts | 8 +++++++- src/platform/nodejs/lib/transport/index.ts | 6 +++++- src/platform/web/lib/transport/index.ts | 9 ++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 0a7fc38c72..48a99b9010 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -410,7 +410,8 @@ class ConnectionManager extends EventEmitter { static initTransports() { WebSocketTransport(ConnectionManager); - Utils.arrForEach(Platform.Transports, function (initFn) { + Utils.arrForEach(Platform.Transports.order, function (transportName) { + const initFn = Platform.Transports.implementations[transportName]!; initFn(ConnectionManager); }); } diff --git a/src/common/platform.ts b/src/common/platform.ts index aa8e133019..3e103063dc 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -7,11 +7,14 @@ import IBufferUtils from './types/IBufferUtils'; import * as WebBufferUtils from '../platform/web/lib/util/bufferutils'; import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils'; import { IUntypedCryptoStatic } from '../common/types/ICryptoStatic'; +import TransportName from './constants/TransportName'; type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; type ToBufferOutput = WebBufferUtils.ToBufferOutput | NodeBufferUtils.ToBufferOutput; +export type TransportImplementations = Partial>; + export default class Platform { static Config: IPlatformConfig; /* @@ -29,7 +32,10 @@ export default class Platform { */ static Crypto: IUntypedCryptoStatic | null; static Http: typeof IHttp; - static Transports: TransportInitialiser[]; + static Transports: { + order: TransportName[]; + implementations: TransportImplementations; + }; static Defaults: IDefaults; static WebStorage: IWebStorage | null; } diff --git a/src/platform/nodejs/lib/transport/index.ts b/src/platform/nodejs/lib/transport/index.ts index 1c0163c500..c5db7c0429 100644 --- a/src/platform/nodejs/lib/transport/index.ts +++ b/src/platform/nodejs/lib/transport/index.ts @@ -1,3 +1,7 @@ +import TransportName from 'common/constants/TransportName'; import initialiseNodeCometTransport from './nodecomettransport'; -export default [initialiseNodeCometTransport]; +export default { + order: [TransportName.Comet], + implementations: { [TransportName.Comet]: initialiseNodeCometTransport }, +}; diff --git a/src/platform/web/lib/transport/index.ts b/src/platform/web/lib/transport/index.ts index 8d97bccaa7..98674dd0e9 100644 --- a/src/platform/web/lib/transport/index.ts +++ b/src/platform/web/lib/transport/index.ts @@ -1,4 +1,11 @@ +import TransportName from 'common/constants/TransportName'; import initialiseXHRPollingTransport from './xhrpollingtransport'; import initialiseXHRStreamingTransport from './xhrstreamingtransport'; -export default [initialiseXHRPollingTransport, initialiseXHRStreamingTransport]; +export default { + order: [TransportName.XhrPolling, TransportName.XhrStreaming], + implementations: { + [TransportName.XhrPolling]: initialiseXHRPollingTransport, + [TransportName.XhrStreaming]: initialiseXHRStreamingTransport, + }, +}; From 4fe06fafe2ba61c0e94c4f37bccc491652efe6f7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 24 Aug 2023 10:35:47 -0300 Subject: [PATCH 179/468] Fix connectionManager type in transport initialisers --- src/common/lib/transport/websockettransport.ts | 2 +- src/platform/web/lib/transport/xhrpollingtransport.ts | 2 +- src/platform/web/lib/transport/xhrstreamingtransport.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 3daafd2f8f..540d9cf799 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -196,7 +196,7 @@ class WebSocketTransport extends Transport { } } -function initialiseTransport(connectionManager: any): typeof WebSocketTransport { +function initialiseTransport(connectionManager: typeof ConnectionManager): typeof WebSocketTransport { if (WebSocketTransport.isAvailable()) connectionManager.supportedTransports[shortName] = WebSocketTransport; return WebSocketTransport; diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index a0dd833a02..5a7dfafd1c 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -34,7 +34,7 @@ class XHRPollingTransport extends CometTransport { } } -function initialiseTransport(connectionManager: any): typeof XHRPollingTransport { +function initialiseTransport(connectionManager: typeof ConnectionManager): typeof XHRPollingTransport { if (XHRPollingTransport.isAvailable()) connectionManager.supportedTransports[shortName] = XHRPollingTransport; return XHRPollingTransport; diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index c7c5c4fa2b..8640eec37e 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -32,7 +32,7 @@ class XHRStreamingTransport extends CometTransport { } } -function initialiseTransport(connectionManager: any): typeof XHRStreamingTransport { +function initialiseTransport(connectionManager: typeof ConnectionManager): typeof XHRStreamingTransport { if (XHRStreamingTransport.isAvailable()) connectionManager.supportedTransports[shortName] = XHRStreamingTransport; return XHRStreamingTransport; From af25aa4bd19f75903753722166bf4e65959ebc71 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 29 Aug 2023 16:53:56 -0300 Subject: [PATCH 180/468] Fix supportedTransports type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using the bare Record type implies that fetching from it will return a value for any string key, which isn’t the case. --- src/common/lib/transport/connectionmanager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 48a99b9010..940cf6bcef 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -99,7 +99,7 @@ function decodeRecoveryKey(recoveryKey: string): RecoveryContext | null { } } -const supportedTransports: Record = {}; +const supportedTransports: Partial> = {}; export class TransportParams { options: ClientOptions; @@ -486,7 +486,7 @@ class ConnectionManager extends EventEmitter { Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.tryATransport()', 'trying ' + candidate); Transport.tryConnect( - ConnectionManager.supportedTransports[candidate], + ConnectionManager.supportedTransports[candidate]!, this, this.realtime.auth, transportParams, From ead86621dffd02e1ee7bdbbfa2dab6ef92d4c9cf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 29 Aug 2023 17:18:46 -0300 Subject: [PATCH 181/468] Properly type client options in connectionmanager.ts --- src/common/lib/transport/connectionmanager.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 940cf6bcef..4f97386c8d 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -18,8 +18,7 @@ import * as API from '../../../../ably'; import { ErrCallback } from 'common/types/utils'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; import BaseRealtime from '../client/baserealtime'; - -type ClientOptions = any; +import { NormalisedClientOptions } from 'common/types/ClientOptions'; let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self; @@ -91,9 +90,9 @@ type RecoveryContext = { channelSerials: { [name: string]: string }; }; -function decodeRecoveryKey(recoveryKey: string): RecoveryContext | null { +function decodeRecoveryKey(recoveryKey: NormalisedClientOptions['recover']): RecoveryContext | null { try { - return JSON.parse(recoveryKey); + return JSON.parse(recoveryKey as string); } catch (e) { return null; } @@ -102,7 +101,7 @@ function decodeRecoveryKey(recoveryKey: string): RecoveryContext | null { const supportedTransports: Partial> = {}; export class TransportParams { - options: ClientOptions; + options: NormalisedClientOptions; host: string | null; mode: string; format?: Utils.Format; @@ -110,7 +109,7 @@ export class TransportParams { stream?: any; heartbeats?: boolean; - constructor(options: ClientOptions, host: string | null, mode: string, connectionKey?: string) { + constructor(options: NormalisedClientOptions, host: string | null, mode: string, connectionKey?: string) { this.options = options; this.host = host; this.mode = mode; @@ -191,7 +190,7 @@ type ConnectionState = { class ConnectionManager extends EventEmitter { realtime: BaseRealtime; - options: ClientOptions; + options: NormalisedClientOptions; states: Record; state: ConnectionState; errorReason: IPartialErrorInfo | string | null; @@ -226,7 +225,7 @@ class ConnectionManager extends EventEmitter { queue: { message: ProtocolMessage; transport: Transport }[]; } = { isProcessing: false, queue: [] }; - constructor(realtime: BaseRealtime, options: ClientOptions) { + constructor(realtime: BaseRealtime, options: NormalisedClientOptions) { super(); ConnectionManager.initTransports(); this.realtime = realtime; @@ -601,7 +600,7 @@ class ConnectionManager extends EventEmitter { if (mode === 'recover' && this.options.recover) { /* After a successful recovery, we unpersist, as a recovery key cannot * be used more than once */ - this.options.recover = null; + delete this.options.recover; this.unpersistConnection(); } }); From f05de53e0c8a97c8ba7a122a761f3430405f2c12 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 29 Aug 2023 17:22:11 -0300 Subject: [PATCH 182/468] Tighten up type in places where transport name is used --- src/common/constants/TransportName.ts | 16 +++++++++++----- src/common/lib/transport/connectionmanager.ts | 19 ++++++++++--------- .../lib/transport/websockettransport.ts | 4 ++-- src/common/lib/util/utils.ts | 4 ++-- src/platform/nodejs/lib/transport/index.ts | 6 +++--- .../lib/transport/nodecomettransport.js | 4 ++-- src/platform/nodejs/lib/util/defaults.ts | 10 +++++----- src/platform/web/lib/transport/index.ts | 8 ++++---- .../web/lib/transport/xhrpollingtransport.ts | 4 ++-- .../lib/transport/xhrstreamingtransport.ts | 4 ++-- src/platform/web/lib/util/defaults.ts | 10 +++++----- 11 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/common/constants/TransportName.ts b/src/common/constants/TransportName.ts index 8c49a76cee..29137c7f4c 100644 --- a/src/common/constants/TransportName.ts +++ b/src/common/constants/TransportName.ts @@ -1,8 +1,14 @@ -enum TransportName { - WebSocket = 'web_socket', - Comet = 'comet', - XhrStreaming = 'xhr_streaming', - XhrPolling = 'xhr_polling', +export namespace TransportNames { + export const WebSocket = 'web_socket' as const; + export const Comet = 'comet' as const; + export const XhrStreaming = 'xhr_streaming' as const; + export const XhrPolling = 'xhr_polling' as const; } +type TransportName = + | typeof TransportNames.WebSocket + | typeof TransportNames.Comet + | typeof TransportNames.XhrStreaming + | typeof TransportNames.XhrPolling; + export default TransportName; diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 4f97386c8d..3915879f62 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -19,6 +19,7 @@ import { ErrCallback } from 'common/types/utils'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; import BaseRealtime from '../client/baserealtime'; import { NormalisedClientOptions } from 'common/types/ClientOptions'; +import TransportName from 'common/constants/TransportName'; let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self; @@ -98,7 +99,7 @@ function decodeRecoveryKey(recoveryKey: NormalisedClientOptions['recover']): Rec } } -const supportedTransports: Partial> = {}; +const supportedTransports: Partial> = {}; export class TransportParams { options: NormalisedClientOptions; @@ -201,9 +202,9 @@ class ConnectionManager extends EventEmitter { connectionKey?: string; connectionStateTtl: number; maxIdleInterval: number | null; - transports: string[]; - baseTransport: string; - upgradeTransports: string[]; + transports: TransportName[]; + baseTransport: TransportName; + upgradeTransports: TransportName[]; transportPreference: string | null; httpHosts: string[]; activeProtocol: null | Protocol; @@ -481,7 +482,7 @@ class ConnectionManager extends EventEmitter { * @param candidate, the transport to try * @param callback */ - tryATransport(transportParams: TransportParams, candidate: string, callback: Function): void { + tryATransport(transportParams: TransportParams, candidate: TransportName, callback: Function): void { Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.tryATransport()', 'trying ' + candidate); Transport.tryConnect( @@ -1674,13 +1675,13 @@ class ConnectionManager extends EventEmitter { this.tryATransport(transportParams, this.baseTransport, hostAttemptCb); } - getUpgradePossibilities(): string[] { + getUpgradePossibilities(): TransportName[] { /* returns the subset of upgradeTransports to the right of the current * transport in upgradeTransports (if it's in there - if not, currentSerial * will be -1, so return upgradeTransports.slice(0) == upgradeTransports */ const current = (this.activeProtocol as Protocol).getTransport().shortName; const currentSerial = Utils.arrIndexOf(this.upgradeTransports, current); - return this.upgradeTransports.slice(currentSerial + 1) as string[]; + return this.upgradeTransports.slice(currentSerial + 1); } upgradeIfNeeded(transportParams: Record): void { @@ -1695,7 +1696,7 @@ class ConnectionManager extends EventEmitter { return; } - Utils.arrForEach(upgradePossibilities, (upgradeTransport: string) => { + Utils.arrForEach(upgradePossibilities, (upgradeTransport: TransportName) => { /* Note: the transport may mutate the params, so give each transport a fresh one */ const upgradeTransportParams = this.createTransportParams(transportParams.host, 'upgrade'); this.tryATransport(upgradeTransportParams, upgradeTransport, noop); @@ -2098,7 +2099,7 @@ class ConnectionManager extends EventEmitter { this.proposedTransports.push(transport); } - getTransportPreference(): string { + getTransportPreference(): TransportName { return this.transportPreference || (haveWebStorage() && Platform.WebStorage?.get?.(transportPreferenceName)); } diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 540d9cf799..079ac13036 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -8,9 +8,9 @@ import ErrorInfo from '../types/errorinfo'; import NodeWebSocket from 'ws'; import ConnectionManager, { TransportParams } from './connectionmanager'; import Auth from '../client/auth'; -import TransportName from 'common/constants/TransportName'; +import { TransportNames } from 'common/constants/TransportName'; -const shortName = TransportName.WebSocket; +const shortName = TransportNames.WebSocket; function isNodeWebSocket(ws: WebSocket | NodeWebSocket): ws is NodeWebSocket { return !!(ws as NodeWebSocket).on; diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index ba8fa6f370..494a4b6a3e 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -156,7 +156,7 @@ export function containsValue(ob: Record, val: unknown): boolea return false; } -export function intersect(arr: Array, ob: string[] | Record): string[] { +export function intersect(arr: Array, ob: K[] | Partial>): K[] { return isArray(ob) ? arrIntersect(arr, ob) : arrIntersectOb(arr, ob); } @@ -169,7 +169,7 @@ export function arrIntersect(arr1: Array, arr2: Array): Array { return result; } -export function arrIntersectOb(arr: Array, ob: Record): string[] { +export function arrIntersectOb(arr: Array, ob: Partial>): K[] { const result = []; for (let i = 0; i < arr.length; i++) { const member = arr[i]; diff --git a/src/platform/nodejs/lib/transport/index.ts b/src/platform/nodejs/lib/transport/index.ts index c5db7c0429..084b0cc955 100644 --- a/src/platform/nodejs/lib/transport/index.ts +++ b/src/platform/nodejs/lib/transport/index.ts @@ -1,7 +1,7 @@ -import TransportName from 'common/constants/TransportName'; +import { TransportNames } from 'common/constants/TransportName'; import initialiseNodeCometTransport from './nodecomettransport'; export default { - order: [TransportName.Comet], - implementations: { [TransportName.Comet]: initialiseNodeCometTransport }, + order: [TransportNames.Comet], + implementations: { [TransportNames.Comet]: initialiseNodeCometTransport }, }; diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.js b/src/platform/nodejs/lib/transport/nodecomettransport.js index f215c97a74..6e6655bb0d 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.js +++ b/src/platform/nodejs/lib/transport/nodecomettransport.js @@ -10,11 +10,11 @@ import http from 'http'; import https from 'https'; import url from 'url'; import util from 'util'; -import TransportName from '../../../../common/constants/TransportName'; +import { TransportNames } from '../../../../common/constants/TransportName'; var NodeCometTransport = function (connectionManager) { var noop = function () {}; - var shortName = TransportName.Comet; + var shortName = TransportNames.Comet; /* * A transport to use with nodejs diff --git a/src/platform/nodejs/lib/util/defaults.ts b/src/platform/nodejs/lib/util/defaults.ts index e6b55f18f8..8b29953cbd 100644 --- a/src/platform/nodejs/lib/util/defaults.ts +++ b/src/platform/nodejs/lib/util/defaults.ts @@ -1,14 +1,14 @@ import IDefaults from '../../../../common/types/IDefaults'; -import TransportName from '../../../../common/constants/TransportName'; +import { TransportNames } from '../../../../common/constants/TransportName'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', /* Note: order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's supported. */ - defaultTransports: [TransportName.WebSocket], - baseTransportOrder: [TransportName.Comet, TransportName.WebSocket], - transportPreferenceOrder: [TransportName.Comet, TransportName.WebSocket], - upgradeTransports: [TransportName.WebSocket], + defaultTransports: [TransportNames.WebSocket], + baseTransportOrder: [TransportNames.Comet, TransportNames.WebSocket], + transportPreferenceOrder: [TransportNames.Comet, TransportNames.WebSocket], + upgradeTransports: [TransportNames.WebSocket], restAgentOptions: { maxSockets: 40, keepAlive: true }, }; diff --git a/src/platform/web/lib/transport/index.ts b/src/platform/web/lib/transport/index.ts index 98674dd0e9..8fb1447186 100644 --- a/src/platform/web/lib/transport/index.ts +++ b/src/platform/web/lib/transport/index.ts @@ -1,11 +1,11 @@ -import TransportName from 'common/constants/TransportName'; +import { TransportNames } from 'common/constants/TransportName'; import initialiseXHRPollingTransport from './xhrpollingtransport'; import initialiseXHRStreamingTransport from './xhrstreamingtransport'; export default { - order: [TransportName.XhrPolling, TransportName.XhrStreaming], + order: [TransportNames.XhrPolling, TransportNames.XhrStreaming], implementations: { - [TransportName.XhrPolling]: initialiseXHRPollingTransport, - [TransportName.XhrStreaming]: initialiseXHRStreamingTransport, + [TransportNames.XhrPolling]: initialiseXHRPollingTransport, + [TransportNames.XhrStreaming]: initialiseXHRStreamingTransport, }, }; diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index 5a7dfafd1c..c063397198 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -4,9 +4,9 @@ import XHRRequest from './xhrrequest'; import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; -import TransportName from 'common/constants/TransportName'; +import { TransportNames } from 'common/constants/TransportName'; -var shortName = TransportName.XhrPolling; +var shortName = TransportNames.XhrPolling; class XHRPollingTransport extends CometTransport { shortName = shortName; constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index 8640eec37e..57cf30e7b7 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -4,9 +4,9 @@ import XHRRequest from './xhrrequest'; import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; -import TransportName from 'common/constants/TransportName'; +import { TransportNames } from 'common/constants/TransportName'; -const shortName = TransportName.XhrStreaming; +const shortName = TransportNames.XhrStreaming; class XHRStreamingTransport extends CometTransport { shortName = shortName; constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { diff --git a/src/platform/web/lib/util/defaults.ts b/src/platform/web/lib/util/defaults.ts index b182008c18..3dbd549733 100644 --- a/src/platform/web/lib/util/defaults.ts +++ b/src/platform/web/lib/util/defaults.ts @@ -1,15 +1,15 @@ import IDefaults from 'common/types/IDefaults'; -import TransportName from 'common/constants/TransportName'; +import { TransportNames } from 'common/constants/TransportName'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', /* Order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's * supported. */ - defaultTransports: [TransportName.XhrPolling, TransportName.XhrStreaming, TransportName.WebSocket], - baseTransportOrder: [TransportName.XhrPolling, TransportName.XhrStreaming, TransportName.WebSocket], - transportPreferenceOrder: [TransportName.XhrPolling, TransportName.XhrStreaming, TransportName.WebSocket], - upgradeTransports: [TransportName.XhrStreaming, TransportName.WebSocket], + defaultTransports: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], + baseTransportOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], + transportPreferenceOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], + upgradeTransports: [TransportNames.XhrStreaming, TransportNames.WebSocket], }; export default Defaults; From 57c09f6f3c65f8c0300af3908059ef3ba8f63daf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 24 Aug 2023 10:33:53 -0300 Subject: [PATCH 183/468] Move supportedTransports storage into ConnectionManager instance Preparation for #1394 (making transports tree-shakable). --- src/common/lib/transport/connectionmanager.ts | 31 ++++++++++++------- .../lib/transport/websockettransport.ts | 6 ++-- .../lib/transport/nodecomettransport.d.ts | 4 +-- .../lib/transport/nodecomettransport.js | 4 +-- .../web/lib/transport/xhrpollingtransport.ts | 6 ++-- .../lib/transport/xhrstreamingtransport.ts | 6 ++-- test/common/modules/shared_helper.js | 1 - 7 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 3915879f62..fb8311824b 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -99,8 +99,6 @@ function decodeRecoveryKey(recoveryKey: NormalisedClientOptions['recover']): Rec } } -const supportedTransports: Partial> = {}; - export class TransportParams { options: NormalisedClientOptions; host: string | null; @@ -190,6 +188,7 @@ type ConnectionState = { }; class ConnectionManager extends EventEmitter { + supportedTransports: Partial> = {}; realtime: BaseRealtime; options: NormalisedClientOptions; states: Record; @@ -228,7 +227,7 @@ class ConnectionManager extends EventEmitter { constructor(realtime: BaseRealtime, options: NormalisedClientOptions) { super(); - ConnectionManager.initTransports(); + this.initTransports(); this.realtime = realtime; this.options = options; const timeouts = options.timeouts; @@ -305,10 +304,7 @@ class ConnectionManager extends EventEmitter { this.connectionStateTtl = timeouts.connectionStateTtl; this.maxIdleInterval = null; - this.transports = Utils.intersect( - options.transports || Defaults.defaultTransports, - ConnectionManager.supportedTransports - ); + this.transports = Utils.intersect(options.transports || Defaults.defaultTransports, this.supportedTransports); /* baseTransports selects the leftmost transport in the Defaults.baseTransportOrder list * that's both requested and supported. */ this.baseTransport = Utils.intersect(Defaults.baseTransportOrder, this.transports)[0]; @@ -404,18 +400,25 @@ class ConnectionManager extends EventEmitter { * transport management *********************/ + // Used by tests static get supportedTransports() { - return supportedTransports; + const storage: TransportStorage = { supportedTransports: {} }; + this.initTransports(storage); + return storage.supportedTransports; } - static initTransports() { - WebSocketTransport(ConnectionManager); + private static initTransports(storage: TransportStorage) { + WebSocketTransport(storage); Utils.arrForEach(Platform.Transports.order, function (transportName) { const initFn = Platform.Transports.implementations[transportName]!; initFn(ConnectionManager); }); } + initTransports() { + ConnectionManager.initTransports(this); + } + createTransportParams(host: string | null, mode: string): TransportParams { return new TransportParams(this.options, host, mode, this.connectionKey); } @@ -486,7 +489,7 @@ class ConnectionManager extends EventEmitter { Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.tryATransport()', 'trying ' + candidate); Transport.tryConnect( - ConnectionManager.supportedTransports[candidate]!, + this.supportedTransports[candidate]!, this, this.realtime.auth, transportParams, @@ -2169,4 +2172,8 @@ class ConnectionManager extends EventEmitter { export default ConnectionManager; -export type TransportInitialiser = (connectionManager: typeof ConnectionManager) => typeof Transport; +export interface TransportStorage { + supportedTransports: Partial>; +} + +export type TransportInitialiser = (transportStorage: TransportStorage) => typeof Transport; diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 079ac13036..60f2cc061a 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -6,7 +6,7 @@ import Logger from '../util/logger'; import ProtocolMessage from '../types/protocolmessage'; import ErrorInfo from '../types/errorinfo'; import NodeWebSocket from 'ws'; -import ConnectionManager, { TransportParams } from './connectionmanager'; +import ConnectionManager, { TransportParams, TransportStorage } from './connectionmanager'; import Auth from '../client/auth'; import { TransportNames } from 'common/constants/TransportName'; @@ -196,8 +196,8 @@ class WebSocketTransport extends Transport { } } -function initialiseTransport(connectionManager: typeof ConnectionManager): typeof WebSocketTransport { - if (WebSocketTransport.isAvailable()) connectionManager.supportedTransports[shortName] = WebSocketTransport; +function initialiseTransport(transportStorage: TransportStorage): typeof WebSocketTransport { + if (WebSocketTransport.isAvailable()) transportStorage.supportedTransports[shortName] = WebSocketTransport; return WebSocketTransport; } diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.d.ts b/src/platform/nodejs/lib/transport/nodecomettransport.d.ts index 9067155ab9..f90fa468b0 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.d.ts +++ b/src/platform/nodejs/lib/transport/nodecomettransport.d.ts @@ -1,5 +1,5 @@ -import ConnectionManager from '../../../../common/lib/transport/connectionmanager'; +import { TransportStorage } from '../../../../common/lib/transport/connectionmanager'; import Transport from '../../../../common/lib/transport/transport'; -declare function initialiseNodeCometTransport(connectionManager: typeof ConnectionManager): typeof Transport; +declare function initialiseNodeCometTransport(transportStorage: TransportStorage): typeof Transport; export default initialiseNodeCometTransport; diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.js b/src/platform/nodejs/lib/transport/nodecomettransport.js index 6e6655bb0d..9a1c29411d 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.js +++ b/src/platform/nodejs/lib/transport/nodecomettransport.js @@ -12,7 +12,7 @@ import url from 'url'; import util from 'util'; import { TransportNames } from '../../../../common/constants/TransportName'; -var NodeCometTransport = function (connectionManager) { +var NodeCometTransport = function (transportStorage) { var noop = function () {}; var shortName = TransportNames.Comet; @@ -32,7 +32,7 @@ var NodeCometTransport = function (connectionManager) { NodeCometTransport.isAvailable = function () { return true; }; - connectionManager.supportedTransports[shortName] = NodeCometTransport; + transportStorage.supportedTransports[shortName] = NodeCometTransport; NodeCometTransport.prototype.toString = function () { return ( diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index c063397198..4d3b2110d3 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -1,7 +1,7 @@ import Platform from '../../../../common/platform'; import CometTransport from '../../../../common/lib/transport/comettransport'; import XHRRequest from './xhrrequest'; -import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; +import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; import { TransportNames } from 'common/constants/TransportName'; @@ -34,8 +34,8 @@ class XHRPollingTransport extends CometTransport { } } -function initialiseTransport(connectionManager: typeof ConnectionManager): typeof XHRPollingTransport { - if (XHRPollingTransport.isAvailable()) connectionManager.supportedTransports[shortName] = XHRPollingTransport; +function initialiseTransport(transportStorage: TransportStorage): typeof XHRPollingTransport { + if (XHRPollingTransport.isAvailable()) transportStorage.supportedTransports[shortName] = XHRPollingTransport; return XHRPollingTransport; } diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index 57cf30e7b7..9ef8631fec 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -1,7 +1,7 @@ import CometTransport from '../../../../common/lib/transport/comettransport'; import Platform from '../../../../common/platform'; import XHRRequest from './xhrrequest'; -import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; +import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; import { TransportNames } from 'common/constants/TransportName'; @@ -32,8 +32,8 @@ class XHRStreamingTransport extends CometTransport { } } -function initialiseTransport(connectionManager: typeof ConnectionManager): typeof XHRStreamingTransport { - if (XHRStreamingTransport.isAvailable()) connectionManager.supportedTransports[shortName] = XHRStreamingTransport; +function initialiseTransport(transportStorage: TransportStorage): typeof XHRStreamingTransport { + if (XHRStreamingTransport.isAvailable()) transportStorage.supportedTransports[shortName] = XHRStreamingTransport; return XHRStreamingTransport; } diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 4d8581ab22..ecf011b760 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -14,7 +14,6 @@ define([ var platform = clientModule.Ably.Realtime.Platform; var BufferUtils = platform.BufferUtils; var expect = chai.expect; - clientModule.Ably.Realtime.ConnectionManager.initTransports(); var availableTransports = utils.keysArray(clientModule.Ably.Realtime.ConnectionManager.supportedTransports), bestTransport = availableTransports[0], /* IANA reserved; requests to it will hang forever */ From d7f9bd5b41651e4692f244eb41385c015b98a332 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 23 Aug 2023 14:21:56 -0300 Subject: [PATCH 184/468] Make transports tree-shakable We expose WebSocketTransport, XHRPolling, and XHRStreaming modules. Resolves #1394. --- scripts/moduleReport.js | 10 +- src/common/lib/client/baserealtime.ts | 21 +++ src/common/lib/client/defaultrealtime.ts | 9 +- src/common/lib/client/modulesmap.ts | 4 + src/common/lib/transport/connectionmanager.ts | 28 ++-- src/common/platform.ts | 3 +- src/platform/nodejs/lib/transport/index.ts | 6 +- src/platform/web/lib/transport/index.ts | 26 +++- src/platform/web/modules.ts | 5 +- src/platform/web/modules/transports.ts | 3 + test/browser/modules.test.js | 147 +++++++++++++----- test/browser/simple.test.js | 2 +- test/common/modules/shared_helper.js | 4 +- 13 files changed, 205 insertions(+), 63 deletions(-) create mode 100644 src/platform/web/modules/transports.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 84d6bac99b..ee5cadf0a8 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -1,7 +1,15 @@ const esbuild = require('esbuild'); // List of all modules accepted in ModulesMap -const moduleNames = ['Rest', 'Crypto', 'MsgPack', 'RealtimePresence']; +const moduleNames = [ + 'Rest', + 'Crypto', + 'MsgPack', + 'RealtimePresence', + 'XHRPolling', + 'XHRStreaming', + 'WebSocketTransport', +]; // List of all free-standing functions exported by the library along with the // ModulesMap entries that we expect them to transitively import diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index ff51a2a773..0fe9558313 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -11,24 +11,45 @@ import ClientOptions from '../../types/ClientOptions'; import * as API from '../../../../ably'; import { ModulesMap } from './modulesmap'; import RealtimePresence from './realtimepresence'; +import { TransportNames } from 'common/constants/TransportName'; +import { TransportImplementations } from 'common/platform'; /** `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. */ class BaseRealtime extends BaseClient { readonly _RealtimePresence: typeof RealtimePresence | null; + // Extra transport implementations available to this client, in addition to those in Platform.Transports.bundledImplementations + readonly _additionalTransportImplementations: TransportImplementations; _channels: any; connection: Connection; constructor(options: ClientOptions, modules: ModulesMap) { super(options, modules); Logger.logAction(Logger.LOG_MINOR, 'Realtime()', ''); + this._additionalTransportImplementations = BaseRealtime.transportImplementationsFromModules(modules); this._RealtimePresence = modules.RealtimePresence ?? null; this.connection = new Connection(this, this.options); this._channels = new Channels(this); if (options.autoConnect !== false) this.connect(); } + private static transportImplementationsFromModules(modules: ModulesMap) { + const transports: TransportImplementations = {}; + + if (modules.WebSocketTransport) { + transports[TransportNames.WebSocket] = modules.WebSocketTransport; + } + if (modules.XHRStreaming) { + transports[TransportNames.XhrStreaming] = modules.XHRStreaming; + } + if (modules.XHRPolling) { + transports[TransportNames.XhrPolling] = modules.XHRPolling; + } + + return transports; + } + get channels() { return this._channels; } diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 242e612288..2204ab71ac 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -9,6 +9,7 @@ import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; +import initialiseWebSocketTransport from '../transport/websockettransport'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -20,7 +21,13 @@ export class DefaultRealtime extends BaseRealtime { throw new Error('Expected DefaultRealtime._MsgPack to have been set'); } - super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined, MsgPack, RealtimePresence }); + super(options, { + ...allCommonModules, + Crypto: DefaultRealtime.Crypto ?? undefined, + MsgPack, + RealtimePresence, + WebSocketTransport: initialiseWebSocketTransport, + }); } static Utils = Utils; diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index a4de0a0d51..ada7de44ee 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -2,12 +2,16 @@ import { Rest } from './rest'; import { IUntypedCryptoStatic } from '../../types/ICryptoStatic'; import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; +import { TransportInitialiser } from '../transport/connectionmanager'; export interface ModulesMap { Rest?: typeof Rest; Crypto?: IUntypedCryptoStatic; MsgPack?: MsgPack; RealtimePresence?: typeof RealtimePresence; + WebSocketTransport?: TransportInitialiser; + XHRPolling?: TransportInitialiser; + XHRStreaming?: TransportInitialiser; } export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index fb8311824b..335e2a01d4 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -2,7 +2,7 @@ import ProtocolMessage from 'common/lib/types/protocolmessage'; import * as Utils from 'common/lib/util/utils'; import Protocol, { PendingMessage } from './protocol'; import Defaults, { getAgentString } from 'common/lib/util/defaults'; -import Platform from 'common/platform'; +import Platform, { TransportImplementations } from 'common/platform'; import EventEmitter from '../util/eventemitter'; import MessageQueue from './messagequeue'; import Logger from '../util/logger'; @@ -12,14 +12,13 @@ import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from 'common/lib/types import Auth from 'common/lib/client/auth'; import Message from 'common/lib/types/message'; import Multicaster, { MulticasterInstance } from 'common/lib/util/multicaster'; -import WebSocketTransport from './websockettransport'; import Transport, { TransportCtor } from './transport'; import * as API from '../../../../ably'; import { ErrCallback } from 'common/types/utils'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; import BaseRealtime from '../client/baserealtime'; import { NormalisedClientOptions } from 'common/types/ClientOptions'; -import TransportName from 'common/constants/TransportName'; +import TransportName, { TransportNames } from 'common/constants/TransportName'; let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self; @@ -227,8 +226,8 @@ class ConnectionManager extends EventEmitter { constructor(realtime: BaseRealtime, options: NormalisedClientOptions) { super(); - this.initTransports(); this.realtime = realtime; + this.initTransports(); this.options = options; const timeouts = options.timeouts; /* connectingTimeout: leave preferenceConnectTimeout (~6s) to try the @@ -401,22 +400,29 @@ class ConnectionManager extends EventEmitter { *********************/ // Used by tests - static get supportedTransports() { + static supportedTransports(additionalImplementations: TransportImplementations) { const storage: TransportStorage = { supportedTransports: {} }; - this.initTransports(storage); + this.initTransports(additionalImplementations, storage); return storage.supportedTransports; } - private static initTransports(storage: TransportStorage) { - WebSocketTransport(storage); + private static initTransports(additionalImplementations: TransportImplementations, storage: TransportStorage) { + const implementations = { ...Platform.Transports.bundledImplementations, ...additionalImplementations }; + + const initialiseWebSocketTransport = implementations[TransportNames.WebSocket]; + if (initialiseWebSocketTransport) { + initialiseWebSocketTransport(storage); + } Utils.arrForEach(Platform.Transports.order, function (transportName) { - const initFn = Platform.Transports.implementations[transportName]!; - initFn(ConnectionManager); + const initFn = implementations[transportName]; + if (initFn) { + initFn(storage); + } }); } initTransports() { - ConnectionManager.initTransports(this); + ConnectionManager.initTransports(this.realtime._additionalTransportImplementations, this); } createTransportParams(host: string | null, mode: string): TransportParams { diff --git a/src/common/platform.ts b/src/common/platform.ts index 3e103063dc..609b232687 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -34,7 +34,8 @@ export default class Platform { static Http: typeof IHttp; static Transports: { order: TransportName[]; - implementations: TransportImplementations; + // Transport implementations that always come with this platform + bundledImplementations: TransportImplementations; }; static Defaults: IDefaults; static WebStorage: IWebStorage | null; diff --git a/src/platform/nodejs/lib/transport/index.ts b/src/platform/nodejs/lib/transport/index.ts index 084b0cc955..80ab1d0115 100644 --- a/src/platform/nodejs/lib/transport/index.ts +++ b/src/platform/nodejs/lib/transport/index.ts @@ -1,7 +1,11 @@ import { TransportNames } from 'common/constants/TransportName'; import initialiseNodeCometTransport from './nodecomettransport'; +import { default as initialiseWebSocketTransport } from '../../../../common/lib/transport/websockettransport'; export default { order: [TransportNames.Comet], - implementations: { [TransportNames.Comet]: initialiseNodeCometTransport }, + bundledImplementations: { + [TransportNames.WebSocket]: initialiseWebSocketTransport, + [TransportNames.Comet]: initialiseNodeCometTransport, + }, }; diff --git a/src/platform/web/lib/transport/index.ts b/src/platform/web/lib/transport/index.ts index 8fb1447186..ced6dc4eef 100644 --- a/src/platform/web/lib/transport/index.ts +++ b/src/platform/web/lib/transport/index.ts @@ -1,11 +1,25 @@ -import { TransportNames } from 'common/constants/TransportName'; +import TransportName from 'common/constants/TransportName'; +import Platform from 'common/platform'; import initialiseXHRPollingTransport from './xhrpollingtransport'; import initialiseXHRStreamingTransport from './xhrstreamingtransport'; +import { default as initialiseWebSocketTransport } from '../../../../common/lib/transport/websockettransport'; -export default { - order: [TransportNames.XhrPolling, TransportNames.XhrStreaming], - implementations: { - [TransportNames.XhrPolling]: initialiseXHRPollingTransport, - [TransportNames.XhrStreaming]: initialiseXHRStreamingTransport, +// For reasons that I don’t understand, if we use [TransportNames.XhrStreaming] and [TransportNames.XhrPolling] for the keys in defaultTransports’s, then defaultTransports does not get tree-shaken. Hence using literals instead. They’re still correctly type-checked. + +const order: TransportName[] = ['xhr_polling', 'xhr_streaming']; + +const defaultTransports: (typeof Platform)['Transports'] = { + order, + bundledImplementations: { + web_socket: initialiseWebSocketTransport, + xhr_polling: initialiseXHRPollingTransport, + xhr_streaming: initialiseXHRStreamingTransport, }, }; + +export default defaultTransports; + +export const ModulesTransports: (typeof Platform)['Transports'] = { + order, + bundledImplementations: {}, +}; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 0488f6bdf3..a3bf018afe 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -10,7 +10,7 @@ import BufferUtils from './lib/util/bufferutils'; import Http from './lib/util/http'; import Config from './config'; // @ts-ignore -import Transports from './lib/transport'; +import { ModulesTransports } from './lib/transport'; import Logger from '../../common/lib/util/logger'; import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from './lib/util/webstorage'; @@ -19,7 +19,7 @@ import PlatformDefaults from './lib/util/defaults'; Platform.BufferUtils = BufferUtils; Platform.Http = Http; Platform.Config = Config; -Platform.Transports = Transports; +Platform.Transports = ModulesTransports; Platform.WebStorage = WebStorage; Logger.initLogHandlers(); @@ -44,5 +44,6 @@ export * from './modules/message'; export * from './modules/presencemessage'; export * from './modules/msgpack'; export * from './modules/realtimepresence'; +export * from './modules/transports'; export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/src/platform/web/modules/transports.ts b/src/platform/web/modules/transports.ts new file mode 100644 index 0000000000..37c0a09cf8 --- /dev/null +++ b/src/platform/web/modules/transports.ts @@ -0,0 +1,3 @@ +export { default as XHRPolling } from '../lib/transport/xhrpollingtransport'; +export { default as XHRStreaming } from '../lib/transport/xhrstreamingtransport'; +export { default as WebSocketTransport } from '../../../common/lib/transport/websockettransport'; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 0e0acd38a7..501bd09f9d 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -14,6 +14,9 @@ import { decodePresenceMessage, decodePresenceMessages, constructPresenceMessage, + XHRPolling, + XHRStreaming, + WebSocketTransport, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -42,13 +45,17 @@ describe('browser/modules', function () { }); describe('without any modules', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, () => { - it('can be constructed', async () => { - expect(() => new clientClass(ablyClientOptions(), {})).not.to.throw(); - }); + describe('BaseRest', () => { + it('can be constructed', () => { + expect(() => new BaseRest(ablyClientOptions(), {})).not.to.throw(); }); - } + }); + + describe('BaseRealtime', () => { + it('throws an error due to absence of a transport module', () => { + expect(() => new BaseRealtime(ablyClientOptions(), {})).to.throw('no requested transports available'); + }); + }); }); describe('Rest', () => { @@ -62,7 +69,7 @@ describe('browser/modules', function () { describe('BaseRealtime with Rest', () => { it('offers REST functionality', async () => { - const client = new BaseRealtime(ablyClientOptions(), { Rest }); + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, Rest }); const time = await client.time(); expect(time).to.be.a('number'); }); @@ -70,7 +77,7 @@ describe('browser/modules', function () { describe('BaseRealtime without Rest', () => { it('throws an error when attempting to use REST functionality', async () => { - const client = new BaseRealtime(ablyClientOptions(), {}); + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport }); expect(() => client.time()).to.throw('Rest module not provided'); }); }); @@ -206,48 +213,68 @@ describe('browser/modules', function () { describe('Crypto', () => { describe('without Crypto', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, () => { + async function testThrowsAnErrorWhenGivenChannelOptionsWithACipher(clientClassConfig) { + const client = new clientClassConfig.clientClass( + ablyClientOptions(), + clientClassConfig.additionalModules ?? {} + ); + const key = await generateRandomKey(); + expect(() => client.channels.get('channel', { cipher: { key } })).to.throw('Crypto module not provided'); + } + + for (const clientClassConfig of [ + { clientClass: BaseRest }, + { clientClass: BaseRealtime, additionalModules: { WebSocketTransport } }, + ]) { + describe(clientClassConfig.clientClass.name, () => { it('throws an error when given channel options with a cipher', async () => { - const client = new clientClass(ablyClientOptions(), {}); - const key = await generateRandomKey(); - expect(() => client.channels.get('channel', { cipher: { key } })).to.throw('Crypto module not provided'); + await testThrowsAnErrorWhenGivenChannelOptionsWithACipher(clientClassConfig); }); }); } }); describe('with Crypto', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, () => { - it('is able to publish encrypted messages', async () => { - const clientOptions = ablyClientOptions(); + async function testIsAbleToPublishEncryptedMessages(clientClassConfig) { + const clientOptions = ablyClientOptions(); - const key = await generateRandomKey(); + const key = await generateRandomKey(); - // Publish the message on a channel configured to use encryption, and receive it on one not configured to use encryption + // Publish the message on a channel configured to use encryption, and receive it on one not configured to use encryption - const rxClient = new BaseRealtime(clientOptions, {}); - const rxChannel = rxClient.channels.get('channel'); - await rxChannel.attach(); + const rxClient = new BaseRealtime(clientOptions, { WebSocketTransport }); + const rxChannel = rxClient.channels.get('channel'); + await rxChannel.attach(); - const rxMessagePromise = new Promise((resolve, _) => rxChannel.subscribe((message) => resolve(message))); + const rxMessagePromise = new Promise((resolve, _) => rxChannel.subscribe((message) => resolve(message))); - const encryptionChannelOptions = { cipher: { key } }; + const encryptionChannelOptions = { cipher: { key } }; - const txMessage = { name: 'message', data: 'data' }; - const txClient = new clientClass(clientOptions, { Crypto }); - const txChannel = txClient.channels.get('channel', encryptionChannelOptions); - await txChannel.publish(txMessage); + const txMessage = { name: 'message', data: 'data' }; + const txClient = new clientClassConfig.clientClass(clientOptions, { + ...(clientClassConfig.additionalModules ?? {}), + Crypto, + }); + const txChannel = txClient.channels.get('channel', encryptionChannelOptions); + await txChannel.publish(txMessage); + + const rxMessage = await rxMessagePromise; - const rxMessage = await rxMessagePromise; + // Verify that the message was published with encryption + expect(rxMessage.encoding).to.equal('utf-8/cipher+aes-256-cbc'); - // Verify that the message was published with encryption - expect(rxMessage.encoding).to.equal('utf-8/cipher+aes-256-cbc'); + // Verify that the message was correctly encrypted + const rxMessageDecrypted = await decodeEncryptedMessage(rxMessage, encryptionChannelOptions); + testMessageEquality(rxMessageDecrypted, txMessage); + } - // Verify that the message was correctly encrypted - const rxMessageDecrypted = await decodeEncryptedMessage(rxMessage, encryptionChannelOptions); - testMessageEquality(rxMessageDecrypted, txMessage); + for (const clientClassConfig of [ + { clientClass: BaseRest }, + { clientClass: BaseRealtime, additionalModules: { WebSocketTransport } }, + ]) { + describe(clientClassConfig.clientClass.name, () => { + it('is able to publish encrypted messages', async () => { + await testIsAbleToPublishEncryptedMessages(clientClassConfig); }); }); } @@ -298,7 +325,9 @@ describe('browser/modules', function () { describe('BaseRealtime', () => { it('uses JSON', async () => { - const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), {}); + const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { + WebSocketTransport, + }); await testRealtimeUsesFormat(client, 'json'); }); }); @@ -307,7 +336,9 @@ describe('browser/modules', function () { describe('with MsgPack', () => { describe('BaseRest', () => { it('uses MessagePack', async () => { - const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { MsgPack }); + const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { + MsgPack, + }); await testRestUsesContentType(client, 'application/x-msgpack'); }); }); @@ -315,6 +346,7 @@ describe('browser/modules', function () { describe('BaseRealtime', () => { it('uses MessagePack', async () => { const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { + WebSocketTransport, MsgPack, }); await testRealtimeUsesFormat(client, 'msgpack'); @@ -327,7 +359,7 @@ describe('browser/modules', function () { describe('RealtimePresence', () => { describe('BaseRealtime without RealtimePresence', () => { it('throws an error when attempting to access the `presence` property', () => { - const client = new BaseRealtime(ablyClientOptions(), {}); + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport }); const channel = client.channels.get('channel'); expect(() => channel.presence).to.throw('RealtimePresence module not provided'); @@ -336,9 +368,12 @@ describe('browser/modules', function () { describe('BaseRealtime with RealtimePresence', () => { it('offers realtime presence functionality', async () => { - const rxChannel = new BaseRealtime(ablyClientOptions(), { RealtimePresence }).channels.get('channel'); + const rxChannel = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, RealtimePresence }).channels.get( + 'channel' + ); const txClientId = randomString(); const txChannel = new BaseRealtime(ablyClientOptions({ clientId: txClientId }), { + WebSocketTransport, RealtimePresence, }).channels.get('channel'); @@ -397,4 +432,40 @@ describe('browser/modules', function () { }); }); }); + + describe('Transports', () => { + describe('BaseRealtime', () => { + for (const scenario of [ + { moduleMapKey: 'WebSocketTransport', transportModule: WebSocketTransport, transportName: 'web_socket' }, + { moduleMapKey: 'XHRPolling', transportModule: XHRPolling, transportName: 'xhr_polling' }, + { moduleMapKey: 'XHRStreaming', transportModule: XHRStreaming, transportName: 'xhr_streaming' }, + ]) { + describe(`with the ${scenario.moduleMapKey} module`, () => { + it(`is able to use the ${scenario.transportName} transport`, async () => { + const realtime = new BaseRealtime( + ablyClientOptions({ autoConnect: false, transports: [scenario.transportName] }), + { + [scenario.moduleMapKey]: scenario.transportModule, + } + ); + + let firstTransportCandidate; + const connectionManager = realtime.connection.connectionManager; + const originalTryATransport = connectionManager.tryATransport; + realtime.connection.connectionManager.tryATransport = (transportParams, candidate, callback) => { + if (!firstTransportCandidate) { + firstTransportCandidate = candidate; + } + originalTryATransport.bind(connectionManager)(transportParams, candidate, callback); + }; + + realtime.connect(); + + await realtime.connection.once('connected'); + expect(firstTransportCandidate).to.equal(scenario.transportName); + }); + }); + } + }); + }); }); diff --git a/test/browser/simple.test.js b/test/browser/simple.test.js index bfc22014d7..23e2667e17 100644 --- a/test/browser/simple.test.js +++ b/test/browser/simple.test.js @@ -17,7 +17,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); function isTransportAvailable(transport) { - return transport in Ably.Realtime.ConnectionManager.supportedTransports; + return transport in Ably.Realtime.ConnectionManager.supportedTransports(Ably.Realtime._transports); } function realtimeConnection(transports) { diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index ecf011b760..465ef5a74f 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -14,7 +14,9 @@ define([ var platform = clientModule.Ably.Realtime.Platform; var BufferUtils = platform.BufferUtils; var expect = chai.expect; - var availableTransports = utils.keysArray(clientModule.Ably.Realtime.ConnectionManager.supportedTransports), + var availableTransports = utils.keysArray( + clientModule.Ably.Realtime.ConnectionManager.supportedTransports(clientModule.Ably.Realtime._transports) + ), bestTransport = availableTransports[0], /* IANA reserved; requests to it will hang forever */ unroutableHost = '10.255.255.1', From 2f95ed4a5d070e61f0d05c17bf7a6440d30832a8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 26 Oct 2023 15:32:20 -0300 Subject: [PATCH 185/468] Remove redundant type annotations in PaginatedResource bodyHandler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (I’m also not sure why the compiler was accepting the code that described the `body` parameter as having type `any`, given that in BodyHandler it’s of type `unknown`.) --- src/common/lib/client/channel.ts | 8 ++++---- src/common/lib/client/presence.ts | 16 ++++++---------- src/common/lib/client/push.ts | 30 +++++++++++++++++------------- src/common/lib/client/rest.ts | 8 ++------ 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/channel.ts index ac7efe4d94..2922de7adb 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/channel.ts @@ -98,11 +98,11 @@ class Channel extends EventEmitter { const options = this.channelOptions; new PaginatedResource(client, this.basePath + '/messages', headers, envelope, async function ( - body: any, - headers: Record, - unpacked?: boolean + body, + headers, + unpacked ) { - return await Message.fromResponseBody(body, options, client._MsgPack, unpacked ? undefined : format); + return await Message.fromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); }).get(params as Record, callback); } diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/presence.ts index ac756ef9b8..8acbed68bf 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/presence.ts @@ -38,13 +38,9 @@ class Presence extends EventEmitter { Utils.mixin(headers, client.options.headers); const options = this.channel.channelOptions; - new PaginatedResource(client, this.basePath, headers, envelope, async function ( - body: any, - headers: Record, - unpacked?: boolean - ) { + new PaginatedResource(client, this.basePath, headers, envelope, async function (body, headers, unpacked) { return await PresenceMessage.fromResponseBody( - body, + body as Record[], options as CipherOptions, client._MsgPack, unpacked ? undefined : format @@ -83,12 +79,12 @@ class Presence extends EventEmitter { const options = this.channel.channelOptions; new PaginatedResource(client, this.basePath + '/history', headers, envelope, async function ( - body: any, - headers: Record, - unpacked?: boolean + body, + headers, + unpacked ) { return await PresenceMessage.fromResponseBody( - body, + body as Record[], options as CipherOptions, client._MsgPack, unpacked ? undefined : format diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 33054fbd0c..3d0b7b9f44 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -151,11 +151,15 @@ class DeviceRegistrations { Utils.mixin(headers, client.options.headers); new PaginatedResource(client, '/push/deviceRegistrations', headers, envelope, async function ( - body: any, - headers: Record, - unpacked?: boolean + body, + headers, + unpacked ) { - return DeviceDetails.fromResponseBody(body, client._MsgPack, unpacked ? undefined : format); + return DeviceDetails.fromResponseBody( + body as Record[], + client._MsgPack, + unpacked ? undefined : format + ); }).get(params, callback); } @@ -269,11 +273,15 @@ class ChannelSubscriptions { Utils.mixin(headers, client.options.headers); new PaginatedResource(client, '/push/channelSubscriptions', headers, envelope, async function ( - body: any, - headers: Record, - unpacked?: boolean + body, + headers, + unpacked ) { - return PushChannelSubscription.fromResponseBody(body, client._MsgPack, unpacked ? undefined : format); + return PushChannelSubscription.fromResponseBody( + body as Record[], + client._MsgPack, + unpacked ? undefined : format + ); }).get(params, callback); } @@ -310,11 +318,7 @@ class ChannelSubscriptions { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - new PaginatedResource(client, '/push/channels', headers, envelope, async function ( - body: unknown, - headers: Record, - unpacked?: boolean - ) { + new PaginatedResource(client, '/push/channels', headers, envelope, async function (body, headers, unpacked) { const parsedBody = ( !unpacked && format ? Utils.decodeBody(body, client._MsgPack, format) : body ) as Array; diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index f58ef1b493..2f398d0a50 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -56,11 +56,7 @@ export class Rest { Utils.mixin(headers, this.client.options.headers); - new PaginatedResource(this.client, '/stats', headers, envelope, function ( - body: unknown, - headers: Record, - unpacked?: boolean - ) { + new PaginatedResource(this.client, '/stats', headers, envelope, function (body, headers, unpacked) { const statsValues = unpacked ? body : JSON.parse(body as string); for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); return statsValues; @@ -160,7 +156,7 @@ export class Rest { path, headers, envelope, - async function (resbody: unknown, headers: Record, unpacked?: boolean) { + async function (resbody, headers, unpacked) { return Utils.ensureArray(unpacked ? resbody : decoder(resbody as string & Buffer)); }, /* useHttpPaginatedResponse: */ true From 98f630a4280a4da4aa725dec8581aa6bd7616883 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 26 Oct 2023 15:33:13 -0300 Subject: [PATCH 186/468] Convert http.d.ts to .ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes some compilation errors that were missed by the fact that it was a .d.ts file. (See #1445 for the issue that aims to convert all .d.ts files to .ts.) This improved type information introduced a compiler error resulting from us trying to directly access HTTP headers on DOM’s Headers object with header names as object property names. I can’t see how the existing code could have been working properly, so I’ve changed the response header handling in fetchrequest.ts. --- src/common/lib/client/paginatedresource.ts | 9 +- src/common/lib/client/resource.ts | 48 ++++------ src/common/lib/client/rest.ts | 9 +- src/common/platform.ts | 4 +- src/common/types/{http.d.ts => http.ts} | 23 +++-- src/platform/nodejs/lib/util/http.ts | 76 ++++++--------- .../web/lib/transport/fetchrequest.ts | 17 +++- src/platform/web/lib/util/http.ts | 92 +++++++------------ tsconfig.json | 2 +- 9 files changed, 116 insertions(+), 164 deletions(-) rename src/common/types/{http.d.ts => http.ts} (71%) diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index fd899c3e14..d224d17403 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -4,8 +4,9 @@ import Resource from './resource'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; import { PaginatedResultCallback } from '../../types/utils'; import BaseClient from './baseclient'; +import { RequestCallbackHeaders } from 'common/types/http'; -export type BodyHandler = (body: unknown, headers: Record, unpacked?: boolean) => Promise; +export type BodyHandler = (body: unknown, headers: RequestCallbackHeaders, unpacked?: boolean) => Promise; function getRelParams(linkUrl: string) { const urlMatch = linkUrl.match(/^\.\/(\w+)\?(.*)$/); @@ -135,7 +136,7 @@ class PaginatedResource { handlePage( err: IPartialErrorInfo | null, body: unknown, - headers: Record | undefined, + headers: RequestCallbackHeaders | undefined, unpacked: boolean | undefined, statusCode: number | undefined, callback: PaginatedResultCallback @@ -249,14 +250,14 @@ export class PaginatedResult { export class HttpPaginatedResponse extends PaginatedResult { statusCode: number; success: boolean; - headers: Record; + headers: RequestCallbackHeaders; errorCode?: number | null; errorMessage?: string | null; constructor( resource: PaginatedResource, items: T[], - headers: Record, + headers: RequestCallbackHeaders, statusCode: number, relParams: any, err: IPartialErrorInfo | null diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 7d506d369e..0fa0b93c45 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -5,12 +5,12 @@ import Auth from './auth'; import HttpMethods from '../../constants/HttpMethods'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import BaseClient from './baseclient'; -import { ErrnoException } from '../../types/http'; import { MsgPack } from 'common/types/msgpack'; +import { RequestCallbackHeaders } from 'common/types/http'; function withAuthDetails( client: BaseClient, - headers: Record, + headers: RequestCallbackHeaders | undefined, params: Record, errCallback: Function, opCallback: Function @@ -130,7 +130,7 @@ function logResponseHandler( export type ResourceCallback = ( err: IPartialErrorInfo | null, body?: T, - headers?: Record, + headers?: RequestCallbackHeaders, unpacked?: boolean, statusCode?: number ) => void; @@ -245,35 +245,21 @@ class Resource { ); } - client.http.do( - method, - client, - path, - headers, - body, - params, - function ( - err: ErrorInfo | ErrnoException | null | undefined, - res: any, - headers: Record, - unpacked?: boolean, - statusCode?: number - ) { - if (err && Auth.isTokenErr(err as ErrorInfo)) { - /* token has expired, so get a new one */ - client.auth.authorize(null, null, function (err: ErrorInfo) { - if (err) { - callback(err); - return; - } - /* retry ... */ - withAuthDetails(client, headers, params, callback, doRequest); - }); - return; - } - callback(err as ErrorInfo, res, headers, unpacked, statusCode); + client.http.do(method, client, path, headers, body, params, function (err, res, headers, unpacked, statusCode) { + if (err && Auth.isTokenErr(err as ErrorInfo)) { + /* token has expired, so get a new one */ + client.auth.authorize(null, null, function (err: ErrorInfo) { + if (err) { + callback(err); + return; + } + /* retry ... */ + withAuthDetails(client, headers, params, callback, doRequest); + }); + return; } - ); + callback(err as ErrorInfo, res as T | undefined, headers, unpacked, statusCode); + }); } withAuthDetails(client, headers, params, callback, doRequest); diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 2f398d0a50..6d1c08c4d0 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -9,7 +9,7 @@ import Stats from '../types/stats'; import HttpMethods from '../../constants/HttpMethods'; import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import { ErrnoException, RequestParams } from '../../types/http'; +import { RequestParams } from '../../types/http'; import * as API from '../../../../ably'; import Resource from './resource'; @@ -88,12 +88,7 @@ export class Rest { headers, null, params as RequestParams, - ( - err?: ErrorInfo | ErrnoException | null, - res?: unknown, - headers?: Record, - unpacked?: boolean - ) => { + (err, res, headers, unpacked) => { if (err) { _callback(err); return; diff --git a/src/common/platform.ts b/src/common/platform.ts index 609b232687..6d5a6245bf 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -1,5 +1,5 @@ import { IPlatformConfig } from './types/IPlatformConfig'; -import { IHttp } from './types/http'; +import { IHttpStatic } from './types/http'; import { TransportInitialiser } from './lib/transport/connectionmanager'; import IDefaults from './types/IDefaults'; import IWebStorage from './types/IWebStorage'; @@ -31,7 +31,7 @@ export default class Platform { comment above. */ static Crypto: IUntypedCryptoStatic | null; - static Http: typeof IHttp; + static Http: IHttpStatic; static Transports: { order: TransportName[]; // Transport implementations that always come with this platform diff --git a/src/common/types/http.d.ts b/src/common/types/http.ts similarity index 71% rename from src/common/types/http.d.ts rename to src/common/types/http.ts index 0727978b52..404fb6c156 100644 --- a/src/common/types/http.d.ts +++ b/src/common/types/http.ts @@ -1,23 +1,28 @@ import HttpMethods from '../constants/HttpMethods'; -import { BaseClient } from '../lib/client/baseclient'; -import ErrorInfo from '../lib/types/errorinfo'; +import BaseClient from '../lib/client/baseclient'; +import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; import { Agents } from 'got'; +import { NormalisedClientOptions } from './ClientOptions'; export type PathParameter = string | ((host: string) => string); +export type RequestCallbackHeaders = Partial>; export type RequestCallback = ( error?: ErrnoException | IPartialErrorInfo | null, body?: unknown, - headers?: IncomingHttpHeaders, + headers?: RequestCallbackHeaders, unpacked?: boolean, statusCode?: number ) => void; export type RequestParams = Record | null; -export declare class IHttp { - constructor(options: NormalisedClientOptions); - static methods: Array; - static methodsWithBody: Array; - static methodsWithoutBody: Array; +export interface IHttpStatic { + new (options: NormalisedClientOptions): IHttp; + methods: Array; + methodsWithBody: Array; + methodsWithoutBody: Array; +} + +export interface IHttp { supportsAuthHeaders: boolean; supportsLinkHeaders: boolean; agent?: Agents | null; @@ -32,7 +37,7 @@ export declare class IHttp { body: unknown, callback: RequestCallback ) => void; - _getHosts: (client: BaseClient | Realtime) => string[]; + _getHosts: (client: BaseClient) => string[]; do( method: HttpMethods, client: BaseClient | null, diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 7f53e13638..e24628fcc9 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -1,7 +1,13 @@ import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo from 'common/lib/types/errorinfo'; -import { ErrnoException, IHttp, PathParameter, RequestCallback, RequestParams } from '../../../../common/types/http'; +import { + ErrnoException, + IHttpStatic, + PathParameter, + RequestCallback, + RequestParams, +} from '../../../../common/types/http'; import HttpMethods from '../../../../common/constants/HttpMethods'; import got, { Response, Options, CancelableRequest, Agents } from 'got'; import http from 'http'; @@ -91,7 +97,7 @@ function getHosts(client: BaseClient): string[] { return Defaults.getHosts(client.options); } -const Http: typeof IHttp = class { +const Http: IHttpStatic = class { static methods = [HttpMethods.Get, HttpMethods.Delete, HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; @@ -126,23 +132,15 @@ const Http: typeof IHttp = class { if (currentFallback) { if (currentFallback.validUntil > Date.now()) { /* Use stored fallback */ - this.doUri( - method, - client, - uriFromHost(currentFallback.host), - headers, - body, - params, - (err?: ErrnoException | ErrorInfo | null, ...args: unknown[]) => { - if (err && shouldFallback(err as ErrnoException)) { - /* unstore the fallback and start from the top with the default sequence */ - client._currentFallback = null; - this.do(method, client, path, headers, body, params, callback); - return; - } - callback(err, ...args); + this.doUri(method, client, uriFromHost(currentFallback.host), headers, body, params, (err, ...args) => { + if (err && shouldFallback(err as ErrnoException)) { + /* unstore the fallback and start from the top with the default sequence */ + client._currentFallback = null; + this.do(method, client, path, headers, body, params, callback); + return; } - ); + callback(err, ...args); + }); return; } else { /* Fallback expired; remove it and fallthrough to normal sequence */ @@ -160,28 +158,20 @@ const Http: typeof IHttp = class { const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { const host = candidateHosts.shift(); - this.doUri( - method, - client, - uriFromHost(host as string), - headers, - body, - params, - function (err?: ErrnoException | ErrorInfo | null, ...args: unknown[]) { - if (err && shouldFallback(err as ErrnoException) && candidateHosts.length) { - tryAHost(candidateHosts, true); - return; - } - if (persistOnSuccess) { - /* RSC15f */ - client._currentFallback = { - host: host as string, - validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, - }; - } - callback(err, ...args); + this.doUri(method, client, uriFromHost(host as string), headers, body, params, function (err, ...args) { + if (err && shouldFallback(err as ErrnoException) && candidateHosts.length) { + tryAHost(candidateHosts, true); + return; } - ); + if (persistOnSuccess) { + /* RSC15f */ + client._currentFallback = { + host: host as string, + validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, + }; + } + callback(err, ...args); + }); }; tryAHost(hosts); } @@ -260,13 +250,7 @@ const Http: typeof IHttp = class { null, null, connectivityCheckParams, - function ( - err?: ErrnoException | ErrorInfo | null, - responseText?: unknown, - headers?: any, - unpacked?: boolean, - statusCode?: number - ) { + function (err, responseText, headers, unpacked, statusCode) { if (!err && !connectivityUrlIsDefault) { callback(null, isSuccessCode(statusCode as number)); return; diff --git a/src/platform/web/lib/transport/fetchrequest.ts b/src/platform/web/lib/transport/fetchrequest.ts index 7203ff62a6..68a9febf0f 100644 --- a/src/platform/web/lib/transport/fetchrequest.ts +++ b/src/platform/web/lib/transport/fetchrequest.ts @@ -1,7 +1,7 @@ import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestCallback, RequestParams } from 'common/types/http'; +import { RequestCallback, RequestCallbackHeaders, RequestParams } from 'common/types/http'; import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import * as Utils from 'common/lib/util/utils'; @@ -17,6 +17,16 @@ function getAblyError(responseBody: unknown, headers: Headers) { } } +function convertHeaders(headers: Headers) { + const result: RequestCallbackHeaders = {}; + + headers.forEach((value, key) => { + result[key] = value; + }); + + return result; +} + export default function fetchRequest( method: HttpMethods, client: BaseClient | null, @@ -64,6 +74,7 @@ export default function fetchRequest( } prom.then((body) => { const unpacked = !!contentType && contentType.indexOf('application/x-msgpack') === -1; + const headers = convertHeaders(res.headers); if (!res.ok) { const err = getAblyError(body, res.headers) || @@ -72,9 +83,9 @@ export default function fetchRequest( null, res.status ); - callback(err, body, res.headers, unpacked, res.status); + callback(err, body, headers, unpacked, res.status); } else { - callback(null, body, res.headers, unpacked, res.status); + callback(null, body, headers, unpacked, res.status); } }); }) diff --git a/src/platform/web/lib/util/http.ts b/src/platform/web/lib/util/http.ts index a417d5f83c..d6cb949357 100644 --- a/src/platform/web/lib/util/http.ts +++ b/src/platform/web/lib/util/http.ts @@ -2,7 +2,7 @@ import Platform from 'common/platform'; import * as Utils from 'common/lib/util/utils'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { ErrnoException, IHttp, RequestCallback, RequestParams } from 'common/types/http'; +import { IHttpStatic, RequestCallback, RequestParams } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import BaseRealtime from 'common/lib/client/baserealtime'; @@ -40,7 +40,7 @@ function getHosts(client: BaseClient): string[] { return Defaults.getHosts(client.options); } -const Http: typeof IHttp = class { +const Http: IHttpStatic = class { static methods = [HttpMethods.Get, HttpMethods.Delete, HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; @@ -95,13 +95,7 @@ const Http: typeof IHttp = class { null, null, connectivityCheckParams, - function ( - err?: ErrorInfo | ErrnoException | null, - responseText?: unknown, - headers?: any, - unpacked?: boolean, - statusCode?: number - ) { + function (err, responseText, headers, unpacked, statusCode) { let result = false; if (!connectivityUrlIsDefault) { result = !err && isSuccessCode(statusCode as number); @@ -119,19 +113,11 @@ const Http: typeof IHttp = class { this.Request = fetchRequest; this.checkConnectivity = function (callback: (err: ErrorInfo | null, connectivity: boolean) => void) { Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl); - this.doUri( - HttpMethods.Get, - null as any, - connectivityCheckUrl, - null, - null, - null, - function (err?: ErrorInfo | ErrnoException | null, responseText?: unknown) { - const result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; - Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Result: ' + result); - callback(null, result); - } - ); + this.doUri(HttpMethods.Get, null as any, connectivityCheckUrl, null, null, null, function (err, responseText) { + const result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; + Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Result: ' + result); + callback(null, result); + }); }; } else { this.Request = (method, client, uri, headers, params, body, callback) => { @@ -165,24 +151,16 @@ const Http: typeof IHttp = class { callback?.(new PartialErrorInfo('Request invoked before assigned to', null, 500)); return; } - this.Request( - method, - client, - uriFromHost(currentFallback.host), - headers, - params, - body, - (err?: ErrnoException | ErrorInfo | null, ...args: unknown[]) => { - // This typecast is safe because ErrnoExceptions are only thrown in NodeJS - if (err && shouldFallback(err as ErrorInfo)) { - /* unstore the fallback and start from the top with the default sequence */ - client._currentFallback = null; - this.do(method, client, path, headers, body, params, callback); - return; - } - callback?.(err, ...args); + this.Request(method, client, uriFromHost(currentFallback.host), headers, params, body, (err?, ...args) => { + // This typecast is safe because ErrnoExceptions are only thrown in NodeJS + if (err && shouldFallback(err as ErrorInfo)) { + /* unstore the fallback and start from the top with the default sequence */ + client._currentFallback = null; + this.do(method, client, path, headers, body, params, callback); + return; } - ); + callback?.(err, ...args); + }); return; } else { /* Fallback expired; remove it and fallthrough to normal sequence */ @@ -201,29 +179,21 @@ const Http: typeof IHttp = class { /* hosts is an array with preferred host plus at least one fallback */ const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { const host = candidateHosts.shift(); - this.doUri( - method, - client, - uriFromHost(host as string), - headers, - body, - params, - function (err?: ErrnoException | ErrorInfo | null, ...args: unknown[]) { - // This typecast is safe because ErrnoExceptions are only thrown in NodeJS - if (err && shouldFallback(err as ErrorInfo) && candidateHosts.length) { - tryAHost(candidateHosts, true); - return; - } - if (persistOnSuccess) { - /* RSC15f */ - client._currentFallback = { - host: host as string, - validUntil: Utils.now() + client.options.timeouts.fallbackRetryTimeout, - }; - } - callback?.(err, ...args); + this.doUri(method, client, uriFromHost(host as string), headers, body, params, function (err, ...args) { + // This typecast is safe because ErrnoExceptions are only thrown in NodeJS + if (err && shouldFallback(err as ErrorInfo) && candidateHosts.length) { + tryAHost(candidateHosts, true); + return; + } + if (persistOnSuccess) { + /* RSC15f */ + client._currentFallback = { + host: host as string, + validUntil: Utils.now() + client.options.timeouts.fallbackRetryTimeout, + }; } - ); + callback?.(err, ...args); + }); }; tryAHost(hosts); } diff --git a/tsconfig.json b/tsconfig.json index b3a547de9d..1146fcb184 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es5", "module": "commonjs", - "lib": ["ES5", "DOM", "webworker"], + "lib": ["ES5", "DOM", "DOM.Iterable", "webworker"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, From fbf30e69df658a409cd726971bc0be61848af82c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 26 Oct 2023 09:03:12 -0300 Subject: [PATCH 187/468] Introduce a directory structure for web HTTP stuff Similar to that used for transports. --- src/platform/nativescript/index.ts | 2 +- src/platform/react-native/index.ts | 2 +- src/platform/web-noencryption/index.ts | 2 +- src/platform/web/index.ts | 2 +- src/platform/web/lib/{util => http}/http.ts | 4 ++-- .../web/lib/{transport => http/request}/fetchrequest.ts | 0 .../web/lib/{transport => http/request}/xhrrequest.ts | 0 src/platform/web/lib/transport/xhrpollingtransport.ts | 2 +- src/platform/web/lib/transport/xhrstreamingtransport.ts | 2 +- src/platform/web/modules.ts | 2 +- 10 files changed, 9 insertions(+), 9 deletions(-) rename src/platform/web/lib/{util => http}/http.ts (98%) rename src/platform/web/lib/{transport => http/request}/fetchrequest.ts (100%) rename src/platform/web/lib/{transport => http/request}/xhrrequest.ts (100%) diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index f1ea2e7285..c8354aa69d 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; import BufferUtils from '../web/lib/util/bufferutils'; // @ts-ignore import { createCryptoClass } from '../web/lib/util/crypto'; -import Http from '../web/lib/util/http'; +import Http from '../web/lib/http/http'; // @ts-ignore import Config from './config'; // @ts-ignore diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index 3b7d6debeb..dc27bb62c8 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; import BufferUtils from '../web/lib/util/bufferutils'; // @ts-ignore import { createCryptoClass } from '../web/lib/util/crypto'; -import Http from '../web/lib/util/http'; +import Http from '../web/lib/http/http'; import configFactory from './config'; // @ts-ignore import Transports from '../web/lib/transport'; diff --git a/src/platform/web-noencryption/index.ts b/src/platform/web-noencryption/index.ts index 4b8d3ed9de..dcb4120123 100644 --- a/src/platform/web-noencryption/index.ts +++ b/src/platform/web-noencryption/index.ts @@ -7,7 +7,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; // @ts-ignore -import Http from '../web/lib/util/http'; +import Http from '../web/lib/http/http'; import Config from '../web/config'; // @ts-ignore import Transports from '../web/lib/transport'; diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index d4566814d5..3e40e123b7 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -8,7 +8,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; import BufferUtils from './lib/util/bufferutils'; // @ts-ignore import { createCryptoClass } from './lib/util/crypto'; -import Http from './lib/util/http'; +import Http from './lib/http/http'; import Config from './config'; // @ts-ignore import Transports from './lib/transport'; diff --git a/src/platform/web/lib/util/http.ts b/src/platform/web/lib/http/http.ts similarity index 98% rename from src/platform/web/lib/util/http.ts rename to src/platform/web/lib/http/http.ts index d6cb949357..f7dcdd1285 100644 --- a/src/platform/web/lib/util/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -6,11 +6,11 @@ import { IHttpStatic, RequestCallback, RequestParams } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import BaseRealtime from 'common/lib/client/baserealtime'; -import XHRRequest from '../transport/xhrrequest'; +import XHRRequest from './request/xhrrequest'; import XHRStates from 'common/constants/XHRStates'; import Logger from 'common/lib/util/logger'; import { StandardCallback } from 'common/types/utils'; -import fetchRequest from '../transport/fetchrequest'; +import fetchRequest from './request/fetchrequest'; import { NormalisedClientOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; diff --git a/src/platform/web/lib/transport/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts similarity index 100% rename from src/platform/web/lib/transport/fetchrequest.ts rename to src/platform/web/lib/http/request/fetchrequest.ts diff --git a/src/platform/web/lib/transport/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts similarity index 100% rename from src/platform/web/lib/transport/xhrrequest.ts rename to src/platform/web/lib/http/request/xhrrequest.ts diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index 4d3b2110d3..bf0f13befd 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -1,6 +1,6 @@ import Platform from '../../../../common/platform'; import CometTransport from '../../../../common/lib/transport/comettransport'; -import XHRRequest from './xhrrequest'; +import XHRRequest from '../http/request/xhrrequest'; import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index 9ef8631fec..5dd77e6f6d 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -1,6 +1,6 @@ import CometTransport from '../../../../common/lib/transport/comettransport'; import Platform from '../../../../common/platform'; -import XHRRequest from './xhrrequest'; +import XHRRequest from '../http/request/xhrrequest'; import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestParams } from 'common/types/http'; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index a3bf018afe..f9ee3a521d 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -7,7 +7,7 @@ import ErrorInfo from '../../common/lib/types/errorinfo'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; // @ts-ignore -import Http from './lib/util/http'; +import Http from './lib/http/http'; import Config from './config'; // @ts-ignore import { ModulesTransports } from './lib/transport'; From 1d35274d14f47aa5db969001950cc51295c55084 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 26 Oct 2023 10:20:04 -0300 Subject: [PATCH 188/468] =?UTF-8?q?Make=20IHttpStatic=E2=80=99s=20construc?= =?UTF-8?q?tor=20options=20param=20optional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To correctly reflect the fact that the tests instantiate Platform.Http without any arguments. --- src/common/types/http.ts | 3 +-- src/platform/nodejs/lib/util/http.ts | 14 +++++++------- src/platform/web/lib/http/http.ts | 13 +++++-------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 404fb6c156..086f55c2d9 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -16,7 +16,7 @@ export type RequestCallback = ( export type RequestParams = Record | null; export interface IHttpStatic { - new (options: NormalisedClientOptions): IHttp; + new (options?: NormalisedClientOptions): IHttp; methods: Array; methodsWithBody: Array; methodsWithoutBody: Array; @@ -26,7 +26,6 @@ export interface IHttp { supportsAuthHeaders: boolean; supportsLinkHeaders: boolean; agent?: Agents | null; - options: NormalisedClientOptions; Request?: ( method: HttpMethods, diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index e24628fcc9..1edeadef17 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -105,10 +105,10 @@ const Http: IHttpStatic = class { _getHosts = getHosts; supportsAuthHeaders = true; supportsLinkHeaders = true; - options: NormalisedClientOptions; + private options: NormalisedClientOptions | null; - constructor(options: NormalisedClientOptions) { - this.options = options || {}; + constructor(options?: NormalisedClientOptions) { + this.options = options ?? null; } /* Unlike for doUri, the 'client' param here is mandatory, as it's used to generate the hosts */ @@ -235,13 +235,13 @@ const Http: IHttpStatic = class { } checkConnectivity = (callback: (errorInfo: ErrorInfo | null, connected?: boolean) => void): void => { - if (this.options.disableConnectivityCheck) { + if (this.options?.disableConnectivityCheck) { callback(null, true); return; } - const connectivityCheckUrl = this.options.connectivityCheckUrl || Defaults.connectivityCheckUrl; - const connectivityCheckParams = this.options.connectivityCheckParams; - const connectivityUrlIsDefault = !this.options.connectivityCheckUrl; + const connectivityCheckUrl = this.options?.connectivityCheckUrl || Defaults.connectivityCheckUrl; + const connectivityCheckParams = this.options?.connectivityCheckParams ?? null; + const connectivityUrlIsDefault = !this.options?.connectivityCheckUrl; this.doUri( HttpMethods.Get, diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index f7dcdd1285..0b6bd62028 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -45,14 +45,11 @@ const Http: IHttpStatic = class { static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; checksInProgress: Array> | null = null; - options: NormalisedClientOptions; - constructor(options: NormalisedClientOptions) { - this.options = options || {}; - - const connectivityCheckUrl = this.options.connectivityCheckUrl || Defaults.connectivityCheckUrl; - const connectivityCheckParams = this.options.connectivityCheckParams; - const connectivityUrlIsDefault = !this.options.connectivityCheckUrl; + constructor(options?: NormalisedClientOptions) { + const connectivityCheckUrl = options?.connectivityCheckUrl || Defaults.connectivityCheckUrl; + const connectivityCheckParams = options?.connectivityCheckParams ?? null; + const connectivityUrlIsDefault = !options?.connectivityCheckUrl; if (Platform.Config.xhrSupported) { this.supportsAuthHeaders = true; this.Request = function ( @@ -77,7 +74,7 @@ const Http: IHttpStatic = class { req.exec(); return req; }; - if (this.options.disableConnectivityCheck) { + if (options?.disableConnectivityCheck) { this.checkConnectivity = function (callback: (err: null, connectivity: true) => void) { callback(null, true); }; From 9ad0a869422f613d52e4f61f7f22988fe5433411 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 26 Oct 2023 10:55:15 -0300 Subject: [PATCH 189/468] =?UTF-8?q?Pass=20client=20to=20IHttpStatic?= =?UTF-8?q?=E2=80=99s=20constructor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for tree-shakable HTTP request implementations, which will be exposed via the client. The constructor needs access to the list of available request implementations so that it can populate the supportsAuthHeaders property. --- src/common/lib/client/auth.ts | 3 -- src/common/lib/client/baseclient.ts | 2 +- src/common/lib/client/resource.ts | 2 +- src/common/lib/client/rest.ts | 1 - src/common/types/http.ts | 6 +--- src/platform/nodejs/lib/util/http.ts | 48 +++++++++++++++------------- src/platform/web/lib/http/http.ts | 44 +++++++++++++------------ test/browser/modules.test.js | 2 +- test/realtime/auth.test.js | 2 +- test/rest/http.test.js | 4 +-- test/rest/message.test.js | 4 +-- test/rest/request.test.js | 2 +- 12 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 3ed509ed6d..b4db9380c4 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -538,7 +538,6 @@ class Auth { const body = Utils.toQueryString(authParams).slice(1); /* slice is to remove the initial '?' */ this.client.http.doUri( HttpMethods.Post, - client, authOptions.authUrl, headers, body, @@ -548,7 +547,6 @@ class Auth { } else { this.client.http.doUri( HttpMethods.Get, - client, authOptions.authUrl, authHeaders || {}, null, @@ -594,7 +592,6 @@ class Auth { ); this.client.http.do( HttpMethods.Post, - client, tokenUri, requestHeaders, JSON.stringify(signedTokenParams), diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 939257edb7..fbb2be3475 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -88,7 +88,7 @@ class BaseClient { this._currentFallback = null; this.serverTimeOffset = null; - this.http = new Platform.Http(normalOptions); + this.http = new Platform.Http(this); this.auth = new Auth(this, normalOptions); this._rest = modules.Rest ? new modules.Rest(this) : null; diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 0fa0b93c45..3f0a8d68da 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -245,7 +245,7 @@ class Resource { ); } - client.http.do(method, client, path, headers, body, params, function (err, res, headers, unpacked, statusCode) { + client.http.do(method, path, headers, body, params, function (err, res, headers, unpacked, statusCode) { if (err && Auth.isTokenErr(err as ErrorInfo)) { /* token has expired, so get a new one */ client.auth.authorize(null, null, function (err: ErrorInfo) { diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 6d1c08c4d0..a3592f9945 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -83,7 +83,6 @@ export class Rest { }; this.client.http.do( HttpMethods.Get, - this.client, timeUri, headers, null, diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 086f55c2d9..a13ba86e49 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -2,7 +2,6 @@ import HttpMethods from '../constants/HttpMethods'; import BaseClient from '../lib/client/baseclient'; import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; import { Agents } from 'got'; -import { NormalisedClientOptions } from './ClientOptions'; export type PathParameter = string | ((host: string) => string); export type RequestCallbackHeaders = Partial>; @@ -16,7 +15,7 @@ export type RequestCallback = ( export type RequestParams = Record | null; export interface IHttpStatic { - new (options?: NormalisedClientOptions): IHttp; + new (client?: BaseClient): IHttp; methods: Array; methodsWithBody: Array; methodsWithoutBody: Array; @@ -29,7 +28,6 @@ export interface IHttp { Request?: ( method: HttpMethods, - client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, @@ -39,7 +37,6 @@ export interface IHttp { _getHosts: (client: BaseClient) => string[]; do( method: HttpMethods, - client: BaseClient | null, path: PathParameter, headers: Record | null, body: unknown, @@ -48,7 +45,6 @@ export interface IHttp { ): void; doUri( method: HttpMethods, - client: BaseClient | null, uri: string, headers: Record | null, body: unknown, diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 1edeadef17..6ed73b76d5 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -14,7 +14,7 @@ import http from 'http'; import https from 'https'; import BaseClient from 'common/lib/client/baseclient'; import BaseRealtime from 'common/lib/client/baserealtime'; -import { NormalisedClientOptions, RestAgentOptions } from 'common/types/ClientOptions'; +import { RestAgentOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; import { shallowEquals, throwMissingModuleError } from 'common/lib/util/utils'; @@ -105,22 +105,26 @@ const Http: IHttpStatic = class { _getHosts = getHosts; supportsAuthHeaders = true; supportsLinkHeaders = true; - private options: NormalisedClientOptions | null; + private client: BaseClient | null; - constructor(options?: NormalisedClientOptions) { - this.options = options ?? null; + constructor(client?: BaseClient) { + this.client = client ?? null; } - /* Unlike for doUri, the 'client' param here is mandatory, as it's used to generate the hosts */ do( method: HttpMethods, - client: BaseClient, path: PathParameter, headers: Record | null, body: unknown, params: RequestParams, callback: RequestCallback ): void { + /* Unlike for doUri, the presence of `this.client` here is mandatory, as it's used to generate the hosts */ + const client = this.client; + if (!client) { + throw new Error('http.do called without client'); + } + const uriFromHost = typeof path === 'function' ? path @@ -132,11 +136,11 @@ const Http: IHttpStatic = class { if (currentFallback) { if (currentFallback.validUntil > Date.now()) { /* Use stored fallback */ - this.doUri(method, client, uriFromHost(currentFallback.host), headers, body, params, (err, ...args) => { + this.doUri(method, uriFromHost(currentFallback.host), headers, body, params, (err, ...args) => { if (err && shouldFallback(err as ErrnoException)) { /* unstore the fallback and start from the top with the default sequence */ client._currentFallback = null; - this.do(method, client, path, headers, body, params, callback); + this.do(method, path, headers, body, params, callback); return; } callback(err, ...args); @@ -152,13 +156,13 @@ const Http: IHttpStatic = class { /* see if we have one or more than one host */ if (hosts.length === 1) { - this.doUri(method, client, uriFromHost(hosts[0]), headers, body, params, callback); + this.doUri(method, uriFromHost(hosts[0]), headers, body, params, callback); return; } const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { const host = candidateHosts.shift(); - this.doUri(method, client, uriFromHost(host as string), headers, body, params, function (err, ...args) { + this.doUri(method, uriFromHost(host as string), headers, body, params, function (err, ...args) { if (err && shouldFallback(err as ErrnoException) && candidateHosts.length) { tryAHost(candidateHosts, true); return; @@ -178,7 +182,6 @@ const Http: IHttpStatic = class { doUri( method: HttpMethods, - client: BaseClient, uri: string, headers: Record | null, body: unknown, @@ -188,7 +191,8 @@ const Http: IHttpStatic = class { /* Will generally be making requests to one or two servers exclusively * (Ably and perhaps an auth server), so for efficiency, use the * foreverAgent to keep the TCP stream alive between requests where possible */ - const agentOptions = (client && client.options.restAgentOptions) || (Defaults.restAgentOptions as RestAgentOptions); + const agentOptions = + (this.client && this.client.options.restAgentOptions) || (Defaults.restAgentOptions as RestAgentOptions); const doOptions: Options = { headers: headers || undefined, responseType: 'buffer' }; if (!this.agent) { @@ -215,7 +219,9 @@ const Http: IHttpStatic = class { doOptions.agent = this.agent; doOptions.url = uri; - doOptions.timeout = { request: ((client && client.options.timeouts) || Defaults.TIMEOUTS).httpRequestTimeout }; + doOptions.timeout = { + request: ((this.client && this.client.options.timeouts) || Defaults.TIMEOUTS).httpRequestTimeout, + }; // We have our own logic that retries appropriate statuscodes to fallback endpoints, // with timeouts constructed appropriately. Don't want `got` doing its own retries to // the same endpoint, inappropriately retrying 429s, etc @@ -223,29 +229,28 @@ const Http: IHttpStatic = class { (got[method](doOptions) as CancelableRequest) .then((res: Response) => { - handler(uri, params, client, callback)(null, res, res.body); + handler(uri, params, this.client, callback)(null, res, res.body); }) .catch((err: ErrnoException) => { if (err instanceof got.HTTPError) { - handler(uri, params, client, callback)(null, err.response, err.response.body); + handler(uri, params, this.client, callback)(null, err.response, err.response.body); return; } - handler(uri, params, client, callback)(err); + handler(uri, params, this.client, callback)(err); }); } checkConnectivity = (callback: (errorInfo: ErrorInfo | null, connected?: boolean) => void): void => { - if (this.options?.disableConnectivityCheck) { + if (this.client?.options.disableConnectivityCheck) { callback(null, true); return; } - const connectivityCheckUrl = this.options?.connectivityCheckUrl || Defaults.connectivityCheckUrl; - const connectivityCheckParams = this.options?.connectivityCheckParams ?? null; - const connectivityUrlIsDefault = !this.options?.connectivityCheckUrl; + const connectivityCheckUrl = this.client?.options.connectivityCheckUrl || Defaults.connectivityCheckUrl; + const connectivityCheckParams = this.client?.options.connectivityCheckParams ?? null; + const connectivityUrlIsDefault = !this.client?.options.connectivityCheckUrl; this.doUri( HttpMethods.Get, - null as any, connectivityCheckUrl, null, null, @@ -262,7 +267,6 @@ const Http: IHttpStatic = class { Request?: ( method: HttpMethods, - client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index 0b6bd62028..b8566c5633 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -11,7 +11,6 @@ import XHRStates from 'common/constants/XHRStates'; import Logger from 'common/lib/util/logger'; import { StandardCallback } from 'common/types/utils'; import fetchRequest from './request/fetchrequest'; -import { NormalisedClientOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; function shouldFallback(errorInfo: ErrorInfo) { @@ -45,16 +44,17 @@ const Http: IHttpStatic = class { static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; checksInProgress: Array> | null = null; + private client: BaseClient | null; - constructor(options?: NormalisedClientOptions) { - const connectivityCheckUrl = options?.connectivityCheckUrl || Defaults.connectivityCheckUrl; - const connectivityCheckParams = options?.connectivityCheckParams ?? null; - const connectivityUrlIsDefault = !options?.connectivityCheckUrl; + constructor(client?: BaseClient) { + this.client = client ?? null; + const connectivityCheckUrl = client?.options.connectivityCheckUrl || Defaults.connectivityCheckUrl; + const connectivityCheckParams = client?.options.connectivityCheckParams ?? null; + const connectivityUrlIsDefault = !client?.options.connectivityCheckUrl; if (Platform.Config.xhrSupported) { this.supportsAuthHeaders = true; this.Request = function ( method: HttpMethods, - client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, @@ -67,14 +67,14 @@ const Http: IHttpStatic = class { params, body, XHRStates.REQ_SEND, - client && client.options.timeouts, + (client && client.options.timeouts) ?? null, method ); req.once('complete', callback); req.exec(); return req; }; - if (options?.disableConnectivityCheck) { + if (client?.options.disableConnectivityCheck) { this.checkConnectivity = function (callback: (err: null, connectivity: true) => void) { callback(null, true); }; @@ -87,7 +87,6 @@ const Http: IHttpStatic = class { ); this.doUri( HttpMethods.Get, - null as any, connectivityCheckUrl, null, null, @@ -107,17 +106,19 @@ const Http: IHttpStatic = class { } } else if (Platform.Config.fetchSupported) { this.supportsAuthHeaders = true; - this.Request = fetchRequest; + this.Request = (method, uri, headers, params, body, callback) => { + fetchRequest(method, client ?? null, uri, headers, params, body, callback); + }; this.checkConnectivity = function (callback: (err: ErrorInfo | null, connectivity: boolean) => void) { Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl); - this.doUri(HttpMethods.Get, null as any, connectivityCheckUrl, null, null, null, function (err, responseText) { + this.doUri(HttpMethods.Get, connectivityCheckUrl, null, null, null, function (err, responseText) { const result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Result: ' + result); callback(null, result); }); }; } else { - this.Request = (method, client, uri, headers, params, body, callback) => { + this.Request = (method, uri, headers, params, body, callback) => { callback(new PartialErrorInfo('no supported HTTP transports available', null, 400), null); }; } @@ -126,13 +127,18 @@ const Http: IHttpStatic = class { /* Unlike for doUri, the 'client' param here is mandatory, as it's used to generate the hosts */ do( method: HttpMethods, - client: BaseClient, path: string, headers: Record | null, body: unknown, params: RequestParams, callback?: RequestCallback ): void { + /* Unlike for doUri, the presence of `this.client` here is mandatory, as it's used to generate the hosts */ + const client = this.client; + if (!client) { + throw new Error('http.do called without client'); + } + const uriFromHost = typeof path == 'function' ? path @@ -148,12 +154,12 @@ const Http: IHttpStatic = class { callback?.(new PartialErrorInfo('Request invoked before assigned to', null, 500)); return; } - this.Request(method, client, uriFromHost(currentFallback.host), headers, params, body, (err?, ...args) => { + this.Request(method, uriFromHost(currentFallback.host), headers, params, body, (err?, ...args) => { // This typecast is safe because ErrnoExceptions are only thrown in NodeJS if (err && shouldFallback(err as ErrorInfo)) { /* unstore the fallback and start from the top with the default sequence */ client._currentFallback = null; - this.do(method, client, path, headers, body, params, callback); + this.do(method, path, headers, body, params, callback); return; } callback?.(err, ...args); @@ -169,14 +175,14 @@ const Http: IHttpStatic = class { /* if there is only one host do it */ if (hosts.length === 1) { - this.doUri(method, client, uriFromHost(hosts[0]), headers, body, params, callback as RequestCallback); + this.doUri(method, uriFromHost(hosts[0]), headers, body, params, callback as RequestCallback); return; } /* hosts is an array with preferred host plus at least one fallback */ const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { const host = candidateHosts.shift(); - this.doUri(method, client, uriFromHost(host as string), headers, body, params, function (err, ...args) { + this.doUri(method, uriFromHost(host as string), headers, body, params, function (err, ...args) { // This typecast is safe because ErrnoExceptions are only thrown in NodeJS if (err && shouldFallback(err as ErrorInfo) && candidateHosts.length) { tryAHost(candidateHosts, true); @@ -197,7 +203,6 @@ const Http: IHttpStatic = class { doUri( method: HttpMethods, - client: BaseClient | null, uri: string, headers: Record | null, body: unknown, @@ -208,12 +213,11 @@ const Http: IHttpStatic = class { callback(new PartialErrorInfo('Request invoked before assigned to', null, 500)); return; } - this.Request(method, client, uri, headers, params, body, callback); + this.Request(method, uri, headers, params, body, callback); } Request?: ( method: HttpMethods, - client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 501bd09f9d..efaf654b3b 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -286,7 +286,7 @@ describe('browser/modules', function () { const channelName = 'channel'; const channel = rest.channels.get(channelName); const contentTypeUsedForPublishPromise = new Promise((resolve, reject) => { - rest.http.do = (method, client, path, headers, body, params, callback) => { + rest.http.do = (method, path, headers, body, params, callback) => { if (!(method == 'post' && path == `/channels/${channelName}/messages`)) { return; } diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 92dde3019a..affb68bae6 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -22,7 +22,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ function getJWT(params, callback) { var authUrl = echoServer + '/createJWT'; - http.doUri('get', null, authUrl, null, null, params, function (err, body) { + http.doUri('get', authUrl, null, null, params, function (err, body) { if (err) { callback(err, null); } diff --git a/test/rest/http.test.js b/test/rest/http.test.js index 315f751551..dcd7ee8262 100644 --- a/test/rest/http.test.js +++ b/test/rest/http.test.js @@ -25,7 +25,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var originalDo = rest.http.do; // Intercept Http.do with test - function testRequestHandler(method, rest, path, headers, body, params, callback) { + function testRequestHandler(method, path, headers, body, params, callback) { expect('X-Ably-Version' in headers, 'Verify version header exists').to.be.ok; expect('Ably-Agent' in headers, 'Verify agent header exists').to.be.ok; @@ -47,7 +47,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { expect(headers['Ably-Agent'].indexOf('nodejs') > -1, 'Verify agent').to.be.ok; } - originalDo.call(rest.http, method, rest, path, headers, body, params, callback); + originalDo.call(rest.http, method, path, headers, body, params, callback); } rest.http.do = testRequestHandler; diff --git a/test/rest/message.test.js b/test/rest/message.test.js index 389e0b7d74..090e6eba70 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -157,8 +157,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async originalPublish.apply(channel, arguments); }; - Ably.Rest.Platform.Http.doUri = function (method, rest, uri, headers, body, params, callback) { - originalDoUri(method, rest, uri, headers, body, params, function (err) { + Ably.Rest.Platform.Http.doUri = function (method, uri, headers, body, params, callback) { + originalDoUri(method, uri, headers, body, params, function (err) { if (err) { callback(err); return; diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 7055f46191..c123e94606 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -25,7 +25,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async restTestOnJsonMsgpack('request_version', function (rest) { const version = 150; // arbitrarily chosen - function testRequestHandler(_, __, ___, headers) { + function testRequestHandler(_, __, headers) { try { expect('X-Ably-Version' in headers, 'Verify version header exists').to.be.ok; expect(headers['X-Ably-Version']).to.equal(version.toString(), 'Verify version number sent in request'); From 674e88afc2a232aac2b934948f665943fd897534 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 5 Sep 2023 10:26:07 +0100 Subject: [PATCH 190/468] Make HTTP request implementations tree-shakable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We expose XHRRequest and FetchRequest modules. The user is required to provide an HTTP module, even for Realtime, since it’s used for the internet connectivity check and for making a token request to the authUrl. Resolves #1395. --- scripts/moduleReport.js | 2 + src/common/lib/client/baseclient.ts | 5 ++ src/common/lib/client/modulesmap.ts | 4 ++ src/platform/nativescript/index.ts | 3 + src/platform/react-native/index.ts | 3 + src/platform/web-noencryption/index.ts | 3 + src/platform/web/index.ts | 3 + src/platform/web/lib/http/http.ts | 45 +++++++++--- src/platform/web/lib/http/request/index.ts | 10 +++ src/platform/web/modules.ts | 4 ++ src/platform/web/modules/http.ts | 2 + test/browser/modules.test.js | 84 +++++++++++++++------- 12 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 src/platform/web/lib/http/request/index.ts create mode 100644 src/platform/web/modules/http.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index ee5cadf0a8..f82a58ea8a 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -9,6 +9,8 @@ const moduleNames = [ 'XHRPolling', 'XHRStreaming', 'WebSocketTransport', + 'XHRRequest', + 'FetchRequest', ]; // List of all free-standing functions exported by the library along with the diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index fbb2be3475..ddc726b4d5 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -15,6 +15,7 @@ import { Rest } from './rest'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; import { throwMissingModuleError } from '../util/utils'; import { MsgPack } from 'common/types/msgpack'; +import { HTTPRequestImplementations } from 'platform/web/lib/http/http'; type BatchResult = API.Types.BatchResult; type BatchPublishSpec = API.Types.BatchPublishSpec; @@ -41,8 +42,12 @@ class BaseClient { private readonly _rest: Rest | null; readonly _Crypto: IUntypedCryptoStatic | null; readonly _MsgPack: MsgPack | null; + // Extra HTTP request implementations available to this client, in addition to those in web’s Http.bundledRequestImplementations + readonly _additionalHTTPRequestImplementations: HTTPRequestImplementations; constructor(options: ClientOptions | string, modules: ModulesMap) { + this._additionalHTTPRequestImplementations = modules; + if (!options) { const msg = 'no options provided'; Logger.logAction(Logger.LOG_ERROR, 'BaseClient()', msg); diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index ada7de44ee..dff32a69e4 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -3,6 +3,8 @@ import { IUntypedCryptoStatic } from '../../types/ICryptoStatic'; import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; import { TransportInitialiser } from '../transport/connectionmanager'; +import XHRRequest from 'platform/web/lib/http/request/xhrrequest'; +import fetchRequest from 'platform/web/lib/http/request/fetchrequest'; export interface ModulesMap { Rest?: typeof Rest; @@ -12,6 +14,8 @@ export interface ModulesMap { WebSocketTransport?: TransportInitialiser; XHRPolling?: TransportInitialiser; XHRStreaming?: TransportInitialiser; + XHRRequest?: typeof XHRRequest; + FetchRequest?: typeof fetchRequest; } export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index c8354aa69d..119fdcb048 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -19,6 +19,7 @@ import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from './lib/util/webstorage'; import PlatformDefaults from '../web/lib/util/defaults'; import msgpack from '../web/lib/util/msgpack'; +import { defaultBundledRequestImplementations } from '../web/lib/http/request'; const Crypto = createCryptoClass(Config, BufferUtils); @@ -34,6 +35,8 @@ for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass._MsgPack = msgpack; } +Http.bundledRequestImplementations = defaultBundledRequestImplementations; + Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index dc27bb62c8..e0539aa92a 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -17,6 +17,7 @@ import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from '../web/lib/util/webstorage'; import PlatformDefaults from '../web/lib/util/defaults'; import msgpack from '../web/lib/util/msgpack'; +import { defaultBundledRequestImplementations } from '../web/lib/http/request'; const Config = configFactory(BufferUtils); @@ -34,6 +35,8 @@ for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass._MsgPack = msgpack; } +Http.bundledRequestImplementations = defaultBundledRequestImplementations; + Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); diff --git a/src/platform/web-noencryption/index.ts b/src/platform/web-noencryption/index.ts index dcb4120123..64dcf02b5e 100644 --- a/src/platform/web-noencryption/index.ts +++ b/src/platform/web-noencryption/index.ts @@ -16,6 +16,7 @@ import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from '../web/lib/util/webstorage'; import PlatformDefaults from '../web/lib/util/defaults'; import msgpack from '../web/lib/util/msgpack'; +import { defaultBundledRequestImplementations } from '../web/lib/http/request'; Platform.Crypto = null; Platform.BufferUtils = BufferUtils; @@ -28,6 +29,8 @@ for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass._MsgPack = msgpack; } +Http.bundledRequestImplementations = defaultBundledRequestImplementations; + Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index 3e40e123b7..27d6c9556b 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -17,6 +17,7 @@ import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from './lib/util/webstorage'; import PlatformDefaults from './lib/util/defaults'; import msgpack from './lib/util/msgpack'; +import { defaultBundledRequestImplementations } from './lib/http/request'; const Crypto = createCryptoClass(Config, BufferUtils); @@ -32,6 +33,8 @@ for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass._MsgPack = msgpack; } +Http.bundledRequestImplementations = defaultBundledRequestImplementations; + Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index b8566c5633..626a946758 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -2,16 +2,17 @@ import Platform from 'common/platform'; import * as Utils from 'common/lib/util/utils'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { IHttpStatic, RequestCallback, RequestParams } from 'common/types/http'; +import { RequestCallback, RequestParams } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import BaseRealtime from 'common/lib/client/baserealtime'; -import XHRRequest from './request/xhrrequest'; import XHRStates from 'common/constants/XHRStates'; import Logger from 'common/lib/util/logger'; import { StandardCallback } from 'common/types/utils'; -import fetchRequest from './request/fetchrequest'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; +import { ModulesMap } from 'common/lib/client/modulesmap'; + +export type HTTPRequestImplementations = Pick; function shouldFallback(errorInfo: ErrorInfo) { const statusCode = errorInfo.statusCode as number; @@ -39,10 +40,20 @@ function getHosts(client: BaseClient): string[] { return Defaults.getHosts(client.options); } -const Http: IHttpStatic = class { +function createMissingImplementationError() { + return new ErrorInfo( + 'No HTTP request module provided. Provide at least one of the FetchRequest or XHRRequest modules.', + 400, + 40000 + ); +} + +const Http = class { static methods = [HttpMethods.Get, HttpMethods.Delete, HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; + // HTTP request implementations that are available even without a BaseClient object (needed by some tests which directly instantiate `Http` without a client) + static bundledRequestImplementations: HTTPRequestImplementations; checksInProgress: Array> | null = null; private client: BaseClient | null; @@ -51,7 +62,20 @@ const Http: IHttpStatic = class { const connectivityCheckUrl = client?.options.connectivityCheckUrl || Defaults.connectivityCheckUrl; const connectivityCheckParams = client?.options.connectivityCheckParams ?? null; const connectivityUrlIsDefault = !client?.options.connectivityCheckUrl; - if (Platform.Config.xhrSupported) { + + const requestImplementations = { + ...Http.bundledRequestImplementations, + ...client?._additionalHTTPRequestImplementations, + }; + const xhrRequestImplementation = requestImplementations.XHRRequest; + const fetchRequestImplementation = requestImplementations.FetchRequest; + const hasImplementation = !!(xhrRequestImplementation || fetchRequestImplementation); + + if (!hasImplementation) { + throw createMissingImplementationError(); + } + + if (Platform.Config.xhrSupported && xhrRequestImplementation) { this.supportsAuthHeaders = true; this.Request = function ( method: HttpMethods, @@ -61,7 +85,7 @@ const Http: IHttpStatic = class { body: unknown, callback: RequestCallback ) { - const req = XHRRequest.createRequest( + const req = xhrRequestImplementation.createRequest( uri, headers, params, @@ -104,10 +128,10 @@ const Http: IHttpStatic = class { ); }; } - } else if (Platform.Config.fetchSupported) { + } else if (Platform.Config.fetchSupported && fetchRequestImplementation) { this.supportsAuthHeaders = true; this.Request = (method, uri, headers, params, body, callback) => { - fetchRequest(method, client ?? null, uri, headers, params, body, callback); + fetchRequestImplementation(method, client ?? null, uri, headers, params, body, callback); }; this.checkConnectivity = function (callback: (err: ErrorInfo | null, connectivity: boolean) => void) { Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl); @@ -119,7 +143,10 @@ const Http: IHttpStatic = class { }; } else { this.Request = (method, uri, headers, params, body, callback) => { - callback(new PartialErrorInfo('no supported HTTP transports available', null, 400), null); + const error = hasImplementation + ? new PartialErrorInfo('no supported HTTP transports available', null, 400) + : createMissingImplementationError(); + callback(error, null); }; } } diff --git a/src/platform/web/lib/http/request/index.ts b/src/platform/web/lib/http/request/index.ts new file mode 100644 index 0000000000..4fccec5b3b --- /dev/null +++ b/src/platform/web/lib/http/request/index.ts @@ -0,0 +1,10 @@ +import { HTTPRequestImplementations } from '../http'; +import XHRRequest from './xhrrequest'; +import fetchRequest from './fetchrequest'; + +export const defaultBundledRequestImplementations: HTTPRequestImplementations = { + XHRRequest: XHRRequest, + FetchRequest: fetchRequest, +}; + +export const modulesBundledRequestImplementations: HTTPRequestImplementations = {}; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index f9ee3a521d..e12ede52e7 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -15,6 +15,7 @@ import Logger from '../../common/lib/util/logger'; import { getDefaults } from '../../common/lib/util/defaults'; import WebStorage from './lib/util/webstorage'; import PlatformDefaults from './lib/util/defaults'; +import { modulesBundledRequestImplementations } from './lib/http/request'; Platform.BufferUtils = BufferUtils; Platform.Http = Http; @@ -22,6 +23,8 @@ Platform.Config = Config; Platform.Transports = ModulesTransports; Platform.WebStorage = WebStorage; +Http.bundledRequestImplementations = modulesBundledRequestImplementations; + Logger.initLogHandlers(); Platform.Defaults = getDefaults(PlatformDefaults); @@ -45,5 +48,6 @@ export * from './modules/presencemessage'; export * from './modules/msgpack'; export * from './modules/realtimepresence'; export * from './modules/transports'; +export * from './modules/http'; export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/src/platform/web/modules/http.ts b/src/platform/web/modules/http.ts new file mode 100644 index 0000000000..24b664f30d --- /dev/null +++ b/src/platform/web/modules/http.ts @@ -0,0 +1,2 @@ +export { default as XHRRequest } from '../lib/http/request/xhrrequest'; +export { default as FetchRequest } from '../lib/http/request/fetchrequest'; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index efaf654b3b..d73b76624e 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -17,6 +17,8 @@ import { XHRPolling, XHRStreaming, WebSocketTransport, + FetchRequest, + XHRRequest, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -45,23 +47,21 @@ describe('browser/modules', function () { }); describe('without any modules', () => { - describe('BaseRest', () => { - it('can be constructed', () => { - expect(() => new BaseRest(ablyClientOptions(), {})).not.to.throw(); - }); - }); - - describe('BaseRealtime', () => { - it('throws an error due to absence of a transport module', () => { - expect(() => new BaseRealtime(ablyClientOptions(), {})).to.throw('no requested transports available'); + for (const clientClass of [BaseRest, BaseRealtime]) { + describe(clientClass.name, () => { + it('throws an error due to the absence of an HTTP module', () => { + expect(() => new clientClass(ablyClientOptions(), {})).to.throw( + 'No HTTP request module provided. Provide at least one of the FetchRequest or XHRRequest modules.' + ); + }); }); - }); + } }); describe('Rest', () => { describe('BaseRest without explicit Rest', () => { it('offers REST functionality', async () => { - const client = new BaseRest(ablyClientOptions(), {}); + const client = new BaseRest(ablyClientOptions(), { FetchRequest }); const time = await client.time(); expect(time).to.be.a('number'); }); @@ -69,7 +69,7 @@ describe('browser/modules', function () { describe('BaseRealtime with Rest', () => { it('offers REST functionality', async () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, Rest }); + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest, Rest }); const time = await client.time(); expect(time).to.be.a('number'); }); @@ -77,7 +77,7 @@ describe('browser/modules', function () { describe('BaseRealtime without Rest', () => { it('throws an error when attempting to use REST functionality', async () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport }); + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); expect(() => client.time()).to.throw('Rest module not provided'); }); }); @@ -214,10 +214,10 @@ describe('browser/modules', function () { describe('Crypto', () => { describe('without Crypto', () => { async function testThrowsAnErrorWhenGivenChannelOptionsWithACipher(clientClassConfig) { - const client = new clientClassConfig.clientClass( - ablyClientOptions(), - clientClassConfig.additionalModules ?? {} - ); + const client = new clientClassConfig.clientClass(ablyClientOptions(), { + ...clientClassConfig.additionalModules, + FetchRequest, + }); const key = await generateRandomKey(); expect(() => client.channels.get('channel', { cipher: { key } })).to.throw('Crypto module not provided'); } @@ -242,7 +242,7 @@ describe('browser/modules', function () { // Publish the message on a channel configured to use encryption, and receive it on one not configured to use encryption - const rxClient = new BaseRealtime(clientOptions, { WebSocketTransport }); + const rxClient = new BaseRealtime(clientOptions, { WebSocketTransport, FetchRequest }); const rxChannel = rxClient.channels.get('channel'); await rxChannel.attach(); @@ -252,7 +252,8 @@ describe('browser/modules', function () { const txMessage = { name: 'message', data: 'data' }; const txClient = new clientClassConfig.clientClass(clientOptions, { - ...(clientClassConfig.additionalModules ?? {}), + ...clientClassConfig.additionalModules, + FetchRequest, Crypto, }); const txChannel = txClient.channels.get('channel', encryptionChannelOptions); @@ -318,7 +319,7 @@ describe('browser/modules', function () { describe('without MsgPack', () => { describe('BaseRest', () => { it('uses JSON', async () => { - const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), {}); + const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { FetchRequest }); await testRestUsesContentType(client, 'application/json'); }); }); @@ -327,6 +328,7 @@ describe('browser/modules', function () { it('uses JSON', async () => { const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { WebSocketTransport, + FetchRequest, }); await testRealtimeUsesFormat(client, 'json'); }); @@ -337,6 +339,7 @@ describe('browser/modules', function () { describe('BaseRest', () => { it('uses MessagePack', async () => { const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { + FetchRequest, MsgPack, }); await testRestUsesContentType(client, 'application/x-msgpack'); @@ -347,6 +350,7 @@ describe('browser/modules', function () { it('uses MessagePack', async () => { const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { WebSocketTransport, + FetchRequest, MsgPack, }); await testRealtimeUsesFormat(client, 'msgpack'); @@ -359,7 +363,7 @@ describe('browser/modules', function () { describe('RealtimePresence', () => { describe('BaseRealtime without RealtimePresence', () => { it('throws an error when attempting to access the `presence` property', () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport }); + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); const channel = client.channels.get('channel'); expect(() => channel.presence).to.throw('RealtimePresence module not provided'); @@ -368,12 +372,15 @@ describe('browser/modules', function () { describe('BaseRealtime with RealtimePresence', () => { it('offers realtime presence functionality', async () => { - const rxChannel = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, RealtimePresence }).channels.get( - 'channel' - ); + const rxChannel = new BaseRealtime(ablyClientOptions(), { + WebSocketTransport, + FetchRequest, + RealtimePresence, + }).channels.get('channel'); const txClientId = randomString(); const txChannel = new BaseRealtime(ablyClientOptions({ clientId: txClientId }), { WebSocketTransport, + FetchRequest, RealtimePresence, }).channels.get('channel'); @@ -435,6 +442,14 @@ describe('browser/modules', function () { describe('Transports', () => { describe('BaseRealtime', () => { + describe('without a transport module', () => { + it('throws an error due to absence of a transport module', () => { + expect(() => new BaseRealtime(ablyClientOptions(), { FetchRequest })).to.throw( + 'no requested transports available' + ); + }); + }); + for (const scenario of [ { moduleMapKey: 'WebSocketTransport', transportModule: WebSocketTransport, transportName: 'web_socket' }, { moduleMapKey: 'XHRPolling', transportModule: XHRPolling, transportName: 'xhr_polling' }, @@ -445,6 +460,7 @@ describe('browser/modules', function () { const realtime = new BaseRealtime( ablyClientOptions({ autoConnect: false, transports: [scenario.transportName] }), { + FetchRequest, [scenario.moduleMapKey]: scenario.transportModule, } ); @@ -468,4 +484,24 @@ describe('browser/modules', function () { } }); }); + + describe('HTTP request implementations', () => { + describe('with multiple HTTP request implementations', () => { + it('prefers XHR', async () => { + let usedXHR = false; + + const XHRRequestSpy = class XHRRequestSpy extends XHRRequest { + static createRequest(...args) { + usedXHR = true; + return super.createRequest(...args); + } + }; + + const rest = new BaseRest(ablyClientOptions(), { FetchRequest, XHRRequest: XHRRequestSpy }); + await rest.time(); + + expect(usedXHR).to.be.true; + }); + }); + }); }); From 61cf994604a73f36bc0e9ff56102e26f7aa4574a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 27 Oct 2023 15:11:04 -0300 Subject: [PATCH 191/468] Extract RealtimeChannel MessageFilter-related code to new class In preparation for #1397 (making MessageFilter functionality tree-shakable). --- .../lib/client/filteredsubscriptions.ts | 112 ++++++++++++++++++ src/common/lib/client/realtimechannel.ts | 108 +---------------- 2 files changed, 117 insertions(+), 103 deletions(-) create mode 100644 src/common/lib/client/filteredsubscriptions.ts diff --git a/src/common/lib/client/filteredsubscriptions.ts b/src/common/lib/client/filteredsubscriptions.ts new file mode 100644 index 0000000000..1588fa4962 --- /dev/null +++ b/src/common/lib/client/filteredsubscriptions.ts @@ -0,0 +1,112 @@ +import * as API from '../../../../ably'; +import RealtimeChannel from './realtimechannel'; +import Message from '../types/message'; + +export class FilteredSubscriptions { + static subscribeFilter( + channel: RealtimeChannel, + filter: API.Types.MessageFilter, + listener: API.Types.messageCallback + ) { + const filteredListener = (m: Message) => { + const mapping: { [key in keyof API.Types.MessageFilter]: any } = { + name: m.name, + refTimeserial: m.extras?.ref?.timeserial, + refType: m.extras?.ref?.type, + isRef: !!m.extras?.ref?.timeserial, + clientId: m.clientId, + }; + // Check if any values are defined in the filter and if they match the value in the message object + if ( + Object.entries(filter).find(([key, value]) => + value !== undefined ? mapping[key as keyof API.Types.MessageFilter] !== value : false + ) + ) { + return; + } + listener(m); + }; + this.addFilteredSubscription(channel, filter, listener, filteredListener); + channel.subscriptions.on(filteredListener); + } + + // Adds a new filtered subscription + static addFilteredSubscription( + channel: RealtimeChannel, + filter: API.Types.MessageFilter, + realListener: API.Types.messageCallback, + filteredListener: API.Types.messageCallback + ) { + if (!channel.filteredSubscriptions) { + channel.filteredSubscriptions = new Map< + API.Types.messageCallback, + Map[]> + >(); + } + if (channel.filteredSubscriptions.has(realListener)) { + const realListenerMap = channel.filteredSubscriptions.get(realListener) as Map< + API.Types.MessageFilter, + API.Types.messageCallback[] + >; + // Add the filtered listener to the map, or append to the array if this filter has already been used + realListenerMap.set(filter, realListenerMap?.get(filter)?.concat(filteredListener) || [filteredListener]); + } else { + channel.filteredSubscriptions.set( + realListener, + new Map[]>([[filter, [filteredListener]]]) + ); + } + } + + static getAndDeleteFilteredSubscriptions( + channel: RealtimeChannel, + filter: API.Types.MessageFilter | undefined, + realListener: API.Types.messageCallback | undefined + ): API.Types.messageCallback[] { + // No filtered subscriptions map means there has been no filtered subscriptions yet, so return nothing + if (!channel.filteredSubscriptions) { + return []; + } + // Only a filter is passed in with no specific listener + if (!realListener && filter) { + // Return each listener which is attached to the specified filter object + return Array.from(channel.filteredSubscriptions.entries()) + .map(([key, filterMaps]) => { + // Get (then delete) the maps matching this filter + let listenerMaps = filterMaps.get(filter); + filterMaps.delete(filter); + // Clear the parent if nothing is left + if (filterMaps.size === 0) { + channel.filteredSubscriptions?.delete(key); + } + return listenerMaps; + }) + .reduce( + (prev, cur) => (cur ? (prev as API.Types.messageCallback[]).concat(...cur) : prev), + [] + ) as API.Types.messageCallback[]; + } + + // No subscriptions for this listener + if (!realListener || !channel.filteredSubscriptions.has(realListener)) { + return []; + } + const realListenerMap = channel.filteredSubscriptions.get(realListener) as Map< + API.Types.MessageFilter, + API.Types.messageCallback[] + >; + // If no filter is specified return all listeners using that function + if (!filter) { + // array.flat is not available unless we support es2019 or higher + const listeners = Array.from(realListenerMap.values()).reduce((prev, cur) => prev.concat(...cur), []); + // remove the listener from the map + channel.filteredSubscriptions.delete(realListener); + return listeners; + } + + let listeners = realListenerMap.get(filter); + realListenerMap.delete(filter); + + return listeners || []; + } +} diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index ee403f7bc5..7080066f05 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -14,6 +14,7 @@ import ConnectionManager from '../transport/connectionmanager'; import ConnectionStateChange from './connectionstatechange'; import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseRealtime from './baserealtime'; +import { FilteredSubscriptions } from './filteredsubscriptions'; interface RealtimeHistoryParams { start?: number; @@ -438,7 +439,7 @@ class RealtimeChannel extends Channel { // Filtered if (event && typeof event === 'object' && !Array.isArray(event)) { - this._subscribeFilter(event, listener); + FilteredSubscriptions.subscribeFilter(this, event, listener); } else { this.subscriptions.on(event, listener); } @@ -446,113 +447,14 @@ class RealtimeChannel extends Channel { return this.attach(callback || noop); } - _subscribeFilter(filter: API.Types.MessageFilter, listener: API.Types.messageCallback) { - const filteredListener = (m: Message) => { - const mapping: { [key in keyof API.Types.MessageFilter]: any } = { - name: m.name, - refTimeserial: m.extras?.ref?.timeserial, - refType: m.extras?.ref?.type, - isRef: !!m.extras?.ref?.timeserial, - clientId: m.clientId, - }; - // Check if any values are defined in the filter and if they match the value in the message object - if ( - Object.entries(filter).find(([key, value]) => - value !== undefined ? mapping[key as keyof API.Types.MessageFilter] !== value : false - ) - ) { - return; - } - listener(m); - }; - this._addFilteredSubscription(filter, listener, filteredListener); - this.subscriptions.on(filteredListener); - } - - // Adds a new filtered subscription - _addFilteredSubscription( - filter: API.Types.MessageFilter, - realListener: API.Types.messageCallback, - filteredListener: API.Types.messageCallback - ) { - if (!this.filteredSubscriptions) { - this.filteredSubscriptions = new Map< - API.Types.messageCallback, - Map[]> - >(); - } - if (this.filteredSubscriptions.has(realListener)) { - const realListenerMap = this.filteredSubscriptions.get(realListener) as Map< - API.Types.MessageFilter, - API.Types.messageCallback[] - >; - // Add the filtered listener to the map, or append to the array if this filter has already been used - realListenerMap.set(filter, realListenerMap?.get(filter)?.concat(filteredListener) || [filteredListener]); - } else { - this.filteredSubscriptions.set( - realListener, - new Map[]>([[filter, [filteredListener]]]) - ); - } - } - - _getAndDeleteFilteredSubscriptions( - filter: API.Types.MessageFilter | undefined, - realListener: API.Types.messageCallback | undefined - ): API.Types.messageCallback[] { - // No filtered subscriptions map means there has been no filtered subscriptions yet, so return nothing - if (!this.filteredSubscriptions) { - return []; - } - // Only a filter is passed in with no specific listener - if (!realListener && filter) { - // Return each listener which is attached to the specified filter object - return Array.from(this.filteredSubscriptions.entries()) - .map(([key, filterMaps]) => { - // Get (then delete) the maps matching this filter - let listenerMaps = filterMaps.get(filter); - filterMaps.delete(filter); - // Clear the parent if nothing is left - if (filterMaps.size === 0) { - this.filteredSubscriptions?.delete(key); - } - return listenerMaps; - }) - .reduce( - (prev, cur) => (cur ? (prev as API.Types.messageCallback[]).concat(...cur) : prev), - [] - ) as API.Types.messageCallback[]; - } - - // No subscriptions for this listener - if (!realListener || !this.filteredSubscriptions.has(realListener)) { - return []; - } - const realListenerMap = this.filteredSubscriptions.get(realListener) as Map< - API.Types.MessageFilter, - API.Types.messageCallback[] - >; - // If no filter is specified return all listeners using that function - if (!filter) { - // array.flat is not available unless we support es2019 or higher - const listeners = Array.from(realListenerMap.values()).reduce((prev, cur) => prev.concat(...cur), []); - // remove the listener from the map - this.filteredSubscriptions.delete(realListener); - return listeners; - } - - let listeners = realListenerMap.get(filter); - realListenerMap.delete(filter); - - return listeners || []; - } - unsubscribe(...args: unknown[] /* [event], listener */): void { const [event, listener] = RealtimeChannel.processListenerArgs(args); // If we either have a filtered listener, a filter or both we need to do additional processing to find the original function(s) if ((typeof event === 'object' && !listener) || this.filteredSubscriptions?.has(listener)) { - this._getAndDeleteFilteredSubscriptions(event, listener).forEach((l) => this.subscriptions.off(l)); + FilteredSubscriptions.getAndDeleteFilteredSubscriptions(this, event, listener).forEach((l) => + this.subscriptions.off(l) + ); return; } From 6a9647212a4e085b85650e40c4ad382c21a18452 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 27 Oct 2023 15:24:48 -0300 Subject: [PATCH 192/468] Make subscription filtering tree-shakable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We expose a MessageIteractions module which allows users to pass a MessageFilter object to RealtimeChannel’s `subscribe` and `unsubscribe`. Resolves #1397. --- scripts/moduleReport.js | 1 + src/common/lib/client/baseclient.ts | 10 +++ src/common/lib/client/defaultrealtime.ts | 2 + src/common/lib/client/modulesmap.ts | 2 + src/common/lib/client/realtimechannel.ts | 9 ++- src/platform/web/modules.ts | 1 + test/browser/modules.test.js | 89 ++++++++++++++++++++++++ 7 files changed, 109 insertions(+), 5 deletions(-) diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index f82a58ea8a..dd1ec550f5 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -11,6 +11,7 @@ const moduleNames = [ 'WebSocketTransport', 'XHRRequest', 'FetchRequest', + 'MessageInteractions', ]; // List of all free-standing functions exported by the library along with the diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index ddc726b4d5..482075fa23 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -16,6 +16,7 @@ import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; import { throwMissingModuleError } from '../util/utils'; import { MsgPack } from 'common/types/msgpack'; import { HTTPRequestImplementations } from 'platform/web/lib/http/http'; +import { FilteredSubscriptions } from './filteredsubscriptions'; type BatchResult = API.Types.BatchResult; type BatchPublishSpec = API.Types.BatchPublishSpec; @@ -44,6 +45,7 @@ class BaseClient { readonly _MsgPack: MsgPack | null; // Extra HTTP request implementations available to this client, in addition to those in web’s Http.bundledRequestImplementations readonly _additionalHTTPRequestImplementations: HTTPRequestImplementations; + private readonly __FilteredSubscriptions: typeof FilteredSubscriptions | null; constructor(options: ClientOptions | string, modules: ModulesMap) { this._additionalHTTPRequestImplementations = modules; @@ -98,6 +100,7 @@ class BaseClient { this._rest = modules.Rest ? new modules.Rest(this) : null; this._Crypto = modules.Crypto ?? null; + this.__FilteredSubscriptions = modules.MessageInteractions ?? null; } private get rest(): Rest { @@ -107,6 +110,13 @@ class BaseClient { return this._rest; } + get _FilteredSubscriptions(): typeof FilteredSubscriptions { + if (!this.__FilteredSubscriptions) { + throwMissingModuleError('MessageInteractions'); + } + return this.__FilteredSubscriptions; + } + get channels() { return this.rest.channels; } diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 2204ab71ac..1dd3d402dd 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -10,6 +10,7 @@ import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; import initialiseWebSocketTransport from '../transport/websockettransport'; +import { FilteredSubscriptions } from './filteredsubscriptions'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -27,6 +28,7 @@ export class DefaultRealtime extends BaseRealtime { MsgPack, RealtimePresence, WebSocketTransport: initialiseWebSocketTransport, + MessageInteractions: FilteredSubscriptions, }); } diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index dff32a69e4..dab5c8b718 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -5,6 +5,7 @@ import RealtimePresence from './realtimepresence'; import { TransportInitialiser } from '../transport/connectionmanager'; import XHRRequest from 'platform/web/lib/http/request/xhrrequest'; import fetchRequest from 'platform/web/lib/http/request/fetchrequest'; +import { FilteredSubscriptions } from './filteredsubscriptions'; export interface ModulesMap { Rest?: typeof Rest; @@ -16,6 +17,7 @@ export interface ModulesMap { XHRStreaming?: TransportInitialiser; XHRRequest?: typeof XHRRequest; FetchRequest?: typeof fetchRequest; + MessageInteractions?: typeof FilteredSubscriptions; } export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 7080066f05..898377dded 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -14,7 +14,6 @@ import ConnectionManager from '../transport/connectionmanager'; import ConnectionStateChange from './connectionstatechange'; import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseRealtime from './baserealtime'; -import { FilteredSubscriptions } from './filteredsubscriptions'; interface RealtimeHistoryParams { start?: number; @@ -439,7 +438,7 @@ class RealtimeChannel extends Channel { // Filtered if (event && typeof event === 'object' && !Array.isArray(event)) { - FilteredSubscriptions.subscribeFilter(this, event, listener); + this.client._FilteredSubscriptions.subscribeFilter(this, event, listener); } else { this.subscriptions.on(event, listener); } @@ -452,9 +451,9 @@ class RealtimeChannel extends Channel { // If we either have a filtered listener, a filter or both we need to do additional processing to find the original function(s) if ((typeof event === 'object' && !listener) || this.filteredSubscriptions?.has(listener)) { - FilteredSubscriptions.getAndDeleteFilteredSubscriptions(this, event, listener).forEach((l) => - this.subscriptions.off(l) - ); + this.client._FilteredSubscriptions + .getAndDeleteFilteredSubscriptions(this, event, listener) + .forEach((l) => this.subscriptions.off(l)); return; } diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index e12ede52e7..0465405a90 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -50,4 +50,5 @@ export * from './modules/realtimepresence'; export * from './modules/transports'; export * from './modules/http'; export { Rest } from '../../common/lib/client/rest'; +export { FilteredSubscriptions as MessageInteractions } from '../../common/lib/client/filteredsubscriptions'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index d73b76624e..bf56cf4fda 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -19,6 +19,7 @@ import { WebSocketTransport, FetchRequest, XHRRequest, + MessageInteractions, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -504,4 +505,92 @@ describe('browser/modules', function () { }); }); }); + + describe('MessageInteractions', () => { + describe('BaseRealtime', () => { + describe('without MessageInteractions', () => { + it('is able to subscribe to and unsubscribe from channel events, as long as a MessageFilter isn’t passed', async () => { + const realtime = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const channel = realtime.channels.get('channel'); + await channel.attach(); + + const subscribeReceivedMessagePromise = new Promise((resolve) => channel.subscribe(resolve)); + + await channel.publish('message', 'body'); + + const subscribeReceivedMessage = await subscribeReceivedMessagePromise; + expect(subscribeReceivedMessage.data).to.equal('body'); + }); + + it('throws an error when attempting to subscribe to channel events using a MessageFilter', async () => { + const realtime = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const channel = realtime.channels.get('channel'); + + let thrownError = null; + try { + await channel.subscribe({ clientId: 'someClientId' }, () => {}); + } catch (error) { + thrownError = error; + } + + expect(thrownError).not.to.be.null; + expect(thrownError.message).to.equal('MessageInteractions module not provided'); + }); + }); + + describe('with MessageInteractions', () => { + it('can take a MessageFilter argument when subscribing to and unsubscribing from channel events', async () => { + const realtime = new BaseRealtime(ablyClientOptions(), { + WebSocketTransport, + FetchRequest, + MessageInteractions, + }); + const channel = realtime.channels.get('channel'); + + await channel.attach(); + + // Test `subscribe` with a filter: send two messages with different clientIds, and check that unfiltered subscription receives both messages but clientId-filtered subscription only receives the matching one. + const messageFilter = { clientId: 'someClientId' }; // note that `unsubscribe` compares filter by reference, I found that a bit surprising + + const filteredSubscriptionReceivedMessages = []; + channel.subscribe(messageFilter, (message) => { + filteredSubscriptionReceivedMessages.push(message); + }); + + const unfilteredSubscriptionReceivedFirstTwoMessagesPromise = new Promise((resolve) => { + const receivedMessages = []; + channel.subscribe(function listener(message) { + receivedMessages.push(message); + if (receivedMessages.length === 2) { + channel.unsubscribe(listener); + resolve(); + } + }); + }); + + await channel.publish(await decodeMessage({ clientId: 'someClientId' })); + await channel.publish(await decodeMessage({ clientId: 'someOtherClientId' })); + await unfilteredSubscriptionReceivedFirstTwoMessagesPromise; + + expect(filteredSubscriptionReceivedMessages.length).to.equal(1); + expect(filteredSubscriptionReceivedMessages[0].clientId).to.equal('someClientId'); + + // Test `unsubscribe` with a filter: call `unsubscribe` with the clientId filter, publish a message matching the filter, check that only the unfiltered listener recieves it + channel.unsubscribe(messageFilter); + + const unfilteredSubscriptionReceivedNextMessagePromise = new Promise((resolve) => { + channel.subscribe(function listener() { + channel.unsubscribe(listener); + resolve(); + }); + }); + + await channel.publish(await decodeMessage({ clientId: 'someClientId' })); + await unfilteredSubscriptionReceivedNextMessagePromise; + + expect(filteredSubscriptionReceivedMessages.length).to./* (still) */ equal(1); + }); + }); + }); + }); }); From 29bce5227e2f52f2cd5ac18a1b652ee490d462c5 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 1 Nov 2023 10:06:51 -0300 Subject: [PATCH 193/468] Also print stdout when shell command run by Grunt fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using this function to run `tsc`, noticed that it wasn’t outputting any useful information about why that command failed, because all of the error information was going to stdout. --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index fc5d605ea2..44e773a512 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -33,7 +33,7 @@ module.exports = function (grunt) { grunt.log.ok('Executing ' + cmd); require('child_process').exec(cmd, function (err, stdout, stderr) { if (err) { - grunt.fatal('Error executing "' + cmd + '": ' + stderr); + grunt.fatal('Error executing "' + cmd + '":\nstderr:\n' + stderr + '\nstdout:\n' + stdout); } console.log(stdout); stderr && console.error(stderr); From 9976a8455a421b0d901bb609fafac2e0654c4f1a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 30 Oct 2023 13:00:39 -0300 Subject: [PATCH 194/468] Add a basic test of NPM package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a package script `test:package`, which performs some basic testing to confirm that a browser-based TypeScript app is able to import the NPM package and use Ably functionality. This hopefully gives us further confidence that we’ve correctly configured the package’s exports and typings. Next, we’ll build on top of this to add similar testing for the tree-shakable version of the library, once we’ve added typings for it in #1442. Some of the approach here is copied from that used for testing the CDN bundle in the Spaces SDK (see commit fa95f9f there). Resolves #1474. --- .github/workflows/test-package.yml | 20 + Gruntfile.js | 86 +- package.json | 1 + test/package/browser/template/README.md | 20 + .../browser/template/package-lock.json | 2451 +++++++++++++++++ test/package/browser/template/package.json | 24 + .../browser/template/playwright.config.js | 12 + .../template/server/resources/index.html | 21 + .../package/browser/template/server/server.ts | 12 + test/package/browser/template/src/index.ts | 20 + test/package/browser/template/src/sandbox.ts | 16 + .../browser/template/test/package.test.ts | 18 + test/package/browser/template/tsconfig.json | 7 + tsconfig.json | 3 +- 14 files changed, 2705 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/test-package.yml create mode 100644 test/package/browser/template/README.md create mode 100644 test/package/browser/template/package-lock.json create mode 100644 test/package/browser/template/package.json create mode 100644 test/package/browser/template/playwright.config.js create mode 100644 test/package/browser/template/server/resources/index.html create mode 100644 test/package/browser/template/server/server.ts create mode 100644 test/package/browser/template/src/index.ts create mode 100644 test/package/browser/template/src/sandbox.ts create mode 100644 test/package/browser/template/test/package.test.ts create mode 100644 test/package/browser/template/tsconfig.json diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml new file mode 100644 index 0000000000..98fd5b58a2 --- /dev/null +++ b/.github/workflows/test-package.yml @@ -0,0 +1,20 @@ +name: Test NPM package +on: + pull_request: + push: + branches: + - main + +jobs: + test-npm-package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Use Node.js 16.x + uses: actions/setup-node@v1 + with: + node-version: 16.x + - run: npm ci + - run: npm run test:package diff --git a/Gruntfile.js b/Gruntfile.js index 44e773a512..8702c4ba40 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,6 +6,7 @@ var webpackConfig = require('./webpack.config'); var esbuild = require('esbuild'); var umdWrapper = require('esbuild-plugin-umd-wrapper'); var banner = require('./src/fragments/license'); +var process = require('process'); module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-concat'); @@ -27,18 +28,27 @@ module.exports = function (grunt) { }; } - function execExternal(cmd) { - return function () { - var done = this.async(); - grunt.log.ok('Executing ' + cmd); + async function execExternalPromises(cmd) { + grunt.log.ok('Executing ' + cmd); + return new Promise(function (resolve, reject) { require('child_process').exec(cmd, function (err, stdout, stderr) { if (err) { grunt.fatal('Error executing "' + cmd + '":\nstderr:\n' + stderr + '\nstdout:\n' + stdout); + reject(err); } console.log(stdout); stderr && console.error(stderr); - done(); + resolve(); }); + }); + } + + function execExternal(cmd) { + return function () { + var done = this.async(); + execExternalPromises(cmd) + .then(() => done()) + .catch((error) => done(error)); }; } @@ -215,5 +225,71 @@ module.exports = function (grunt) { } ); + (function () { + const baseDir = path.join(__dirname, 'test', 'package', 'browser'); + const buildDir = path.join(baseDir, 'build'); + + grunt.registerTask( + 'test:package:browser:prepare-project', + 'Prepare an app to be used for testing the NPM package in a browser environment', + function () { + const done = this.async(); + + (async function () { + if (grunt.file.exists(buildDir)) { + grunt.file.delete(buildDir); + } + + // Create an app based on the template + grunt.file.copy(path.join(baseDir, 'template'), buildDir); + + // Use `npm pack` to generate a .tgz NPM package + await execExternalPromises('npm run build'); + await execExternalPromises('npm pack --pack-destination test/package/browser/build'); + const version = grunt.file.readJSON('package.json').version; + const packFileName = `ably-${version}.tgz`; + + // Configure app to consume the generated .tgz file + const pwd = process.cwd(); + process.chdir(buildDir); + await execExternalPromises(`npm install ${packFileName}`); + + // Install further dependencies required for testing the app + await execExternalPromises('npm run test:install-deps'); + process.chdir(pwd); + })() + .then(() => done(true)) + .catch((error) => done(error)); + } + ); + + grunt.registerTask('test:package:browser:test', 'Test the NPM package in a browser environment', function () { + const done = this.async(); + + (async function () { + grunt.task.requires('test:package:browser:prepare-project'); + + const pwd = process.cwd(); + process.chdir(buildDir); + + // Perform type checking on TypeScript code that imports ably-js + await execExternalPromises('npm run typecheck'); + + // Build bundle including ably-js + await execExternalPromises('npm run build'); + + // Test that the code which exercises ably-js behaves as expected + await execExternalPromises('npm run test'); + + process.chdir(pwd); + })() + .then(() => done(true)) + .catch((error) => done(error)); + }); + })(); + + grunt.registerTask('test:package:browser', ['test:package:browser:prepare-project', 'test:package:browser:test']); + grunt.registerTask('test:package', ['test:package:browser']); + grunt.registerTask('default', 'all'); }; diff --git a/package.json b/package.json index 8c95387aa4..828499c041 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "test:webserver": "grunt test:webserver", "test:playwright": "node test/support/runPlaywrightTests.js", "test:react": "vitest run", + "test:package": "grunt test:package", "concat": "grunt concat", "build": "grunt build:all && npm run build:react", "build:node": "grunt build:node", diff --git a/test/package/browser/template/README.md b/test/package/browser/template/README.md new file mode 100644 index 0000000000..2773d0a826 --- /dev/null +++ b/test/package/browser/template/README.md @@ -0,0 +1,20 @@ +# ably-js NPM package test (for browser) + +This directory is intended to be used for testing the following aspects of the ably-js NPM package when used in a browser-based app: + +- that its exports are correctly configured and provide access to ably-js’s functionality +- that its TypeScript typings are correctly configured and can be successfully used from a TypeScript-based app that imports the package + +The file `src/index.ts` imports the ably-js package and exports a function which briefly exercises its functionality. + +## Why is `ably` not in `package.json`? + +The `ably` dependency gets added when we run the repository’s `test:package` package script. That script copies the contents of this `template` directory to a new temporary directory, and then adds the `ably` dependency to the copy. We do this so that we can check this directory’s `package-lock.json` into Git, without needing to modify it whenever ably-js’s dependencies change. + +## Package scripts + +This directory exposes three package scripts that are to be used for testing: + +- `build`: Uses esbuild to create a bundle containing `src/index.ts` and ably-js. +- `test`: Using the bundle created by `build`, tests that the code that exercises ably-js’s functionality is working correctly in a browser. +- `typecheck`: Type-checks the code that imports ably-js. diff --git a/test/package/browser/template/package-lock.json b/test/package/browser/template/package-lock.json new file mode 100644 index 0000000000..590e78aaa9 --- /dev/null +++ b/test/package/browser/template/package-lock.json @@ -0,0 +1,2451 @@ +{ + "name": "template", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "template", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.39.0", + "@tsconfig/node16": "^16.1.1", + "@types/express": "^4.17.20", + "esbuild": "^0.19.5", + "express": "^4.18.2", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", + "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", + "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", + "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", + "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", + "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", + "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", + "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", + "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", + "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", + "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", + "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", + "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", + "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", + "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", + "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", + "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", + "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", + "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", + "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", + "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", + "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", + "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@playwright/test": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "dev": true, + "dependencies": { + "playwright": "1.39.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.1.tgz", + "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", + "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", + "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.8.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/esbuild": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", + "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.5", + "@esbuild/android-arm64": "0.19.5", + "@esbuild/android-x64": "0.19.5", + "@esbuild/darwin-arm64": "0.19.5", + "@esbuild/darwin-x64": "0.19.5", + "@esbuild/freebsd-arm64": "0.19.5", + "@esbuild/freebsd-x64": "0.19.5", + "@esbuild/linux-arm": "0.19.5", + "@esbuild/linux-arm64": "0.19.5", + "@esbuild/linux-ia32": "0.19.5", + "@esbuild/linux-loong64": "0.19.5", + "@esbuild/linux-mips64el": "0.19.5", + "@esbuild/linux-ppc64": "0.19.5", + "@esbuild/linux-riscv64": "0.19.5", + "@esbuild/linux-s390x": "0.19.5", + "@esbuild/linux-x64": "0.19.5", + "@esbuild/netbsd-x64": "0.19.5", + "@esbuild/openbsd-x64": "0.19.5", + "@esbuild/sunos-x64": "0.19.5", + "@esbuild/win32-arm64": "0.19.5", + "@esbuild/win32-ia32": "0.19.5", + "@esbuild/win32-x64": "0.19.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "dev": true, + "dependencies": { + "playwright-core": "1.39.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "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/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@esbuild/android-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", + "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", + "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", + "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", + "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", + "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", + "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", + "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", + "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", + "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", + "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", + "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", + "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", + "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", + "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", + "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", + "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", + "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", + "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", + "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", + "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", + "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", + "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "dev": true, + "optional": true + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@playwright/test": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "dev": true, + "requires": { + "playwright": "1.39.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.1.tgz", + "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", + "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", + "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/http-errors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "dev": true + }, + "@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "dev": true + }, + "@types/node": { + "version": "20.8.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "dev": true + }, + "@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true + }, + "acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "esbuild": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", + "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.19.5", + "@esbuild/android-arm64": "0.19.5", + "@esbuild/android-x64": "0.19.5", + "@esbuild/darwin-arm64": "0.19.5", + "@esbuild/darwin-x64": "0.19.5", + "@esbuild/freebsd-arm64": "0.19.5", + "@esbuild/freebsd-x64": "0.19.5", + "@esbuild/linux-arm": "0.19.5", + "@esbuild/linux-arm64": "0.19.5", + "@esbuild/linux-ia32": "0.19.5", + "@esbuild/linux-loong64": "0.19.5", + "@esbuild/linux-mips64el": "0.19.5", + "@esbuild/linux-ppc64": "0.19.5", + "@esbuild/linux-riscv64": "0.19.5", + "@esbuild/linux-s390x": "0.19.5", + "@esbuild/linux-x64": "0.19.5", + "@esbuild/netbsd-x64": "0.19.5", + "@esbuild/openbsd-x64": "0.19.5", + "@esbuild/sunos-x64": "0.19.5", + "@esbuild/win32-arm64": "0.19.5", + "@esbuild/win32-ia32": "0.19.5", + "@esbuild/win32-x64": "0.19.5" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "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.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.2" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.39.0" + } + }, + "playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + } + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/test/package/browser/template/package.json b/test/package/browser/template/package.json new file mode 100644 index 0000000000..0b1e74ff9c --- /dev/null +++ b/test/package/browser/template/package.json @@ -0,0 +1,24 @@ +{ + "name": "template", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "esbuild --bundle src/index.ts --outdir=dist", + "typecheck": "tsc -noEmit", + "test-support:server": "ts-node server/server.ts", + "test": "playwright test", + "test:install-deps": "playwright install chromium" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.39.0", + "@tsconfig/node16": "^16.1.1", + "@types/express": "^4.17.20", + "esbuild": "^0.19.5", + "express": "^4.18.2", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/test/package/browser/template/playwright.config.js b/test/package/browser/template/playwright.config.js new file mode 100644 index 0000000000..6cd19e6687 --- /dev/null +++ b/test/package/browser/template/playwright.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: 'test', + webServer: { + command: 'npm run test-support:server', + url: 'http://localhost:4567', + }, + use: { + baseURL: 'http://localhost:4567', + }, +}); diff --git a/test/package/browser/template/server/resources/index.html b/test/package/browser/template/server/resources/index.html new file mode 100644 index 0000000000..0e5fbaa1d1 --- /dev/null +++ b/test/package/browser/template/server/resources/index.html @@ -0,0 +1,21 @@ + + + + + Ably NPM package test + + + + + + diff --git a/test/package/browser/template/server/server.ts b/test/package/browser/template/server/server.ts new file mode 100644 index 0000000000..c371589aa3 --- /dev/null +++ b/test/package/browser/template/server/server.ts @@ -0,0 +1,12 @@ +import express from 'express'; +import path from 'node:path'; + +async function startWebServer(listenPort: number) { + const server = express(); + server.use(express.static(path.join(__dirname, '/resources'))); + server.use('/index.js', express.static(path.join(__dirname, '..', 'dist', 'index.js'))); + + server.listen(listenPort); +} + +startWebServer(4567); diff --git a/test/package/browser/template/src/index.ts b/test/package/browser/template/src/index.ts new file mode 100644 index 0000000000..e360efcc34 --- /dev/null +++ b/test/package/browser/template/src/index.ts @@ -0,0 +1,20 @@ +import { Realtime } from 'ably'; +import { createSandboxAblyAPIKey } from './sandbox'; + +globalThis.testAblyPackage = async function () { + const key = await createSandboxAblyAPIKey(); + + const realtime = new Realtime({ key, environment: 'sandbox' }); + + const channel = realtime.channels.get('channel'); + await channel.attach(); + + const receivedMessagePromise = new Promise((resolve) => { + channel.subscribe(() => { + resolve(); + }); + }); + + await channel.publish('message', { foo: 'bar' }); + await receivedMessagePromise; +}; diff --git a/test/package/browser/template/src/sandbox.ts b/test/package/browser/template/src/sandbox.ts new file mode 100644 index 0000000000..100ab22f00 --- /dev/null +++ b/test/package/browser/template/src/sandbox.ts @@ -0,0 +1,16 @@ +import testAppSetup from '../../../../common/ably-common/test-resources/test-app-setup.json'; + +export async function createSandboxAblyAPIKey() { + const response = await fetch('https://sandbox-rest.ably.io/apps', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(testAppSetup.post_apps), + }); + + if (!response.ok) { + throw new Error(`Response not OK (${response.status})`); + } + + const testApp = await response.json(); + return testApp.keys[0].keyStr; +} diff --git a/test/package/browser/template/test/package.test.ts b/test/package/browser/template/test/package.test.ts new file mode 100644 index 0000000000..8b4915b5a3 --- /dev/null +++ b/test/package/browser/template/test/package.test.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test.describe('NPM package', () => { + test('can be imported and provides access to Ably functionality', async ({ page }) => { + const pageResultPromise = new Promise((resolve, reject) => { + page.exposeFunction('onResult', (error: Error | null) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + + await page.goto('/'); + await pageResultPromise; + }); +}); diff --git a/test/package/browser/template/tsconfig.json b/test/package/browser/template/tsconfig.json new file mode 100644 index 0000000000..4928ea369e --- /dev/null +++ b/test/package/browser/template/tsconfig.json @@ -0,0 +1,7 @@ +{ + "include": ["src/**/*.ts"], + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true + } +} diff --git a/tsconfig.json b/tsconfig.json index 1146fcb184..c0a2b4227c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "baseUrl": "./src" }, "exclude": [ - "src/platform/react-hooks" + "src/platform/react-hooks", + "test/package" ] } From 01775fd704ffab24ba10d6303a1bb52c1f7ae46e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 9 Nov 2023 15:54:09 -0300 Subject: [PATCH 195/468] Improve tests around functionality provided by Rest module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit List all of the functionality that’s gated off by this module and check that it works when the module is provided, and doesn’t when it’s not. Also test that Realtime doesn’t need this module in order to publish and subscribe. --- test/browser/modules.test.js | 80 ++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index bf56cf4fda..a7350ce449 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -60,27 +60,83 @@ describe('browser/modules', function () { }); describe('Rest', () => { + const restScenarios = [ + { + description: 'use push admin functionality', + action: (client) => client.push.admin.publish({ clientId: 'foo' }, { data: { bar: 'baz' } }), + }, + { description: 'call `time()`', action: (client) => client.time() }, + { description: 'call `request(...)`', action: (client) => client.request('get', '/channels/channel', 2) }, + { + description: 'call `batchPublish(...)`', + action: (client) => client.batchPublish({ channels: ['channel'], messages: { data: { foo: 'bar' } } }), + }, + { + description: 'call `batchPresence(...)`', + action: (client) => client.batchPresence(['channel']), + }, + ]; + describe('BaseRest without explicit Rest', () => { - it('offers REST functionality', async () => { - const client = new BaseRest(ablyClientOptions(), { FetchRequest }); - const time = await client.time(); - expect(time).to.be.a('number'); - }); + for (const scenario of restScenarios) { + it(`allows you to ${scenario.description}`, async () => { + const client = new BaseRest(ablyClientOptions(), { FetchRequest }); + + let thrownError = null; + try { + await scenario.action(client); + } catch (error) { + thrownError = error; + } + + expect(thrownError).to.be.null; + }); + } }); describe('BaseRealtime with Rest', () => { - it('offers REST functionality', async () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest, Rest }); - const time = await client.time(); - expect(time).to.be.a('number'); - }); + for (const scenario of restScenarios) { + it(`allows you to ${scenario.description}`, async () => { + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest, Rest }); + + let thrownError = null; + try { + await scenario.action(client); + } catch (error) { + thrownError = error; + } + + expect(thrownError).to.be.null; + }); + } }); describe('BaseRealtime without Rest', () => { - it('throws an error when attempting to use REST functionality', async () => { + it('still allows publishing and subscribing', async () => { const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); - expect(() => client.time()).to.throw('Rest module not provided'); + + const channel = client.channels.get('channel'); + await channel.attach(); + + const recievedMessagePromise = new Promise((resolve) => { + channel.subscribe((message) => { + resolve(message); + }); + }); + + await channel.publish({ data: { foo: 'bar' } }); + + const receivedMessage = await recievedMessagePromise; + expect(receivedMessage.data).to.eql({ foo: 'bar' }); }); + + for (const scenario of restScenarios) { + it(`throws an error when attempting to ${scenario.description}`, () => { + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + + expect(() => scenario.action(client)).to.throw('Rest module not provided'); + }); + } }); }); From ce9a3cf619a3002db017073393164750e6a74148 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 9 Nov 2023 10:08:59 -0300 Subject: [PATCH 196/468] Remove callbacks code from revokeTokens Not having to deal with the overload signature will make an upcoming refactor easier. --- src/common/lib/client/auth.ts | 61 +++++++++++++---------------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index b4db9380c4..bb2e5cf0ec 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -1038,32 +1038,14 @@ class Auth { revokeTokens( specifiers: TokenRevocationTargetSpecifier[], options?: TokenRevocationOptions - ): Promise; - revokeTokens( - specifiers: TokenRevocationTargetSpecifier[], - optionsOrCallbackArg?: TokenRevocationOptions | StandardCallback, - callbackArg?: StandardCallback - ): void | Promise { + ): Promise { if (useTokenAuth(this.client.options)) { throw new ErrorInfo('Cannot revoke tokens when using token auth', 40162, 401); } const keyName = this.client.options.keyName!; - let resolvedOptions: TokenRevocationOptions; - - if (typeof optionsOrCallbackArg === 'function') { - callbackArg = optionsOrCallbackArg; - resolvedOptions = {}; - } else { - resolvedOptions = optionsOrCallbackArg ?? {}; - } - - if (callbackArg === undefined) { - return Utils.promisify(this, 'revokeTokens', [specifiers, resolvedOptions]); - } - - const callback = callbackArg; + let resolvedOptions = options ?? {}; const requestBodyDTO = { targets: specifiers.map((specifier) => `${specifier.type}:${specifier.value}`), @@ -1076,26 +1058,29 @@ class Auth { if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); - Resource.post( - this.client, - `/keys/${keyName}/revokeTokens`, - requestBody, - headers, - { newBatchResponse: 'true' }, - null, - (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } - const batchResult = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as TokenRevocationResult; + return new Promise((resolve, reject) => { + Resource.post( + this.client, + `/keys/${keyName}/revokeTokens`, + requestBody, + headers, + { newBatchResponse: 'true' }, + null, + (err, body, headers, unpacked) => { + if (err) { + reject(err); + return; + } - callback(null, batchResult); - } - ); + const batchResult = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as TokenRevocationResult; + + resolve(batchResult); + } + ); + }); } } From 38a0ca8ba96f1630b7872398ee739105a6894a9a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 9 Nov 2023 10:13:33 -0300 Subject: [PATCH 197/468] Gate token revocation behind the Rest module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I consider this to be REST functionality, and so to reduce bundle size it shouldn’t be bundled with a BaseRealtime client. I should have done this in 89c0761. --- src/common/lib/client/auth.ts | 46 ++---------------------- src/common/lib/client/baseclient.ts | 2 +- src/common/lib/client/rest.ts | 56 +++++++++++++++++++++++++++++ test/browser/modules.test.js | 23 ++++++++++-- 4 files changed, 79 insertions(+), 48 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index bb2e5cf0ec..c2a0ef0382 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -11,7 +11,6 @@ import ClientOptions from '../../types/ClientOptions'; import HttpMethods from '../../constants/HttpMethods'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; import Platform from '../../platform'; -import Resource from './resource'; import Defaults from '../util/defaults'; type BatchResult = API.Types.BatchResult; @@ -96,7 +95,7 @@ function basicAuthForced(options: ClientOptions) { } /* RSA4 */ -function useTokenAuth(options: ClientOptions) { +export function useTokenAuth(options: ClientOptions) { return ( options.useTokenAuth || (!basicAuthForced(options) && (options.authCallback || options.authUrl || options.token || options.tokenDetails)) @@ -1039,48 +1038,7 @@ class Auth { specifiers: TokenRevocationTargetSpecifier[], options?: TokenRevocationOptions ): Promise { - if (useTokenAuth(this.client.options)) { - throw new ErrorInfo('Cannot revoke tokens when using token auth', 40162, 401); - } - - const keyName = this.client.options.keyName!; - - let resolvedOptions = options ?? {}; - - const requestBodyDTO = { - targets: specifiers.map((specifier) => `${specifier.type}:${specifier.value}`), - ...resolvedOptions, - }; - - const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Defaults.defaultPostHeaders(this.client.options, { format }); - - if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); - - const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); - - return new Promise((resolve, reject) => { - Resource.post( - this.client, - `/keys/${keyName}/revokeTokens`, - requestBody, - headers, - { newBatchResponse: 'true' }, - null, - (err, body, headers, unpacked) => { - if (err) { - reject(err); - return; - } - - const batchResult = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as TokenRevocationResult; - - resolve(batchResult); - } - ); - }); + return this.client.rest.revokeTokens(specifiers, options); } } diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 482075fa23..70eb307bff 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -103,7 +103,7 @@ class BaseClient { this.__FilteredSubscriptions = modules.MessageInteractions ?? null; } - private get rest(): Rest { + get rest(): Rest { if (!this._rest) { throwMissingModuleError('Rest'); } diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index a3592f9945..4a5f60d66f 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -15,8 +15,10 @@ import Resource from './resource'; import Platform from '../../platform'; import BaseClient from './baseclient'; +import { useTokenAuth } from './auth'; type BatchResult = API.Types.BatchResult; + type BatchPublishSpec = API.Types.BatchPublishSpec; type BatchPublishSuccessResult = API.Types.BatchPublishSuccessResult; type BatchPublishFailureResult = API.Types.BatchPublishFailureResult; @@ -25,6 +27,12 @@ type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult; type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; type BatchPresenceResult = BatchResult; +type TokenRevocationTargetSpecifier = API.Types.TokenRevocationTargetSpecifier; +type TokenRevocationOptions = API.Types.TokenRevocationOptions; +type TokenRevocationSuccessResult = API.Types.TokenRevocationSuccessResult; +type TokenRevocationFailureResult = API.Types.TokenRevocationFailureResult; +type TokenRevocationResult = BatchResult; + const noop = function () {}; export class Rest { private readonly client: BaseClient; @@ -261,6 +269,54 @@ export class Rest { ); } + revokeTokens( + specifiers: TokenRevocationTargetSpecifier[], + options?: TokenRevocationOptions + ): Promise { + if (useTokenAuth(this.client.options)) { + throw new ErrorInfo('Cannot revoke tokens when using token auth', 40162, 401); + } + + const keyName = this.client.options.keyName!; + + let resolvedOptions = options ?? {}; + + const requestBodyDTO = { + targets: specifiers.map((specifier) => `${specifier.type}:${specifier.value}`), + ...resolvedOptions, + }; + + const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Defaults.defaultPostHeaders(this.client.options, { format }); + + if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); + + const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); + + return new Promise((resolve, reject) => { + Resource.post( + this.client, + `/keys/${keyName}/revokeTokens`, + requestBody, + headers, + { newBatchResponse: 'true' }, + null, + (err, body, headers, unpacked) => { + if (err) { + reject(err); + return; + } + + const batchResult = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as TokenRevocationResult; + + resolve(batchResult); + } + ); + }); + } + setLog(logOptions: LoggerOptions): void { Logger.setLog(logOptions.level, logOptions.handler); } diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index a7350ce449..c726510fe9 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -31,12 +31,14 @@ describe('browser/modules', function () { let loadTestData; let testMessageEquality; let randomString; + let getTestApp; before((done) => { ablyClientOptions = window.ablyHelpers.ablyClientOptions; testResourcesPath = window.ablyHelpers.testResourcesPath; testMessageEquality = window.ablyHelpers.testMessageEquality; randomString = window.ablyHelpers.randomString; + getTestApp = window.ablyHelpers.getTestApp; loadTestData = async (dataPath) => { return new Promise((resolve, reject) => { @@ -75,12 +77,20 @@ describe('browser/modules', function () { description: 'call `batchPresence(...)`', action: (client) => client.batchPresence(['channel']), }, + { + description: 'call `auth.revokeTokens(...)`', + getAdditionalClientOptions: () => { + const testApp = getTestApp(); + return { key: testApp.keys[4].keyStr /* this key has revocableTokens enabled */ }; + }, + action: (client) => client.auth.revokeTokens([{ type: 'clientId', value: 'foo' }]), + }, ]; describe('BaseRest without explicit Rest', () => { for (const scenario of restScenarios) { it(`allows you to ${scenario.description}`, async () => { - const client = new BaseRest(ablyClientOptions(), { FetchRequest }); + const client = new BaseRest(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { FetchRequest }); let thrownError = null; try { @@ -97,7 +107,11 @@ describe('browser/modules', function () { describe('BaseRealtime with Rest', () => { for (const scenario of restScenarios) { it(`allows you to ${scenario.description}`, async () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest, Rest }); + const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { + WebSocketTransport, + FetchRequest, + Rest, + }); let thrownError = null; try { @@ -132,7 +146,10 @@ describe('browser/modules', function () { for (const scenario of restScenarios) { it(`throws an error when attempting to ${scenario.description}`, () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { + WebSocketTransport, + FetchRequest, + }); expect(() => scenario.action(client)).to.throw('Rest module not provided'); }); From 9636131b19f191c97be99c7928497f2a44a21e39 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 9 Nov 2023 10:48:41 -0300 Subject: [PATCH 198/468] Add `Rest` prefix to Channel, Presence class names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Groundwork for removing the Realtime versions’ inheritance. --- src/common/lib/client/realtimechannel.ts | 8 ++++---- src/common/lib/client/realtimepresence.ts | 6 +++--- src/common/lib/client/rest.ts | 6 +++--- .../lib/client/{channel.ts => restchannel.ts} | 16 ++++++++-------- .../lib/client/{presence.ts => restpresence.ts} | 14 +++++++------- 5 files changed, 25 insertions(+), 25 deletions(-) rename src/common/lib/client/{channel.ts => restchannel.ts} (94%) rename src/common/lib/client/{presence.ts => restpresence.ts} (89%) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 898377dded..3200088bb6 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -1,7 +1,7 @@ import ProtocolMessage from '../types/protocolmessage'; import EventEmitter from '../util/eventemitter'; import * as Utils from '../util/utils'; -import Channel from './channel'; +import RestChannel from './restchannel'; import Logger from '../util/logger'; import RealtimePresence from './realtimepresence'; import Message, { CipherOptions } from '../types/message'; @@ -48,7 +48,7 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) { } } -class RealtimeChannel extends Channel { +class RealtimeChannel extends RestChannel { realtime: BaseRealtime; private _realtimePresence: RealtimePresence | null; get presence(): RealtimePresence { @@ -156,7 +156,7 @@ class RealtimeChannel extends Channel { _callback(err); return; } - Channel.prototype.setOptions.call(this, options); + RestChannel.prototype.setOptions.call(this, options); if (this._decodingContext) this._decodingContext.channelOptions = this.channelOptions; if (this._shouldReattachToSetOptions(options)) { /* This does not just do _attach(true, null, callback) because that would put us @@ -900,7 +900,7 @@ class RealtimeChannel extends Channel { params.from_serial = this.properties.attachSerial; } - Channel.prototype._history.call(this, params, callback); + RestChannel.prototype._history.call(this, params, callback); } as any; whenState = ((state: string, listener: ErrCallback) => { diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 5d5e84c346..7fc5a63c47 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -1,5 +1,5 @@ import * as Utils from '../util/utils'; -import Presence from './presence'; +import RestPresence from './restpresence'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; @@ -78,7 +78,7 @@ function newerThan(item: PresenceMessage, existing: PresenceMessage) { } } -class RealtimePresence extends Presence { +class RealtimePresence extends RestPresence { channel: RealtimeChannel; pendingPresence: { presence: PresenceMessage; callback: ErrCallback }[]; syncComplete: boolean; @@ -319,7 +319,7 @@ class RealtimePresence extends Presence { } } - Presence.prototype._history.call(this, params, callback); + RestPresence.prototype._history.call(this, params, callback); } setPresence(presenceSet: PresenceMessage[], isSync: boolean, syncChannelSerial?: string): void { diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 4a5f60d66f..f1fb22badd 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -3,7 +3,7 @@ import Logger, { LoggerOptions } from '../util/logger'; import Defaults from '../util/defaults'; import Push from './push'; import PaginatedResource, { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; -import Channel from './channel'; +import RestChannel from './restchannel'; import ErrorInfo from '../types/errorinfo'; import Stats from '../types/stats'; import HttpMethods from '../../constants/HttpMethods'; @@ -324,7 +324,7 @@ export class Rest { class Channels { client: BaseClient; - all: Record; + all: Record; constructor(client: BaseClient) { this.client = client; @@ -335,7 +335,7 @@ class Channels { name = String(name); let channel = this.all[name]; if (!channel) { - this.all[name] = channel = new Channel(this.client, name, channelOptions); + this.all[name] = channel = new RestChannel(this.client, name, channelOptions); } else if (channelOptions) { channel.setOptions(channelOptions); } diff --git a/src/common/lib/client/channel.ts b/src/common/lib/client/restchannel.ts similarity index 94% rename from src/common/lib/client/channel.ts rename to src/common/lib/client/restchannel.ts index 2922de7adb..b88fa807b9 100644 --- a/src/common/lib/client/channel.ts +++ b/src/common/lib/client/restchannel.ts @@ -1,7 +1,7 @@ import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; -import Presence from './presence'; +import RestPresence from './restpresence'; import Message, { CipherOptions } from '../types/message'; import ErrorInfo from '../types/errorinfo'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; @@ -46,23 +46,23 @@ function normaliseChannelOptions(Crypto: IUntypedCryptoStatic | null, options?: return channelOptions; } -class Channel extends EventEmitter { +class RestChannel extends EventEmitter { client: BaseClient; name: string; basePath: string; - private _presence: Presence; - get presence(): Presence { + private _presence: RestPresence; + get presence(): RestPresence { return this._presence; } channelOptions: ChannelOptions; constructor(client: BaseClient, name: string, channelOptions?: ChannelOptions) { super(); - Logger.logAction(Logger.LOG_MINOR, 'Channel()', 'started; name = ' + name); + Logger.logAction(Logger.LOG_MINOR, 'RestChannel()', 'started; name = ' + name); this.client = client; this.name = name; this.basePath = '/channels/' + encodeURIComponent(name); - this._presence = new Presence(this); + this._presence = new RestPresence(this); this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, channelOptions); } @@ -74,7 +74,7 @@ class Channel extends EventEmitter { params: RestHistoryParams | null, callback: PaginatedResultCallback ): Promise> | void { - Logger.logAction(Logger.LOG_MICRO, 'Channel.history()', 'channel = ' + this.name); + Logger.logAction(Logger.LOG_MICRO, 'RestChannel.history()', 'channel = ' + this.name); /* params and callback are optional; see if params contains the callback */ if (callback === undefined) { if (typeof params == 'function') { @@ -200,4 +200,4 @@ class Channel extends EventEmitter { } } -export default Channel; +export default RestChannel; diff --git a/src/common/lib/client/presence.ts b/src/common/lib/client/restpresence.ts similarity index 89% rename from src/common/lib/client/presence.ts rename to src/common/lib/client/restpresence.ts index 8acbed68bf..7a4f73960e 100644 --- a/src/common/lib/client/presence.ts +++ b/src/common/lib/client/restpresence.ts @@ -5,22 +5,22 @@ import PaginatedResource, { PaginatedResult } from './paginatedresource'; import PresenceMessage from '../types/presencemessage'; import { CipherOptions } from '../types/message'; import { PaginatedResultCallback } from '../../types/utils'; -import Channel from './channel'; +import RestChannel from './restchannel'; import RealtimeChannel from './realtimechannel'; import Defaults from '../util/defaults'; -class Presence extends EventEmitter { - channel: RealtimeChannel | Channel; +class RestPresence extends EventEmitter { + channel: RealtimeChannel | RestChannel; basePath: string; - constructor(channel: RealtimeChannel | Channel) { + constructor(channel: RealtimeChannel | RestChannel) { super(); this.channel = channel; this.basePath = channel.basePath + '/presence'; } get(params: any, callback: PaginatedResultCallback): void | Promise { - Logger.logAction(Logger.LOG_MICRO, 'Presence.get()', 'channel = ' + this.channel.name); + Logger.logAction(Logger.LOG_MICRO, 'RestPresence.get()', 'channel = ' + this.channel.name); /* params and callback are optional; see if params contains the callback */ if (callback === undefined) { if (typeof params == 'function') { @@ -52,7 +52,7 @@ class Presence extends EventEmitter { params: any, callback: PaginatedResultCallback ): void | Promise> { - Logger.logAction(Logger.LOG_MICRO, 'Presence.history()', 'channel = ' + this.channel.name); + Logger.logAction(Logger.LOG_MICRO, 'RestPresence.history()', 'channel = ' + this.channel.name); return this._history(params, callback); } @@ -93,4 +93,4 @@ class Presence extends EventEmitter { } } -export default Presence; +export default RestPresence; From b75eb5bc0908d84f5a24f4157f123839b172f27d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 9 Nov 2023 13:33:54 -0300 Subject: [PATCH 199/468] Make Realtime Channel and Presence not inherit from REST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is code that’s exclusively needed by the REST version of these classes (e.g. presence `get()`, channel `publish()`), and which, in order to reduce bundle size, we don’t want to pull in when importing the BaseRealtime class. In order to do this, we need to sever the inheritance relation between the Realtime and REST version of these classes. (It’s also worth noting that, similarly to what I mentioned in 69c35f1, the IDL doesn’t mention any inheritance relation.) The REST versions of these classes also contain functionality (channel history and presence history, channel status) that should only be available to a BaseRealtime client if the user has explicitly requested REST functionality (by importing the Rest module). So, we gate this functionality behind the Rest module (I should really have done this in 89c0761) and in doing so further reduce the bundle size of a REST-less BaseRealtime. I’ve moved the channel and presence REST code that’s also conditionally needed by Realtime into classes called RestChannelMixin and RestPresenceMixin. There’s no massively compelling reason for these classes to exist, I just thought it might be good not to dump everything directly inside the Rest module. Resolves #1489. --- src/common/lib/client/realtimechannel.ts | 65 +++++++++------- src/common/lib/client/realtimepresence.ts | 21 +++-- src/common/lib/client/rest.ts | 5 ++ src/common/lib/client/restchannel.ts | 91 +++++----------------- src/common/lib/client/restchannelmixin.ts | 67 ++++++++++++++++ src/common/lib/client/restpresence.ts | 71 +++++------------ src/common/lib/client/restpresencemixin.ts | 52 +++++++++++++ src/common/lib/util/defaults.ts | 18 +++++ test/browser/modules.test.js | 27 ++++++- 9 files changed, 257 insertions(+), 160 deletions(-) create mode 100644 src/common/lib/client/restchannelmixin.ts create mode 100644 src/common/lib/client/restpresencemixin.ts diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 3200088bb6..e3d054ca15 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -1,7 +1,6 @@ import ProtocolMessage from '../types/protocolmessage'; import EventEmitter from '../util/eventemitter'; import * as Utils from '../util/utils'; -import RestChannel from './restchannel'; import Logger from '../util/logger'; import RealtimePresence from './realtimepresence'; import Message, { CipherOptions } from '../types/message'; @@ -14,6 +13,8 @@ import ConnectionManager from '../transport/connectionmanager'; import ConnectionStateChange from './connectionstatechange'; import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseRealtime from './baserealtime'; +import { ChannelOptions } from '../../types/channel'; +import { normaliseChannelOptions } from '../util/defaults'; interface RealtimeHistoryParams { start?: number; @@ -48,14 +49,16 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) { } } -class RealtimeChannel extends RestChannel { - realtime: BaseRealtime; - private _realtimePresence: RealtimePresence | null; +class RealtimeChannel extends EventEmitter { + name: string; + channelOptions: ChannelOptions; + client: BaseRealtime; + private _presence: RealtimePresence | null; get presence(): RealtimePresence { - if (!this._realtimePresence) { + if (!this._presence) { Utils.throwMissingModuleError('RealtimePresence'); } - return this._realtimePresence; + return this._presence; } connectionManager: ConnectionManager; state: API.Types.ChannelState; @@ -86,12 +89,14 @@ class RealtimeChannel extends RestChannel { retryTimer?: number | NodeJS.Timeout | null; retryCount: number = 0; - constructor(realtime: BaseRealtime, name: string, options?: API.Types.ChannelOptions) { - super(realtime, name, options); + constructor(client: BaseRealtime, name: string, options?: API.Types.ChannelOptions) { + super(); Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel()', 'started; name = ' + name); - this.realtime = realtime; - this._realtimePresence = realtime._RealtimePresence ? new realtime._RealtimePresence(this) : null; - this.connectionManager = realtime.connection.connectionManager; + this.name = name; + this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, options); + this.client = client; + this._presence = client._RealtimePresence ? new client._RealtimePresence(this) : null; + this.connectionManager = client.connection.connectionManager; this.state = 'initialized'; this.subscriptions = new EventEmitter(); this.syncChannelSerial = undefined; @@ -106,7 +111,7 @@ class RealtimeChannel extends RestChannel { this._attachResume = false; this._decodingContext = { channelOptions: this.channelOptions, - plugins: realtime.options.plugins || {}, + plugins: client.options.plugins || {}, baseEncodedPreviousPayload: undefined, }; this._lastPayload = { @@ -156,7 +161,7 @@ class RealtimeChannel extends RestChannel { _callback(err); return; } - RestChannel.prototype.setOptions.call(this, options); + this.channelOptions = normaliseChannelOptions(this.client._Crypto ?? null, options); if (this._decodingContext) this._decodingContext.channelOptions = this.channelOptions; if (this._shouldReattachToSetOptions(options)) { /* This does not just do _attach(true, null, callback) because that would put us @@ -236,7 +241,7 @@ class RealtimeChannel extends RestChannel { } else { messages = [Message.fromValues({ name: args[0], data: args[1] })]; } - const maxMessageSize = this.realtime.options.maxMessageSize; + const maxMessageSize = this.client.options.maxMessageSize; Message.encodeArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { if (err) { callback(err); @@ -258,12 +263,11 @@ class RealtimeChannel extends RestChannel { ); return; } - this.__publish(messages, callback); + this._publish(messages, callback); }); } - // Double underscore used to prevent type conflict with underlying Channel._publish method - __publish(messages: Array, callback: ErrCallback) { + _publish(messages: Array, callback: ErrCallback) { Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.publish()', 'message count = ' + messages.length); const state = this.state; switch (state) { @@ -483,7 +487,7 @@ class RealtimeChannel extends RestChannel { } sendMessage(msg: ProtocolMessage, callback?: ErrCallback): void { - this.connectionManager.send(msg, this.realtime.options.queueMessages, callback); + this.connectionManager.send(msg, this.client.options.queueMessages, callback); } sendPresence(presence: PresenceMessage | PresenceMessage[], callback?: ErrCallback): void { @@ -523,8 +527,8 @@ class RealtimeChannel extends RestChannel { if (this.state === 'attached') { if (!resumed) { /* On a loss of continuity, the presence set needs to be re-synced */ - if (this._realtimePresence) { - this._realtimePresence.onAttached(hasPresence); + if (this._presence) { + this._presence.onAttached(hasPresence); } } const change = new ChannelStateChange(this.state, this.state, resumed, hasBacklog, message.error); @@ -583,8 +587,8 @@ class RealtimeChannel extends RestChannel { Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString()); } } - if (this._realtimePresence) { - this._realtimePresence.setPresence(presence, isSync, syncChannelSerial as any); + if (this._presence) { + this._presence.setPresence(presence, isSync, syncChannelSerial as any); } break; } @@ -721,8 +725,8 @@ class RealtimeChannel extends RestChannel { if (state === this.state) { return; } - if (this._realtimePresence) { - this._realtimePresence.actOnChannelState(state, hasPresence, reason); + if (this._presence) { + this._presence.actOnChannelState(state, hasPresence, reason); } if (state === 'suspended' && this.connectionManager.state.sendEvents) { this.startRetryTimer(); @@ -829,7 +833,7 @@ class RealtimeChannel extends RestChannel { Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel.startStateTimerIfNotRunning', 'timer expired'); this.stateTimer = null; this.timeoutPendingState(); - }, this.realtime.options.timeouts.realtimeRequestTimeout); + }, this.client.options.timeouts.realtimeRequestTimeout); } } @@ -845,7 +849,7 @@ class RealtimeChannel extends RestChannel { if (this.retryTimer) return; this.retryCount++; - const retryDelay = Utils.getRetryTime(this.realtime.options.timeouts.channelRetryTimeout, this.retryCount); + const retryDelay = Utils.getRetryTime(this.client.options.timeouts.channelRetryTimeout, this.retryCount); this.retryTimer = setTimeout(() => { /* If connection is not connected, just leave in suspended, a reattach @@ -881,6 +885,9 @@ class RealtimeChannel extends RestChannel { } } + // We fetch this first so that any module-not-provided error takes priority over other errors + const restMixin = this.client.rest.channelMixin; + if (params && params.untilAttach) { if (this.state !== 'attached') { callback(new ErrorInfo('option untilAttach requires the channel to be attached', 40000, 400)); @@ -900,7 +907,7 @@ class RealtimeChannel extends RestChannel { params.from_serial = this.properties.attachSerial; } - RestChannel.prototype._history.call(this, params, callback); + return restMixin.history(this, params, callback); } as any; whenState = ((state: string, listener: ErrCallback) => { @@ -934,6 +941,10 @@ class RealtimeChannel extends RestChannel { this.properties.channelSerial = channelSerial; } } + + status(callback?: StandardCallback): void | Promise { + return this.client.rest.channelMixin.status(this, callback); + } } export default RealtimeChannel; diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 7fc5a63c47..0027e22e20 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -1,5 +1,4 @@ import * as Utils from '../util/utils'; -import RestPresence from './restpresence'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; @@ -27,11 +26,11 @@ interface RealtimeHistoryParams { } function getClientId(realtimePresence: RealtimePresence) { - return realtimePresence.channel.realtime.auth.clientId; + return realtimePresence.channel.client.auth.clientId; } function isAnonymousOrWildcard(realtimePresence: RealtimePresence) { - const realtime = realtimePresence.channel.realtime; + const realtime = realtimePresence.channel.client; /* If not currently connected, we can't assume that we're an anonymous * client, as realtime may inform us of our clientId in the CONNECTED * message. So assume we're not anonymous and leave it to realtime to @@ -78,7 +77,7 @@ function newerThan(item: PresenceMessage, existing: PresenceMessage) { } } -class RealtimePresence extends RestPresence { +class RealtimePresence extends EventEmitter { channel: RealtimeChannel; pendingPresence: { presence: PresenceMessage; callback: ErrCallback }[]; syncComplete: boolean; @@ -88,7 +87,7 @@ class RealtimePresence extends RestPresence { name?: string; constructor(channel: RealtimeChannel) { - super(channel); + super(); this.channel = channel; this.syncComplete = false; this.members = new PresenceMap(this, (item) => item.clientId + ':' + item.connectionId); @@ -244,8 +243,11 @@ class RealtimePresence extends RestPresence { } } - // Return type is any to avoid conflict with base Presence class - get(this: RealtimePresence, params: RealtimePresenceParams, callback: StandardCallback): any { + get( + this: RealtimePresence, + params: RealtimePresenceParams, + callback: StandardCallback + ): void | Promise { const args = Array.prototype.slice.call(arguments); if (args.length == 1 && typeof args[0] == 'function') args.unshift(null); @@ -304,6 +306,9 @@ class RealtimePresence extends RestPresence { } } + // We fetch this first so that any module-not-provided error takes priority over other errors + const restMixin = this.channel.client.rest.presenceMixin; + if (params && params.untilAttach) { if (this.channel.state === 'attached') { delete params.untilAttach; @@ -319,7 +324,7 @@ class RealtimePresence extends RestPresence { } } - RestPresence.prototype._history.call(this, params, callback); + return restMixin.history(this, params, callback); } setPresence(presenceSet: PresenceMessage[], isSync: boolean, syncChannelSerial?: string): void { diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index f1fb22badd..c9a26f0061 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -16,6 +16,8 @@ import Resource from './resource'; import Platform from '../../platform'; import BaseClient from './baseclient'; import { useTokenAuth } from './auth'; +import { RestChannelMixin } from './restchannelmixin'; +import { RestPresenceMixin } from './restpresencemixin'; type BatchResult = API.Types.BatchResult; @@ -39,6 +41,9 @@ export class Rest { readonly channels: Channels; readonly push: Push; + readonly channelMixin = RestChannelMixin; + readonly presenceMixin = RestPresenceMixin; + constructor(client: BaseClient) { this.client = client; this.channels = new Channels(this.client); diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index b88fa807b9..6197c65191 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -1,26 +1,16 @@ import * as Utils from '../util/utils'; -import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; import RestPresence from './restpresence'; import Message, { CipherOptions } from '../types/message'; import ErrorInfo from '../types/errorinfo'; -import PaginatedResource, { PaginatedResult } from './paginatedresource'; +import { PaginatedResult } from './paginatedresource'; import Resource, { ResourceCallback } from './resource'; import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import BaseClient from './baseclient'; +import BaseRest from './baseclient'; import * as API from '../../../../ably'; -import Defaults from '../util/defaults'; -import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; - -interface RestHistoryParams { - start?: number; - end?: number; - direction?: string; - limit?: number; -} - -function noop() {} +import Defaults, { normaliseChannelOptions } from '../util/defaults'; +import { RestHistoryParams } from './restchannelmixin'; const MSG_ID_ENTROPY_BYTES = 9; @@ -30,39 +20,17 @@ function allEmptyIds(messages: Array) { }); } -function normaliseChannelOptions(Crypto: IUntypedCryptoStatic | null, options?: ChannelOptions) { - const channelOptions = options || {}; - if (channelOptions.cipher) { - if (!Crypto) Utils.throwMissingModuleError('Crypto'); - const cipher = Crypto.getCipher(channelOptions.cipher); - channelOptions.cipher = cipher.cipherParams; - channelOptions.channelCipher = cipher.cipher; - } else if ('cipher' in channelOptions) { - /* Don't deactivate an existing cipher unless options - * has a 'cipher' key that's falsey */ - channelOptions.cipher = undefined; - channelOptions.channelCipher = null; - } - return channelOptions; -} - -class RestChannel extends EventEmitter { - client: BaseClient; +class RestChannel { + client: BaseRest; name: string; - basePath: string; - private _presence: RestPresence; - get presence(): RestPresence { - return this._presence; - } + presence: RestPresence; channelOptions: ChannelOptions; - constructor(client: BaseClient, name: string, channelOptions?: ChannelOptions) { - super(); + constructor(client: BaseRest, name: string, channelOptions?: ChannelOptions) { Logger.logAction(Logger.LOG_MINOR, 'RestChannel()', 'started; name = ' + name); - this.client = client; this.name = name; - this.basePath = '/channels/' + encodeURIComponent(name); - this._presence = new RestPresence(this); + this.client = client; + this.presence = new RestPresence(this); this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, channelOptions); } @@ -85,25 +53,7 @@ class RestChannel extends EventEmitter { } } - this._history(params, callback); - } - - _history(params: RestHistoryParams | null, callback: PaginatedResultCallback): void { - const client = this.client, - format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.client.http.supportsLinkHeaders ? undefined : format, - headers = Defaults.defaultGetHeaders(client.options, { format }); - - Utils.mixin(headers, client.options.headers); - - const options = this.channelOptions; - new PaginatedResource(client, this.basePath + '/messages', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return await Message.fromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); - }).get(params as Record, callback); + this.client.rest.channelMixin.history(this, params, callback); } publish(): void | Promise { @@ -185,18 +135,19 @@ class RestChannel extends EventEmitter { } _publish(requestBody: unknown, headers: Record, params: any, callback: ResourceCallback): void { - Resource.post(this.client, this.basePath + '/messages', requestBody, headers, params, null, callback); + Resource.post( + this.client, + this.client.rest.channelMixin.basePath(this) + '/messages', + requestBody, + headers, + params, + null, + callback + ); } status(callback?: StandardCallback): void | Promise { - if (typeof callback !== 'function') { - return Utils.promisify(this, 'status', []); - } - - const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; - const headers = Defaults.defaultPostHeaders(this.client.options, { format }); - - Resource.get(this.client, this.basePath, headers, {}, format, callback || noop); + return this.client.rest.channelMixin.status(this, callback); } } diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts new file mode 100644 index 0000000000..9986dc4e74 --- /dev/null +++ b/src/common/lib/client/restchannelmixin.ts @@ -0,0 +1,67 @@ +import * as API from '../../../../ably'; +import RestChannel from './restchannel'; +import RealtimeChannel from './realtimechannel'; +import * as Utils from '../util/utils'; +import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; +import Message from '../types/message'; +import Defaults from '../util/defaults'; +import PaginatedResource from './paginatedresource'; +import Resource from './resource'; + +export interface RestHistoryParams { + start?: number; + end?: number; + direction?: string; + limit?: number; +} + +const noop = function () {}; + +export class RestChannelMixin { + static basePath(channel: RestChannel | RealtimeChannel) { + return '/channels/' + encodeURIComponent(channel.name); + } + + static history( + channel: RestChannel | RealtimeChannel, + params: RestHistoryParams | null, + callback: PaginatedResultCallback + ): void { + const client = channel.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = channel.client.http.supportsLinkHeaders ? undefined : format, + headers = Defaults.defaultGetHeaders(client.options, { format }); + + Utils.mixin(headers, client.options.headers); + + const options = channel.channelOptions; + new PaginatedResource(client, this.basePath(channel) + '/messages', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return await Message.fromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); + }).get(params as Record, callback); + } + + static status( + channel: RestChannel | RealtimeChannel, + callback?: StandardCallback + ): void | Promise { + if (typeof callback !== 'function') { + return Utils.promisify(this, 'status', [channel]); + } + + const format = channel.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; + const headers = Defaults.defaultPostHeaders(channel.client.options, { format }); + + Resource.get( + channel.client, + this.basePath(channel), + headers, + {}, + format, + callback || noop + ); + } +} diff --git a/src/common/lib/client/restpresence.ts b/src/common/lib/client/restpresence.ts index 7a4f73960e..7aec14dcb2 100644 --- a/src/common/lib/client/restpresence.ts +++ b/src/common/lib/client/restpresence.ts @@ -1,22 +1,17 @@ import * as Utils from '../util/utils'; -import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; import PresenceMessage from '../types/presencemessage'; import { CipherOptions } from '../types/message'; import { PaginatedResultCallback } from '../../types/utils'; import RestChannel from './restchannel'; -import RealtimeChannel from './realtimechannel'; import Defaults from '../util/defaults'; -class RestPresence extends EventEmitter { - channel: RealtimeChannel | RestChannel; - basePath: string; +class RestPresence { + channel: RestChannel; - constructor(channel: RealtimeChannel | RestChannel) { - super(); + constructor(channel: RestChannel) { this.channel = channel; - this.basePath = channel.basePath + '/presence'; } get(params: any, callback: PaginatedResultCallback): void | Promise { @@ -38,14 +33,20 @@ class RestPresence extends EventEmitter { Utils.mixin(headers, client.options.headers); const options = this.channel.channelOptions; - new PaginatedResource(client, this.basePath, headers, envelope, async function (body, headers, unpacked) { - return await PresenceMessage.fromResponseBody( - body as Record[], - options as CipherOptions, - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, callback); + new PaginatedResource( + client, + this.channel.client.rest.presenceMixin.basePath(this), + headers, + envelope, + async function (body, headers, unpacked) { + return await PresenceMessage.fromResponseBody( + body as Record[], + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); + } + ).get(params, callback); } history( @@ -53,43 +54,7 @@ class RestPresence extends EventEmitter { callback: PaginatedResultCallback ): void | Promise> { Logger.logAction(Logger.LOG_MICRO, 'RestPresence.history()', 'channel = ' + this.channel.name); - return this._history(params, callback); - } - - _history( - params: any, - callback: PaginatedResultCallback - ): void | Promise> { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, '_history', [params]); - } - } - - const client = this.channel.client, - format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.channel.client.http.supportsLinkHeaders ? undefined : format, - headers = Defaults.defaultGetHeaders(client.options, { format }); - - Utils.mixin(headers, client.options.headers); - - const options = this.channel.channelOptions; - new PaginatedResource(client, this.basePath + '/history', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return await PresenceMessage.fromResponseBody( - body as Record[], - options as CipherOptions, - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, callback); + return this.channel.client.rest.presenceMixin.history(this, params, callback); } } diff --git a/src/common/lib/client/restpresencemixin.ts b/src/common/lib/client/restpresencemixin.ts new file mode 100644 index 0000000000..7e600fdee3 --- /dev/null +++ b/src/common/lib/client/restpresencemixin.ts @@ -0,0 +1,52 @@ +import RestPresence from './restpresence'; +import RealtimePresence from './realtimepresence'; +import * as Utils from '../util/utils'; +import { PaginatedResultCallback } from '../../types/utils'; +import Defaults from '../util/defaults'; +import PaginatedResource, { PaginatedResult } from './paginatedresource'; +import PresenceMessage from '../types/presencemessage'; +import { CipherOptions } from '../types/message'; +import { RestChannelMixin } from './restchannelmixin'; + +export class RestPresenceMixin { + static basePath(presence: RestPresence | RealtimePresence) { + return RestChannelMixin.basePath(presence.channel) + '/presence'; + } + + static history( + presence: RestPresence | RealtimePresence, + params: any, + callback: PaginatedResultCallback + ): void | Promise> { + /* params and callback are optional; see if params contains the callback */ + if (callback === undefined) { + if (typeof params == 'function') { + callback = params; + params = null; + } else { + return Utils.promisify(this, 'history', [presence, params]); + } + } + + const client = presence.channel.client, + format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = presence.channel.client.http.supportsLinkHeaders ? undefined : format, + headers = Defaults.defaultGetHeaders(client.options, { format }); + + Utils.mixin(headers, client.options.headers); + + const options = presence.channel.channelOptions; + new PaginatedResource(client, this.basePath(presence) + '/history', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return await PresenceMessage.fromResponseBody( + body as Record[], + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params, callback); + } +} diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 9939c975c7..489d651307 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -6,6 +6,8 @@ import { version } from '../../../../package.json'; import ClientOptions, { InternalClientOptions, NormalisedClientOptions } from 'common/types/ClientOptions'; import IDefaults from '../../types/IDefaults'; import { MsgPack } from 'common/types/msgpack'; +import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; +import { ChannelOptions } from 'common/types/channel'; let agent = 'ably-js/' + version; @@ -265,6 +267,22 @@ export function normaliseOptions(options: InternalClientOptions, MsgPack: MsgPac }; } +export function normaliseChannelOptions(Crypto: IUntypedCryptoStatic | null, options?: ChannelOptions) { + const channelOptions = options || {}; + if (channelOptions.cipher) { + if (!Crypto) Utils.throwMissingModuleError('Crypto'); + const cipher = Crypto.getCipher(channelOptions.cipher); + channelOptions.cipher = cipher.cipherParams; + channelOptions.channelCipher = cipher.cipher; + } else if ('cipher' in channelOptions) { + /* Don't deactivate an existing cipher unless options + * has a 'cipher' key that's falsey */ + channelOptions.cipher = undefined; + channelOptions.channelCipher = null; + } + return channelOptions; +} + const contentTypes = { json: 'application/json', xml: 'application/xml', diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index c726510fe9..f2ba4b77cb 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -85,6 +85,19 @@ describe('browser/modules', function () { }, action: (client) => client.auth.revokeTokens([{ type: 'clientId', value: 'foo' }]), }, + { + description: 'call channel’s `history()`', + action: (client) => client.channels.get('channel').history(), + }, + { + description: 'call channel’s `presence.history()`', + additionalRealtimeModules: { RealtimePresence }, + action: (client) => client.channels.get('channel').presence.history(), + }, + { + description: 'call channel’s `status()`', + action: (client) => client.channels.get('channel').status(), + }, ]; describe('BaseRest without explicit Rest', () => { @@ -111,6 +124,7 @@ describe('browser/modules', function () { WebSocketTransport, FetchRequest, Rest, + ...scenario.additionalRealtimeModules, }); let thrownError = null; @@ -145,13 +159,22 @@ describe('browser/modules', function () { }); for (const scenario of restScenarios) { - it(`throws an error when attempting to ${scenario.description}`, () => { + it(`throws an error when attempting to ${scenario.description}`, async () => { const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { WebSocketTransport, FetchRequest, + ...scenario.additionalRealtimeModules, }); - expect(() => scenario.action(client)).to.throw('Rest module not provided'); + let thrownError = null; + try { + await scenario.action(client); + } catch (error) { + thrownError = error; + } + + expect(thrownError).not.to.be.null; + expect(thrownError.message).to.equal('Rest module not provided'); }); } }); From fe27acbb9dbb11158e5bb3eb0c8fc37b369ded2f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 10 Nov 2023 12:02:30 -0300 Subject: [PATCH 200/468] Remove static methods from Message and PresenceMessage typings These declarations are unreachable via the typings. The user needs to access these methods via the MessageStatic and PresenceMessageStatic interfaces. --- ably.d.ts | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index c938527491..908ccd6153 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2393,22 +2393,6 @@ declare namespace Types { * @internal */ constructor(); - /** - * A static factory method to create a `Message` object from a deserialized Message-like object encoded using Ably's wire protocol. - * - * @param JsonObject - A `Message`-like deserialized object. - * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A promise which will be fulfilled with a `Message` object. - */ - static fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; - /** - * A static factory method to create an array of `Message` objects from an array of deserialized Message-like object encoded using Ably's wire protocol. - * - * @param JsonArray - An array of `Message`-like deserialized objects. - * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A promise which will be fulfilled with an array of {@link Message} objects. - */ - static fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; /** * The client ID of the publisher of this message. */ @@ -2475,20 +2459,6 @@ declare namespace Types { * @internal */ constructor(); - /** - * Decodes and decrypts a deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. - * - * @param JsonObject - The deserialized `PresenceMessage`-like object to decode and decrypt. - * @param channelOptions - A {@link ChannelOptions} object containing the cipher. - */ - static fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; - /** - * Decodes and decrypts an array of deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. - * - * @param JsonArray - An array of deserialized `PresenceMessage`-like objects to decode and decrypt. - * @param channelOptions - A {@link ChannelOptions} object containing the cipher. - */ - static fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; /** * The type of {@link PresenceAction} the `PresenceMessage` is for. */ From c7fd90ca76b0687ab40e749d3bc746253787b877 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 10 Nov 2023 12:05:12 -0300 Subject: [PATCH 201/468] Remove Message and PresenceMessage constructors from typings These declarations are unreachable via the typings. --- ably.d.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 908ccd6153..126779e264 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2387,12 +2387,6 @@ declare namespace Types { * Contains an individual message that is sent to, or received from, Ably. */ class Message { - /** - * Constructor for internal use. - * - * @internal - */ - constructor(); /** * The client ID of the publisher of this message. */ @@ -2453,12 +2447,6 @@ declare namespace Types { * Contains an individual presence update sent to, or received from, Ably. */ class PresenceMessage { - /** - * Constructor for internal use. - * - * @internal - */ - constructor(); /** * The type of {@link PresenceAction} the `PresenceMessage` is for. */ From de5ddfa7d97951697e13ed19d3396aae1aadb742 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 10 Nov 2023 14:57:44 -0300 Subject: [PATCH 202/468] Switch to using package.json exports for entrypoints This allows users to import the module-based version by writing ``` import { BaseRealtime } from 'ably/modules' ``` instead of having to import 'ably/build/modules'. I followed the guidance in [1] and [2]. [1] https://nodejs.org/api/packages.html#exports [2] https://webpack.js.org/guides/package-exports/ --- package.json | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 828499c041..101cbcac45 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,17 @@ "url": "https://github.com/ably/ably-js/issues", "email": "support@ably.com" }, - "main": "./build/ably-node.js", - "typings": "./ably.d.ts", - "react-native": { - "./build/ably-node.js": "./build/ably-reactnative.js" - }, - "browser": { - "./build/ably-node.js": "./build/ably.js" + "exports": { + ".": { + "node": "./build/ably-node.js", + "react-native": "./build/ably-reactnative.js", + "default": "./build/ably.js" + }, + "./modules": { + "default": "./build/modules/index.js" + } }, + "typings": "./ably.d.ts", "files": [ "build/**", "ably.d.ts", From be44206acae21b95bcc2eeab04912f607374834d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 10 Nov 2023 16:55:16 -0300 Subject: [PATCH 203/468] Test that we can import the Types namespace --- test/package/browser/template/src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/package/browser/template/src/index.ts b/test/package/browser/template/src/index.ts index e360efcc34..865bc21e60 100644 --- a/test/package/browser/template/src/index.ts +++ b/test/package/browser/template/src/index.ts @@ -1,13 +1,18 @@ -import { Realtime } from 'ably'; +import { Realtime, Types } from 'ably'; import { createSandboxAblyAPIKey } from './sandbox'; +// This function exists to check that we can import the Types namespace and refer to its types. +async function attachChannel(channel: Types.RealtimeChannel) { + await channel.attach(); +} + globalThis.testAblyPackage = async function () { const key = await createSandboxAblyAPIKey(); const realtime = new Realtime({ key, environment: 'sandbox' }); const channel = realtime.channels.get('channel'); - await channel.attach(); + await attachChannel(channel); const receivedMessagePromise = new Promise((resolve) => { channel.subscribe(() => { From b8397159a372939136ae1f41c071c010578880ca Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 1 Nov 2023 11:09:16 -0300 Subject: [PATCH 204/468] Add typings for tree-shakable version of library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I wasn’t sure of the best way to approach the typings of the modules (`Rest` etc). They should be opaque to the user (they shouldn’t be interacting with them directly and we want be free to change their interface in the future). There is an open issue to add support for opaque types to TypeScript [1], and people have suggested various sorts of ways of approximating them, which revolve around the use of `unique symbol` declarations. However, I don’t fully understand these solutions and so thought it best not to include them in our public API. So, for now, let’s just use `unknown`, the same way as we do for `CipherParams.key`. Resolves #1442. [1] https://github.com/Microsoft/Typescript/issues/202 --- .eslintrc.js | 2 +- .github/workflows/check.yml | 2 +- ably.d.ts | 107 +++++++++--------- modules.d.ts | 45 ++++++++ package.json | 6 +- test/package/browser/template/README.md | 11 +- test/package/browser/template/package.json | 2 +- .../server/resources/index-default.html | 11 ++ .../server/resources/index-modules.html | 11 ++ .../template/server/resources/index.html | 21 ---- .../template/server/resources/runTest.js | 9 ++ .../package/browser/template/server/server.ts | 5 +- .../src/{index.ts => index-default.ts} | 0 .../browser/template/src/index-modules.ts | 34 ++++++ .../browser/template/test/package.test.ts | 31 +++-- 15 files changed, 202 insertions(+), 95 deletions(-) create mode 100644 modules.d.ts create mode 100644 test/package/browser/template/server/resources/index-default.html create mode 100644 test/package/browser/template/server/resources/index-modules.html delete mode 100644 test/package/browser/template/server/resources/index.html create mode 100644 test/package/browser/template/server/resources/runTest.js rename test/package/browser/template/src/{index.ts => index-default.ts} (100%) create mode 100644 test/package/browser/template/src/index-modules.ts diff --git a/.eslintrc.js b/.eslintrc.js index 7f2ba0089d..8704799ce8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,7 +44,7 @@ module.exports = { }, }, { - files: 'ably.d.ts', + files: ['ably.d.ts', 'modules.d.ts'], extends: [ 'plugin:jsdoc/recommended', ], diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 675343c945..f131a149da 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -21,5 +21,5 @@ jobs: - run: npm ci - run: npm run lint - run: npm run format:check - - run: npx tsc --noEmit ably.d.ts build/ably-webworker.min.d.ts + - run: npx tsc --noEmit ably.d.ts modules.d.ts build/ably-webworker.min.d.ts - run: npm audit --production diff --git a/ably.d.ts b/ably.d.ts index 126779e264..fa58110328 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1700,32 +1700,7 @@ declare namespace Types { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ - class Rest { - /** - * Construct a client object using an Ably {@link Types.ClientOptions} object. - * - * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. - */ - constructor(options: Types.ClientOptions); - /** - * Constructs a client object using an Ably API key or token string. - * - * @param keyOrToken - The Ably API key or token string used to validate the client. - */ - constructor(keyOrToken: string); - /** - * The cryptographic functions available in the library. - */ - static Crypto: Types.Crypto; - /** - * Static utilities related to messages. - */ - static Message: Types.MessageStatic; - /** - * Static utilities related to presence messages. - */ - static PresenceMessage: Types.PresenceMessageStatic; - + abstract class Rest { /** * An {@link Types.Auth} object. */ @@ -1799,31 +1774,7 @@ declare namespace Types { /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ - class Realtime { - /** - * Construct a client object using an Ably {@link Types.ClientOptions} object. - * - * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. - */ - constructor(options: Types.ClientOptions); - /** - * Constructs a client object using an Ably API key or token string. - * - * @param keyOrToken - The Ably API key or token string used to validate the client. - */ - constructor(keyOrToken: string); - /** - * The cryptographic functions available in the library. - */ - static Crypto: Types.Crypto; - /** - * Static utilities related to messages. - */ - static Message: Types.MessageStatic; - /** - * Static utilities related to presence messages. - */ - static PresenceMessage: Types.PresenceMessageStatic; + abstract class Realtime { /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. */ @@ -2849,12 +2800,62 @@ declare namespace Types { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare class Rest extends Types.Rest {} +export declare class Rest extends Types.Rest { + /** + * Construct a client object using an Ably {@link Types.ClientOptions} object. + * + * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + */ + constructor(options: Types.ClientOptions); + /** + * Constructs a client object using an Ably API key or token string. + * + * @param keyOrToken - The Ably API key or token string used to validate the client. + */ + constructor(keyOrToken: string); + /** + * The cryptographic functions available in the library. + */ + static Crypto: Types.Crypto; + /** + * Static utilities related to messages. + */ + static Message: Types.MessageStatic; + /** + * Static utilities related to presence messages. + */ + static PresenceMessage: Types.PresenceMessageStatic; +} /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ -export declare class Realtime extends Types.Realtime {} +export declare class Realtime extends Types.Realtime { + /** + * Construct a client object using an Ably {@link Types.ClientOptions} object. + * + * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + */ + constructor(options: Types.ClientOptions); + /** + * Constructs a client object using an Ably API key or token string. + * + * @param keyOrToken - The Ably API key or token string used to validate the client. + */ + constructor(keyOrToken: string); + /** + * The cryptographic functions available in the library. + */ + static Crypto: Types.Crypto; + /** + * Static utilities related to messages. + */ + static Message: Types.MessageStatic; + /** + * Static utilities related to presence messages. + */ + static PresenceMessage: Types.PresenceMessageStatic; +} /** * A generic Ably error object that contains an Ably-specific status code, and a generic status code. Errors returned from the Ably server are compatible with the `ErrorInfo` structure and should result in errors that inherit from `ErrorInfo`. diff --git a/modules.d.ts b/modules.d.ts new file mode 100644 index 0000000000..45e48bd18f --- /dev/null +++ b/modules.d.ts @@ -0,0 +1,45 @@ +import { Types } from './ably'; + +export declare const generateRandomKey: Types.Crypto['generateRandomKey']; +export declare const getDefaultCryptoParams: Types.Crypto['getDefaultParams']; +export declare const decodeMessage: Types.MessageStatic['fromEncoded']; +export declare const decodeEncryptedMessage: Types.MessageStatic['fromEncoded']; +export declare const decodeMessages: Types.MessageStatic['fromEncodedArray']; +export declare const decodeEncryptedMessages: Types.MessageStatic['fromEncodedArray']; +export declare const decodePresenceMessage: Types.PresenceMessageStatic['fromEncoded']; +export declare const decodePresenceMessages: Types.PresenceMessageStatic['fromEncodedArray']; +export declare const constructPresenceMessage: Types.PresenceMessageStatic['fromValues']; + +export declare const Rest: unknown; +export declare const Crypto: unknown; +export declare const MsgPack: unknown; +export declare const RealtimePresence: unknown; +export declare const WebSocketTransport: unknown; +export declare const XHRPolling: unknown; +export declare const XHRStreaming: unknown; +export declare const XHRRequest: unknown; +export declare const FetchRequest: unknown; +export declare const MessageInteractions: unknown; + +export interface ModulesMap { + Rest?: typeof Rest; + Crypto?: typeof Crypto; + MsgPack?: typeof MsgPack; + RealtimePresence?: typeof RealtimePresence; + WebSocketTransport?: typeof WebSocketTransport; + XHRPolling?: typeof XHRPolling; + XHRStreaming?: typeof XHRStreaming; + XHRRequest?: typeof XHRRequest; + FetchRequest?: typeof FetchRequest; + MessageInteractions?: typeof MessageInteractions; +} + +export declare class BaseRest extends Types.Rest { + constructor(options: Types.ClientOptions, modules: ModulesMap); +} + +export declare class BaseRealtime extends Types.Realtime { + constructor(options: Types.ClientOptions, modules: ModulesMap); +} + +export { Types }; diff --git a/package.json b/package.json index 101cbcac45..c4054bf155 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "default": "./build/ably.js" }, "./modules": { + "types": "./modules.d.ts", "default": "./build/modules/index.js" } }, @@ -21,6 +22,7 @@ "files": [ "build/**", "ably.d.ts", + "modules.d.ts", "resources/**", "src/**", "react/**" @@ -126,8 +128,8 @@ "lint": "eslint .", "lint:fix": "eslint --fix .", "prepare": "npm run build", - "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/*.js docs/chrome-mv3.md", - "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts webpack.config.js Gruntfile.js scripts/*.js", + "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js docs/chrome-mv3.md", + "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js", "sourcemap": "source-map-explorer build/ably.min.js", "sourcemap:noencryption": "source-map-explorer build/ably.noencryption.min.js", "modulereport": "node scripts/moduleReport.js", diff --git a/test/package/browser/template/README.md b/test/package/browser/template/README.md index 2773d0a826..2a576916ac 100644 --- a/test/package/browser/template/README.md +++ b/test/package/browser/template/README.md @@ -5,7 +5,10 @@ This directory is intended to be used for testing the following aspects of the a - that its exports are correctly configured and provide access to ably-js’s functionality - that its TypeScript typings are correctly configured and can be successfully used from a TypeScript-based app that imports the package -The file `src/index.ts` imports the ably-js package and exports a function which briefly exercises its functionality. +It contains two files, each of which import ably-js in different manners, and which export a function which briefly exercises its functionality: + +- `src/index-default.ts` imports the default ably-js package (`import { Realtime } from 'ably'`). +- `src/index-modules.ts` imports the tree-shakable ably-js package (`import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'`). ## Why is `ably` not in `package.json`? @@ -15,6 +18,8 @@ The `ably` dependency gets added when we run the repository’s `test:package` p This directory exposes three package scripts that are to be used for testing: -- `build`: Uses esbuild to create a bundle containing `src/index.ts` and ably-js. -- `test`: Using the bundle created by `build`, tests that the code that exercises ably-js’s functionality is working correctly in a browser. +- `build`: Uses esbuild to create: + 1. a bundle containing `src/index-default.ts` and ably-js; + 2. a bundle containing `src/index-modules.ts` and ably-js. +- `test`: Using the bundles created by `build`, tests that the code that exercises ably-js’s functionality is working correctly in a browser. - `typecheck`: Type-checks the code that imports ably-js. diff --git a/test/package/browser/template/package.json b/test/package/browser/template/package.json index 0b1e74ff9c..1c91a386eb 100644 --- a/test/package/browser/template/package.json +++ b/test/package/browser/template/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "build": "esbuild --bundle src/index.ts --outdir=dist", + "build": "esbuild --bundle src/index-default.ts --outdir=dist && esbuild --bundle src/index-modules.ts --outdir=dist", "typecheck": "tsc -noEmit", "test-support:server": "ts-node server/server.ts", "test": "playwright test", diff --git a/test/package/browser/template/server/resources/index-default.html b/test/package/browser/template/server/resources/index-default.html new file mode 100644 index 0000000000..f71012bf99 --- /dev/null +++ b/test/package/browser/template/server/resources/index-default.html @@ -0,0 +1,11 @@ + + + + + Ably NPM package test (default export) + + + + + + diff --git a/test/package/browser/template/server/resources/index-modules.html b/test/package/browser/template/server/resources/index-modules.html new file mode 100644 index 0000000000..0135008065 --- /dev/null +++ b/test/package/browser/template/server/resources/index-modules.html @@ -0,0 +1,11 @@ + + + + + Ably NPM package test (tree-shakable export) + + + + + + diff --git a/test/package/browser/template/server/resources/index.html b/test/package/browser/template/server/resources/index.html deleted file mode 100644 index 0e5fbaa1d1..0000000000 --- a/test/package/browser/template/server/resources/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Ably NPM package test - - - - - - diff --git a/test/package/browser/template/server/resources/runTest.js b/test/package/browser/template/server/resources/runTest.js new file mode 100644 index 0000000000..ccdc2d18e7 --- /dev/null +++ b/test/package/browser/template/server/resources/runTest.js @@ -0,0 +1,9 @@ +(async () => { + try { + await testAblyPackage(); + onResult(null); + } catch (error) { + console.log('Caught error', error); + onResult(error); + } +})(); diff --git a/test/package/browser/template/server/server.ts b/test/package/browser/template/server/server.ts index c371589aa3..6eefe26aba 100644 --- a/test/package/browser/template/server/server.ts +++ b/test/package/browser/template/server/server.ts @@ -3,8 +3,11 @@ import path from 'node:path'; async function startWebServer(listenPort: number) { const server = express(); + server.get('/', (req, res) => res.send('OK')); server.use(express.static(path.join(__dirname, '/resources'))); - server.use('/index.js', express.static(path.join(__dirname, '..', 'dist', 'index.js'))); + for (const filename of ['index-default.js', 'index-modules.js']) { + server.use(`/${filename}`, express.static(path.join(__dirname, '..', 'dist', filename))); + } server.listen(listenPort); } diff --git a/test/package/browser/template/src/index.ts b/test/package/browser/template/src/index-default.ts similarity index 100% rename from test/package/browser/template/src/index.ts rename to test/package/browser/template/src/index-default.ts diff --git a/test/package/browser/template/src/index-modules.ts b/test/package/browser/template/src/index-modules.ts new file mode 100644 index 0000000000..07a7751e3d --- /dev/null +++ b/test/package/browser/template/src/index-modules.ts @@ -0,0 +1,34 @@ +import { BaseRealtime, Types, WebSocketTransport, FetchRequest, generateRandomKey } from 'ably/modules'; +import { createSandboxAblyAPIKey } from './sandbox'; + +// This function exists to check that we can import the Types namespace and refer to its types. +async function attachChannel(channel: Types.RealtimeChannel) { + await channel.attach(); +} + +// This function exists to check that one of the free-standing functions (arbitrarily chosen) can be imported and does something vaguely sensible. +async function checkStandaloneFunction() { + const generatedKey = await generateRandomKey(); + if (!(generatedKey instanceof ArrayBuffer)) { + throw new Error('Expected to get an ArrayBuffer from generateRandomKey'); + } +} + +globalThis.testAblyPackage = async function () { + const key = await createSandboxAblyAPIKey(); + + const realtime = new BaseRealtime({ key, environment: 'sandbox' }, { WebSocketTransport, FetchRequest }); + + const channel = realtime.channels.get('channel'); + await attachChannel(channel); + + const receivedMessagePromise = new Promise((resolve) => { + channel.subscribe(() => { + resolve(); + }); + }); + + await channel.publish('message', { foo: 'bar' }); + await receivedMessagePromise; + await checkStandaloneFunction(); +}; diff --git a/test/package/browser/template/test/package.test.ts b/test/package/browser/template/test/package.test.ts index 8b4915b5a3..bb8e35fe43 100644 --- a/test/package/browser/template/test/package.test.ts +++ b/test/package/browser/template/test/package.test.ts @@ -1,18 +1,25 @@ import { test, expect } from '@playwright/test'; test.describe('NPM package', () => { - test('can be imported and provides access to Ably functionality', async ({ page }) => { - const pageResultPromise = new Promise((resolve, reject) => { - page.exposeFunction('onResult', (error: Error | null) => { - if (error) { - reject(error); - } else { - resolve(); - } + for (const scenario of [ + { name: 'default export', path: '/index-default.html' }, + { name: 'modular export', path: '/index-modules.html' }, + ]) { + test.describe(scenario.name, () => { + test('can be imported and provides access to Ably functionality', async ({ page }) => { + const pageResultPromise = new Promise((resolve, reject) => { + page.exposeFunction('onResult', (error: Error | null) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + + await page.goto(scenario.path); + await pageResultPromise; }); }); - - await page.goto('/'); - await pageResultPromise; - }); + } }); From dc47992bc6697adb80d3e638ce20495975813f31 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 13 Nov 2023 10:38:02 -0300 Subject: [PATCH 205/468] Remove the noencryption variant of the library This is superseded by not passing the Crypto module in the tree-shakable variant of the library. Resolves #1499. --- .github/workflows/bundle-report.yml | 1 - Gruntfile.js | 12 ------ package.json | 1 - scripts/cdn_deploy.js | 2 +- src/platform/web-noencryption/index.ts | 48 ----------------------- test/common/globals/named_dependencies.js | 1 - test/support/browser_file_list.js | 2 - test/support/browser_setup.js | 3 -- 8 files changed, 1 insertion(+), 69 deletions(-) delete mode 100644 src/platform/web-noencryption/index.ts diff --git a/.github/workflows/bundle-report.yml b/.github/workflows/bundle-report.yml index 6c91f72b25..269019c3c7 100644 --- a/.github/workflows/bundle-report.yml +++ b/.github/workflows/bundle-report.yml @@ -26,7 +26,6 @@ jobs: run: | mkdir bundle-reports npm run sourcemap -- --html bundle-reports/index.html - npm run sourcemap:noencryption -- --html bundle-reports/noencryption.html - uses: aws-actions/configure-aws-credentials@v1 with: aws-region: eu-west-2 diff --git a/Gruntfile.js b/Gruntfile.js index 8702c4ba40..1c67b7c050 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -139,18 +139,6 @@ module.exports = function (grunt) { outfile: 'build/ably.min.js', minify: true, }), - esbuild.build({ - ...baseConfig, - entryPoints: ['src/platform/web-noencryption/index.ts'], - outfile: 'build/ably.noencryption.js', - }), - - esbuild.build({ - ...baseConfig, - entryPoints: ['src/platform/web-noencryption/index.ts'], - outfile: 'build/ably.noencryption.min.js', - minify: true, - }), ]).then(() => { console.log('esbuild succeeded'); done(true); diff --git a/package.json b/package.json index 69eab5a2c0..4593b3ff9d 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,6 @@ "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js docs/chrome-mv3.md", "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js", "sourcemap": "source-map-explorer build/ably.min.js", - "sourcemap:noencryption": "source-map-explorer build/ably.noencryption.min.js", "modulereport": "node scripts/moduleReport.js", "docs": "typedoc --entryPoints ably.d.ts --out docs/generated --readme docs/landing-page.md" } diff --git a/scripts/cdn_deploy.js b/scripts/cdn_deploy.js index a8f5f944b2..dd02db325c 100755 --- a/scripts/cdn_deploy.js +++ b/scripts/cdn_deploy.js @@ -21,7 +21,7 @@ async function run() { // Comma separated directories (relative to `path`) to exclude from upload excludeDirs: 'node_modules,.git', // Regex to match files against for upload - fileRegex: '^ably(\\.noencryption)?(\\.min)?\\.js$', + fileRegex: '^ably?(\\.min)?\\.js$', ...argv, }; diff --git a/src/platform/web-noencryption/index.ts b/src/platform/web-noencryption/index.ts deleted file mode 100644 index 64dcf02b5e..0000000000 --- a/src/platform/web-noencryption/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Common -import { DefaultRest } from '../../common/lib/client/defaultrest'; -import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; -import Platform from '../../common/platform'; -import ErrorInfo from '../../common/lib/types/errorinfo'; - -// Platform Specific -import BufferUtils from '../web/lib/util/bufferutils'; -// @ts-ignore -import Http from '../web/lib/http/http'; -import Config from '../web/config'; -// @ts-ignore -import Transports from '../web/lib/transport'; -import Logger from '../../common/lib/util/logger'; -import { getDefaults } from '../../common/lib/util/defaults'; -import WebStorage from '../web/lib/util/webstorage'; -import PlatformDefaults from '../web/lib/util/defaults'; -import msgpack from '../web/lib/util/msgpack'; -import { defaultBundledRequestImplementations } from '../web/lib/http/request'; - -Platform.Crypto = null; -Platform.BufferUtils = BufferUtils; -Platform.Http = Http; -Platform.Config = Config; -Platform.Transports = Transports; -Platform.WebStorage = WebStorage; - -for (const clientClass of [DefaultRest, DefaultRealtime]) { - clientClass._MsgPack = msgpack; -} - -Http.bundledRequestImplementations = defaultBundledRequestImplementations; - -Logger.initLogHandlers(); - -Platform.Defaults = getDefaults(PlatformDefaults); - -if (Platform.Config.agent) { - // @ts-ignore - Platform.Defaults.agent += ' ' + Platform.Config.agent; -} - -export default { - ErrorInfo, - Rest: DefaultRest, - Realtime: DefaultRealtime, - msgpack, -}; diff --git a/test/common/globals/named_dependencies.js b/test/common/globals/named_dependencies.js index 4fc3a7407e..0558b575e7 100644 --- a/test/common/globals/named_dependencies.js +++ b/test/common/globals/named_dependencies.js @@ -3,7 +3,6 @@ define(function () { return (module.exports = { // Ably modules ably: { browser: 'build/ably', node: 'build/ably-node' }, - 'ably.noencryption': { browser: 'build/ably.noencryption' }, 'vcdiff-decoder': { browser: 'node_modules/@ably/vcdiff-decoder/dist/vcdiff-decoder', node: 'node_modules/@ably/vcdiff-decoder', diff --git a/test/support/browser_file_list.js b/test/support/browser_file_list.js index 4d0fa8522d..5a4ceedc16 100644 --- a/test/support/browser_file_list.js +++ b/test/support/browser_file_list.js @@ -6,8 +6,6 @@ window.__testFiles__.files = { 'build/ably-webworker.min.js': true, 'build/ably.js': true, 'build/ably.min.js': true, - 'build/ably.noencryption.js': true, - 'build/ably.noencryption.min.js': true, 'browser/lib/util/base64.js': true, 'node_modules/async/lib/async.js': true, 'node_modules/@ably/vcdiff-decoder/dist/vcdiff-decoder.js': true, diff --git a/test/support/browser_setup.js b/test/support/browser_setup.js index 6ab5e74ab7..af7fc6b693 100644 --- a/test/support/browser_setup.js +++ b/test/support/browser_setup.js @@ -55,9 +55,6 @@ require([(baseUrl + '/test/common/globals/named_dependencies.js').replace('//', ably: { exports: 'Ably', }, - 'ably.noencryption': { - exports: 'Ably', - }, 'browser-base64': { exports: 'Base64', }, From b26af82df517d765bcc4fa0527cafe682576694e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 13 Nov 2023 11:29:02 -0300 Subject: [PATCH 206/468] Move TypeDoc-related stuff into its own directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I think the docs/ directory is something that’s meant to contain documentation for direct consumption by users or developers, and not fragments that need to be glued together with something else to be useful. --- .eslintrc.js | 2 +- .github/workflows/docs.yml | 2 +- .gitignore | 2 +- package.json | 2 +- {docs => typedoc}/landing-page.md | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename {docs => typedoc}/landing-page.md (100%) diff --git a/.eslintrc.js b/.eslintrc.js index 8704799ce8..2094269b8f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -55,7 +55,7 @@ module.exports = { "test", "tools", "scripts", - "docs", + "typedoc/generated", "react", "Gruntfile.js", ], diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 96023da288..8b5492280a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -37,6 +37,6 @@ jobs: - name: Upload Documentation uses: ably/sdk-upload-action@v1 with: - sourcePath: docs/generated + sourcePath: typedoc/generated githubToken: ${{ secrets.GITHUB_TOKEN }} artifactName: typedoc diff --git a/.gitignore b/.gitignore index 0389a38a71..832e8c2b90 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ npm-debug.log .tool-versions build/ react/ -docs/generated/ +typedoc/generated/ diff --git a/package.json b/package.json index 4593b3ff9d..858cf24c51 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,6 @@ "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js", "sourcemap": "source-map-explorer build/ably.min.js", "modulereport": "node scripts/moduleReport.js", - "docs": "typedoc --entryPoints ably.d.ts --out docs/generated --readme docs/landing-page.md" + "docs": "typedoc --entryPoints ably.d.ts --out typedoc/generated --readme typedoc/landing-page.md" } } diff --git a/docs/landing-page.md b/typedoc/landing-page.md similarity index 100% rename from docs/landing-page.md rename to typedoc/landing-page.md From 48ede7f9aebdbb13463d50981dd1001f3ea3c9ff Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 13 Nov 2023 15:52:22 -0300 Subject: [PATCH 207/468] Add documentation for the modular version of the library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I imagine there’s plenty of room for improvement here, but it’s a start. Hopefully a tech writer will be able to have a look at it before we release, too. For handling TypeDoc documentation for the two different variants of the library, I’ve resurrected the approach that we used when we had callback-based and Promise-based variants of the library (removed in 2a2ed49). Resolves #1443. --- README.md | 31 ++++ ably.d.ts | 2 + modules.d.ts | 214 ++++++++++++++++++++++ package.json | 2 +- typedoc/landing-page.md | 7 - typedoc/landing-pages/choose-library.html | 31 ++++ typedoc/landing-pages/default.md | 5 + typedoc/landing-pages/modules.md | 21 +++ 8 files changed, 305 insertions(+), 8 deletions(-) delete mode 100644 typedoc/landing-page.md create mode 100644 typedoc/landing-pages/choose-library.html create mode 100644 typedoc/landing-pages/default.md create mode 100644 typedoc/landing-pages/modules.md diff --git a/README.md b/README.md index f8d4607101..55f1ab07e5 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,37 @@ WebPack will search your `node_modules` folder by default, so if you include `ab If that doesn't work for some reason (e.g. you are using a custom webpack target), you can reference the `ably.js` static file directly: `require('ably/build/ably.js');` (or `import * as Ably from 'ably/build/ably.js'` for typescript / ES6 modules). +#### Modular (tree-shakable) variant + +Aimed at those who are concerned about their app’s bundle size, the modular variant of the library allows you to create a client which has only the functionality that you choose. Unused functionality can then be tree-shaken by your module bundler. + +The modular variant of the library provides: + +- a `BaseRealtime` class; +- various modules that add functionality to a `BaseRealtime` instance, such as `Rest`, `RealtimePresence`, etc. + +To use this variant of the library, import the `BaseRealtime` class from `ably/modules`, along with the modules that you wish to use. Then, pass these modules to the `BaseRealtime` constructor as shown in the example below: + +```javascript +import { BaseRealtime, WebSocketTransport, FetchRequest, RealtimePresence } from 'ably/modules'; + +const options = { key: 'YOUR_ABLY_API_KEY' /* Replace with a real key from the Ably dashboard */ }; +const client = new BaseRealtime(options, { + WebSocketTransport, + FetchRequest, + RealtimePresence +}); +``` + +You must provide: + +- at least one HTTP request implementation; that is, one of `FetchRequest` or `XHRRequest`; +- at least one realtime transport implementation; that is, one of `WebSocketTransport`, `XHRStreaming`, or `XHRPolling`. + +`BaseRealtime` offers the same API as the `Realtime` class described in the rest of this `README`. This means that you can develop an application using the default variant of the SDK and switch to the modular version when you wish to optimize your bundle size. + +For more information, see the [generated documentation](https://sdk.ably.com/builds/ably/ably-js/main/typedoc/modules/index.html) (this link points to the documentation for the `main` branch). + ### TypeScript The TypeScript typings are included in the package and so all you have to do is: diff --git a/ably.d.ts b/ably.d.ts index fa58110328..ffaf96a8ee 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2219,6 +2219,8 @@ declare namespace Types { */ subscribe(events: Array, listener?: messageCallback): Promise; /** + * {@label WITH_MESSAGE_FILTER} + * * Registers a listener for messages on this channel that match the supplied filter. * * @param filter - A {@link MessageFilter}. diff --git a/modules.d.ts b/modules.d.ts index 45e48bd18f..0ea246a164 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -10,35 +10,249 @@ export declare const decodePresenceMessage: Types.PresenceMessageStatic['fromEnc export declare const decodePresenceMessages: Types.PresenceMessageStatic['fromEncodedArray']; export declare const constructPresenceMessage: Types.PresenceMessageStatic['fromValues']; +/** + * Provides REST-related functionality to a {@link BaseRealtime} client. + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest, Rest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, Rest }); + * ``` + * + * When provided, the following functionality becomes available: + * + * - { @link Types.Push | push admin } + * - { @link BaseRealtime.time | retrieving Ably service time } + * - { @link BaseRealtime.request | making arbitrary REST requests } + * - { @link BaseRealtime.batchPublish | batch publishing of messages } + * - { @link BaseRealtime.batchPresence | batch retrieval of channel presence state } + * - { @link Types.Auth.revokeTokens | requesting the revocation of tokens } + * - { @link Types.RealtimeChannel.history | retrieving the message history of a channel } + * - { @link Types.RealtimePresence.history | retrieving the presence history of a channel } + * + * If this module is not provided, then trying to use the above functionality will cause a runtime error. + */ export declare const Rest: unknown; + +/** + * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to encrypt and decrypt {@link Types.Message} payloads. + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest, Crypto } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, Crypto }); + * ``` + * + * When provided, you can configure message encryption on a channel via the {@link Types.ChannelOptions.cipher} property of the `ChannelOptions` that you pass when {@link Types.Channels.get | fetching a channel}. If this module is not provided, then passing a `ChannelOptions` with a `cipher` property will cause a runtime error. + */ export declare const Crypto: unknown; + +/** + * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to communicate with the Ably service using the more space-efficient [MessagePack](https://msgpack.org/index.html) format. + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest, MsgPack } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, MsgPack }); + * ``` + * + * When provided, you can control whether the client uses MessagePack via the {@link Types.ClientOptions.useBinaryProtocol} client option. If you do not provide this module, then the library will always JSON format for encoding messages. + */ export declare const MsgPack: unknown; + +/** + * Provides a {@link BaseRealtime} instance with the ability to interact with a channel’s presence set. + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest, RealtimePresence } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, RealtimePresence }); + * ``` + * + * If you do not provide this module, then attempting to access a channel’s {@link Types.RealtimeChannel.presence} property will cause a runtime error. + */ export declare const RealtimePresence: unknown; + +/** + * Provides a {@link BaseRealtime} instance with the ability to establish a connection with the Ably realtime service using a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) connection. + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest }); + * ``` + * + * Note that network conditions, such as firewalls or proxies, might prevent the client from establishing a WebSocket connection. For this reason, you may wish to provide the `BaseRealtime` instance with the ability to alternatively establish a connection using a transport that is less susceptible to these external conditions. You do this by passing one or more alternative transport modules, namely {@link XHRStreaming} and/or {@link XHRPolling}, alongside `WebSocketTransport`: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, XHRStreaming, FetchRequest }); + * ``` + */ export declare const WebSocketTransport: unknown; + +/** + * Provides a {@link BaseRealtime} instance with the ability to establish a connection with the Ably realtime service using the browser’s [XMLHttpRequest API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). + * + * `XHRPolling` uses HTTP long polling; that is, it will make a new HTTP request each time a message is received from Ably. This is less efficient than {@link XHRStreaming}, but is also more likely to succeed in the presence of certain network conditions such as firewalls or proxies. + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { XHRPolling, FetchRequest }); + * ``` + * + * Provide this module if, for example, you wish the client to have an alternative mechanism for connecting to Ably if it’s unable to establish a WebSocket connection. + */ export declare const XHRPolling: unknown; + +/** + * Provides a {@link BaseRealtime} instance with the ability to establish a connection with the Ably realtime service using the browser’s [XMLHttpRequest API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). + * + * `XHRStreaming` uses HTTP streaming; that is, in contrast to {@link XHRPolling}, it does not need to make a new HTTP request each time a message is received from Ably. This is more efficient than `XHRPolling`, but is more likely to be blocked by certain network conditions such as firewalls or proxies. + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { XHRStreaming, FetchRequest }); + * ``` + * + * Provide this module if, for example, you wish the client to have an alternative mechanism for connecting to Ably if it’s unable to establish a WebSocket connection. + */ export declare const XHRStreaming: unknown; + +/** + * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to make HTTP requests using the browser’s [XMLHttpRequest API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, XHRRequest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, XHRRequest }); + * ``` + */ export declare const XHRRequest: unknown; + +/** + * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to make HTTP requests using the browser’s [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest }); + * ``` + */ export declare const FetchRequest: unknown; + +/** + * Provides a {@link BaseRealtime} instance with the ability to filter channel subscriptions at runtime using { @link Types.RealtimeChannel.subscribe:WITH_MESSAGE_FILTER | the overload of `subscribe()` that accepts a `MessageFilter` }. + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest, MessageInteractions } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, MessageInteractions }); + * ``` + * + * If you do not provide this module, then attempting to use this overload of `subscribe()` will cause a runtime error. + */ export declare const MessageInteractions: unknown; +/** + * Pass a `ModulesMap` to { @link BaseRest.constructor | the constructor of BaseRest } or {@link BaseRealtime.constructor | that of BaseRealtime} to specify which functionality should be made available to that client. + */ export interface ModulesMap { + /** + * See {@link Rest | documentation for the `Rest` module}. + */ Rest?: typeof Rest; + + /** + * See {@link Crypto | documentation for the `Crypto` module}. + */ Crypto?: typeof Crypto; + + /** + * See {@link MsgPack | documentation for the `MsgPack` module}. + */ MsgPack?: typeof MsgPack; + + /** + * See {@link RealtimePresence | documentation for the `RealtimePresence` module}. + */ RealtimePresence?: typeof RealtimePresence; + + /** + * See {@link WebSocketTransport | documentation for the `WebSocketTransport` module}. + */ WebSocketTransport?: typeof WebSocketTransport; + + /** + * See {@link XHRPolling | documentation for the `XHRPolling` module}. + */ XHRPolling?: typeof XHRPolling; + + /** + * See {@link XHRStreaming | documentation for the `XHRStreaming` module}. + */ XHRStreaming?: typeof XHRStreaming; + + /** + * See {@link XHRRequest | documentation for the `XHRRequest` module}. + */ XHRRequest?: typeof XHRRequest; + + /** + * See {@link FetchRequest | documentation for the `FetchRequest` module}. + */ FetchRequest?: typeof FetchRequest; + + /** + * See {@link MessageInteractions | documentation for the `MessageInteractions` module}. + */ MessageInteractions?: typeof MessageInteractions; } +/** + * A client that offers a simple stateless API to interact directly with Ably's REST API. + * + * `BaseRest` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Rest`](../../default/classes/Rest.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. + */ export declare class BaseRest extends Types.Rest { + /** + * Construct a client object using an Ably {@link Types.ClientOptions} object. + * + * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + * @param modules - An object which describes which functionality the client should offer. See the documentation for {@link ModulesMap}. + * + * You must provide at least one HTTP request implementation; that is, one of {@link FetchRequest} or {@link XHRRequest}. For minimum bundle size, favour `FetchRequest`. + * + * The {@link Rest} module is always implicitly included. + */ constructor(options: Types.ClientOptions, modules: ModulesMap); } +/** + * A client that extends the functionality of {@link BaseRest} and provides additional realtime-specific features. + * + * `BaseRealtime` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Realtime`](../../default/classes/Realtime.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. + */ export declare class BaseRealtime extends Types.Realtime { + /** + * Construct a client object using an Ably {@link Types.ClientOptions} object. + * + * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + * @param modules - An object which describes which functionality the client should offer. See the documentation for {@link ModulesMap}. + * + * You must provide: + * + * - at least one HTTP request implementation; that is, one of {@link FetchRequest} or {@link XHRRequest} — for minimum bundle size, favour `FetchRequest`; + * - at least one realtime transport implementation; that is, one of {@link WebSocketTransport}, {@link XHRStreaming}, or {@link XHRPolling} — for minimum bundle size, favour `WebSocketTransport`. + */ constructor(options: Types.ClientOptions, modules: ModulesMap); } diff --git a/package.json b/package.json index 858cf24c51..7667646f9b 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,6 @@ "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js", "sourcemap": "source-map-explorer build/ably.min.js", "modulereport": "node scripts/moduleReport.js", - "docs": "typedoc --entryPoints ably.d.ts --out typedoc/generated --readme typedoc/landing-page.md" + "docs": "typedoc --entryPoints ably.d.ts --out typedoc/generated/default --readme typedoc/landing-pages/default.md && typedoc --entryPoints modules.d.ts --out typedoc/generated/modules --name \"ably (modular version)\" --readme typedoc/landing-pages/modules.md && cp typedoc/landing-pages/choose-library.html typedoc/generated/index.html" } } diff --git a/typedoc/landing-page.md b/typedoc/landing-page.md deleted file mode 100644 index f9a0bb6212..0000000000 --- a/typedoc/landing-page.md +++ /dev/null @@ -1,7 +0,0 @@ -# Ably JavaScript Client Library SDK API Reference - -The JavaScript Client Library SDK supports a realtime and a REST interface. The JavaScript API references are generated from the [Ably JavaScript Client Library SDK source code](https://github.com/ably/ably-js/) using [TypeDoc](https://typedoc.org) and structured by classes. - -The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels. The REST interface is stateless and typically implemented server-side. It is used to make requests such as retrieving statistics, token authentication and publishing to a channel. - -View the [Ably docs](https://ably.com/docs/) for conceptual information on using Ably, and for API references featuring all languages. The combined [API references](https://ably.com/docs/api/) are organized by features and split between the [realtime](https://ably.com/docs/api/realtime-sdk) and [REST](https://ably.com/docs/api/rest-sdk) interfaces. diff --git a/typedoc/landing-pages/choose-library.html b/typedoc/landing-pages/choose-library.html new file mode 100644 index 0000000000..d6a91bb42e --- /dev/null +++ b/typedoc/landing-pages/choose-library.html @@ -0,0 +1,31 @@ + + + + + Ably JavaScript Client Library SDK API Reference + + +

Ably JavaScript Client Library SDK API Reference

+ +

+ The JavaScript Client Library SDK supports a realtime and a REST interface. The JavaScript API references are generated from the Ably JavaScript Client Library SDK source code using TypeDoc and structured by classes. +

+ +

+ The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels. The REST interface is stateless and typically implemented server-side. It is used to make requests such as retrieving statistics, token authentication and publishing to a channel. +

+ +

+ There are two variants of the Ably JavaScript Client Library SDK: + +

    +
  • Default variant: This variant of the SDK always creates a fully-featured Ably client.
  • +
  • Modular (tree-shakable) variant: Aimed at those who are concerned about their app’s bundle size, this allows you to create a client which has only the functionality that you choose.
  • +
+

+ +

+ View the Ably docs for conceptual information on using Ably, and for API references featuring all languages. The combined API references are organized by features and split between the realtime and REST interfaces. +

+ + diff --git a/typedoc/landing-pages/default.md b/typedoc/landing-pages/default.md new file mode 100644 index 0000000000..9f2e9698e8 --- /dev/null +++ b/typedoc/landing-pages/default.md @@ -0,0 +1,5 @@ +# Ably JavaScript Client Library SDK API Reference + +You are currently viewing the default variant of the Ably JavaScript Client Library SDK. View the modular variant [here](../modules/index.html). + +To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. diff --git a/typedoc/landing-pages/modules.md b/typedoc/landing-pages/modules.md new file mode 100644 index 0000000000..2a9ff63dea --- /dev/null +++ b/typedoc/landing-pages/modules.md @@ -0,0 +1,21 @@ +# Ably JavaScript Client Library SDK API Reference (modular variant) + +You are currently viewing the modular (tree-shakable) variant of the Ably JavaScript Client Library SDK. View the callback-based variant [here](../default/index.html). + +To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. + +## No `static` class functionality + +In contrast to the default variant of the SDK, the modular variant does not expose any functionality via `static` class properties or methods, since they cannot be tree-shaken by module bundlers. Instead, it exports free-standing functions which provide the same functionality. These are: + +| `static` version | Replacement in modular variant | +| ------------------------------------------ | ----------------------------------------------------------------------- | +| `Crypto.generateRandomKey()` | [`generateRandomKey()`](functions/generateRandomKey.html) | +| `Crypto.getDefaultParams()` | [`getDefaultCryptoParams()`](functions/getDefaultCryptoParams.html) | +| `MessageStatic.fromEncoded()` | [`decodeMessage()`](functions/decodeMessage.html) | +| `MessageStatic.fromEncoded()` | [`decodeEncryptedMessage()`](functions/decodeEncryptedMessage.html) | +| `MessageStatic.fromEncodedArray()` | [`decodeMessages()`](functions/decodeMessages.html) | +| `MessageStatic.fromEncodedArray()` | [`decodeEncryptedMessages()`](functions/decodeEncryptedMessages.html) | +| `PresenceMessageStatic.fromEncoded()` | [`decodePresenceMessage()`](functions/decodePresenceMessage.html) | +| `PresenceMessageStatic.fromEncodedArray()` | [`decodePresenceMessages()`](functions/decodePresenceMessages.html) | +| `PresenceMessageStatic.fromValues()` | [`constructPresenceMessage()`](functions/constructPresenceMessage.html) | From 87b76d5a0a89be6650e4eca6070e9575950a24f7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 16 Nov 2023 10:13:22 -0300 Subject: [PATCH 208/468] =?UTF-8?q?Move=20simpleAttributes=20const=20insid?= =?UTF-8?q?e=20only=20place=20it=E2=80=99s=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And define it more simply. --- src/common/lib/types/protocolmessage.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index b721fe9e10..a8b2f360ea 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -58,8 +58,6 @@ function toStringArray(array?: any[]): string { return '[ ' + result.join(', ') + ' ]'; } -const simpleAttributes = 'id channel channelSerial connectionId count msgSerial timestamp'.split(' '); - class ProtocolMessage { action?: number; flags?: number; @@ -133,6 +131,7 @@ class ProtocolMessage { let result = '[ProtocolMessage'; if (msg.action !== undefined) result += '; action=' + ProtocolMessage.ActionName[msg.action] || msg.action; + const simpleAttributes = ['id', 'channel', 'channelSerial', 'connectionId', 'count', 'msgSerial', 'timestamp']; let attribute; for (let attribIndex = 0; attribIndex < simpleAttributes.length; attribIndex++) { attribute = simpleAttributes[attribIndex]; From 875282c2695fa1eeaada36d9d9d323e8c054ecea Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 14 Nov 2023 17:33:22 -0300 Subject: [PATCH 209/468] Remove static stuff from *Message classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace them by standalone exports, which can be tree-shaken. (My eye was in particular caught by the Message.encrypt function, which I was thinking it would be good to — at some point — bundle only if the Crypto module is used. This change isn’t made here though.) --- src/common/lib/client/realtimechannel.ts | 48 +- src/common/lib/client/realtimepresence.ts | 12 +- src/common/lib/client/restchannel.ts | 21 +- src/common/lib/client/restchannelmixin.ts | 4 +- src/common/lib/client/restpresence.ts | 4 +- src/common/lib/client/restpresencemixin.ts | 4 +- src/common/lib/transport/comettransport.ts | 12 +- src/common/lib/transport/connectionmanager.ts | 17 +- src/common/lib/transport/protocol.ts | 6 +- src/common/lib/transport/transport.ts | 17 +- .../lib/transport/websockettransport.ts | 9 +- src/common/lib/types/defaultmessage.ts | 29 +- src/common/lib/types/message.ts | 414 +++++++++--------- src/common/lib/types/presencemessage.ts | 98 ++--- src/common/lib/types/protocolmessage.ts | 127 +++--- src/platform/nativescript/index.ts | 2 + src/platform/nodejs/index.ts | 2 + src/platform/react-native/index.ts | 2 + src/platform/web/index.ts | 3 +- test/realtime/channel.test.js | 2 +- test/realtime/connection.test.js | 2 +- test/realtime/failure.test.js | 2 +- test/realtime/message.test.js | 2 +- test/realtime/presence.test.js | 2 +- test/realtime/sync.test.js | 2 +- 25 files changed, 454 insertions(+), 389 deletions(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index e3d054ca15..6a4694690d 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -1,12 +1,27 @@ -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + actions, + channelModes, + fromValues as protocolMessageFromValues, +} from '../types/protocolmessage'; import EventEmitter from '../util/eventemitter'; import * as Utils from '../util/utils'; import Logger from '../util/logger'; import RealtimePresence from './realtimepresence'; -import Message, { CipherOptions } from '../types/message'; +import Message, { + fromValues as messageFromValues, + fromValuesArray as messagesFromValuesArray, + encodeArray as encodeMessagesArray, + decode as decodeMessage, + getMessagesSize, + CipherOptions, +} from '../types/message'; import ChannelStateChange from './channelstatechange'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; -import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; +import PresenceMessage, { + fromValues as presenceMessageFromValues, + fromValuesArray as presenceMessagesFromValuesArray, + decode as decodePresenceMessage, +} from '../types/presencemessage'; import ConnectionErrors from '../transport/connectionerrors'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; @@ -25,7 +40,6 @@ interface RealtimeHistoryParams { from_serial?: string; } -const actions = ProtocolMessage.Action; const noop = function () {}; function validateChannelOptions(options?: API.Types.ChannelOptions) { @@ -41,7 +55,7 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) { if ( !currentMode || typeof currentMode !== 'string' || - !Utils.arrIn(ProtocolMessage.channelModes, String.prototype.toUpperCase.call(currentMode)) + !Utils.arrIn(channelModes, String.prototype.toUpperCase.call(currentMode)) ) { return new ErrorInfo('Invalid channel mode: ' + currentMode, 40000, 400); } @@ -230,8 +244,8 @@ class RealtimeChannel extends EventEmitter { return; } if (argCount == 2) { - if (Utils.isObject(messages)) messages = [Message.fromValues(messages)]; - else if (Utils.isArray(messages)) messages = Message.fromValuesArray(messages); + if (Utils.isObject(messages)) messages = [messageFromValues(messages)]; + else if (Utils.isArray(messages)) messages = messagesFromValuesArray(messages); else throw new ErrorInfo( 'The single-argument form of publish() expects a message object or an array of message objects', @@ -239,16 +253,16 @@ class RealtimeChannel extends EventEmitter { 400 ); } else { - messages = [Message.fromValues({ name: args[0], data: args[1] })]; + messages = [messageFromValues({ name: args[0], data: args[1] })]; } const maxMessageSize = this.client.options.maxMessageSize; - Message.encodeArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { + encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { if (err) { callback(err); return; } /* RSL1i */ - const size = Message.getMessagesSize(messages); + const size = getMessagesSize(messages); if (size > maxMessageSize) { callback( new ErrorInfo( @@ -354,7 +368,7 @@ class RealtimeChannel extends EventEmitter { attachImpl(): void { Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.attachImpl()', 'sending ATTACH message'); - const attachMsg = ProtocolMessage.fromValues({ + const attachMsg = protocolMessageFromValues({ action: actions.ATTACH, channel: this.name, params: this.channelOptions.params, @@ -424,7 +438,7 @@ class RealtimeChannel extends EventEmitter { detachImpl(callback?: ErrCallback): void { Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.detach()', 'sending DETACH message'); - const msg = ProtocolMessage.fromValues({ action: actions.DETACH, channel: this.name }); + const msg = protocolMessageFromValues({ action: actions.DETACH, channel: this.name }); this.sendMessage(msg, callback || noop); } @@ -479,7 +493,7 @@ class RealtimeChannel extends EventEmitter { } /* send sync request */ - const syncMessage = ProtocolMessage.fromValues({ action: actions.SYNC, channel: this.name }); + const syncMessage = protocolMessageFromValues({ action: actions.SYNC, channel: this.name }); if (this.syncChannelSerial) { syncMessage.channelSerial = this.syncChannelSerial; } @@ -491,11 +505,11 @@ class RealtimeChannel extends EventEmitter { } sendPresence(presence: PresenceMessage | PresenceMessage[], callback?: ErrCallback): void { - const msg = ProtocolMessage.fromValues({ + const msg = protocolMessageFromValues({ action: actions.PRESENCE, channel: this.name, presence: Utils.isArray(presence) - ? PresenceMessage.fromValuesArray(presence) + ? presenceMessagesFromValuesArray(presence) : [presenceMessageFromValues(presence)], }); this.sendMessage(msg, callback); @@ -579,7 +593,7 @@ class RealtimeChannel extends EventEmitter { for (let i = 0; i < presence.length; i++) { try { presenceMsg = presence[i]; - await PresenceMessage.decode(presenceMsg, options); + await decodePresenceMessage(presenceMsg, options); if (!presenceMsg.connectionId) presenceMsg.connectionId = connectionId; if (!presenceMsg.timestamp) presenceMsg.timestamp = timestamp; if (!presenceMsg.id) presenceMsg.id = id + ':' + i; @@ -635,7 +649,7 @@ class RealtimeChannel extends EventEmitter { for (let i = 0; i < messages.length; i++) { const msg = messages[i]; try { - await Message.decode(msg, this._decodingContext); + await decodeMessage(msg, this._decodingContext); } catch (e) { /* decrypt failed .. the most likely cause is that we have the wrong key */ Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString()); diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 0027e22e20..51f460a618 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -1,7 +1,11 @@ import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; -import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; +import PresenceMessage, { + fromValues as presenceMessageFromValues, + fromData as presenceMessageFromData, + encode as encodePresenceMessage, +} from '../types/presencemessage'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import RealtimeChannel from './realtimechannel'; import Multicaster from '../util/multicaster'; @@ -147,7 +151,7 @@ class RealtimePresence extends EventEmitter { 'channel = ' + channel.name + ', id = ' + id + ', client = ' + (clientId || '(implicit) ' + getClientId(this)) ); - const presence = PresenceMessage.fromData(data); + const presence = presenceMessageFromData(data); presence.action = action; if (id) { presence.id = id; @@ -156,7 +160,7 @@ class RealtimePresence extends EventEmitter { presence.clientId = clientId; } - PresenceMessage.encode(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { + encodePresenceMessage(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { if (err) { callback(err); return; @@ -214,7 +218,7 @@ class RealtimePresence extends EventEmitter { 'RealtimePresence.leaveClient()', 'leaving; channel = ' + this.channel.name + ', client = ' + clientId ); - const presence = PresenceMessage.fromData(data); + const presence = presenceMessageFromData(data); presence.action = 'leave'; if (clientId) { presence.clientId = clientId; diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 6197c65191..95fe706227 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -1,7 +1,14 @@ import * as Utils from '../util/utils'; import Logger from '../util/logger'; import RestPresence from './restpresence'; -import Message, { CipherOptions } from '../types/message'; +import Message, { + fromValues as messageFromValues, + fromValuesArray as messagesFromValuesArray, + encodeArray as encodeMessagesArray, + serialize as serializeMessage, + getMessagesSize, + CipherOptions, +} from '../types/message'; import ErrorInfo from '../types/errorinfo'; import { PaginatedResult } from './paginatedresource'; import Resource, { ResourceCallback } from './resource'; @@ -70,13 +77,13 @@ class RestChannel { if (typeof first === 'string' || first === null) { /* (name, data, ...) */ - messages = [Message.fromValues({ name: first, data: second })]; + messages = [messageFromValues({ name: first, data: second })]; params = arguments[2]; } else if (Utils.isObject(first)) { - messages = [Message.fromValues(first)]; + messages = [messageFromValues(first)]; params = arguments[1]; } else if (Utils.isArray(first)) { - messages = Message.fromValuesArray(first); + messages = messagesFromValuesArray(first); params = arguments[1]; } else { throw new ErrorInfo( @@ -106,14 +113,14 @@ class RestChannel { }); } - Message.encodeArray(messages, this.channelOptions as CipherOptions, (err: Error) => { + encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error) => { if (err) { callback(err); return; } /* RSL1i */ - const size = Message.getMessagesSize(messages), + const size = getMessagesSize(messages), maxMessageSize = options.maxMessageSize; if (size > maxMessageSize) { callback( @@ -130,7 +137,7 @@ class RestChannel { return; } - this._publish(Message.serialize(messages, client._MsgPack, format), headers, params, callback); + this._publish(serializeMessage(messages, client._MsgPack, format), headers, params, callback); }); } diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts index 9986dc4e74..2e3ffc84f1 100644 --- a/src/common/lib/client/restchannelmixin.ts +++ b/src/common/lib/client/restchannelmixin.ts @@ -3,7 +3,7 @@ import RestChannel from './restchannel'; import RealtimeChannel from './realtimechannel'; import * as Utils from '../util/utils'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import Message from '../types/message'; +import Message, { fromResponseBody as messageFromResponseBody } from '../types/message'; import Defaults from '../util/defaults'; import PaginatedResource from './paginatedresource'; import Resource from './resource'; @@ -40,7 +40,7 @@ export class RestChannelMixin { headers, unpacked ) { - return await Message.fromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); + return await messageFromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); }).get(params as Record, callback); } diff --git a/src/common/lib/client/restpresence.ts b/src/common/lib/client/restpresence.ts index 7aec14dcb2..8997f53526 100644 --- a/src/common/lib/client/restpresence.ts +++ b/src/common/lib/client/restpresence.ts @@ -1,7 +1,7 @@ import * as Utils from '../util/utils'; import Logger from '../util/logger'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; -import PresenceMessage from '../types/presencemessage'; +import PresenceMessage, { fromResponseBody as presenceMessageFromResponseBody } from '../types/presencemessage'; import { CipherOptions } from '../types/message'; import { PaginatedResultCallback } from '../../types/utils'; import RestChannel from './restchannel'; @@ -39,7 +39,7 @@ class RestPresence { headers, envelope, async function (body, headers, unpacked) { - return await PresenceMessage.fromResponseBody( + return await presenceMessageFromResponseBody( body as Record[], options as CipherOptions, client._MsgPack, diff --git a/src/common/lib/client/restpresencemixin.ts b/src/common/lib/client/restpresencemixin.ts index 7e600fdee3..296a1ec6ff 100644 --- a/src/common/lib/client/restpresencemixin.ts +++ b/src/common/lib/client/restpresencemixin.ts @@ -4,7 +4,7 @@ import * as Utils from '../util/utils'; import { PaginatedResultCallback } from '../../types/utils'; import Defaults from '../util/defaults'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; -import PresenceMessage from '../types/presencemessage'; +import PresenceMessage, { fromResponseBody as presenceMessageFromResponseBody } from '../types/presencemessage'; import { CipherOptions } from '../types/message'; import { RestChannelMixin } from './restchannelmixin'; @@ -41,7 +41,7 @@ export class RestPresenceMixin { headers, unpacked ) { - return await PresenceMessage.fromResponseBody( + return await presenceMessageFromResponseBody( body as Record[], options as CipherOptions, client._MsgPack, diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index c2c68fa3da..2a68701bd1 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -1,5 +1,9 @@ import * as Utils from '../util/utils'; -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + actions, + fromValues as protocolMessageFromValues, + fromDeserialized as protocolMessageFromDeserialized, +} from '../types/protocolmessage'; import Transport from './transport'; import Logger from '../util/logger'; import Defaults from '../util/defaults'; @@ -29,9 +33,9 @@ function protocolMessageFromRawError(err: ErrorInfo) { /* err will be either a legacy (non-protocolmessage) comet error response * (which will have an err.code), or a xhr/network error (which won't). */ if (shouldBeErrorAction(err)) { - return [ProtocolMessage.fromValues({ action: ProtocolMessage.Action.ERROR, error: err })]; + return [protocolMessageFromValues({ action: actions.ERROR, error: err })]; } else { - return [ProtocolMessage.fromValues({ action: ProtocolMessage.Action.DISCONNECTED, error: err })]; + return [protocolMessageFromValues({ action: actions.DISCONNECTED, error: err })]; } } @@ -344,7 +348,7 @@ abstract class CometTransport extends Transport { try { const items = this.decodeResponse(responseData); if (items && items.length) - for (let i = 0; i < items.length; i++) this.onProtocolMessage(ProtocolMessage.fromDeserialized(items[i])); + for (let i = 0; i < items.length; i++) this.onProtocolMessage(protocolMessageFromDeserialized(items[i])); } catch (e) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 335e2a01d4..8e38f79f79 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1,4 +1,8 @@ -import ProtocolMessage from 'common/lib/types/protocolmessage'; +import ProtocolMessage, { + actions, + stringify as stringifyProtocolMessage, + fromValues as protocolMessageFromValues, +} from 'common/lib/types/protocolmessage'; import * as Utils from 'common/lib/util/utils'; import Protocol, { PendingMessage } from './protocol'; import Defaults, { getAgentString } from 'common/lib/util/defaults'; @@ -10,7 +14,7 @@ import ConnectionStateChange from 'common/lib/client/connectionstatechange'; import ConnectionErrors, { isRetriable } from './connectionerrors'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from 'common/lib/types/errorinfo'; import Auth from 'common/lib/client/auth'; -import Message from 'common/lib/types/message'; +import Message, { getMessagesSize } from 'common/lib/types/message'; import Multicaster, { MulticasterInstance } from 'common/lib/util/multicaster'; import Transport, { TransportCtor } from './transport'; import * as API from '../../../../ably'; @@ -24,7 +28,6 @@ let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'u const haveWebStorage = () => typeof Platform.WebStorage !== 'undefined' && Platform.WebStorage?.localSupported; const haveSessionStorage = () => typeof Platform.WebStorage !== 'undefined' && Platform.WebStorage?.sessionSupported; -const actions = ProtocolMessage.Action; const noop = function () {}; const transportPreferenceName = 'ably-transport-preference'; @@ -62,7 +65,7 @@ function bundleWith(dest: ProtocolMessage, src: ProtocolMessage, maxSize: number } const kind = action === actions.PRESENCE ? 'presence' : 'messages', proposed = (dest as Record)[kind].concat((src as Record)[kind]), - size = Message.getMessagesSize(proposed); + size = getMessagesSize(proposed); if (size > maxSize) { /* RTL6d1 */ return false; @@ -732,7 +735,7 @@ class ConnectionManager extends EventEmitter { // Send ACTIVATE to tell the server to make this transport the // active transport, which suspends channels until we re-attach. transport.send( - ProtocolMessage.fromValues({ + protocolMessageFromValues({ action: actions.ACTIVATE, }) ); @@ -1777,7 +1780,7 @@ class ConnectionManager extends EventEmitter { activeTransport.onAuthUpdated(tokenDetails); } - const authMsg = ProtocolMessage.fromValues({ + const authMsg = protocolMessageFromValues({ action: actions.AUTH, auth: { accessToken: tokenDetails.token, @@ -1911,7 +1914,7 @@ class ConnectionManager extends EventEmitter { return; } if (Logger.shouldLog(Logger.LOG_MICRO)) { - Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.send()', 'queueing msg; ' + ProtocolMessage.stringify(msg)); + Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.send()', 'queueing msg; ' + stringifyProtocolMessage(msg)); } this.queue(msg, callback); } diff --git a/src/common/lib/transport/protocol.ts b/src/common/lib/transport/protocol.ts index 630dc70ac3..7725cd478a 100644 --- a/src/common/lib/transport/protocol.ts +++ b/src/common/lib/transport/protocol.ts @@ -1,4 +1,4 @@ -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { actions, stringify as stringifyProtocolMessage } from '../types/protocolmessage'; import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; @@ -7,8 +7,6 @@ import ErrorInfo from '../types/errorinfo'; import Transport from './transport'; import { ErrCallback } from '../../types/utils'; -const actions = ProtocolMessage.Action; - export class PendingMessage { message: ProtocolMessage; callback?: ErrCallback; @@ -76,7 +74,7 @@ class Protocol extends EventEmitter { Logger.logAction( Logger.LOG_MICRO, 'Protocol.send()', - 'sending msg; ' + ProtocolMessage.stringify(pendingMessage.message) + 'sending msg; ' + stringifyProtocolMessage(pendingMessage.message) ); } pendingMessage.sendAttempted = true; diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 593de97b85..3c83644aba 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -1,4 +1,8 @@ -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + actions, + fromValues as protocolMessageFromValues, + stringify as stringifyProtocolMessage, +} from '../types/protocolmessage'; import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; @@ -21,9 +25,8 @@ export type TransportCtor = new ( forceJsonProtocol?: boolean ) => Transport; -const actions = ProtocolMessage.Action; -const closeMessage = ProtocolMessage.fromValues({ action: actions.CLOSE }); -const disconnectMessage = ProtocolMessage.fromValues({ action: actions.DISCONNECT }); +const closeMessage = protocolMessageFromValues({ action: actions.CLOSE }); +const disconnectMessage = protocolMessageFromValues({ action: actions.DISCONNECT }); /* * Transport instances inherit from EventEmitter and emit the following events: @@ -120,7 +123,7 @@ abstract class Transport extends EventEmitter { 'received on ' + this.shortName + ': ' + - ProtocolMessage.stringify(message) + + stringifyProtocolMessage(message) + '; connectionId = ' + this.connectionManager.connectionId ); @@ -239,9 +242,9 @@ abstract class Transport extends EventEmitter { } ping(id: string): void { - const msg: Record = { action: ProtocolMessage.Action.HEARTBEAT }; + const msg: Record = { action: actions.HEARTBEAT }; if (id) msg.id = id; - this.send(ProtocolMessage.fromValues(msg)); + this.send(protocolMessageFromValues(msg)); } dispose(): void { diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 60f2cc061a..43c53c986b 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -3,7 +3,10 @@ import * as Utils from '../util/utils'; import Transport from './transport'; import Defaults from '../util/defaults'; import Logger from '../util/logger'; -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + serialize as serializeProtocolMessage, + deserialize as deserializeProtocolMessage, +} from '../types/protocolmessage'; import ErrorInfo from '../types/errorinfo'; import NodeWebSocket from 'ws'; import ConnectionManager, { TransportParams, TransportStorage } from './connectionmanager'; @@ -104,7 +107,7 @@ class WebSocketTransport extends Transport { } try { (wsConnection as NodeWebSocket).send( - ProtocolMessage.serialize(message, this.connectionManager.realtime._MsgPack, this.params.format) + serializeProtocolMessage(message, this.connectionManager.realtime._MsgPack, this.params.format) ); } catch (e) { const msg = 'Exception from ws connection when trying to send: ' + Utils.inspectError(e); @@ -122,7 +125,7 @@ class WebSocketTransport extends Transport { 'data received; length = ' + data.length + '; type = ' + typeof data ); try { - this.onProtocolMessage(ProtocolMessage.deserialize(data, this.connectionManager.realtime._MsgPack, this.format)); + this.onProtocolMessage(deserializeProtocolMessage(data, this.connectionManager.realtime._MsgPack, this.format)); } catch (e) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/lib/types/defaultmessage.ts b/src/common/lib/types/defaultmessage.ts index 3ef7dab056..34135869fd 100644 --- a/src/common/lib/types/defaultmessage.ts +++ b/src/common/lib/types/defaultmessage.ts @@ -1,6 +1,15 @@ -import Message, { fromEncoded, fromEncodedArray } from './message'; +import Message, { + CipherOptions, + fromEncoded, + fromEncodedArray, + encode, + decode, + EncodingDecodingContext, +} from './message'; import * as API from '../../../../ably'; import Platform from 'common/platform'; +import PresenceMessage from './presencemessage'; +import { ChannelOptions } from 'common/types/channel'; /** `DefaultMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `Message` static property. It introduces the static methods described in the `MessageStatic` interface of the public API of the non tree-shakable version of the library. @@ -13,4 +22,22 @@ export class DefaultMessage extends Message { static async fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Promise { return fromEncodedArray(Platform.Crypto, encodedArray, options); } + + // Used by tests + static fromValues(values: unknown): Message { + return Object.assign(new Message(), values); + } + + // Used by tests + static encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { + encode(msg, options, callback); + } + + // Used by tests + static async decode( + message: Message | PresenceMessage, + inputContext: CipherOptions | EncodingDecodingContext | ChannelOptions + ): Promise { + return decode(message, inputContext); + } } diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 48285adae3..75fd1e33fe 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -22,7 +22,7 @@ export type CipherOptions = { }; }; -type EncodingDecodingContext = { +export type EncodingDecodingContext = { channelOptions: ChannelOptions; plugins: { vcdiff?: { @@ -81,12 +81,12 @@ export async function fromEncoded( encoded: unknown, inputOptions?: API.Types.ChannelOptions ): Promise { - const msg = Message.fromValues(encoded); + const msg = fromValues(encoded); const options = normalizeCipherOptions(Crypto, inputOptions ?? null); /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ try { - await Message.decode(msg, options); + await decode(msg, options); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'Message.fromEncoded()', (e as Error).toString()); } @@ -105,6 +105,208 @@ export async function fromEncodedArray( ); } +function encrypt(msg: Message | PresenceMessage, options: CipherOptions, callback: Function) { + let data = msg.data, + encoding = msg.encoding, + cipher = options.channelCipher; + + encoding = encoding ? encoding + '/' : ''; + if (!Platform.BufferUtils.isBuffer(data)) { + data = Platform.BufferUtils.utf8Encode(String(data)); + encoding = encoding + 'utf-8/'; + } + cipher.encrypt(data, function (err: Error, data: unknown) { + if (err) { + callback(err); + return; + } + msg.data = data; + msg.encoding = encoding + 'cipher+' + cipher.algorithm; + callback(null, msg); + }); +} + +export function encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { + const data = msg.data; + const nativeDataType = + typeof data == 'string' || Platform.BufferUtils.isBuffer(data) || data === null || data === undefined; + + if (!nativeDataType) { + if (Utils.isObject(data) || Utils.isArray(data)) { + msg.data = JSON.stringify(data); + msg.encoding = msg.encoding ? msg.encoding + '/json' : 'json'; + } else { + throw new ErrorInfo('Data type is unsupported', 40013, 400); + } + } + + if (options != null && options.cipher) { + encrypt(msg, options, callback); + } else { + callback(null, msg); + } +} + +export function encodeArray(messages: Array, options: CipherOptions, callback: Function): void { + let processed = 0; + for (let i = 0; i < messages.length; i++) { + encode(messages[i], options, function (err: Error) { + if (err) { + callback(err); + return; + } + processed++; + if (processed == messages.length) { + callback(null, messages); + } + }); + } +} + +export const serialize = Utils.encodeBody; + +export async function decode( + message: Message | PresenceMessage, + inputContext: CipherOptions | EncodingDecodingContext | ChannelOptions +): Promise { + const context = normaliseContext(inputContext); + + let lastPayload = message.data; + const encoding = message.encoding; + if (encoding) { + const xforms = encoding.split('/'); + let lastProcessedEncodingIndex, + encodingsToProcess = xforms.length, + data = message.data; + + let xform = ''; + try { + while ((lastProcessedEncodingIndex = encodingsToProcess) > 0) { + // eslint-disable-next-line security/detect-unsafe-regex + const match = xforms[--encodingsToProcess].match(/([-\w]+)(\+([\w-]+))?/); + if (!match) break; + xform = match[1]; + switch (xform) { + case 'base64': + data = Platform.BufferUtils.base64Decode(String(data)); + if (lastProcessedEncodingIndex == xforms.length) { + lastPayload = data; + } + continue; + case 'utf-8': + data = Platform.BufferUtils.utf8Decode(data); + continue; + case 'json': + data = JSON.parse(data); + continue; + case 'cipher': + if ( + context.channelOptions != null && + context.channelOptions.cipher && + context.channelOptions.channelCipher + ) { + const xformAlgorithm = match[3], + cipher = context.channelOptions.channelCipher; + /* don't attempt to decrypt unless the cipher params are compatible */ + if (xformAlgorithm != cipher.algorithm) { + throw new Error('Unable to decrypt message with given cipher; incompatible cipher params'); + } + data = await cipher.decrypt(data); + continue; + } else { + throw new Error('Unable to decrypt message; not an encrypted channel'); + } + case 'vcdiff': + if (!context.plugins || !context.plugins.vcdiff) { + throw new ErrorInfo('Missing Vcdiff decoder (https://github.com/ably-forks/vcdiff-decoder)', 40019, 400); + } + if (typeof Uint8Array === 'undefined') { + throw new ErrorInfo( + 'Delta decoding not supported on this browser (need ArrayBuffer & Uint8Array)', + 40020, + 400 + ); + } + try { + let deltaBase = context.baseEncodedPreviousPayload; + if (typeof deltaBase === 'string') { + deltaBase = Platform.BufferUtils.utf8Encode(deltaBase); + } + + // vcdiff expects Uint8Arrays, can't copy with ArrayBuffers. + deltaBase = Platform.BufferUtils.toBuffer(deltaBase as Buffer); + data = Platform.BufferUtils.toBuffer(data); + + data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBase)); + lastPayload = data; + } catch (e) { + throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400); + } + continue; + default: + throw new Error('Unknown encoding'); + } + } + } catch (e) { + const err = e as ErrorInfo; + throw new ErrorInfo( + 'Error processing the ' + xform + ' encoding, decoder returned ‘' + err.message + '’', + err.code || 40013, + 400 + ); + } finally { + message.encoding = + (lastProcessedEncodingIndex as number) <= 0 ? null : xforms.slice(0, lastProcessedEncodingIndex).join('/'); + message.data = data; + } + } + context.baseEncodedPreviousPayload = lastPayload; +} + +export async function fromResponseBody( + body: Array, + options: ChannelOptions | EncodingDecodingContext, + MsgPack: MsgPack | null, + format?: Utils.Format +): Promise { + if (format) { + body = Utils.decodeBody(body, MsgPack, format); + } + + for (let i = 0; i < body.length; i++) { + const msg = (body[i] = fromValues(body[i])); + try { + await decode(msg, options); + } catch (e) { + Logger.logAction(Logger.LOG_ERROR, 'Message.fromResponseBody()', (e as Error).toString()); + } + } + return body; +} + +export function fromValues(values: unknown): Message { + return Object.assign(new Message(), values); +} + +export function fromValuesArray(values: unknown[]): Message[] { + const count = values.length, + result = new Array(count); + for (let i = 0; i < count; i++) result[i] = fromValues(values[i]); + return result; +} + +/* This should be called on encode()d (and encrypt()d) Messages (as it + * assumes the data is a string or buffer) */ +export function getMessagesSize(messages: Message[]): number { + let msg, + total = 0; + for (let i = 0; i < messages.length; i++) { + msg = messages[i]; + total += msg.size || (msg.size = getMessageSize(msg)); + } + return total; +} + class Message { name?: string; id?: string; @@ -170,212 +372,6 @@ class Message { result += ']'; return result; } - - static encrypt(msg: Message | PresenceMessage, options: CipherOptions, callback: Function) { - let data = msg.data, - encoding = msg.encoding, - cipher = options.channelCipher; - - encoding = encoding ? encoding + '/' : ''; - if (!Platform.BufferUtils.isBuffer(data)) { - data = Platform.BufferUtils.utf8Encode(String(data)); - encoding = encoding + 'utf-8/'; - } - cipher.encrypt(data, function (err: Error, data: unknown) { - if (err) { - callback(err); - return; - } - msg.data = data; - msg.encoding = encoding + 'cipher+' + cipher.algorithm; - callback(null, msg); - }); - } - - static encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { - const data = msg.data; - const nativeDataType = - typeof data == 'string' || Platform.BufferUtils.isBuffer(data) || data === null || data === undefined; - - if (!nativeDataType) { - if (Utils.isObject(data) || Utils.isArray(data)) { - msg.data = JSON.stringify(data); - msg.encoding = msg.encoding ? msg.encoding + '/json' : 'json'; - } else { - throw new ErrorInfo('Data type is unsupported', 40013, 400); - } - } - - if (options != null && options.cipher) { - Message.encrypt(msg, options, callback); - } else { - callback(null, msg); - } - } - - static encodeArray(messages: Array, options: CipherOptions, callback: Function): void { - let processed = 0; - for (let i = 0; i < messages.length; i++) { - Message.encode(messages[i], options, function (err: Error) { - if (err) { - callback(err); - return; - } - processed++; - if (processed == messages.length) { - callback(null, messages); - } - }); - } - } - - static serialize = Utils.encodeBody; - - static async decode( - message: Message | PresenceMessage, - inputContext: CipherOptions | EncodingDecodingContext | ChannelOptions - ): Promise { - const context = normaliseContext(inputContext); - - let lastPayload = message.data; - const encoding = message.encoding; - if (encoding) { - const xforms = encoding.split('/'); - let lastProcessedEncodingIndex, - encodingsToProcess = xforms.length, - data = message.data; - - let xform = ''; - try { - while ((lastProcessedEncodingIndex = encodingsToProcess) > 0) { - // eslint-disable-next-line security/detect-unsafe-regex - const match = xforms[--encodingsToProcess].match(/([-\w]+)(\+([\w-]+))?/); - if (!match) break; - xform = match[1]; - switch (xform) { - case 'base64': - data = Platform.BufferUtils.base64Decode(String(data)); - if (lastProcessedEncodingIndex == xforms.length) { - lastPayload = data; - } - continue; - case 'utf-8': - data = Platform.BufferUtils.utf8Decode(data); - continue; - case 'json': - data = JSON.parse(data); - continue; - case 'cipher': - if ( - context.channelOptions != null && - context.channelOptions.cipher && - context.channelOptions.channelCipher - ) { - const xformAlgorithm = match[3], - cipher = context.channelOptions.channelCipher; - /* don't attempt to decrypt unless the cipher params are compatible */ - if (xformAlgorithm != cipher.algorithm) { - throw new Error('Unable to decrypt message with given cipher; incompatible cipher params'); - } - data = await cipher.decrypt(data); - continue; - } else { - throw new Error('Unable to decrypt message; not an encrypted channel'); - } - case 'vcdiff': - if (!context.plugins || !context.plugins.vcdiff) { - throw new ErrorInfo( - 'Missing Vcdiff decoder (https://github.com/ably-forks/vcdiff-decoder)', - 40019, - 400 - ); - } - if (typeof Uint8Array === 'undefined') { - throw new ErrorInfo( - 'Delta decoding not supported on this browser (need ArrayBuffer & Uint8Array)', - 40020, - 400 - ); - } - try { - let deltaBase = context.baseEncodedPreviousPayload; - if (typeof deltaBase === 'string') { - deltaBase = Platform.BufferUtils.utf8Encode(deltaBase); - } - - // vcdiff expects Uint8Arrays, can't copy with ArrayBuffers. - deltaBase = Platform.BufferUtils.toBuffer(deltaBase as Buffer); - data = Platform.BufferUtils.toBuffer(data); - - data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBase)); - lastPayload = data; - } catch (e) { - throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400); - } - continue; - default: - throw new Error('Unknown encoding'); - } - } - } catch (e) { - const err = e as ErrorInfo; - throw new ErrorInfo( - 'Error processing the ' + xform + ' encoding, decoder returned ‘' + err.message + '’', - err.code || 40013, - 400 - ); - } finally { - message.encoding = - (lastProcessedEncodingIndex as number) <= 0 ? null : xforms.slice(0, lastProcessedEncodingIndex).join('/'); - message.data = data; - } - } - context.baseEncodedPreviousPayload = lastPayload; - } - - static async fromResponseBody( - body: Array, - options: ChannelOptions | EncodingDecodingContext, - MsgPack: MsgPack | null, - format?: Utils.Format - ): Promise { - if (format) { - body = Utils.decodeBody(body, MsgPack, format); - } - - for (let i = 0; i < body.length; i++) { - const msg = (body[i] = Message.fromValues(body[i])); - try { - await Message.decode(msg, options); - } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'Message.fromResponseBody()', (e as Error).toString()); - } - } - return body; - } - - static fromValues(values: unknown): Message { - return Object.assign(new Message(), values); - } - - static fromValuesArray(values: unknown[]): Message[] { - const count = values.length, - result = new Array(count); - for (let i = 0; i < count; i++) result[i] = Message.fromValues(values[i]); - return result; - } - - /* This should be called on encode()d (and encrypt()d) Messages (as it - * assumes the data is a string or buffer) */ - static getMessagesSize(messages: Message[]): number { - let msg, - total = 0; - for (let i = 0; i < messages.length; i++) { - msg = messages[i]; - total += msg.size || (msg.size = getMessageSize(msg)); - } - return total; - } } export default Message; diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index c1fa4db450..b9969df241 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -1,12 +1,14 @@ import Logger from '../util/logger'; import Platform from 'common/platform'; -import Message, { CipherOptions } from './message'; +import { encode as encodeMessage, decode as decodeMessage, getMessagesSize, CipherOptions } from './message'; import * as Utils from '../util/utils'; import * as API from '../../../../ably'; import { MsgPack } from 'common/types/msgpack'; +const actions = ['absent', 'present', 'enter', 'leave', 'update']; + function toActionValue(actionString: string) { - return PresenceMessage.Actions.indexOf(actionString); + return actions.indexOf(actionString); } export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { @@ -14,7 +16,7 @@ export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelO /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ try { - await PresenceMessage.decode(msg, options ?? {}); + await decode(msg, options ?? {}); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromEncoded()', (e as Error).toString()); } @@ -37,11 +39,54 @@ export function fromValues( stringifyAction?: boolean ): PresenceMessage { if (stringifyAction) { - values.action = PresenceMessage.Actions[values.action as number]; + values.action = actions[values.action as number]; } return Object.assign(new PresenceMessage(), values); } +export { encodeMessage as encode }; +export const decode = decodeMessage; + +export async function fromResponseBody( + body: Record[], + options: CipherOptions, + MsgPack: MsgPack | null, + format?: Utils.Format +): Promise { + const messages: PresenceMessage[] = []; + if (format) { + body = Utils.decodeBody(body, MsgPack, format); + } + + for (let i = 0; i < body.length; i++) { + const msg = (messages[i] = fromValues(body[i], true)); + try { + await decode(msg, options); + } catch (e) { + Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromResponseBody()', (e as Error).toString()); + } + } + return messages; +} + +export function fromValuesArray(values: unknown[]): PresenceMessage[] { + const count = values.length, + result = new Array(count); + for (let i = 0; i < count; i++) result[i] = fromValues(values[i] as Record); + return result; +} + +export function fromData(data: unknown): PresenceMessage { + if (data instanceof PresenceMessage) { + return data; + } + return fromValues({ + data, + }); +} + +export { getMessagesSize }; + class PresenceMessage { action?: string | number; id?: string; @@ -53,8 +98,6 @@ class PresenceMessage { extras?: any; size?: number; - static Actions = ['absent', 'present', 'enter', 'leave', 'update']; - /* Returns whether this presenceMessage is synthesized, i.e. was not actually * sent by the connection (usually means a leave event sent 15s after a * disconnection). This is useful because synthesized messages cannot be @@ -138,49 +181,6 @@ class PresenceMessage { result += ']'; return result; } - - static encode = Message.encode; - static decode = Message.decode; - - static async fromResponseBody( - body: Record[], - options: CipherOptions, - MsgPack: MsgPack | null, - format?: Utils.Format - ): Promise { - const messages: PresenceMessage[] = []; - if (format) { - body = Utils.decodeBody(body, MsgPack, format); - } - - for (let i = 0; i < body.length; i++) { - const msg = (messages[i] = fromValues(body[i], true)); - try { - await PresenceMessage.decode(msg, options); - } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromResponseBody()', (e as Error).toString()); - } - } - return messages; - } - - static fromValuesArray(values: unknown[]): PresenceMessage[] { - const count = values.length, - result = new Array(count); - for (let i = 0; i < count; i++) result[i] = fromValues(values[i] as Record); - return result; - } - - static fromData(data: unknown): PresenceMessage { - if (data instanceof PresenceMessage) { - return data; - } - return fromValues({ - data, - }); - } - - static getMessagesSize = Message.getMessagesSize; } export default PresenceMessage; diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index a8b2f360ea..fdcedf6c7d 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -2,10 +2,13 @@ import { MsgPack } from 'common/types/msgpack'; import { Types } from '../../../../ably'; import * as Utils from '../util/utils'; import ErrorInfo from './errorinfo'; -import Message from './message'; -import PresenceMessage, { fromValues as presenceMessageFromValues } from './presencemessage'; +import Message, { fromValues as messageFromValues, fromValuesArray as messagesFromValuesArray } from './message'; +import PresenceMessage, { + fromValues as presenceMessageFromValues, + fromValuesArray as presenceMessagesFromValuesArray, +} from './presencemessage'; -const actions = { +export const actions = { HEARTBEAT: 0, ACK: 1, NACK: 2, @@ -27,7 +30,7 @@ const actions = { ACTIVATE: 18, }; -const ActionName: string[] = []; +export const ActionName: string[] = []; Object.keys(actions).forEach(function (name) { ActionName[(actions as { [key: string]: number })[name]] = name; }); @@ -58,6 +61,61 @@ function toStringArray(array?: any[]): string { return '[ ' + result.join(', ') + ' ]'; } +export const channelModes = ['PRESENCE', 'PUBLISH', 'SUBSCRIBE', 'PRESENCE_SUBSCRIBE']; + +export const serialize = Utils.encodeBody; + +export function deserialize(serialized: unknown, MsgPack: MsgPack | null, format?: Utils.Format): ProtocolMessage { + const deserialized = Utils.decodeBody>(serialized, MsgPack, format); + return fromDeserialized(deserialized); +} + +export function fromDeserialized(deserialized: Record): ProtocolMessage { + const error = deserialized.error; + if (error) deserialized.error = ErrorInfo.fromValues(error as ErrorInfo); + const messages = deserialized.messages as Message[]; + if (messages) for (let i = 0; i < messages.length; i++) messages[i] = messageFromValues(messages[i]); + const presence = deserialized.presence as PresenceMessage[]; + if (presence) for (let i = 0; i < presence.length; i++) presence[i] = presenceMessageFromValues(presence[i], true); + return Object.assign(new ProtocolMessage(), deserialized); +} + +export function fromValues(values: unknown): ProtocolMessage { + return Object.assign(new ProtocolMessage(), values); +} + +export function stringify(msg: any): string { + let result = '[ProtocolMessage'; + if (msg.action !== undefined) result += '; action=' + ActionName[msg.action] || msg.action; + + const simpleAttributes = ['id', 'channel', 'channelSerial', 'connectionId', 'count', 'msgSerial', 'timestamp']; + let attribute; + for (let attribIndex = 0; attribIndex < simpleAttributes.length; attribIndex++) { + attribute = simpleAttributes[attribIndex]; + if (msg[attribute] !== undefined) result += '; ' + attribute + '=' + msg[attribute]; + } + + if (msg.messages) result += '; messages=' + toStringArray(messagesFromValuesArray(msg.messages)); + if (msg.presence) result += '; presence=' + toStringArray(presenceMessagesFromValuesArray(msg.presence)); + if (msg.error) result += '; error=' + ErrorInfo.fromValues(msg.error).toString(); + if (msg.auth && msg.auth.accessToken) result += '; token=' + msg.auth.accessToken; + if (msg.flags) result += '; flags=' + flagNames.filter(msg.hasFlag).join(','); + if (msg.params) { + let stringifiedParams = ''; + Utils.forInOwnNonNullProperties(msg.params, function (prop: string) { + if (stringifiedParams.length > 0) { + stringifiedParams += '; '; + } + stringifiedParams += prop + '=' + msg.params[prop]; + }); + if (stringifiedParams.length > 0) { + result += '; params=[' + stringifiedParams + ']'; + } + } + result += ']'; + return result; +} + class ProtocolMessage { action?: number; flags?: number; @@ -74,12 +132,6 @@ class ProtocolMessage { auth?: unknown; connectionDetails?: Record; - static Action = actions; - - static channelModes = ['PRESENCE', 'PUBLISH', 'SUBSCRIBE', 'PRESENCE_SUBSCRIBE']; - - static ActionName = ActionName; - hasFlag = (flag: string): boolean => { return ((this.flags as number) & flags[flag]) > 0; }; @@ -98,66 +150,13 @@ class ProtocolMessage { decodeModesFromFlags(): string[] | undefined { const modes: string[] = []; - ProtocolMessage.channelModes.forEach((mode) => { + channelModes.forEach((mode) => { if (this.hasFlag(mode)) { modes.push(mode); } }); return modes.length > 0 ? modes : undefined; } - - static serialize = Utils.encodeBody; - - static deserialize = function (serialized: unknown, MsgPack: MsgPack | null, format?: Utils.Format): ProtocolMessage { - const deserialized = Utils.decodeBody>(serialized, MsgPack, format); - return ProtocolMessage.fromDeserialized(deserialized); - }; - - static fromDeserialized = function (deserialized: Record): ProtocolMessage { - const error = deserialized.error; - if (error) deserialized.error = ErrorInfo.fromValues(error as ErrorInfo); - const messages = deserialized.messages as Message[]; - if (messages) for (let i = 0; i < messages.length; i++) messages[i] = Message.fromValues(messages[i]); - const presence = deserialized.presence as PresenceMessage[]; - if (presence) for (let i = 0; i < presence.length; i++) presence[i] = presenceMessageFromValues(presence[i], true); - return Object.assign(new ProtocolMessage(), deserialized); - }; - - static fromValues(values: unknown): ProtocolMessage { - return Object.assign(new ProtocolMessage(), values); - } - - static stringify = function (msg: any): string { - let result = '[ProtocolMessage'; - if (msg.action !== undefined) result += '; action=' + ProtocolMessage.ActionName[msg.action] || msg.action; - - const simpleAttributes = ['id', 'channel', 'channelSerial', 'connectionId', 'count', 'msgSerial', 'timestamp']; - let attribute; - for (let attribIndex = 0; attribIndex < simpleAttributes.length; attribIndex++) { - attribute = simpleAttributes[attribIndex]; - if (msg[attribute] !== undefined) result += '; ' + attribute + '=' + msg[attribute]; - } - - if (msg.messages) result += '; messages=' + toStringArray(Message.fromValuesArray(msg.messages)); - if (msg.presence) result += '; presence=' + toStringArray(PresenceMessage.fromValuesArray(msg.presence)); - if (msg.error) result += '; error=' + ErrorInfo.fromValues(msg.error).toString(); - if (msg.auth && msg.auth.accessToken) result += '; token=' + msg.auth.accessToken; - if (msg.flags) result += '; flags=' + flagNames.filter(msg.hasFlag).join(','); - if (msg.params) { - let stringifiedParams = ''; - Utils.forInOwnNonNullProperties(msg.params, function (prop: string) { - if (stringifiedParams.length > 0) { - stringifiedParams += '; '; - } - stringifiedParams += prop + '=' + msg.params[prop]; - }); - if (stringifiedParams.length > 0) { - result += '; params=[' + stringifiedParams + ']'; - } - } - result += ']'; - return result; - }; } export default ProtocolMessage; diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index 119fdcb048..85c86eed5b 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -3,6 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; +import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; @@ -51,4 +52,5 @@ export default { Rest: DefaultRest, Realtime: DefaultRealtime, msgpack, + protocolMessageFromDeserialized, }; diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index b066225b6b..7decdf1303 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -3,6 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; +import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; @@ -45,4 +46,5 @@ export default { Rest: DefaultRest, Realtime: DefaultRealtime, msgpack: null, + protocolMessageFromDeserialized, }; diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index e0539aa92a..69ab522797 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -3,6 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; +import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; @@ -51,4 +52,5 @@ export default { Rest: DefaultRest, Realtime: DefaultRealtime, msgpack, + protocolMessageFromDeserialized, }; diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index 27d6c9556b..e4e5a70693 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -3,6 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; +import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; @@ -52,7 +53,7 @@ if (Platform.Config.noUpgrade) { Platform.Defaults.upgradeTransports = []; } -export { DefaultRest as Rest, DefaultRealtime as Realtime, msgpack }; +export { DefaultRest as Rest, DefaultRealtime as Realtime, msgpack, protocolMessageFromDeserialized }; export default { ErrorInfo, diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index 25bd313a0a..1ed1aab1dc 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -7,7 +7,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var displayError = helper.displayError; var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; - var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; + var createPM = Ably.protocolMessageFromDeserialized; var testOnAllTransports = helper.testOnAllTransports; var whenPromiseSettles = helper.whenPromiseSettles; var randomString = helper.randomString; diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index 16f13c65ac..04d193c4fe 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -3,7 +3,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async, chai) { var expect = chai.expect; var closeAndFinish = helper.closeAndFinish; - var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; + var createPM = Ably.protocolMessageFromDeserialized; var displayError = helper.displayError; var monitorConnection = helper.monitorConnection; var whenPromiseSettles = helper.whenPromiseSettles; diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index 61256cdce3..23c05a7e3a 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -6,7 +6,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var utils = helper.Utils; var noop = function () {}; var simulateDroppedConnection = helper.simulateDroppedConnection; - var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; + var createPM = Ably.protocolMessageFromDeserialized; var availableTransports = helper.availableTransports; var whenPromiseSettles = helper.whenPromiseSettles; diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index bcc4aa9128..11f5ff37e4 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -6,7 +6,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var utils = helper.Utils; let config = Ably.Realtime.Platform.Config; var closeAndFinish = helper.closeAndFinish; - var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; + var createPM = Ably.protocolMessageFromDeserialized; var monitorConnection = helper.monitorConnection; var testOnAllTransports = helper.testOnAllTransports; var whenPromiseSettles = helper.whenPromiseSettles; diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 6c2f11dcae..d915eba312 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -3,7 +3,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async, chai) { var expect = chai.expect; var utils = helper.Utils; - var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; + var createPM = Ably.protocolMessageFromDeserialized; var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; var PresenceMessage = Ably.Realtime.PresenceMessage; diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index 3c306d9bcc..7b29fd4d27 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -5,7 +5,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var displayError = helper.displayError; var utils = helper.Utils; var closeAndFinish = helper.closeAndFinish; - var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized; + var createPM = Ably.protocolMessageFromDeserialized; var monitorConnection = helper.monitorConnection; var whenPromiseSettles = helper.whenPromiseSettles; From 10bac3ccc0b757aa05d00539a7178db66941d702 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 15 Nov 2023 14:56:20 -0300 Subject: [PATCH 210/468] Exclude PresenceMessage from BaseRealtime bundle Now PresenceMessage only gets included if you provide the RealtimePresence module. --- src/common/lib/client/baserealtime.ts | 5 +-- src/common/lib/client/defaultrealtime.ts | 10 ++++- src/common/lib/client/modulesmap.ts | 15 ++++++- src/common/lib/client/realtimechannel.ts | 19 ++++----- src/common/lib/transport/comettransport.ts | 5 ++- src/common/lib/transport/connectionmanager.ts | 6 ++- src/common/lib/transport/protocol.ts | 3 +- src/common/lib/transport/transport.ts | 2 +- .../lib/transport/websockettransport.ts | 9 ++++- src/common/lib/types/protocolmessage.ts | 39 +++++++++++++++---- src/platform/nativescript/index.ts | 2 +- src/platform/nodejs/index.ts | 2 +- src/platform/react-native/index.ts | 2 +- src/platform/web/index.ts | 2 +- src/platform/web/modules/realtimepresence.ts | 15 ++++++- test/browser/modules.test.js | 23 +++++++++++ 16 files changed, 127 insertions(+), 32 deletions(-) diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 0fe9558313..3787709326 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -9,8 +9,7 @@ import ProtocolMessage from '../types/protocolmessage'; import { ChannelOptions } from '../../types/channel'; import ClientOptions from '../../types/ClientOptions'; import * as API from '../../../../ably'; -import { ModulesMap } from './modulesmap'; -import RealtimePresence from './realtimepresence'; +import { ModulesMap, RealtimePresenceModule } from './modulesmap'; import { TransportNames } from 'common/constants/TransportName'; import { TransportImplementations } from 'common/platform'; @@ -18,7 +17,7 @@ import { TransportImplementations } from 'common/platform'; `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. */ class BaseRealtime extends BaseClient { - readonly _RealtimePresence: typeof RealtimePresence | null; + readonly _RealtimePresence: RealtimePresenceModule | null; // Extra transport implementations available to this client, in addition to those in Platform.Transports.bundledImplementations readonly _additionalTransportImplementations: TransportImplementations; _channels: any; diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 1dd3d402dd..9e5f581dff 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -11,6 +11,10 @@ import RealtimePresence from './realtimepresence'; import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; import initialiseWebSocketTransport from '../transport/websockettransport'; import { FilteredSubscriptions } from './filteredsubscriptions'; +import { + fromValues as presenceMessageFromValues, + fromValuesArray as presenceMessagesFromValuesArray, +} from '../types/presencemessage'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -26,7 +30,11 @@ export class DefaultRealtime extends BaseRealtime { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined, MsgPack, - RealtimePresence, + RealtimePresence: { + RealtimePresence, + presenceMessageFromValues, + presenceMessagesFromValuesArray, + }, WebSocketTransport: initialiseWebSocketTransport, MessageInteractions: FilteredSubscriptions, }); diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index dab5c8b718..e52730aa32 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -6,12 +6,25 @@ import { TransportInitialiser } from '../transport/connectionmanager'; import XHRRequest from 'platform/web/lib/http/request/xhrrequest'; import fetchRequest from 'platform/web/lib/http/request/fetchrequest'; import { FilteredSubscriptions } from './filteredsubscriptions'; +import { + fromValues as presenceMessageFromValues, + fromValuesArray as presenceMessagesFromValuesArray, +} from '../types/presencemessage'; + +export interface PresenceMessageModule { + presenceMessageFromValues: typeof presenceMessageFromValues; + presenceMessagesFromValuesArray: typeof presenceMessagesFromValuesArray; +} + +export type RealtimePresenceModule = PresenceMessageModule & { + RealtimePresence: typeof RealtimePresence; +}; export interface ModulesMap { Rest?: typeof Rest; Crypto?: IUntypedCryptoStatic; MsgPack?: MsgPack; - RealtimePresence?: typeof RealtimePresence; + RealtimePresence?: RealtimePresenceModule; WebSocketTransport?: TransportInitialiser; XHRPolling?: TransportInitialiser; XHRStreaming?: TransportInitialiser; diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 6a4694690d..6b6f7bad67 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -17,11 +17,7 @@ import Message, { } from '../types/message'; import ChannelStateChange from './channelstatechange'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; -import PresenceMessage, { - fromValues as presenceMessageFromValues, - fromValuesArray as presenceMessagesFromValuesArray, - decode as decodePresenceMessage, -} from '../types/presencemessage'; +import PresenceMessage, { decode as decodePresenceMessage } from '../types/presencemessage'; import ConnectionErrors from '../transport/connectionerrors'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; @@ -109,7 +105,7 @@ class RealtimeChannel extends EventEmitter { this.name = name; this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, options); this.client = client; - this._presence = client._RealtimePresence ? new client._RealtimePresence(this) : null; + this._presence = client._RealtimePresence ? new client._RealtimePresence.RealtimePresence(this) : null; this.connectionManager = client.connection.connectionManager; this.state = 'initialized'; this.subscriptions = new EventEmitter(); @@ -509,8 +505,8 @@ class RealtimeChannel extends EventEmitter { action: actions.PRESENCE, channel: this.name, presence: Utils.isArray(presence) - ? presenceMessagesFromValuesArray(presence) - : [presenceMessageFromValues(presence)], + ? this.client._RealtimePresence!.presenceMessagesFromValuesArray(presence) + : [this.client._RealtimePresence!.presenceMessageFromValues(presence)], }); this.sendMessage(msg, callback); } @@ -585,7 +581,12 @@ class RealtimeChannel extends EventEmitter { if (!message.presence) break; // eslint-disable-next-line no-fallthrough case actions.PRESENCE: { - const presence = message.presence as Array; + const presence = message.presence; + + if (!presence) { + break; + } + const { id, connectionId, timestamp } = message; const options = this.channelOptions; diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index 2a68701bd1..af1f9a020d 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -348,7 +348,10 @@ abstract class CometTransport extends Transport { try { const items = this.decodeResponse(responseData); if (items && items.length) - for (let i = 0; i < items.length; i++) this.onProtocolMessage(protocolMessageFromDeserialized(items[i])); + for (let i = 0; i < items.length; i++) + this.onProtocolMessage( + protocolMessageFromDeserialized(items[i], this.connectionManager.realtime._RealtimePresence) + ); } catch (e) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 8e38f79f79..3b4484fde4 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1914,7 +1914,11 @@ class ConnectionManager extends EventEmitter { return; } if (Logger.shouldLog(Logger.LOG_MICRO)) { - Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.send()', 'queueing msg; ' + stringifyProtocolMessage(msg)); + Logger.logAction( + Logger.LOG_MICRO, + 'ConnectionManager.send()', + 'queueing msg; ' + stringifyProtocolMessage(msg, this.realtime._RealtimePresence) + ); } this.queue(msg, callback); } diff --git a/src/common/lib/transport/protocol.ts b/src/common/lib/transport/protocol.ts index 7725cd478a..436fb7d202 100644 --- a/src/common/lib/transport/protocol.ts +++ b/src/common/lib/transport/protocol.ts @@ -74,7 +74,8 @@ class Protocol extends EventEmitter { Logger.logAction( Logger.LOG_MICRO, 'Protocol.send()', - 'sending msg; ' + stringifyProtocolMessage(pendingMessage.message) + 'sending msg; ' + + stringifyProtocolMessage(pendingMessage.message, this.transport.connectionManager.realtime._RealtimePresence) ); } pendingMessage.sendAttempted = true; diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 3c83644aba..76e9b49f94 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -123,7 +123,7 @@ abstract class Transport extends EventEmitter { 'received on ' + this.shortName + ': ' + - stringifyProtocolMessage(message) + + stringifyProtocolMessage(message, this.connectionManager.realtime._RealtimePresence) + '; connectionId = ' + this.connectionManager.connectionId ); diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 43c53c986b..d56848ec6b 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -125,7 +125,14 @@ class WebSocketTransport extends Transport { 'data received; length = ' + data.length + '; type = ' + typeof data ); try { - this.onProtocolMessage(deserializeProtocolMessage(data, this.connectionManager.realtime._MsgPack, this.format)); + this.onProtocolMessage( + deserializeProtocolMessage( + data, + this.connectionManager.realtime._MsgPack, + this.connectionManager.realtime._RealtimePresence, + this.format + ) + ); } catch (e) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index fdcedf6c7d..945d09378d 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -1,5 +1,6 @@ import { MsgPack } from 'common/types/msgpack'; import { Types } from '../../../../ably'; +import { PresenceMessageModule } from '../client/modulesmap'; import * as Utils from '../util/utils'; import ErrorInfo from './errorinfo'; import Message, { fromValues as messageFromValues, fromValuesArray as messagesFromValuesArray } from './message'; @@ -65,26 +66,46 @@ export const channelModes = ['PRESENCE', 'PUBLISH', 'SUBSCRIBE', 'PRESENCE_SUBSC export const serialize = Utils.encodeBody; -export function deserialize(serialized: unknown, MsgPack: MsgPack | null, format?: Utils.Format): ProtocolMessage { +export function deserialize( + serialized: unknown, + MsgPack: MsgPack | null, + presenceMessageModule: PresenceMessageModule | null, + format?: Utils.Format +): ProtocolMessage { const deserialized = Utils.decodeBody>(serialized, MsgPack, format); - return fromDeserialized(deserialized); + return fromDeserialized(deserialized, presenceMessageModule); } -export function fromDeserialized(deserialized: Record): ProtocolMessage { +export function fromDeserialized( + deserialized: Record, + presenceMessageModule: PresenceMessageModule | null +): ProtocolMessage { const error = deserialized.error; if (error) deserialized.error = ErrorInfo.fromValues(error as ErrorInfo); const messages = deserialized.messages as Message[]; if (messages) for (let i = 0; i < messages.length; i++) messages[i] = messageFromValues(messages[i]); - const presence = deserialized.presence as PresenceMessage[]; - if (presence) for (let i = 0; i < presence.length; i++) presence[i] = presenceMessageFromValues(presence[i], true); - return Object.assign(new ProtocolMessage(), deserialized); + + const presence = presenceMessageModule ? (deserialized.presence as PresenceMessage[]) : undefined; + if (presenceMessageModule) { + if (presence && presenceMessageModule) + for (let i = 0; i < presence.length; i++) + presence[i] = presenceMessageModule.presenceMessageFromValues(presence[i], true); + } + return Object.assign(new ProtocolMessage(), { ...deserialized, presence }); +} + +/** + * Used by the tests. + */ +export function fromDeserializedIncludingDependencies(deserialized: Record): ProtocolMessage { + return fromDeserialized(deserialized, { presenceMessageFromValues, presenceMessagesFromValuesArray }); } export function fromValues(values: unknown): ProtocolMessage { return Object.assign(new ProtocolMessage(), values); } -export function stringify(msg: any): string { +export function stringify(msg: any, presenceMessageModule: PresenceMessageModule | null): string { let result = '[ProtocolMessage'; if (msg.action !== undefined) result += '; action=' + ActionName[msg.action] || msg.action; @@ -96,7 +117,8 @@ export function stringify(msg: any): string { } if (msg.messages) result += '; messages=' + toStringArray(messagesFromValuesArray(msg.messages)); - if (msg.presence) result += '; presence=' + toStringArray(presenceMessagesFromValuesArray(msg.presence)); + if (msg.presence && presenceMessageModule) + result += '; presence=' + toStringArray(presenceMessageModule.presenceMessagesFromValuesArray(msg.presence)); if (msg.error) result += '; error=' + ErrorInfo.fromValues(msg.error).toString(); if (msg.auth && msg.auth.accessToken) result += '; token=' + msg.auth.accessToken; if (msg.flags) result += '; flags=' + flagNames.filter(msg.hasFlag).join(','); @@ -128,6 +150,7 @@ class ProtocolMessage { channelSerial?: string | null; msgSerial?: number; messages?: Message[]; + // This will be undefined if we skipped decoding this property due to user not requesting presence functionality — see `fromDeserialized` presence?: PresenceMessage[]; auth?: unknown; connectionDetails?: Record; diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index 85c86eed5b..5a57dbe073 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; -import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; +import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index 7decdf1303..d53b83dadd 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; -import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; +import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index 69ab522797..998c2dd19a 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; -import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; +import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index e4e5a70693..9f3b0b0a06 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest'; import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; -import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; +import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; diff --git a/src/platform/web/modules/realtimepresence.ts b/src/platform/web/modules/realtimepresence.ts index 425677272c..850bcfa2ad 100644 --- a/src/platform/web/modules/realtimepresence.ts +++ b/src/platform/web/modules/realtimepresence.ts @@ -1 +1,14 @@ -export { default as RealtimePresence } from '../../../common/lib/client/realtimepresence'; +import { RealtimePresenceModule } from 'common/lib/client/modulesmap'; +import { default as realtimePresenceClass } from '../../../common/lib/client/realtimepresence'; +import { + fromValues as presenceMessageFromValues, + fromValuesArray as presenceMessagesFromValuesArray, +} from '../../../common/lib/types/presencemessage'; + +const RealtimePresence: RealtimePresenceModule = { + RealtimePresence: realtimePresenceClass, + presenceMessageFromValues, + presenceMessagesFromValuesArray, +}; + +export { RealtimePresence }; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index f2ba4b77cb..c7bdc90432 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -465,6 +465,29 @@ describe('browser/modules', function () { expect(() => channel.presence).to.throw('RealtimePresence module not provided'); }); + + it('doesn’t break when it receives a PRESENCE ProtocolMessage', async () => { + const rxClient = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const rxChannel = rxClient.channels.get('channel'); + + await rxChannel.attach(); + + const receivedMessagePromise = new Promise((resolve) => rxChannel.subscribe(resolve)); + + const txClient = new BaseRealtime(ablyClientOptions({ clientId: randomString() }), { + WebSocketTransport, + FetchRequest, + RealtimePresence, + }); + const txChannel = txClient.channels.get('channel'); + + await txChannel.publish('message', 'body'); + await txChannel.presence.enter(); + + // The idea being here that in order for receivedMessagePromise to resolve, rxClient must have first processed the PRESENCE ProtocolMessage that resulted from txChannel.presence.enter() + + await receivedMessagePromise; + }); }); describe('BaseRealtime with RealtimePresence', () => { From 2d69c476ec47ab3bb80735875c278dee3bc4bee9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 17 Nov 2023 10:04:11 -0300 Subject: [PATCH 211/468] Convert modulereport script to TypeScript --- package-lock.json | 41 ++++++++++++++++++++ package.json | 7 ++-- scripts/{moduleReport.js => moduleReport.ts} | 8 ++-- 3 files changed, 49 insertions(+), 7 deletions(-) rename scripts/{moduleReport.js => moduleReport.ts} (96%) diff --git a/package-lock.json b/package-lock.json index fdfc9dbb39..f0c3388fd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", + "esbuild-runner": "^2.2.2", "eslint": "^7.13.0", "eslint-plugin-import": "^2.28.0", "eslint-plugin-jsdoc": "^40.0.0", @@ -3774,6 +3775,28 @@ "integrity": "sha512-Jo1S3rczXupUzPGt4eXF643FF/J7nDkwwwHH2PS6ic1l0ye7kTS0plAJQoD9u349ybLyt4wyG9fFa/RCuI2dXw==", "dev": true }, + "node_modules/esbuild-runner": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz", + "integrity": "sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==", + "dev": true, + "dependencies": { + "source-map-support": "0.5.21", + "tslib": "2.4.0" + }, + "bin": { + "esr": "bin/esr.js" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/esbuild-runner/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, "node_modules/esbuild-sunos-64": { "version": "0.15.18", "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", @@ -13283,6 +13306,24 @@ "integrity": "sha512-Jo1S3rczXupUzPGt4eXF643FF/J7nDkwwwHH2PS6ic1l0ye7kTS0plAJQoD9u349ybLyt4wyG9fFa/RCuI2dXw==", "dev": true }, + "esbuild-runner": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz", + "integrity": "sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==", + "dev": true, + "requires": { + "source-map-support": "0.5.21", + "tslib": "2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + } + } + }, "esbuild-sunos-64": { "version": "0.15.18", "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", diff --git a/package.json b/package.json index 7667646f9b..162779976d 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", + "esbuild-runner": "^2.2.2", "eslint": "^7.13.0", "eslint-plugin-import": "^2.28.0", "eslint-plugin-jsdoc": "^40.0.0", @@ -128,10 +129,10 @@ "lint": "eslint .", "lint:fix": "eslint --fix .", "prepare": "npm run build", - "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js docs/chrome-mv3.md", - "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.js", + "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md", + "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s", "sourcemap": "source-map-explorer build/ably.min.js", - "modulereport": "node scripts/moduleReport.js", + "modulereport": "tsc --noEmit scripts/moduleReport.ts && esr scripts/moduleReport.ts", "docs": "typedoc --entryPoints ably.d.ts --out typedoc/generated/default --readme typedoc/landing-pages/default.md && typedoc --entryPoints modules.d.ts --out typedoc/generated/modules --name \"ably (modular version)\" --readme typedoc/landing-pages/modules.md && cp typedoc/landing-pages/choose-library.html typedoc/generated/index.html" } } diff --git a/scripts/moduleReport.js b/scripts/moduleReport.ts similarity index 96% rename from scripts/moduleReport.js rename to scripts/moduleReport.ts index dd1ec550f5..d34be0c820 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.ts @@ -1,4 +1,4 @@ -const esbuild = require('esbuild'); +import * as esbuild from 'esbuild'; // List of all modules accepted in ModulesMap const moduleNames = [ @@ -28,14 +28,14 @@ const functions = [ { name: 'constructPresenceMessage', transitiveImports: [] }, ]; -function formatBytes(bytes) { +function formatBytes(bytes: number) { const kibibytes = bytes / 1024; const formatted = kibibytes.toFixed(2); return `${formatted} KiB`; } // Gets the bundled size in bytes of an array of named exports from 'ably/modules' -function getImportSize(modules) { +function getImportSize(modules: string[]) { const outfile = modules.join(''); const result = esbuild.buildSync({ stdin: { @@ -52,7 +52,7 @@ function getImportSize(modules) { return result.metafile.outputs[outfile].bytes; } -const errors = []; +const errors: Error[] = []; ['BaseRest', 'BaseRealtime'].forEach((baseClient) => { const baseClientSize = getImportSize([baseClient]); From 1a35f2ebdf079451d483f900d099bf15dfff0a5f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 17 Nov 2023 11:53:35 -0300 Subject: [PATCH 212/468] Split modulereport script into functions --- scripts/moduleReport.ts | 93 ++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index d34be0c820..2a765a15fd 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -52,53 +52,70 @@ function getImportSize(modules: string[]) { return result.metafile.outputs[outfile].bytes; } -const errors: Error[] = []; +function printAndCheckModuleSizes() { + const errors: Error[] = []; -['BaseRest', 'BaseRealtime'].forEach((baseClient) => { - const baseClientSize = getImportSize([baseClient]); + ['BaseRest', 'BaseRealtime'].forEach((baseClient) => { + const baseClientSize = getImportSize([baseClient]); - // First display the size of the base client - console.log(`${baseClient}: ${formatBytes(baseClientSize)}`); + // First display the size of the base client + console.log(`${baseClient}: ${formatBytes(baseClientSize)}`); - // Then display the size of each export together with the base client - [...moduleNames, ...Object.values(functions).map((functionData) => functionData.name)].forEach((exportName) => { - const size = getImportSize([baseClient, exportName]); - console.log(`${baseClient} + ${exportName}: ${formatBytes(size)}`); + // Then display the size of each export together with the base client + [...moduleNames, ...Object.values(functions).map((functionData) => functionData.name)].forEach((exportName) => { + const size = getImportSize([baseClient, exportName]); + console.log(`${baseClient} + ${exportName}: ${formatBytes(size)}`); - if (!(baseClientSize < size) && !(baseClient === 'BaseRest' && exportName === 'Rest')) { - // Emit an error if adding the module does not increase the bundle size - // (this means that the module is not being tree-shaken correctly). - errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); - } + if (!(baseClientSize < size) && !(baseClient === 'BaseRest' && exportName === 'Rest')) { + // Emit an error if adding the module does not increase the bundle size + // (this means that the module is not being tree-shaken correctly). + errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); + } + }); }); -}); - -for (const functionData of functions) { - const { name: functionName, transitiveImports } = functionData; - - // First display the size of the function - const standaloneSize = getImportSize([functionName]); - console.log(`${functionName}: ${formatBytes(standaloneSize)}`); - - // Then display the size of the function together with the modules we expect - // it to transitively import - if (transitiveImports.length > 0) { - const withTransitiveImportsSize = getImportSize([functionName, ...transitiveImports]); - console.log(`${functionName} + ${transitiveImports.join(' + ')}: ${formatBytes(withTransitiveImportsSize)}`); - - if (withTransitiveImportsSize > standaloneSize) { - // Emit an error if the bundle size is increased by adding the modules - // that we expect this function to have transitively imported anyway. - // This seemed like a useful sense check, but it might need tweaking in - // the future if we make future optimisations that mean that the - // standalone functions don’t necessarily import the whole module. - errors.push( - new Error(`Adding ${transitiveImports.join(' + ')} to ${functionName} unexpectedly increases the bundle size.`) - ); + + return errors; +} + +function printAndCheckFunctionSizes() { + const errors: Error[] = []; + + for (const functionData of functions) { + const { name: functionName, transitiveImports } = functionData; + + // First display the size of the function + const standaloneSize = getImportSize([functionName]); + console.log(`${functionName}: ${formatBytes(standaloneSize)}`); + + // Then display the size of the function together with the modules we expect + // it to transitively import + if (transitiveImports.length > 0) { + const withTransitiveImportsSize = getImportSize([functionName, ...transitiveImports]); + console.log(`${functionName} + ${transitiveImports.join(' + ')}: ${formatBytes(withTransitiveImportsSize)}`); + + if (withTransitiveImportsSize > standaloneSize) { + // Emit an error if the bundle size is increased by adding the modules + // that we expect this function to have transitively imported anyway. + // This seemed like a useful sense check, but it might need tweaking in + // the future if we make future optimisations that mean that the + // standalone functions don’t necessarily import the whole module. + errors.push( + new Error( + `Adding ${transitiveImports.join(' + ')} to ${functionName} unexpectedly increases the bundle size.` + ) + ); + } } } + + return errors; } +const errors: Error[] = []; + +errors.push(...printAndCheckModuleSizes()); +errors.push(...printAndCheckFunctionSizes()); + if (errors.length > 0) { for (const error of errors) { console.log(error.message); From bfb3dfdf52ea4f2fce43053dfb6deedc90e7dec3 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 17 Nov 2023 11:19:44 -0300 Subject: [PATCH 213/468] Convert moduleReport.ts to IIFE Want to use `await` in an upcoming commit. --- scripts/moduleReport.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 2a765a15fd..42336349c8 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -111,14 +111,16 @@ function printAndCheckFunctionSizes() { return errors; } -const errors: Error[] = []; +(function run() { + const errors: Error[] = []; -errors.push(...printAndCheckModuleSizes()); -errors.push(...printAndCheckFunctionSizes()); + errors.push(...printAndCheckModuleSizes()); + errors.push(...printAndCheckFunctionSizes()); -if (errors.length > 0) { - for (const error of errors) { - console.log(error.message); + if (errors.length > 0) { + for (const error of errors) { + console.log(error.message); + } + process.exit(1); } - process.exit(1); -} +})(); From 1826708ed3a8ac8b0288afb605f239f4f9821ac7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 17 Nov 2023 10:23:14 -0300 Subject: [PATCH 214/468] Perform a check of which files contribute to BaseRealtime bundle size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To give us further confidence that something unexpected won’t sneak in. This just automates a rather crude process that I’ve been performing manually from time to time — tweak the moduleReport script to spit out source file + source map, then run source-map-explorer on the output and look at which files are making a significant contribution to the bundle size. Part of #1497. --- scripts/moduleReport.ts | 127 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 42336349c8..e03fd6662f 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -1,4 +1,6 @@ import * as esbuild from 'esbuild'; +import * as path from 'path'; +import { explore } from 'source-map-explorer'; // List of all modules accepted in ModulesMap const moduleNames = [ @@ -34,8 +36,14 @@ function formatBytes(bytes: number) { return `${formatted} KiB`; } -// Gets the bundled size in bytes of an array of named exports from 'ably/modules' -function getImportSize(modules: string[]) { +interface BundleInfo { + byteSize: number; + code: Uint8Array; + sourceMap: Uint8Array; +} + +// Uses esbuild to create a bundle containing the named exports from 'ably/modules' +function getBundleInfo(modules: string[]): BundleInfo { const outfile = modules.join(''); const result = esbuild.buildSync({ stdin: { @@ -47,9 +55,36 @@ function getImportSize(modules: string[]) { bundle: true, outfile, write: false, + sourcemap: 'external', }); - return result.metafile.outputs[outfile].bytes; + const pathHasBase = (component: string) => { + return (outputFile: esbuild.OutputFile) => { + return path.parse(outputFile.path).base === component; + }; + }; + + const codeOutputFile = result.outputFiles.find(pathHasBase(outfile))!; + const sourceMapOutputFile = result.outputFiles.find(pathHasBase(`${outfile}.map`))!; + + return { + byteSize: result.metafile.outputs[outfile].bytes, + code: codeOutputFile.contents, + sourceMap: sourceMapOutputFile.contents, + }; +} + +// Gets the bundled size in bytes of an array of named exports from 'ably/modules' +function getImportSize(modules: string[]) { + const bundleInfo = getBundleInfo(modules); + return bundleInfo.byteSize; +} + +async function runSourceMapExplorer(bundleInfo: BundleInfo) { + return explore({ + code: Buffer.from(bundleInfo.code), + map: Buffer.from(bundleInfo.sourceMap), + }); } function printAndCheckModuleSizes() { @@ -111,11 +146,95 @@ function printAndCheckFunctionSizes() { return errors; } -(function run() { +// Performs a sense check that there are no unexpected files making a large contribution to the BaseRealtime bundle size. +async function checkBaseRealtimeFiles() { + const baseRealtimeBundleInfo = getBundleInfo(['BaseRealtime']); + const exploreResult = await runSourceMapExplorer(baseRealtimeBundleInfo); + + const files = exploreResult.bundles[0].files; + delete files['[sourceMappingURL]']; + delete files['[unmapped]']; + delete files['[EOLs]']; + + const thresholdBytes = 100; + const filesAboveThreshold = Object.entries(files).filter((file) => file[1].size >= thresholdBytes); + + // These are the files that are allowed to contribute >= `threshold` bytes to the BaseRealtime bundle. + // + // The threshold is chosen pretty arbitrarily. There are some files (e.g. presencemessage.ts) whose bulk should not be included in the BaseRealtime bundle, but which make a small contribution to the bundle (probably because we make use of one exported constant or something; I haven’t looked into it). + const allowedFiles = new Set([ + 'src/common/constants/HttpStatusCodes.ts', + 'src/common/constants/TransportName.ts', + 'src/common/constants/XHRStates.ts', + 'src/common/lib/client/auth.ts', + 'src/common/lib/client/baseclient.ts', + 'src/common/lib/client/baserealtime.ts', + 'src/common/lib/client/channelstatechange.ts', + 'src/common/lib/client/connection.ts', + 'src/common/lib/client/connectionstatechange.ts', + 'src/common/lib/client/realtimechannel.ts', + 'src/common/lib/transport/connectionerrors.ts', + 'src/common/lib/transport/connectionmanager.ts', + 'src/common/lib/transport/messagequeue.ts', + 'src/common/lib/transport/protocol.ts', + 'src/common/lib/transport/transport.ts', + 'src/common/lib/types/errorinfo.ts', + 'src/common/lib/types/message.ts', + 'src/common/lib/types/protocolmessage.ts', + 'src/common/lib/types/pushchannelsubscription.ts', // TODO why? https://github.com/ably/ably-js/issues/1506 + 'src/common/lib/util/defaults.ts', + 'src/common/lib/util/eventemitter.ts', + 'src/common/lib/util/logger.ts', + 'src/common/lib/util/multicaster.ts', + 'src/common/lib/util/utils.ts', + 'src/platform/web/config.ts', + 'src/platform/web/lib/http/http.ts', + 'src/platform/web/lib/util/bufferutils.ts', + 'src/platform/web/lib/util/defaults.ts', + 'src/platform/web/lib/util/hmac-sha256.ts', + 'src/platform/web/lib/util/webstorage.ts', + 'src/platform/web/modules.ts', + ]); + + const errors: Error[] = []; + + // Check that no files other than those allowed above make a large contribution to the bundle size + for (const file of filesAboveThreshold) { + if (!allowedFiles.has(file[0])) { + errors.push( + new Error( + `Unexpected file ${file[0]}, contributes ${file[1].size}B to BaseRealtime, more than allowed ${thresholdBytes}B` + ) + ); + } + } + + // Check that there are no stale entries in the allowed list + for (const allowedFile of Array.from(allowedFiles)) { + const file = files[allowedFile]; + + if (file) { + if (file.size < thresholdBytes) { + errors.push( + new Error( + `File ${allowedFile} contributes ${file.size}B, which is less than the expected minimum of ${thresholdBytes}B. Remove it from the \`allowedFiles\` list.` + ) + ); + } + } else { + errors.push(new Error(`File ${allowedFile} is referenced in \`allowedFiles\` but does not contribute to bundle`)); + } + } + + return errors; +} + +(async function run() { const errors: Error[] = []; errors.push(...printAndCheckModuleSizes()); errors.push(...printAndCheckFunctionSizes()); + errors.push(...(await checkBaseRealtimeFiles())); if (errors.length > 0) { for (const error of errors) { From 3f12e9c7d55793d674347b4e6fd906150a37f8a4 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 17 Nov 2023 14:16:59 -0300 Subject: [PATCH 215/468] Perform a check on bundle size of minimal useful Realtime client Another protection against regressions in bundle size. Resolves #1497. --- scripts/moduleReport.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index e03fd6662f..f3deda7b30 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -2,6 +2,9 @@ import * as esbuild from 'esbuild'; import * as path from 'path'; import { explore } from 'source-map-explorer'; +// The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) +const minimalUsefulRealtimeBundleSizeThresholdKiB = 108; + // List of all modules accepted in ModulesMap const moduleNames = [ 'Rest', @@ -146,6 +149,27 @@ function printAndCheckFunctionSizes() { return errors; } +function printAndCheckMinimalUsefulRealtimeBundleSize() { + const errors: Error[] = []; + + const exports = ['BaseRealtime', 'FetchRequest', 'WebSocketTransport']; + const size = getImportSize(exports); + + console.log(`Minimal useful Realtime (${exports.join(' + ')}): ${formatBytes(size)}`); + + if (size > minimalUsefulRealtimeBundleSizeThresholdKiB * 1024) { + errors.push( + new Error( + `Minimal useful Realtime bundle is ${formatBytes( + size + )}, which is greater than allowed maximum of ${minimalUsefulRealtimeBundleSizeThresholdKiB} KiB.` + ) + ); + } + + return errors; +} + // Performs a sense check that there are no unexpected files making a large contribution to the BaseRealtime bundle size. async function checkBaseRealtimeFiles() { const baseRealtimeBundleInfo = getBundleInfo(['BaseRealtime']); @@ -232,6 +256,7 @@ async function checkBaseRealtimeFiles() { (async function run() { const errors: Error[] = []; + errors.push(...printAndCheckMinimalUsefulRealtimeBundleSize()); errors.push(...printAndCheckModuleSizes()); errors.push(...printAndCheckFunctionSizes()); errors.push(...(await checkBaseRealtimeFiles())); From f6b834d238c2adabfef1418e45eaaa330fbf59cc Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 28 Nov 2023 11:39:41 -0300 Subject: [PATCH 216/468] Add stats to list of functionality gated off by Rest module Missed this in 01775fd. --- modules.d.ts | 1 + test/browser/modules.test.js | 1 + 2 files changed, 2 insertions(+) diff --git a/modules.d.ts b/modules.d.ts index 0ea246a164..2c3ea38bdf 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -24,6 +24,7 @@ export declare const constructPresenceMessage: Types.PresenceMessageStatic['from * * - { @link Types.Push | push admin } * - { @link BaseRealtime.time | retrieving Ably service time } + * - { @link BaseRealtime.stats | retrieving your application’s usage statistics } * - { @link BaseRealtime.request | making arbitrary REST requests } * - { @link BaseRealtime.batchPublish | batch publishing of messages } * - { @link BaseRealtime.batchPresence | batch retrieval of channel presence state } diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index c7bdc90432..3fd5495a7f 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -68,6 +68,7 @@ describe('browser/modules', function () { action: (client) => client.push.admin.publish({ clientId: 'foo' }, { data: { bar: 'baz' } }), }, { description: 'call `time()`', action: (client) => client.time() }, + { description: 'call `stats()`', action: (client) => client.stats() }, { description: 'call `request(...)`', action: (client) => client.request('get', '/channels/channel', 2) }, { description: 'call `batchPublish(...)`', From 562aceded7acc57a5f70980dfa567f576d884988 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 27 Nov 2023 10:48:21 -0300 Subject: [PATCH 217/468] Update vcdiff-decoder to 1.0.6 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0c3388fd6..58689871d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "ws": "^8.14.2" }, "devDependencies": { - "@ably/vcdiff-decoder": "1.0.4", + "@ably/vcdiff-decoder": "1.0.6", "@testing-library/react": "^13.3.0", "@types/node": "^18.0.0", "@types/request": "^2.48.7", @@ -100,9 +100,9 @@ } }, "node_modules/@ably/vcdiff-decoder": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@ably/vcdiff-decoder/-/vcdiff-decoder-1.0.4.tgz", - "integrity": "sha512-D2j7j+keGWOI48ahTjtKmPEahSXtnm2PLVSp1fDCctibyGd/ywRWyJP2TzNtVMwL1IDD9PAaKPPK3+cTo0WP3g==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@ably/vcdiff-decoder/-/vcdiff-decoder-1.0.6.tgz", + "integrity": "sha512-VdA8vat5GIaG7gQaUL4ZpF5x3iHH9IolfcztEtQXJa7OSw++G/x9UrNQFXsdv4ZyiMAti6bRMR70G1zNkL67/g==", "dev": true }, "node_modules/@ampproject/remapping": { @@ -10683,9 +10683,9 @@ } }, "@ably/vcdiff-decoder": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@ably/vcdiff-decoder/-/vcdiff-decoder-1.0.4.tgz", - "integrity": "sha512-D2j7j+keGWOI48ahTjtKmPEahSXtnm2PLVSp1fDCctibyGd/ywRWyJP2TzNtVMwL1IDD9PAaKPPK3+cTo0WP3g==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@ably/vcdiff-decoder/-/vcdiff-decoder-1.0.6.tgz", + "integrity": "sha512-VdA8vat5GIaG7gQaUL4ZpF5x3iHH9IolfcztEtQXJa7OSw++G/x9UrNQFXsdv4ZyiMAti6bRMR70G1zNkL67/g==", "dev": true }, "@ampproject/remapping": { diff --git a/package.json b/package.json index 162779976d..299aa4f043 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ } }, "devDependencies": { - "@ably/vcdiff-decoder": "1.0.4", + "@ably/vcdiff-decoder": "1.0.6", "@testing-library/react": "^13.3.0", "@types/node": "^18.0.0", "@types/request": "^2.48.7", From 7859aca6cec8ee13a29dc21a2db65baa4863b55e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 20 Nov 2023 10:15:05 -0300 Subject: [PATCH 218/468] Fix type of RealtimeChannel._decodingContext --- src/common/lib/client/realtimechannel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 6b6f7bad67..e83c32e25f 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -14,6 +14,7 @@ import Message, { decode as decodeMessage, getMessagesSize, CipherOptions, + EncodingDecodingContext, } from '../types/message'; import ChannelStateChange from './channelstatechange'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; @@ -86,7 +87,7 @@ class RealtimeChannel extends EventEmitter { _requestedFlags: Array | null; _mode?: null | number; _attachResume: boolean; - _decodingContext: { channelOptions: API.Types.ChannelOptions; plugins: any; baseEncodedPreviousPayload: undefined }; + _decodingContext: EncodingDecodingContext; _lastPayload: { messageId?: string | null; protocolMessageChannelSerial?: string | null; From 7f7fe75b75e541156b5751e30e89bf7c35aa2f10 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 21 Nov 2023 16:59:29 -0300 Subject: [PATCH 219/468] Remove unused property I suspect this was a copy-and-paste error in a7d9a5e. --- src/common/lib/types/message.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 75fd1e33fe..7450000208 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -26,7 +26,6 @@ export type EncodingDecodingContext = { channelOptions: ChannelOptions; plugins: { vcdiff?: { - encrypt: Function; decode: Function; }; }; From 4fe4e0b0fcad97c5f0eff4381c1340f759d519e8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 21 Nov 2023 17:03:11 -0300 Subject: [PATCH 220/468] Tighten up type of EncodingDecodingContext.plugins.vcdiff.decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make it match the signature of @ably/vcdiff-decoder’s `decode`. --- src/common/lib/types/message.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 7450000208..1f149772ed 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -26,7 +26,7 @@ export type EncodingDecodingContext = { channelOptions: ChannelOptions; plugins: { vcdiff?: { - decode: Function; + decode: (delta: Uint8Array, source: Uint8Array) => Uint8Array; }; }; baseEncodedPreviousPayload?: Buffer | BrowserBufferlike; @@ -233,10 +233,10 @@ export async function decode( } // vcdiff expects Uint8Arrays, can't copy with ArrayBuffers. - deltaBase = Platform.BufferUtils.toBuffer(deltaBase as Buffer); + const deltaBaseBuffer = Platform.BufferUtils.toBuffer(deltaBase as Buffer); data = Platform.BufferUtils.toBuffer(data); - data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBase)); + data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBaseBuffer)); lastPayload = data; } catch (e) { throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400); From b939b5daf1d51a4a6b77d572ccd5c30450693395 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 21 Nov 2023 17:21:29 -0300 Subject: [PATCH 221/468] Fix missing module error Fix inverted code and statusCode, and use the "Required client library plugin not present" [1] error code (which we currently use when the vcdiff plugin is missing). [1] https://github.com/ably/ably-common/blob/main/protocol/errors.json --- src/common/lib/util/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 494a4b6a3e..9388ad2db2 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -569,5 +569,5 @@ export function arrEquals(a: any[], b: any[]) { } export function throwMissingModuleError(moduleName: keyof ModulesMap): never { - throw new ErrorInfo(`${moduleName} module not provided`, 400, 40000); + throw new ErrorInfo(`${moduleName} module not provided`, 40019, 400); } From 09354d4b78398c9fb8ff954813def7253c5cda67 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 21 Nov 2023 14:38:33 -0300 Subject: [PATCH 222/468] Extract delta tests into a reusable function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m going to use them inside `modules.test.js` as part of #1492 (removing the ClientOptions.plugins mechanism). --- test/common/globals/named_dependencies.js | 1 + test/realtime/delta.test.js | 275 +-------------------- test/realtime/shared/delta_tests.js | 284 ++++++++++++++++++++++ 3 files changed, 287 insertions(+), 273 deletions(-) create mode 100644 test/realtime/shared/delta_tests.js diff --git a/test/common/globals/named_dependencies.js b/test/common/globals/named_dependencies.js index 0558b575e7..a7234844ec 100644 --- a/test/common/globals/named_dependencies.js +++ b/test/common/globals/named_dependencies.js @@ -13,5 +13,6 @@ define(function () { shared_helper: { browser: 'test/common/modules/shared_helper', node: 'test/common/modules/shared_helper' }, async: { browser: 'node_modules/async/lib/async' }, chai: { browser: 'node_modules/chai/chai', node: 'node_modules/chai/chai' }, + delta_tests: { browser: 'test/realtime/shared/delta_tests', node: 'test/realtime/shared/delta_tests' }, }); }); diff --git a/test/realtime/delta.test.js b/test/realtime/delta.test.js index 006133c26d..83575f46f5 100644 --- a/test/realtime/delta.test.js +++ b/test/realtime/delta.test.js @@ -1,276 +1,5 @@ 'use strict'; -define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, vcdiffDecoder, async, chai) { - var expect = chai.expect; - var displayError = helper.displayError; - var closeAndFinish = helper.closeAndFinish; - var monitorConnection = helper.monitorConnection; - var whenPromiseSettles = helper.whenPromiseSettles; - var testData = [ - { foo: 'bar', count: 1, status: 'active' }, - { foo: 'bar', count: 2, status: 'active' }, - { foo: 'bar', count: 2, status: 'inactive' }, - { foo: 'bar', count: 3, status: 'inactive' }, - { foo: 'bar', count: 3, status: 'active' }, - ]; - - function equals(a, b) { - return JSON.stringify(a) === JSON.stringify(b); - } - - function getTestVcdiffDecoder() { - return { - numberOfCalls: 0, - decode: function (delta, base) { - this.numberOfCalls++; - return vcdiffDecoder.decode(delta, base); - }, - }; - } - - describe('realtime/delta', function () { - this.timeout(60 * 1000); - - before(function (done) { - helper.setupApp(function (err) { - if (err) { - done(err); - } - done(); - }); - }); - - it('deltaPlugin', function (done) { - var testName = 'deltaPlugin'; - try { - var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: testVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - - channel.on('attaching', function (stateChange) { - done( - new Error( - 'Channel reattaching, presumably due to decode failure; reason =' + displayError(stateChange.reason) - ) - ); - }); - - channel.subscribe(function (message) { - try { - var index = Number(message.name); - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; - - if (index === testData.length - 1) { - expect(testVcdiffDecoder.numberOfCalls).to.equal(testData.length - 1, 'Check number of delta messages'); - closeAndFinish(done, realtime); - } - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); - }); - }); - - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - it('unusedPlugin', function (done) { - var testName = 'unusedPlugin'; - try { - var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: testVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.subscribe(function (message) { - try { - var index = Number(message.name); - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; - - if (index === testData.length - 1) { - expect(testVcdiffDecoder.numberOfCalls).to.equal(0, 'Check number of delta messages'); - closeAndFinish(done, realtime); - } - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); - }); - }); - - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - it('lastMessageNotFoundRecovery', function (done) { - var testName = 'lastMessageNotFoundRecovery'; - try { - var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: testVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.subscribe(function (message) { - var index = Number(message.name); - try { - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; - } catch (err) { - closeAndFinish(done, realtime, err); - } - - if (index === 1) { - /* Simulate issue */ - channel._lastPayload.messageId = null; - channel.once('attaching', function (stateChange) { - try { - expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - channel.on('attaching', function (stateChange) { - closeAndFinish( - done, - realtime, - new Error('Check no further decode failures; reason =' + displayError(stateChange.reason)) - ); - }); - }); - } else if (index === testData.length - 1) { - try { - expect(testVcdiffDecoder.numberOfCalls).to.equal(testData.length - 2, 'Check number of delta messages'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); - } - }); - - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); - }); - }); - - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - it('deltaDecodeFailureRecovery', function (done) { - var testName = 'deltaDecodeFailureRecovery'; - try { - var failingTestVcdiffDecoder = { - decode: function (delta, base) { - throw new Error('Failed to decode delta.'); - }, - }; - - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: failingTestVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.on('attaching', function (stateChange) { - try { - expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - channel.subscribe(function (message) { - var index = Number(message.name); - try { - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; - } catch (err) { - closeAndFinish(done, realtime, err); - } - - if (index === testData.length - 1) { - closeAndFinish(done, realtime); - } - }); - - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); - }); - }); - - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* Check that channel becomes failed if we get deltas when we don't have a vcdiff plugin */ - it('noPlugin', function (done) { - try { - var realtime = helper.AblyRealtime(); - var channel = realtime.channels.get('noPlugin', { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.once('failed', function (stateChange) { - try { - expect(stateChange.reason.code).to.equal(40019, 'Check error code'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); - }); - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); - }); - }); - - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - }); +define(['delta_tests'], function (registerDeltaTests) { + registerDeltaTests('realtime/delta'); }); diff --git a/test/realtime/shared/delta_tests.js b/test/realtime/shared/delta_tests.js new file mode 100644 index 0000000000..20ccf8183e --- /dev/null +++ b/test/realtime/shared/delta_tests.js @@ -0,0 +1,284 @@ +define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, vcdiffDecoder, async, chai) { + function registerDeltaTests(describeLabel) { + var expect = chai.expect; + var displayError = helper.displayError; + var closeAndFinish = helper.closeAndFinish; + var monitorConnection = helper.monitorConnection; + var whenPromiseSettles = helper.whenPromiseSettles; + var testData = [ + { foo: 'bar', count: 1, status: 'active' }, + { foo: 'bar', count: 2, status: 'active' }, + { foo: 'bar', count: 2, status: 'inactive' }, + { foo: 'bar', count: 3, status: 'inactive' }, + { foo: 'bar', count: 3, status: 'active' }, + ]; + + function equals(a, b) { + return JSON.stringify(a) === JSON.stringify(b); + } + + function getTestVcdiffDecoder() { + return { + numberOfCalls: 0, + decode: function (delta, base) { + this.numberOfCalls++; + return vcdiffDecoder.decode(delta, base); + }, + }; + } + + describe(describeLabel, function () { + this.timeout(60 * 1000); + + before(function (done) { + helper.setupApp(function (err) { + if (err) { + done(err); + } + done(); + }); + }); + + it('deltaPlugin', function (done) { + var testName = 'deltaPlugin'; + try { + var testVcdiffDecoder = getTestVcdiffDecoder(); + var realtime = helper.AblyRealtime({ + plugins: { + vcdiff: testVcdiffDecoder, + }, + }); + var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); + + whenPromiseSettles(channel.attach(), function (err) { + if (err) { + closeAndFinish(done, realtime, err); + } + + channel.on('attaching', function (stateChange) { + done( + new Error( + 'Channel reattaching, presumably due to decode failure; reason =' + displayError(stateChange.reason) + ) + ); + }); + + channel.subscribe(function (message) { + try { + var index = Number(message.name); + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + + if (index === testData.length - 1) { + expect(testVcdiffDecoder.numberOfCalls).to.equal( + testData.length - 1, + 'Check number of delta messages' + ); + closeAndFinish(done, realtime); + } + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); + }); + + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + it('unusedPlugin', function (done) { + var testName = 'unusedPlugin'; + try { + var testVcdiffDecoder = getTestVcdiffDecoder(); + var realtime = helper.AblyRealtime({ + plugins: { + vcdiff: testVcdiffDecoder, + }, + }); + var channel = realtime.channels.get(testName); + + whenPromiseSettles(channel.attach(), function (err) { + if (err) { + closeAndFinish(done, realtime, err); + } + channel.subscribe(function (message) { + try { + var index = Number(message.name); + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + + if (index === testData.length - 1) { + expect(testVcdiffDecoder.numberOfCalls).to.equal(0, 'Check number of delta messages'); + closeAndFinish(done, realtime); + } + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); + }); + + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + it('lastMessageNotFoundRecovery', function (done) { + var testName = 'lastMessageNotFoundRecovery'; + try { + var testVcdiffDecoder = getTestVcdiffDecoder(); + var realtime = helper.AblyRealtime({ + plugins: { + vcdiff: testVcdiffDecoder, + }, + }); + var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); + + whenPromiseSettles(channel.attach(), function (err) { + if (err) { + closeAndFinish(done, realtime, err); + } + channel.subscribe(function (message) { + var index = Number(message.name); + try { + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + } catch (err) { + closeAndFinish(done, realtime, err); + } + + if (index === 1) { + /* Simulate issue */ + channel._lastPayload.messageId = null; + channel.once('attaching', function (stateChange) { + try { + expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + channel.on('attaching', function (stateChange) { + closeAndFinish( + done, + realtime, + new Error('Check no further decode failures; reason =' + displayError(stateChange.reason)) + ); + }); + }); + } else if (index === testData.length - 1) { + try { + expect(testVcdiffDecoder.numberOfCalls).to.equal( + testData.length - 2, + 'Check number of delta messages' + ); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + } + }); + + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); + }); + + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + it('deltaDecodeFailureRecovery', function (done) { + var testName = 'deltaDecodeFailureRecovery'; + try { + var failingTestVcdiffDecoder = { + decode: function (delta, base) { + throw new Error('Failed to decode delta.'); + }, + }; + + var realtime = helper.AblyRealtime({ + plugins: { + vcdiff: failingTestVcdiffDecoder, + }, + }); + var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); + + whenPromiseSettles(channel.attach(), function (err) { + if (err) { + closeAndFinish(done, realtime, err); + } + channel.on('attaching', function (stateChange) { + try { + expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + channel.subscribe(function (message) { + var index = Number(message.name); + try { + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + } catch (err) { + closeAndFinish(done, realtime, err); + } + + if (index === testData.length - 1) { + closeAndFinish(done, realtime); + } + }); + + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); + }); + + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + /* Check that channel becomes failed if we get deltas when we don't have a vcdiff plugin */ + it('noPlugin', function (done) { + try { + var realtime = helper.AblyRealtime(); + var channel = realtime.channels.get('noPlugin', { params: { delta: 'vcdiff' } }); + + whenPromiseSettles(channel.attach(), function (err) { + if (err) { + closeAndFinish(done, realtime, err); + } + channel.once('failed', function (stateChange) { + try { + expect(stateChange.reason.code).to.equal(40019, 'Check error code'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }); + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); + }); + + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + }); + } + + return (module.exports = registerDeltaTests); +}); From 8f2804bba1ae086ad994f3d5ebf2de3e7d12c7c1 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 21 Nov 2023 15:14:31 -0300 Subject: [PATCH 223/468] Make RequireJS work in modules.test.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This gives these tests access to the same test helper APIs that the other ones use. And, it means that we can access test helpers inside the body of a `describe` block, which I’ll need in an upcoming commit in order to call the shared delta tests added in 09354d4. --- test/browser/modules.test.js | 1080 +++++++++++++------------- test/common/modules/shared_helper.js | 10 +- test/support/browser_setup.js | 12 +- 3 files changed, 554 insertions(+), 548 deletions(-) diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 3fd5495a7f..aa96b3a62d 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -22,695 +22,699 @@ import { MessageInteractions, } from '../../build/modules/index.js'; -describe('browser/modules', function () { - this.timeout(10 * 1000); - const expect = chai.expect; - const BufferUtils = BaseRest.Platform.BufferUtils; - let ablyClientOptions; - let testResourcesPath; - let loadTestData; - let testMessageEquality; - let randomString; - let getTestApp; - - before((done) => { - ablyClientOptions = window.ablyHelpers.ablyClientOptions; - testResourcesPath = window.ablyHelpers.testResourcesPath; - testMessageEquality = window.ablyHelpers.testMessageEquality; - randomString = window.ablyHelpers.randomString; - getTestApp = window.ablyHelpers.getTestApp; - - loadTestData = async (dataPath) => { +function registerAblyModulesTests(helper) { + describe('browser/modules', function () { + this.timeout(10 * 1000); + const expect = chai.expect; + const BufferUtils = BaseRest.Platform.BufferUtils; + const ablyClientOptions = helper.ablyClientOptions; + const testResourcesPath = helper.testResourcesPath; + const testMessageEquality = helper.testMessageEquality; + const randomString = helper.randomString; + const getTestApp = helper.getTestApp; + const loadTestData = async (dataPath) => { return new Promise((resolve, reject) => { - window.ablyHelpers.loadTestData(dataPath, (err, testData) => (err ? reject(err) : resolve(testData))); + helper.loadTestData(dataPath, (err, testData) => (err ? reject(err) : resolve(testData))); }); }; - window.ablyHelpers.setupApp(done); - }); + before((done) => { + helper.setupApp(done); + }); - describe('without any modules', () => { - for (const clientClass of [BaseRest, BaseRealtime]) { - describe(clientClass.name, () => { - it('throws an error due to the absence of an HTTP module', () => { - expect(() => new clientClass(ablyClientOptions(), {})).to.throw( - 'No HTTP request module provided. Provide at least one of the FetchRequest or XHRRequest modules.' - ); + describe('without any modules', () => { + for (const clientClass of [BaseRest, BaseRealtime]) { + describe(clientClass.name, () => { + it('throws an error due to the absence of an HTTP module', () => { + expect(() => new clientClass(ablyClientOptions(), {})).to.throw( + 'No HTTP request module provided. Provide at least one of the FetchRequest or XHRRequest modules.' + ); + }); }); - }); - } - }); + } + }); - describe('Rest', () => { - const restScenarios = [ - { - description: 'use push admin functionality', - action: (client) => client.push.admin.publish({ clientId: 'foo' }, { data: { bar: 'baz' } }), - }, - { description: 'call `time()`', action: (client) => client.time() }, - { description: 'call `stats()`', action: (client) => client.stats() }, - { description: 'call `request(...)`', action: (client) => client.request('get', '/channels/channel', 2) }, - { - description: 'call `batchPublish(...)`', - action: (client) => client.batchPublish({ channels: ['channel'], messages: { data: { foo: 'bar' } } }), - }, - { - description: 'call `batchPresence(...)`', - action: (client) => client.batchPresence(['channel']), - }, - { - description: 'call `auth.revokeTokens(...)`', - getAdditionalClientOptions: () => { - const testApp = getTestApp(); - return { key: testApp.keys[4].keyStr /* this key has revocableTokens enabled */ }; + describe('Rest', () => { + const restScenarios = [ + { + description: 'use push admin functionality', + action: (client) => client.push.admin.publish({ clientId: 'foo' }, { data: { bar: 'baz' } }), }, - action: (client) => client.auth.revokeTokens([{ type: 'clientId', value: 'foo' }]), - }, - { - description: 'call channel’s `history()`', - action: (client) => client.channels.get('channel').history(), - }, - { - description: 'call channel’s `presence.history()`', - additionalRealtimeModules: { RealtimePresence }, - action: (client) => client.channels.get('channel').presence.history(), - }, - { - description: 'call channel’s `status()`', - action: (client) => client.channels.get('channel').status(), - }, - ]; - - describe('BaseRest without explicit Rest', () => { - for (const scenario of restScenarios) { - it(`allows you to ${scenario.description}`, async () => { - const client = new BaseRest(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { FetchRequest }); + { description: 'call `time()`', action: (client) => client.time() }, + { description: 'call `stats()`', action: (client) => client.stats() }, + { description: 'call `request(...)`', action: (client) => client.request('get', '/channels/channel', 2) }, + { + description: 'call `batchPublish(...)`', + action: (client) => client.batchPublish({ channels: ['channel'], messages: { data: { foo: 'bar' } } }), + }, + { + description: 'call `batchPresence(...)`', + action: (client) => client.batchPresence(['channel']), + }, + { + description: 'call `auth.revokeTokens(...)`', + getAdditionalClientOptions: () => { + const testApp = getTestApp(); + return { key: testApp.keys[4].keyStr /* this key has revocableTokens enabled */ }; + }, + action: (client) => client.auth.revokeTokens([{ type: 'clientId', value: 'foo' }]), + }, + { + description: 'call channel’s `history()`', + action: (client) => client.channels.get('channel').history(), + }, + { + description: 'call channel’s `presence.history()`', + additionalRealtimeModules: { RealtimePresence }, + action: (client) => client.channels.get('channel').presence.history(), + }, + { + description: 'call channel’s `status()`', + action: (client) => client.channels.get('channel').status(), + }, + ]; - let thrownError = null; - try { - await scenario.action(client); - } catch (error) { - thrownError = error; - } + describe('BaseRest without explicit Rest', () => { + for (const scenario of restScenarios) { + it(`allows you to ${scenario.description}`, async () => { + const client = new BaseRest(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { FetchRequest }); - expect(thrownError).to.be.null; - }); - } - }); + let thrownError = null; + try { + await scenario.action(client); + } catch (error) { + thrownError = error; + } - describe('BaseRealtime with Rest', () => { - for (const scenario of restScenarios) { - it(`allows you to ${scenario.description}`, async () => { - const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { - WebSocketTransport, - FetchRequest, - Rest, - ...scenario.additionalRealtimeModules, + expect(thrownError).to.be.null; }); + } + }); - let thrownError = null; - try { - await scenario.action(client); - } catch (error) { - thrownError = error; - } + describe('BaseRealtime with Rest', () => { + for (const scenario of restScenarios) { + it(`allows you to ${scenario.description}`, async () => { + const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { + WebSocketTransport, + FetchRequest, + Rest, + ...scenario.additionalRealtimeModules, + }); - expect(thrownError).to.be.null; - }); - } - }); + let thrownError = null; + try { + await scenario.action(client); + } catch (error) { + thrownError = error; + } - describe('BaseRealtime without Rest', () => { - it('still allows publishing and subscribing', async () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + expect(thrownError).to.be.null; + }); + } + }); - const channel = client.channels.get('channel'); - await channel.attach(); + describe('BaseRealtime without Rest', () => { + it('still allows publishing and subscribing', async () => { + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); - const recievedMessagePromise = new Promise((resolve) => { - channel.subscribe((message) => { - resolve(message); + const channel = client.channels.get('channel'); + await channel.attach(); + + const recievedMessagePromise = new Promise((resolve) => { + channel.subscribe((message) => { + resolve(message); + }); }); + + await channel.publish({ data: { foo: 'bar' } }); + + const receivedMessage = await recievedMessagePromise; + expect(receivedMessage.data).to.eql({ foo: 'bar' }); }); - await channel.publish({ data: { foo: 'bar' } }); + for (const scenario of restScenarios) { + it(`throws an error when attempting to ${scenario.description}`, async () => { + const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { + WebSocketTransport, + FetchRequest, + ...scenario.additionalRealtimeModules, + }); - const receivedMessage = await recievedMessagePromise; - expect(receivedMessage.data).to.eql({ foo: 'bar' }); - }); + let thrownError = null; + try { + await scenario.action(client); + } catch (error) { + thrownError = error; + } - for (const scenario of restScenarios) { - it(`throws an error when attempting to ${scenario.description}`, async () => { - const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { - WebSocketTransport, - FetchRequest, - ...scenario.additionalRealtimeModules, + expect(thrownError).not.to.be.null; + expect(thrownError.message).to.equal('Rest module not provided'); }); + } + }); + }); + + describe('Crypto standalone functions', () => { + it('generateRandomKey', async () => { + const key = await generateRandomKey(); + expect(key).to.be.an('ArrayBuffer'); + }); + + it('getDefaultCryptoParams', async () => { + const key = await generateRandomKey(); + const params = getDefaultCryptoParams({ key }); + expect(params).to.be.an('object'); + }); + }); + + describe('Message standalone functions', () => { + async function testDecodesMessageData(functionUnderTest) { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + + const item = testData.items[1]; + const decoded = await functionUnderTest(item.encoded); + + expect(decoded.data).to.be.an('ArrayBuffer'); + } + + describe('decodeMessage', () => { + it('decodes a message’s data', async () => { + testDecodesMessageData(decodeMessage); + }); + + it('throws an error when given channel options with a cipher', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); let thrownError = null; try { - await scenario.action(client); + await decodeMessage(testData.items[0].encrypted, { cipher: { key, iv } }); } catch (error) { thrownError = error; } expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('Rest module not provided'); + expect(thrownError.message).to.equal('Crypto module not provided'); }); - } - }); - }); - - describe('Crypto standalone functions', () => { - it('generateRandomKey', async () => { - const key = await generateRandomKey(); - expect(key).to.be.an('ArrayBuffer'); - }); + }); - it('getDefaultCryptoParams', async () => { - const key = await generateRandomKey(); - const params = getDefaultCryptoParams({ key }); - expect(params).to.be.an('object'); - }); - }); + describe('decodeEncryptedMessage', async () => { + it('decodes a message’s data', async () => { + testDecodesMessageData(decodeEncryptedMessage); + }); - describe('Message standalone functions', () => { - async function testDecodesMessageData(functionUnderTest) { - const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + it('decrypts a message', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); - const item = testData.items[1]; - const decoded = await functionUnderTest(item.encoded); + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); - expect(decoded.data).to.be.an('ArrayBuffer'); - } + for (const item of testData.items) { + const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ + decodeMessage(item.encoded), + decodeEncryptedMessage(item.encrypted, { cipher: { key, iv } }), + ]); - describe('decodeMessage', () => { - it('decodes a message’s data', async () => { - testDecodesMessageData(decodeMessage); + testMessageEquality(decodedFromEncoded, decodedFromEncrypted); + } + }); }); - it('throws an error when given channel options with a cipher', async () => { + async function testDecodesMessagesData(functionUnderTest) { const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); - - let thrownError = null; - try { - await decodeMessage(testData.items[0].encrypted, { cipher: { key, iv } }); - } catch (error) { - thrownError = error; - } - expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('Crypto module not provided'); - }); - }); + const items = [testData.items[1], testData.items[3]]; + const decoded = await functionUnderTest(items.map((item) => item.encoded)); - describe('decodeEncryptedMessage', async () => { - it('decodes a message’s data', async () => { - testDecodesMessageData(decodeEncryptedMessage); - }); + expect(decoded[0].data).to.be.an('ArrayBuffer'); + expect(decoded[1].data).to.be.an('array'); + } - it('decrypts a message', async () => { - const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + describe('decodeMessages', () => { + it('decodes messages’ data', async () => { + testDecodesMessagesData(decodeMessages); + }); - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); + it('throws an error when given channel options with a cipher', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); - for (const item of testData.items) { - const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ - decodeMessage(item.encoded), - decodeEncryptedMessage(item.encrypted, { cipher: { key, iv } }), - ]); + let thrownError = null; + try { + await decodeMessages( + testData.items.map((item) => item.encrypted), + { cipher: { key, iv } } + ); + } catch (error) { + thrownError = error; + } - testMessageEquality(decodedFromEncoded, decodedFromEncrypted); - } + expect(thrownError).not.to.be.null; + expect(thrownError.message).to.equal('Crypto module not provided'); + }); }); - }); - async function testDecodesMessagesData(functionUnderTest) { - const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + describe('decodeEncryptedMessages', () => { + it('decodes messages’ data', async () => { + testDecodesMessagesData(decodeEncryptedMessages); + }); - const items = [testData.items[1], testData.items[3]]; - const decoded = await functionUnderTest(items.map((item) => item.encoded)); + it('decrypts messages', async () => { + const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); - expect(decoded[0].data).to.be.an('ArrayBuffer'); - expect(decoded[1].data).to.be.an('array'); - } + const key = BufferUtils.base64Decode(testData.key); + const iv = BufferUtils.base64Decode(testData.iv); - describe('decodeMessages', () => { - it('decodes messages’ data', async () => { - testDecodesMessagesData(decodeMessages); + const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ + decodeMessages(testData.items.map((item) => item.encoded)), + decodeEncryptedMessages( + testData.items.map((item) => item.encrypted), + { cipher: { key, iv } } + ), + ]); + + for (let i = 0; i < decodedFromEncoded.length; i++) { + testMessageEquality(decodedFromEncoded[i], decodedFromEncrypted[i]); + } + }); }); + }); - it('throws an error when given channel options with a cipher', async () => { - const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); - - let thrownError = null; - try { - await decodeMessages( - testData.items.map((item) => item.encrypted), - { cipher: { key, iv } } - ); - } catch (error) { - thrownError = error; + describe('Crypto', () => { + describe('without Crypto', () => { + async function testThrowsAnErrorWhenGivenChannelOptionsWithACipher(clientClassConfig) { + const client = new clientClassConfig.clientClass(ablyClientOptions(), { + ...clientClassConfig.additionalModules, + FetchRequest, + }); + const key = await generateRandomKey(); + expect(() => client.channels.get('channel', { cipher: { key } })).to.throw('Crypto module not provided'); } - expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('Crypto module not provided'); + for (const clientClassConfig of [ + { clientClass: BaseRest }, + { clientClass: BaseRealtime, additionalModules: { WebSocketTransport } }, + ]) { + describe(clientClassConfig.clientClass.name, () => { + it('throws an error when given channel options with a cipher', async () => { + await testThrowsAnErrorWhenGivenChannelOptionsWithACipher(clientClassConfig); + }); + }); + } }); - }); - describe('decodeEncryptedMessages', () => { - it('decodes messages’ data', async () => { - testDecodesMessagesData(decodeEncryptedMessages); - }); + describe('with Crypto', () => { + async function testIsAbleToPublishEncryptedMessages(clientClassConfig) { + const clientOptions = ablyClientOptions(); - it('decrypts messages', async () => { - const testData = await loadTestData(testResourcesPath + 'crypto-data-128.json'); + const key = await generateRandomKey(); - const key = BufferUtils.base64Decode(testData.key); - const iv = BufferUtils.base64Decode(testData.iv); + // Publish the message on a channel configured to use encryption, and receive it on one not configured to use encryption - const [decodedFromEncoded, decodedFromEncrypted] = await Promise.all([ - decodeMessages(testData.items.map((item) => item.encoded)), - decodeEncryptedMessages( - testData.items.map((item) => item.encrypted), - { cipher: { key, iv } } - ), - ]); + const rxClient = new BaseRealtime(clientOptions, { WebSocketTransport, FetchRequest }); + const rxChannel = rxClient.channels.get('channel'); + await rxChannel.attach(); - for (let i = 0; i < decodedFromEncoded.length; i++) { - testMessageEquality(decodedFromEncoded[i], decodedFromEncrypted[i]); - } - }); - }); - }); + const rxMessagePromise = new Promise((resolve, _) => rxChannel.subscribe((message) => resolve(message))); - describe('Crypto', () => { - describe('without Crypto', () => { - async function testThrowsAnErrorWhenGivenChannelOptionsWithACipher(clientClassConfig) { - const client = new clientClassConfig.clientClass(ablyClientOptions(), { - ...clientClassConfig.additionalModules, - FetchRequest, - }); - const key = await generateRandomKey(); - expect(() => client.channels.get('channel', { cipher: { key } })).to.throw('Crypto module not provided'); - } + const encryptionChannelOptions = { cipher: { key } }; - for (const clientClassConfig of [ - { clientClass: BaseRest }, - { clientClass: BaseRealtime, additionalModules: { WebSocketTransport } }, - ]) { - describe(clientClassConfig.clientClass.name, () => { - it('throws an error when given channel options with a cipher', async () => { - await testThrowsAnErrorWhenGivenChannelOptionsWithACipher(clientClassConfig); + const txMessage = { name: 'message', data: 'data' }; + const txClient = new clientClassConfig.clientClass(clientOptions, { + ...clientClassConfig.additionalModules, + FetchRequest, + Crypto, }); - }); - } - }); + const txChannel = txClient.channels.get('channel', encryptionChannelOptions); + await txChannel.publish(txMessage); - describe('with Crypto', () => { - async function testIsAbleToPublishEncryptedMessages(clientClassConfig) { - const clientOptions = ablyClientOptions(); - - const key = await generateRandomKey(); + const rxMessage = await rxMessagePromise; - // Publish the message on a channel configured to use encryption, and receive it on one not configured to use encryption + // Verify that the message was published with encryption + expect(rxMessage.encoding).to.equal('utf-8/cipher+aes-256-cbc'); - const rxClient = new BaseRealtime(clientOptions, { WebSocketTransport, FetchRequest }); - const rxChannel = rxClient.channels.get('channel'); - await rxChannel.attach(); - - const rxMessagePromise = new Promise((resolve, _) => rxChannel.subscribe((message) => resolve(message))); + // Verify that the message was correctly encrypted + const rxMessageDecrypted = await decodeEncryptedMessage(rxMessage, encryptionChannelOptions); + testMessageEquality(rxMessageDecrypted, txMessage); + } - const encryptionChannelOptions = { cipher: { key } }; + for (const clientClassConfig of [ + { clientClass: BaseRest }, + { clientClass: BaseRealtime, additionalModules: { WebSocketTransport } }, + ]) { + describe(clientClassConfig.clientClass.name, () => { + it('is able to publish encrypted messages', async () => { + await testIsAbleToPublishEncryptedMessages(clientClassConfig); + }); + }); + } + }); + }); - const txMessage = { name: 'message', data: 'data' }; - const txClient = new clientClassConfig.clientClass(clientOptions, { - ...clientClassConfig.additionalModules, - FetchRequest, - Crypto, + describe('MsgPack', () => { + async function testRestUsesContentType(rest, expectedContentType) { + const channelName = 'channel'; + const channel = rest.channels.get(channelName); + const contentTypeUsedForPublishPromise = new Promise((resolve, reject) => { + rest.http.do = (method, path, headers, body, params, callback) => { + if (!(method == 'post' && path == `/channels/${channelName}/messages`)) { + return; + } + resolve(headers['content-type']); + callback(null); + }; }); - const txChannel = txClient.channels.get('channel', encryptionChannelOptions); - await txChannel.publish(txMessage); - const rxMessage = await rxMessagePromise; + await channel.publish('message', 'body'); - // Verify that the message was published with encryption - expect(rxMessage.encoding).to.equal('utf-8/cipher+aes-256-cbc'); - - // Verify that the message was correctly encrypted - const rxMessageDecrypted = await decodeEncryptedMessage(rxMessage, encryptionChannelOptions); - testMessageEquality(rxMessageDecrypted, txMessage); + const contentTypeUsedForPublish = await contentTypeUsedForPublishPromise; + expect(contentTypeUsedForPublish).to.equal(expectedContentType); } - for (const clientClassConfig of [ - { clientClass: BaseRest }, - { clientClass: BaseRealtime, additionalModules: { WebSocketTransport } }, - ]) { - describe(clientClassConfig.clientClass.name, () => { - it('is able to publish encrypted messages', async () => { - await testIsAbleToPublishEncryptedMessages(clientClassConfig); - }); + async function testRealtimeUsesFormat(realtime, expectedFormat) { + const formatUsedForConnectionPromise = new Promise((resolve, reject) => { + realtime.connection.connectionManager.connectImpl = (transportParams) => { + resolve(transportParams.format); + }; }); - } - }); - }); + realtime.connect(); - describe('MsgPack', () => { - async function testRestUsesContentType(rest, expectedContentType) { - const channelName = 'channel'; - const channel = rest.channels.get(channelName); - const contentTypeUsedForPublishPromise = new Promise((resolve, reject) => { - rest.http.do = (method, path, headers, body, params, callback) => { - if (!(method == 'post' && path == `/channels/${channelName}/messages`)) { - return; - } - resolve(headers['content-type']); - callback(null); - }; - }); - - await channel.publish('message', 'body'); - - const contentTypeUsedForPublish = await contentTypeUsedForPublishPromise; - expect(contentTypeUsedForPublish).to.equal(expectedContentType); - } + const formatUsedForConnection = await formatUsedForConnectionPromise; + expect(formatUsedForConnection).to.equal(expectedFormat); + } - async function testRealtimeUsesFormat(realtime, expectedFormat) { - const formatUsedForConnectionPromise = new Promise((resolve, reject) => { - realtime.connection.connectionManager.connectImpl = (transportParams) => { - resolve(transportParams.format); - }; - }); - realtime.connect(); - - const formatUsedForConnection = await formatUsedForConnectionPromise; - expect(formatUsedForConnection).to.equal(expectedFormat); - } - - // TODO once https://github.com/ably/ably-js/issues/1424 is fixed, this should also test the case where the useBinaryProtocol option is not specified - describe('with useBinaryProtocol client option', () => { - describe('without MsgPack', () => { - describe('BaseRest', () => { - it('uses JSON', async () => { - const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { FetchRequest }); - await testRestUsesContentType(client, 'application/json'); + // TODO once https://github.com/ably/ably-js/issues/1424 is fixed, this should also test the case where the useBinaryProtocol option is not specified + describe('with useBinaryProtocol client option', () => { + describe('without MsgPack', () => { + describe('BaseRest', () => { + it('uses JSON', async () => { + const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { FetchRequest }); + await testRestUsesContentType(client, 'application/json'); + }); }); - }); - describe('BaseRealtime', () => { - it('uses JSON', async () => { - const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { - WebSocketTransport, - FetchRequest, + describe('BaseRealtime', () => { + it('uses JSON', async () => { + const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { + WebSocketTransport, + FetchRequest, + }); + await testRealtimeUsesFormat(client, 'json'); }); - await testRealtimeUsesFormat(client, 'json'); }); }); - }); - describe('with MsgPack', () => { - describe('BaseRest', () => { - it('uses MessagePack', async () => { - const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { - FetchRequest, - MsgPack, + describe('with MsgPack', () => { + describe('BaseRest', () => { + it('uses MessagePack', async () => { + const client = new BaseRest(ablyClientOptions({ useBinaryProtocol: true }), { + FetchRequest, + MsgPack, + }); + await testRestUsesContentType(client, 'application/x-msgpack'); }); - await testRestUsesContentType(client, 'application/x-msgpack'); }); - }); - describe('BaseRealtime', () => { - it('uses MessagePack', async () => { - const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { - WebSocketTransport, - FetchRequest, - MsgPack, + describe('BaseRealtime', () => { + it('uses MessagePack', async () => { + const client = new BaseRealtime(ablyClientOptions({ useBinaryProtocol: true, autoConnect: false }), { + WebSocketTransport, + FetchRequest, + MsgPack, + }); + await testRealtimeUsesFormat(client, 'msgpack'); }); - await testRealtimeUsesFormat(client, 'msgpack'); }); }); }); }); - }); - describe('RealtimePresence', () => { - describe('BaseRealtime without RealtimePresence', () => { - it('throws an error when attempting to access the `presence` property', () => { - const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); - const channel = client.channels.get('channel'); + describe('RealtimePresence', () => { + describe('BaseRealtime without RealtimePresence', () => { + it('throws an error when attempting to access the `presence` property', () => { + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const channel = client.channels.get('channel'); - expect(() => channel.presence).to.throw('RealtimePresence module not provided'); - }); - - it('doesn’t break when it receives a PRESENCE ProtocolMessage', async () => { - const rxClient = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); - const rxChannel = rxClient.channels.get('channel'); + expect(() => channel.presence).to.throw('RealtimePresence module not provided'); + }); - await rxChannel.attach(); + it('doesn’t break when it receives a PRESENCE ProtocolMessage', async () => { + const rxClient = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const rxChannel = rxClient.channels.get('channel'); - const receivedMessagePromise = new Promise((resolve) => rxChannel.subscribe(resolve)); + await rxChannel.attach(); - const txClient = new BaseRealtime(ablyClientOptions({ clientId: randomString() }), { - WebSocketTransport, - FetchRequest, - RealtimePresence, - }); - const txChannel = txClient.channels.get('channel'); + const receivedMessagePromise = new Promise((resolve) => rxChannel.subscribe(resolve)); - await txChannel.publish('message', 'body'); - await txChannel.presence.enter(); + const txClient = new BaseRealtime(ablyClientOptions({ clientId: randomString() }), { + WebSocketTransport, + FetchRequest, + RealtimePresence, + }); + const txChannel = txClient.channels.get('channel'); - // The idea being here that in order for receivedMessagePromise to resolve, rxClient must have first processed the PRESENCE ProtocolMessage that resulted from txChannel.presence.enter() + await txChannel.publish('message', 'body'); + await txChannel.presence.enter(); - await receivedMessagePromise; - }); - }); + // The idea being here that in order for receivedMessagePromise to resolve, rxClient must have first processed the PRESENCE ProtocolMessage that resulted from txChannel.presence.enter() - describe('BaseRealtime with RealtimePresence', () => { - it('offers realtime presence functionality', async () => { - const rxChannel = new BaseRealtime(ablyClientOptions(), { - WebSocketTransport, - FetchRequest, - RealtimePresence, - }).channels.get('channel'); - const txClientId = randomString(); - const txChannel = new BaseRealtime(ablyClientOptions({ clientId: txClientId }), { - WebSocketTransport, - FetchRequest, - RealtimePresence, - }).channels.get('channel'); - - let resolveRxPresenceMessagePromise; - const rxPresenceMessagePromise = new Promise((resolve, reject) => { - resolveRxPresenceMessagePromise = resolve; + await receivedMessagePromise; }); - await rxChannel.presence.subscribe('enter', resolveRxPresenceMessagePromise); - await txChannel.presence.enter(); - - const rxPresenceMessage = await rxPresenceMessagePromise; - expect(rxPresenceMessage.clientId).to.equal(txClientId); }); - }); - }); - describe('PresenceMessage standalone functions', () => { - describe('decodePresenceMessage', () => { - it('decodes a presence message’s data', async () => { - const buffer = BufferUtils.utf8Encode('foo'); - const encodedMessage = { data: BufferUtils.base64Encode(buffer), encoding: 'base64' }; + describe('BaseRealtime with RealtimePresence', () => { + it('offers realtime presence functionality', async () => { + const rxChannel = new BaseRealtime(ablyClientOptions(), { + WebSocketTransport, + FetchRequest, + RealtimePresence, + }).channels.get('channel'); + const txClientId = randomString(); + const txChannel = new BaseRealtime(ablyClientOptions({ clientId: txClientId }), { + WebSocketTransport, + FetchRequest, + RealtimePresence, + }).channels.get('channel'); - const decodedMessage = await decodePresenceMessage(encodedMessage); + let resolveRxPresenceMessagePromise; + const rxPresenceMessagePromise = new Promise((resolve, reject) => { + resolveRxPresenceMessagePromise = resolve; + }); + await rxChannel.presence.subscribe('enter', resolveRxPresenceMessagePromise); + await txChannel.presence.enter(); - expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffer)).to.be.true; - expect(decodedMessage.encoding).to.be.null; + const rxPresenceMessage = await rxPresenceMessagePromise; + expect(rxPresenceMessage.clientId).to.equal(txClientId); + }); }); }); - describe('decodeMessages', () => { - it('decodes presence messages’ data', async () => { - const buffers = ['foo', 'bar'].map((data) => BufferUtils.utf8Encode(data)); - const encodedMessages = buffers.map((buffer) => ({ - data: BufferUtils.base64Encode(buffer), - encoding: 'base64', - })); - - const decodedMessages = await decodePresenceMessages(encodedMessages); + describe('PresenceMessage standalone functions', () => { + describe('decodePresenceMessage', () => { + it('decodes a presence message’s data', async () => { + const buffer = BufferUtils.utf8Encode('foo'); + const encodedMessage = { data: BufferUtils.base64Encode(buffer), encoding: 'base64' }; - for (let i = 0; i < decodedMessages.length; i++) { - const decodedMessage = decodedMessages[i]; + const decodedMessage = await decodePresenceMessage(encodedMessage); - expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffers[i])).to.be.true; + expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffer)).to.be.true; expect(decodedMessage.encoding).to.be.null; - } + }); }); - }); - describe('constructPresenceMessage', () => { - it('creates a PresenceMessage instance', async () => { - const extras = { foo: 'bar' }; - const presenceMessage = constructPresenceMessage({ extras }); + describe('decodeMessages', () => { + it('decodes presence messages’ data', async () => { + const buffers = ['foo', 'bar'].map((data) => BufferUtils.utf8Encode(data)); + const encodedMessages = buffers.map((buffer) => ({ + data: BufferUtils.base64Encode(buffer), + encoding: 'base64', + })); - expect(presenceMessage.constructor.name).to.contain('PresenceMessage'); - expect(presenceMessage.extras).to.equal(extras); - }); - }); - }); + const decodedMessages = await decodePresenceMessages(encodedMessages); - describe('Transports', () => { - describe('BaseRealtime', () => { - describe('without a transport module', () => { - it('throws an error due to absence of a transport module', () => { - expect(() => new BaseRealtime(ablyClientOptions(), { FetchRequest })).to.throw( - 'no requested transports available' - ); + for (let i = 0; i < decodedMessages.length; i++) { + const decodedMessage = decodedMessages[i]; + + expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffers[i])).to.be.true; + expect(decodedMessage.encoding).to.be.null; + } }); }); - for (const scenario of [ - { moduleMapKey: 'WebSocketTransport', transportModule: WebSocketTransport, transportName: 'web_socket' }, - { moduleMapKey: 'XHRPolling', transportModule: XHRPolling, transportName: 'xhr_polling' }, - { moduleMapKey: 'XHRStreaming', transportModule: XHRStreaming, transportName: 'xhr_streaming' }, - ]) { - describe(`with the ${scenario.moduleMapKey} module`, () => { - it(`is able to use the ${scenario.transportName} transport`, async () => { - const realtime = new BaseRealtime( - ablyClientOptions({ autoConnect: false, transports: [scenario.transportName] }), - { - FetchRequest, - [scenario.moduleMapKey]: scenario.transportModule, - } - ); - - let firstTransportCandidate; - const connectionManager = realtime.connection.connectionManager; - const originalTryATransport = connectionManager.tryATransport; - realtime.connection.connectionManager.tryATransport = (transportParams, candidate, callback) => { - if (!firstTransportCandidate) { - firstTransportCandidate = candidate; - } - originalTryATransport.bind(connectionManager)(transportParams, candidate, callback); - }; + describe('constructPresenceMessage', () => { + it('creates a PresenceMessage instance', async () => { + const extras = { foo: 'bar' }; + const presenceMessage = constructPresenceMessage({ extras }); - realtime.connect(); + expect(presenceMessage.constructor.name).to.contain('PresenceMessage'); + expect(presenceMessage.extras).to.equal(extras); + }); + }); + }); - await realtime.connection.once('connected'); - expect(firstTransportCandidate).to.equal(scenario.transportName); + describe('Transports', () => { + describe('BaseRealtime', () => { + describe('without a transport module', () => { + it('throws an error due to absence of a transport module', () => { + expect(() => new BaseRealtime(ablyClientOptions(), { FetchRequest })).to.throw( + 'no requested transports available' + ); }); }); - } + + for (const scenario of [ + { moduleMapKey: 'WebSocketTransport', transportModule: WebSocketTransport, transportName: 'web_socket' }, + { moduleMapKey: 'XHRPolling', transportModule: XHRPolling, transportName: 'xhr_polling' }, + { moduleMapKey: 'XHRStreaming', transportModule: XHRStreaming, transportName: 'xhr_streaming' }, + ]) { + describe(`with the ${scenario.moduleMapKey} module`, () => { + it(`is able to use the ${scenario.transportName} transport`, async () => { + const realtime = new BaseRealtime( + ablyClientOptions({ autoConnect: false, transports: [scenario.transportName] }), + { + FetchRequest, + [scenario.moduleMapKey]: scenario.transportModule, + } + ); + + let firstTransportCandidate; + const connectionManager = realtime.connection.connectionManager; + const originalTryATransport = connectionManager.tryATransport; + realtime.connection.connectionManager.tryATransport = (transportParams, candidate, callback) => { + if (!firstTransportCandidate) { + firstTransportCandidate = candidate; + } + originalTryATransport.bind(connectionManager)(transportParams, candidate, callback); + }; + + realtime.connect(); + + await realtime.connection.once('connected'); + expect(firstTransportCandidate).to.equal(scenario.transportName); + }); + }); + } + }); }); - }); - describe('HTTP request implementations', () => { - describe('with multiple HTTP request implementations', () => { - it('prefers XHR', async () => { - let usedXHR = false; + describe('HTTP request implementations', () => { + describe('with multiple HTTP request implementations', () => { + it('prefers XHR', async () => { + let usedXHR = false; - const XHRRequestSpy = class XHRRequestSpy extends XHRRequest { - static createRequest(...args) { - usedXHR = true; - return super.createRequest(...args); - } - }; + const XHRRequestSpy = class XHRRequestSpy extends XHRRequest { + static createRequest(...args) { + usedXHR = true; + return super.createRequest(...args); + } + }; - const rest = new BaseRest(ablyClientOptions(), { FetchRequest, XHRRequest: XHRRequestSpy }); - await rest.time(); + const rest = new BaseRest(ablyClientOptions(), { FetchRequest, XHRRequest: XHRRequestSpy }); + await rest.time(); - expect(usedXHR).to.be.true; + expect(usedXHR).to.be.true; + }); }); }); - }); - describe('MessageInteractions', () => { - describe('BaseRealtime', () => { - describe('without MessageInteractions', () => { - it('is able to subscribe to and unsubscribe from channel events, as long as a MessageFilter isn’t passed', async () => { - const realtime = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); - const channel = realtime.channels.get('channel'); - await channel.attach(); + describe('MessageInteractions', () => { + describe('BaseRealtime', () => { + describe('without MessageInteractions', () => { + it('is able to subscribe to and unsubscribe from channel events, as long as a MessageFilter isn’t passed', async () => { + const realtime = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const channel = realtime.channels.get('channel'); + await channel.attach(); - const subscribeReceivedMessagePromise = new Promise((resolve) => channel.subscribe(resolve)); + const subscribeReceivedMessagePromise = new Promise((resolve) => channel.subscribe(resolve)); - await channel.publish('message', 'body'); + await channel.publish('message', 'body'); - const subscribeReceivedMessage = await subscribeReceivedMessagePromise; - expect(subscribeReceivedMessage.data).to.equal('body'); - }); + const subscribeReceivedMessage = await subscribeReceivedMessagePromise; + expect(subscribeReceivedMessage.data).to.equal('body'); + }); - it('throws an error when attempting to subscribe to channel events using a MessageFilter', async () => { - const realtime = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); - const channel = realtime.channels.get('channel'); + it('throws an error when attempting to subscribe to channel events using a MessageFilter', async () => { + const realtime = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + const channel = realtime.channels.get('channel'); - let thrownError = null; - try { - await channel.subscribe({ clientId: 'someClientId' }, () => {}); - } catch (error) { - thrownError = error; - } + let thrownError = null; + try { + await channel.subscribe({ clientId: 'someClientId' }, () => {}); + } catch (error) { + thrownError = error; + } - expect(thrownError).not.to.be.null; - expect(thrownError.message).to.equal('MessageInteractions module not provided'); + expect(thrownError).not.to.be.null; + expect(thrownError.message).to.equal('MessageInteractions module not provided'); + }); }); - }); - describe('with MessageInteractions', () => { - it('can take a MessageFilter argument when subscribing to and unsubscribing from channel events', async () => { - const realtime = new BaseRealtime(ablyClientOptions(), { - WebSocketTransport, - FetchRequest, - MessageInteractions, - }); - const channel = realtime.channels.get('channel'); + describe('with MessageInteractions', () => { + it('can take a MessageFilter argument when subscribing to and unsubscribing from channel events', async () => { + const realtime = new BaseRealtime(ablyClientOptions(), { + WebSocketTransport, + FetchRequest, + MessageInteractions, + }); + const channel = realtime.channels.get('channel'); - await channel.attach(); + await channel.attach(); - // Test `subscribe` with a filter: send two messages with different clientIds, and check that unfiltered subscription receives both messages but clientId-filtered subscription only receives the matching one. - const messageFilter = { clientId: 'someClientId' }; // note that `unsubscribe` compares filter by reference, I found that a bit surprising + // Test `subscribe` with a filter: send two messages with different clientIds, and check that unfiltered subscription receives both messages but clientId-filtered subscription only receives the matching one. + const messageFilter = { clientId: 'someClientId' }; // note that `unsubscribe` compares filter by reference, I found that a bit surprising - const filteredSubscriptionReceivedMessages = []; - channel.subscribe(messageFilter, (message) => { - filteredSubscriptionReceivedMessages.push(message); - }); + const filteredSubscriptionReceivedMessages = []; + channel.subscribe(messageFilter, (message) => { + filteredSubscriptionReceivedMessages.push(message); + }); - const unfilteredSubscriptionReceivedFirstTwoMessagesPromise = new Promise((resolve) => { - const receivedMessages = []; - channel.subscribe(function listener(message) { - receivedMessages.push(message); - if (receivedMessages.length === 2) { - channel.unsubscribe(listener); - resolve(); - } + const unfilteredSubscriptionReceivedFirstTwoMessagesPromise = new Promise((resolve) => { + const receivedMessages = []; + channel.subscribe(function listener(message) { + receivedMessages.push(message); + if (receivedMessages.length === 2) { + channel.unsubscribe(listener); + resolve(); + } + }); }); - }); - await channel.publish(await decodeMessage({ clientId: 'someClientId' })); - await channel.publish(await decodeMessage({ clientId: 'someOtherClientId' })); - await unfilteredSubscriptionReceivedFirstTwoMessagesPromise; + await channel.publish(await decodeMessage({ clientId: 'someClientId' })); + await channel.publish(await decodeMessage({ clientId: 'someOtherClientId' })); + await unfilteredSubscriptionReceivedFirstTwoMessagesPromise; - expect(filteredSubscriptionReceivedMessages.length).to.equal(1); - expect(filteredSubscriptionReceivedMessages[0].clientId).to.equal('someClientId'); + expect(filteredSubscriptionReceivedMessages.length).to.equal(1); + expect(filteredSubscriptionReceivedMessages[0].clientId).to.equal('someClientId'); - // Test `unsubscribe` with a filter: call `unsubscribe` with the clientId filter, publish a message matching the filter, check that only the unfiltered listener recieves it - channel.unsubscribe(messageFilter); + // Test `unsubscribe` with a filter: call `unsubscribe` with the clientId filter, publish a message matching the filter, check that only the unfiltered listener recieves it + channel.unsubscribe(messageFilter); - const unfilteredSubscriptionReceivedNextMessagePromise = new Promise((resolve) => { - channel.subscribe(function listener() { - channel.unsubscribe(listener); - resolve(); + const unfilteredSubscriptionReceivedNextMessagePromise = new Promise((resolve) => { + channel.subscribe(function listener() { + channel.unsubscribe(listener); + resolve(); + }); }); - }); - await channel.publish(await decodeMessage({ clientId: 'someClientId' })); - await unfilteredSubscriptionReceivedNextMessagePromise; + await channel.publish(await decodeMessage({ clientId: 'someClientId' })); + await unfilteredSubscriptionReceivedNextMessagePromise; - expect(filteredSubscriptionReceivedMessages.length).to./* (still) */ equal(1); + expect(filteredSubscriptionReceivedMessages.length).to./* (still) */ equal(1); + }); }); }); }); }); -}); +} + +// This function is called by browser_setup.js once `require` is available +window.registerAblyModulesTests = async () => { + return new Promise((resolve) => { + require(['shared_helper'], (helper) => { + registerAblyModulesTests(helper); + resolve(); + }); + }); +}; diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 465ef5a74f..6a6cfd43c5 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -250,7 +250,7 @@ define([ expect(json1 === json2, 'JSON data contents mismatch.').to.be.ok; } - var exports = { + return (module.exports = { setupApp: testAppModule.setup, tearDownApp: testAppModule.tearDown, createStats: testAppModule.createStatsFixtureData, @@ -284,11 +284,5 @@ define([ whenPromiseSettles: whenPromiseSettles, randomString: randomString, testMessageEquality: testMessageEquality, - }; - - if (typeof window !== 'undefined') { - window.ablyHelpers = exports; - } - - return (module.exports = exports); + }); }); diff --git a/test/support/browser_setup.js b/test/support/browser_setup.js index af7fc6b693..b668c84a45 100644 --- a/test/support/browser_setup.js +++ b/test/support/browser_setup.js @@ -66,7 +66,15 @@ require([(baseUrl + '/test/common/globals/named_dependencies.js').replace('//', // dynamically load all test files deps: allTestFiles, - // we have to kickoff mocha - callback: () => mocha.run(), + callback: () => { + // (For some reason things don’t work if you return a Promise from this callback, hence the nested async function) + (async () => { + // Let modules.test.js register its tests before we run the test suite + await registerAblyModulesTests(); + + // we have to kickoff mocha + mocha.run(); + })(); + }, }); }); From c1f70b064342ace993cd775677aed102ce339b05 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 20 Nov 2023 09:18:24 -0300 Subject: [PATCH 224/468] Replace the existing plugins mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A summary of the discussion on #1492: - the modular API should be the only way to pass optional functionality to the SDK - this means we need to replace the existing ClientOptions.plugins mechanism, which is currently used to pass a Vcdiff decoder - since the modular variant of the SDK only exists for web at the moment, we will bundle Vcdiff decoding into all other platforms (in which bundle size is not much of a concern) - on web, if you want deltas, you have to use the modular variant of the SDK So, we remove the ClientOptions.plugins mechanism and introduce a tree-shakable Vcdiff module, which bundles the vcdiff-decoder library (meaning that users no longer need to directly import this library). Note that this means that, currently, it is no longer possible to use deltas inside a Web Worker. We’ll address this in #1514. The README example of configuring a channel to use deltas is copied from the README of the vcdiff-decoder library. (Once ably-js v2 is released, we should update the instructions in the vcdiff-decoder library’s README to make it clear they only apply to v1. I’ve raised #1513 for this.) Resolves #1492. --- README.md | 41 ++- ably.d.ts | 10 - modules.d.ts | 19 + scripts/moduleReport.ts | 1 + src/common/lib/client/baserealtime.ts | 5 +- src/common/lib/client/modulesmap.ts | 2 + src/common/lib/client/realtimechannel.ts | 2 +- src/common/lib/types/message.ts | 19 +- src/common/platform.ts | 7 + src/platform/nativescript/index.ts | 2 + src/platform/nodejs/index.ts | 2 + src/platform/react-native/index.ts | 2 + src/platform/web/index.ts | 5 + src/platform/web/modules.ts | 2 + src/platform/web/modules/vcdiff.ts | 1 + test/browser/modules.test.js | 36 +- test/common/globals/named_dependencies.js | 4 - test/realtime/delta.test.js | 28 +- test/realtime/shared/delta_tests.js | 418 +++++++++++----------- test/support/browser_file_list.js | 1 - test/support/browser_setup.js | 3 - 21 files changed, 362 insertions(+), 248 deletions(-) create mode 100644 src/platform/web/modules/vcdiff.ts diff --git a/README.md b/README.md index 55f1ab07e5..309bae8705 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,39 @@ channel.subscribe('myEvent', function (message) { Subscribing to a channel in delta mode enables [delta compression](https://www.ably.com/docs/realtime/channels/channel-parameters/deltas). This is a way for a client to subscribe to a channel so that message payloads sent contain only the difference (ie the delta) between the present message and the previous message on the channel. -Configuring a channel for deltas is detailed in the [@ably-forks/vcdiff-decoder documentation](https://github.com/ably-forks/vcdiff-decoder#usage). +To subscribe to a channel in delta mode, you must: + +1. Create a client that supports deltas (this only applies when running in a browser); +2. Configure the channel to operate in delta mode. + +#### Creating a client that supports deltas + +This section only applies when running in a browser. The Realtime client on all other platforms includes delta support. + +To use delta functionality in the browser, you must use the [modular variant of the library](#modular-tree-shakable-variant) and create a client that includes the `Vcdiff` module: + + ```javascript + import { BaseRealtime, WebSocketTransport, FetchRequest, Vcdiff } from 'ably/modules'; + + const options = { key: 'YOUR_ABLY_KEY' }; + const client = new BaseRealtime(options, { + WebSocketTransport, + FetchRequest, + Vcdiff + }); + ``` + +#### Configuring a channel to operate in delta mode + +To configure a channel to operate in delta mode, specify channel parameters of `{ delta: 'vcdiff' }` when fetching the channel: + +```javascript +const channel = realtime.channels.get('your-ably-channel', { + params: { + delta: 'vcdiff' + } +}); +``` Beyond specifying channel options, the rest is transparent and requires no further changes to your application. The `message.data` instances that are delivered to your listening function continue to contain the values that were originally published. @@ -466,13 +498,6 @@ const nextPage = await statsPage.next(); // retrieves the next page as Pa const time = await client.time(); // time is in ms since epoch ``` -## Delta Plugin - -From version 1.2 this client library supports subscription to a stream of Vcdiff formatted delta messages from the Ably service. For certain applications this can bring significant data efficiency savings. -This is an optional feature so our - -See the [@ably-forks/vcdiff-decoder documentation](https://github.com/ably-forks/vcdiff-decoder#usage) for setup and usage examples. - ## Support, feedback and troubleshooting Please visit http://support.ably.com/ for access to our knowledgebase and to ask for any assistance. diff --git a/ably.d.ts b/ably.d.ts index ffaf96a8ee..bd7a7a5dac 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -565,16 +565,6 @@ declare namespace Types { * @defaultValue 10s */ realtimeRequestTimeout?: number; - - /** - * A map between a plugin type and a plugin object. - */ - plugins?: { - /** - * A plugin capable of decoding `vcdiff`-encoded messages. For more information on how to configure a channel to use delta encoding, see the [documentation for the `@ably-forks/vcdiff-decoder` package](https://github.com/ably-forks/vcdiff-decoder#usage). - */ - vcdiff?: any; - }; } /** diff --git a/modules.d.ts b/modules.d.ts index 2c3ea38bdf..76f54a5aee 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -163,6 +163,20 @@ export declare const FetchRequest: unknown; */ export declare const MessageInteractions: unknown; +/** + * Provides a {@link BaseRealtime} instance with the ability to use [delta compression](https://www.ably.com/docs/realtime/channels/channel-parameters/deltas). + * + * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: + * + * ```javascript + * import { BaseRealtime, WebSocketTransport, FetchRequest, Vcdiff } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, Vcdiff }); + * ``` + * + * For information on how to configure a channel to use delta encoding, see [the documentation in the `README`](https://github.com/ably/ably-js/blob/main/README.md#configuring-a-channel-to-operate-in-delta-mode). + */ +export declare const Vcdiff: unknown; + /** * Pass a `ModulesMap` to { @link BaseRest.constructor | the constructor of BaseRest } or {@link BaseRealtime.constructor | that of BaseRealtime} to specify which functionality should be made available to that client. */ @@ -216,6 +230,11 @@ export interface ModulesMap { * See {@link MessageInteractions | documentation for the `MessageInteractions` module}. */ MessageInteractions?: typeof MessageInteractions; + + /** + * See {@link Vcdiff | documentation for the `Vcdiff` module}. + */ + Vcdiff?: typeof Vcdiff; } /** diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index f3deda7b30..56ebce35c2 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -17,6 +17,7 @@ const moduleNames = [ 'XHRRequest', 'FetchRequest', 'MessageInteractions', + 'Vcdiff', ]; // List of all free-standing functions exported by the library along with the diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 3787709326..821b86b6e5 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -11,13 +11,15 @@ import ClientOptions from '../../types/ClientOptions'; import * as API from '../../../../ably'; import { ModulesMap, RealtimePresenceModule } from './modulesmap'; import { TransportNames } from 'common/constants/TransportName'; -import { TransportImplementations } from 'common/platform'; +import Platform, { TransportImplementations } from 'common/platform'; +import { VcdiffDecoder } from '../types/message'; /** `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. */ class BaseRealtime extends BaseClient { readonly _RealtimePresence: RealtimePresenceModule | null; + readonly _decodeVcdiff: VcdiffDecoder | null; // Extra transport implementations available to this client, in addition to those in Platform.Transports.bundledImplementations readonly _additionalTransportImplementations: TransportImplementations; _channels: any; @@ -28,6 +30,7 @@ class BaseRealtime extends BaseClient { Logger.logAction(Logger.LOG_MINOR, 'Realtime()', ''); this._additionalTransportImplementations = BaseRealtime.transportImplementationsFromModules(modules); this._RealtimePresence = modules.RealtimePresence ?? null; + this._decodeVcdiff = (modules.Vcdiff ?? (Platform.Vcdiff.supported && Platform.Vcdiff.bundledDecode)) || null; this.connection = new Connection(this, this.options); this._channels = new Channels(this); if (options.autoConnect !== false) this.connect(); diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index e52730aa32..7a3fb55b5a 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -10,6 +10,7 @@ import { fromValues as presenceMessageFromValues, fromValuesArray as presenceMessagesFromValuesArray, } from '../types/presencemessage'; +import { VcdiffDecoder } from '../types/message'; export interface PresenceMessageModule { presenceMessageFromValues: typeof presenceMessageFromValues; @@ -31,6 +32,7 @@ export interface ModulesMap { XHRRequest?: typeof XHRRequest; FetchRequest?: typeof fetchRequest; MessageInteractions?: typeof FilteredSubscriptions; + Vcdiff?: VcdiffDecoder; } export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index e83c32e25f..27d409ea38 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -122,7 +122,7 @@ class RealtimeChannel extends EventEmitter { this._attachResume = false; this._decodingContext = { channelOptions: this.channelOptions, - plugins: client.options.plugins || {}, + decodeVcdiff: client._decodeVcdiff ?? undefined, baseEncodedPreviousPayload: undefined, }; this._lastPayload = { diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 1f149772ed..aafba56ca9 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -22,13 +22,11 @@ export type CipherOptions = { }; }; +export type VcdiffDecoder = (delta: Uint8Array, source: Uint8Array) => Uint8Array; + export type EncodingDecodingContext = { channelOptions: ChannelOptions; - plugins: { - vcdiff?: { - decode: (delta: Uint8Array, source: Uint8Array) => Uint8Array; - }; - }; + decodeVcdiff?: VcdiffDecoder; baseEncodedPreviousPayload?: Buffer | BrowserBufferlike; }; @@ -36,7 +34,6 @@ function normaliseContext(context: CipherOptions | EncodingDecodingContext | Cha if (!context || !(context as EncodingDecodingContext).channelOptions) { return { channelOptions: context as ChannelOptions, - plugins: {}, baseEncodedPreviousPayload: undefined, }; } @@ -216,8 +213,12 @@ export async function decode( throw new Error('Unable to decrypt message; not an encrypted channel'); } case 'vcdiff': - if (!context.plugins || !context.plugins.vcdiff) { - throw new ErrorInfo('Missing Vcdiff decoder (https://github.com/ably-forks/vcdiff-decoder)', 40019, 400); + if (!context.decodeVcdiff) { + if (Platform.Vcdiff.supported) { + Utils.throwMissingModuleError('Vcdiff'); + } else { + throw new ErrorInfo(Platform.Vcdiff.errorMessage, 40019, 400); + } } if (typeof Uint8Array === 'undefined') { throw new ErrorInfo( @@ -236,7 +237,7 @@ export async function decode( const deltaBaseBuffer = Platform.BufferUtils.toBuffer(deltaBase as Buffer); data = Platform.BufferUtils.toBuffer(data); - data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBaseBuffer)); + data = Platform.BufferUtils.arrayBufferViewToBuffer(context.decodeVcdiff(data, deltaBaseBuffer)); lastPayload = data; } catch (e) { throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400); diff --git a/src/common/platform.ts b/src/common/platform.ts index 6d5a6245bf..bfd1c5fd64 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -8,6 +8,7 @@ import * as WebBufferUtils from '../platform/web/lib/util/bufferutils'; import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils'; import { IUntypedCryptoStatic } from '../common/types/ICryptoStatic'; import TransportName from './constants/TransportName'; +import { VcdiffDecoder } from './lib/types/message'; type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; @@ -39,4 +40,10 @@ export default class Platform { }; static Defaults: IDefaults; static WebStorage: IWebStorage | null; + static Vcdiff: + | { supported: false; errorMessage: string /* explains why this platform does not support vcdiff */ } + | { + supported: true; + bundledDecode: VcdiffDecoder | null /* { supported: true, bundledDecode: null } means that the decode implementation can be provided via ModulesMap */; + }; } diff --git a/src/platform/nativescript/index.ts b/src/platform/nativescript/index.ts index 5a57dbe073..a88a8fba64 100644 --- a/src/platform/nativescript/index.ts +++ b/src/platform/nativescript/index.ts @@ -4,6 +4,7 @@ import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; +import { decode as decodeVcdiff } from '@ably/vcdiff-decoder'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; @@ -30,6 +31,7 @@ Platform.Http = Http; Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; +Platform.Vcdiff = { supported: true, bundledDecode: decodeVcdiff }; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index d53b83dadd..34ffd94957 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -4,6 +4,7 @@ import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; +import { decode as decodeVcdiff } from '@ably/vcdiff-decoder'; // Platform Specific import BufferUtils from './lib/util/bufferutils'; @@ -26,6 +27,7 @@ Platform.Http = Http; Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = null; +Platform.Vcdiff = { supported: true, bundledDecode: decodeVcdiff }; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; diff --git a/src/platform/react-native/index.ts b/src/platform/react-native/index.ts index 998c2dd19a..1724fa1efa 100644 --- a/src/platform/react-native/index.ts +++ b/src/platform/react-native/index.ts @@ -4,6 +4,7 @@ import { DefaultRealtime } from '../../common/lib/client/defaultrealtime'; import Platform from '../../common/platform'; import ErrorInfo from '../../common/lib/types/errorinfo'; import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage'; +import { decode as decodeVcdiff } from '@ably/vcdiff-decoder'; // Platform Specific import BufferUtils from '../web/lib/util/bufferutils'; @@ -30,6 +31,7 @@ Platform.Http = Http; Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; +Platform.Vcdiff = { supported: true, bundledDecode: decodeVcdiff }; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index 9f3b0b0a06..a598774244 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -28,6 +28,11 @@ Platform.Http = Http; Platform.Config = Config; Platform.Transports = Transports; Platform.WebStorage = WebStorage; +// To use vcdiff on web you must use the modular variant of the library +Platform.Vcdiff = { + supported: false, + errorMessage: 'For vcdiff functionality in the browser, you must use the modular variant of ably-js', +}; for (const clientClass of [DefaultRest, DefaultRealtime]) { clientClass.Crypto = Crypto; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 0465405a90..16c8343efb 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -22,6 +22,7 @@ Platform.Http = Http; Platform.Config = Config; Platform.Transports = ModulesTransports; Platform.WebStorage = WebStorage; +Platform.Vcdiff = { supported: true, bundledDecode: null }; Http.bundledRequestImplementations = modulesBundledRequestImplementations; @@ -49,6 +50,7 @@ export * from './modules/msgpack'; export * from './modules/realtimepresence'; export * from './modules/transports'; export * from './modules/http'; +export * from './modules/vcdiff'; export { Rest } from '../../common/lib/client/rest'; export { FilteredSubscriptions as MessageInteractions } from '../../common/lib/client/filteredsubscriptions'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/src/platform/web/modules/vcdiff.ts b/src/platform/web/modules/vcdiff.ts new file mode 100644 index 0000000000..002dad7ea3 --- /dev/null +++ b/src/platform/web/modules/vcdiff.ts @@ -0,0 +1 @@ +export { decode as Vcdiff } from '@ably/vcdiff-decoder'; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index aa96b3a62d..22b16f8672 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -20,9 +20,10 @@ import { FetchRequest, XHRRequest, MessageInteractions, + Vcdiff, } from '../../build/modules/index.js'; -function registerAblyModulesTests(helper) { +function registerAblyModulesTests(helper, registerDeltaTests) { describe('browser/modules', function () { this.timeout(10 * 1000); const expect = chai.expect; @@ -706,14 +707,43 @@ function registerAblyModulesTests(helper) { }); }); }); + + // Tests for the Vcdiff module + // + // Note: Unlike the other tests in this file, which only test how the + // absence or presence of a module affects the client, assuming that the + // underlying functionality is tested in detail in the test suite for the + // default variant of the library, the tests for the Vcdiff module actually + // test the library’s delta encoding functionality. This is because on web, + // delta encoding functionality is only available in the modular variant of + // the library. + (() => { + const config = { + createRealtimeWithDeltaPlugin: (options) => { + return new BaseRealtime(options, { + WebSocketTransport, + FetchRequest, + Vcdiff, + }); + }, + createRealtimeWithoutDeltaPlugin: (options) => { + return new BaseRealtime(options, { + WebSocketTransport, + FetchRequest, + }); + }, + }; + + registerDeltaTests('Vcdiff', config); + })(); }); } // This function is called by browser_setup.js once `require` is available window.registerAblyModulesTests = async () => { return new Promise((resolve) => { - require(['shared_helper'], (helper) => { - registerAblyModulesTests(helper); + require(['shared_helper', 'delta_tests'], (helper, registerDeltaTests) => { + registerAblyModulesTests(helper, registerDeltaTests); resolve(); }); }); diff --git a/test/common/globals/named_dependencies.js b/test/common/globals/named_dependencies.js index a7234844ec..4d25af26be 100644 --- a/test/common/globals/named_dependencies.js +++ b/test/common/globals/named_dependencies.js @@ -3,10 +3,6 @@ define(function () { return (module.exports = { // Ably modules ably: { browser: 'build/ably', node: 'build/ably-node' }, - 'vcdiff-decoder': { - browser: 'node_modules/@ably/vcdiff-decoder/dist/vcdiff-decoder', - node: 'node_modules/@ably/vcdiff-decoder', - }, // test modules globals: { browser: 'test/common/globals/environment', node: 'test/common/globals/environment' }, diff --git a/test/realtime/delta.test.js b/test/realtime/delta.test.js index 83575f46f5..af127e0a1e 100644 --- a/test/realtime/delta.test.js +++ b/test/realtime/delta.test.js @@ -1,5 +1,29 @@ 'use strict'; -define(['delta_tests'], function (registerDeltaTests) { - registerDeltaTests('realtime/delta'); +define(['shared_helper', 'delta_tests'], function (helper, registerDeltaTests) { + const Platform = helper.Ably.Realtime.Platform; + + let config; + + if (Platform.Vcdiff.supported) { + if (Platform.Vcdiff.bundledDecode) { + config = { + createRealtimeWithDeltaPlugin: (options) => { + return helper.AblyRealtime(options); + }, + }; + } else { + throw new Error( + 'vcdiff is supported but not bundled; this should only be the case for the modular variant of the library, which this test doesn’t exercise' + ); + } + } else { + config = { + createRealtimeWithoutDeltaPlugin: (options) => { + return new helper.AblyRealtime(options); + }, + }; + } + + registerDeltaTests('realtime/delta', config); }); diff --git a/test/realtime/shared/delta_tests.js b/test/realtime/shared/delta_tests.js index 20ccf8183e..0de7ebde1f 100644 --- a/test/realtime/shared/delta_tests.js +++ b/test/realtime/shared/delta_tests.js @@ -1,5 +1,5 @@ -define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, vcdiffDecoder, async, chai) { - function registerDeltaTests(describeLabel) { +define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { + function registerDeltaTests(describeLabel, config) { var expect = chai.expect; var displayError = helper.displayError; var closeAndFinish = helper.closeAndFinish; @@ -17,13 +17,26 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v return JSON.stringify(a) === JSON.stringify(b); } - function getTestVcdiffDecoder() { + function getTestVcdiffDecoder(realtime) { + if (!realtime._decodeVcdiff) { + throw new Error('Expected client to expose vcdiff decoder via _decodeVcdiff property'); + } + + let numberOfCalls = 0; + + const originalDecodeVcdiff = realtime._decodeVcdiff; + const testDecodeVcdiff = function (delta, base) { + numberOfCalls++; + return originalDecodeVcdiff(delta, base); + }; + + realtime._decodeVcdiff = testDecodeVcdiff; + return { - numberOfCalls: 0, - decode: function (delta, base) { - this.numberOfCalls++; - return vcdiffDecoder.decode(delta, base); + get numberOfCalls() { + return numberOfCalls; }, + decode: testDecodeVcdiff, }; } @@ -39,244 +52,237 @@ define(['shared_helper', 'vcdiff-decoder', 'async', 'chai'], function (helper, v }); }); - it('deltaPlugin', function (done) { - var testName = 'deltaPlugin'; - try { - var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: testVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - - channel.on('attaching', function (stateChange) { - done( - new Error( - 'Channel reattaching, presumably due to decode failure; reason =' + displayError(stateChange.reason) - ) - ); - }); - - channel.subscribe(function (message) { - try { - var index = Number(message.name); - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + if (config.createRealtimeWithDeltaPlugin) { + it('deltaPlugin', function (done) { + var testName = 'deltaPlugin'; + try { + var realtime = config.createRealtimeWithDeltaPlugin(helper.ablyClientOptions()); + var testVcdiffDecoder = getTestVcdiffDecoder(realtime); + var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - if (index === testData.length - 1) { - expect(testVcdiffDecoder.numberOfCalls).to.equal( - testData.length - 1, - 'Check number of delta messages' - ); - closeAndFinish(done, realtime); - } - } catch (err) { + whenPromiseSettles(channel.attach(), function (err) { + if (err) { closeAndFinish(done, realtime, err); } - }); - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + channel.on('attaching', function (stateChange) { + done( + new Error( + 'Channel reattaching, presumably due to decode failure; reason =' + displayError(stateChange.reason) + ) + ); + }); + + channel.subscribe(function (message) { + try { + var index = Number(message.name); + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + + if (index === testData.length - 1) { + expect(testVcdiffDecoder.numberOfCalls).to.equal( + testData.length - 1, + 'Check number of delta messages' + ); + closeAndFinish(done, realtime); + } + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); }); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + } - it('unusedPlugin', function (done) { - var testName = 'unusedPlugin'; - try { - var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: testVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.subscribe(function (message) { - try { - var index = Number(message.name); - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + if (config.createRealtimeWithDeltaPlugin) { + it('unusedPlugin', function (done) { + var testName = 'unusedPlugin'; + try { + var realtime = config.createRealtimeWithDeltaPlugin(helper.ablyClientOptions()); + var testVcdiffDecoder = getTestVcdiffDecoder(realtime); + var channel = realtime.channels.get(testName); - if (index === testData.length - 1) { - expect(testVcdiffDecoder.numberOfCalls).to.equal(0, 'Check number of delta messages'); - closeAndFinish(done, realtime); - } - } catch (err) { + whenPromiseSettles(channel.attach(), function (err) { + if (err) { closeAndFinish(done, realtime, err); } - }); + channel.subscribe(function (message) { + try { + var index = Number(message.name); + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + if (index === testData.length - 1) { + expect(testVcdiffDecoder.numberOfCalls).to.equal(0, 'Check number of delta messages'); + closeAndFinish(done, realtime); + } + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); }); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + } + + if (config.createRealtimeWithDeltaPlugin) { + it('lastMessageNotFoundRecovery', function (done) { + var testName = 'lastMessageNotFoundRecovery'; + try { + var realtime = config.createRealtimeWithDeltaPlugin(helper.ablyClientOptions()); + var testVcdiffDecoder = getTestVcdiffDecoder(realtime); + var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - it('lastMessageNotFoundRecovery', function (done) { - var testName = 'lastMessageNotFoundRecovery'; - try { - var testVcdiffDecoder = getTestVcdiffDecoder(); - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: testVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.subscribe(function (message) { - var index = Number(message.name); - try { - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; - } catch (err) { + whenPromiseSettles(channel.attach(), function (err) { + if (err) { closeAndFinish(done, realtime, err); } + channel.subscribe(function (message) { + var index = Number(message.name); + try { + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + } catch (err) { + closeAndFinish(done, realtime, err); + } - if (index === 1) { - /* Simulate issue */ - channel._lastPayload.messageId = null; - channel.once('attaching', function (stateChange) { + if (index === 1) { + /* Simulate issue */ + channel._lastPayload.messageId = null; + channel.once('attaching', function (stateChange) { + try { + expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + channel.on('attaching', function (stateChange) { + closeAndFinish( + done, + realtime, + new Error('Check no further decode failures; reason =' + displayError(stateChange.reason)) + ); + }); + }); + } else if (index === testData.length - 1) { try { - expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); + expect(testVcdiffDecoder.numberOfCalls).to.equal( + testData.length - 2, + 'Check number of delta messages' + ); } catch (err) { closeAndFinish(done, realtime, err); return; } - channel.on('attaching', function (stateChange) { - closeAndFinish( - done, - realtime, - new Error('Check no further decode failures; reason =' + displayError(stateChange.reason)) - ); - }); - }); - } else if (index === testData.length - 1) { - try { - expect(testVcdiffDecoder.numberOfCalls).to.equal( - testData.length - 2, - 'Check number of delta messages' - ); - } catch (err) { - closeAndFinish(done, realtime, err); - return; + closeAndFinish(done, realtime); } - closeAndFinish(done, realtime); - } - }); + }); - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); }); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + } - it('deltaDecodeFailureRecovery', function (done) { - var testName = 'deltaDecodeFailureRecovery'; - try { - var failingTestVcdiffDecoder = { - decode: function (delta, base) { + if (config.createRealtimeWithDeltaPlugin) { + it('deltaDecodeFailureRecovery', function (done) { + var testName = 'deltaDecodeFailureRecovery'; + try { + var realtime = config.createRealtimeWithDeltaPlugin(helper.ablyClientOptions()); + + realtime._decodeVcdiff = function (delta, base) { throw new Error('Failed to decode delta.'); - }, - }; - - var realtime = helper.AblyRealtime({ - plugins: { - vcdiff: failingTestVcdiffDecoder, - }, - }); - var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.on('attaching', function (stateChange) { - try { - expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - channel.subscribe(function (message) { - var index = Number(message.name); - try { - expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; - } catch (err) { + }; + + var channel = realtime.channels.get(testName, { params: { delta: 'vcdiff' } }); + + whenPromiseSettles(channel.attach(), function (err) { + if (err) { closeAndFinish(done, realtime, err); } + channel.on('attaching', function (stateChange) { + try { + expect(stateChange.reason.code).to.equal(40018, 'Check error code passed through per RTL18c'); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + channel.subscribe(function (message) { + var index = Number(message.name); + try { + expect(equals(testData[index], message.data), 'Check message.data').to.be.ok; + } catch (err) { + closeAndFinish(done, realtime, err); + } - if (index === testData.length - 1) { - closeAndFinish(done, realtime); - } - }); + if (index === testData.length - 1) { + closeAndFinish(done, realtime); + } + }); - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); }); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + } - /* Check that channel becomes failed if we get deltas when we don't have a vcdiff plugin */ - it('noPlugin', function (done) { - try { - var realtime = helper.AblyRealtime(); - var channel = realtime.channels.get('noPlugin', { params: { delta: 'vcdiff' } }); - - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - channel.once('failed', function (stateChange) { - try { - expect(stateChange.reason.code).to.equal(40019, 'Check error code'); - } catch (err) { + if (config.createRealtimeWithoutDeltaPlugin) { + /* Check that channel becomes failed if we get deltas when we don't have a vcdiff plugin */ + it('noPlugin', function (done) { + try { + var realtime = config.createRealtimeWithoutDeltaPlugin(helper.ablyClientOptions()); + var channel = realtime.channels.get('noPlugin', { params: { delta: 'vcdiff' } }); + + whenPromiseSettles(channel.attach(), function (err) { + if (err) { closeAndFinish(done, realtime, err); - return; } - closeAndFinish(done, realtime); - }); - async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + channel.once('failed', function (stateChange) { + try { + expect(stateChange.reason.code).to.equal(40019, 'Check error code'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }); + async.timesSeries(testData.length, function (i, cb) { + channel.publish(i.toString(), testData[i], cb); + }); }); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); + monitorConnection(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } + }); + } }); } diff --git a/test/support/browser_file_list.js b/test/support/browser_file_list.js index 5a4ceedc16..7350578e0a 100644 --- a/test/support/browser_file_list.js +++ b/test/support/browser_file_list.js @@ -8,7 +8,6 @@ window.__testFiles__.files = { 'build/ably.min.js': true, 'browser/lib/util/base64.js': true, 'node_modules/async/lib/async.js': true, - 'node_modules/@ably/vcdiff-decoder/dist/vcdiff-decoder.js': true, 'test/common/globals/environment.js': true, 'test/common/globals/named_dependencies.js': true, 'test/common/modules/client_module.js': true, diff --git a/test/support/browser_setup.js b/test/support/browser_setup.js index b668c84a45..0ce70489f1 100644 --- a/test/support/browser_setup.js +++ b/test/support/browser_setup.js @@ -58,9 +58,6 @@ require([(baseUrl + '/test/common/globals/named_dependencies.js').replace('//', 'browser-base64': { exports: 'Base64', }, - 'vcdiff-decoder': { - exports: 'vcdiffDecoder', - }, }, // dynamically load all test files From 4e3733f427fe70754dc36a6a9928182c79e81d72 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 23 Nov 2023 09:40:01 -0300 Subject: [PATCH 225/468] Fix optionality of Message properties This makes us consistent with the IDL in the feature spec. (The only properties that are guaranteed to be populated on a message received from Ably are `id` and `timestamp`.) --- ably.d.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index bd7a7a5dac..bdcf0fac1a 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2333,7 +2333,7 @@ declare namespace Types { /** * The client ID of the publisher of this message. */ - clientId: string; + clientId?: string; /** * The connection ID of the publisher of this message. */ @@ -2341,15 +2341,15 @@ declare namespace Types { /** * The message payload, if provided. */ - data: any; + data?: any; /** * This is typically empty, as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute contains the remaining transformations not applied to the `data` payload. */ - encoding: string; + encoding?: string; /** * A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads. Valid payloads include `push`, `delta`, `ref` and `headers`. */ - extras: any; + extras?: any; /** * Unique ID assigned by Ably to this message. */ @@ -2357,7 +2357,7 @@ declare namespace Types { /** * The event name. */ - name: string; + name?: string; /** * Timestamp of when the message was received by Ably, as milliseconds since the Unix epoch. */ From 19a8d475fb892c9b308685ebf015a50d6ba8e9b2 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 23 Nov 2023 10:04:06 -0300 Subject: [PATCH 226/468] Tighten type of publishing methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current type of `any` for an outgoing message is overly permissive and doesn’t help users understand the shape of the object they need to provide. So, we: 1. change the Message class to an interface, which represents a Message-shaped object; 2. make Message’s `id` and `timestamp` properties optional (since we’re going to also use this interface for outgoing messages, which don’t necessarily have these properties), and compensate for this loosening of the Message type by introducing an InboundMessage type to represent messages received from Ably; 3. update the signature publishing methods to accept a Message object. Note that we deviate from the feature spec in that, in the feature spec, the publishing methods accept a Message instance. There are a couple of reasons for this deviation: 1. Accepting a Message-shaped object instead of a Message instance is consistent with our usage of the library in all of our existing example code and our tests, and is, I think, how things tend to be done in JavaScript. 2. The types in the feature spec are wrong; as things stand there, a user needs to provide a Message to the publishing methods, but Message has non-optional `id` and `timestamp` properties even though a user is not expected to populate them. We haven’t yet figured out how to address this error in the feature spec (i.e. do we introduce an InboundMessage type like we have here, or do we add some comments and leave it for library authors to figure out?); I started a dicussion about it in [1], but we’ve decided that we’d like to proceed with this ably-js change (which, since it’s a breaking change, we want to get into v2) without waiting for it to be addressed in the feature spec. Resolves #1472. [1] https://github.com/ably/specification/pull/156 --- ably.d.ts | 65 ++++++++++--------- .../browser/template/src/index-default.ts | 17 +++-- .../browser/template/src/index-modules.ts | 18 +++-- 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index bdcf0fac1a..ff61ca4534 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2055,12 +2055,12 @@ declare namespace Types { */ presence: Presence; /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link Message} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. + * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link InboundMessage} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. * * @param params - A set of parameters which are used to specify which messages should be retrieved. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Message} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link InboundMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - history(params?: RestHistoryParams): Promise>; + history(params?: RestHistoryParams): Promise>; /** * Publishes an array of messages to the channel. * @@ -2068,7 +2068,7 @@ declare namespace Types { * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. */ - publish(messages: any[], options?: PublishOptions): Promise; + publish(messages: Message[], options?: PublishOptions): Promise; /** * Publishes a message to the channel. * @@ -2076,7 +2076,7 @@ declare namespace Types { * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. */ - publish(message: any, options?: PublishOptions): Promise; + publish(message: Message, options?: PublishOptions): Promise; /** * Publishes a single message to the channel with the given event name and payload. * @@ -2124,14 +2124,14 @@ declare namespace Types { * @param event - The event name. * @param listener - An event listener function. */ - unsubscribe(event: string, listener: messageCallback): void; + unsubscribe(event: string, listener: messageCallback): void; /** * Deregisters the given listener from all event names in the array. * * @param events - An array of event names. * @param listener - An event listener function. */ - unsubscribe(events: Array, listener: messageCallback): void; + unsubscribe(events: Array, listener: messageCallback): void; /** * Deregisters all listeners for the given event name. * @@ -2150,13 +2150,13 @@ declare namespace Types { * @param filter - A {@link MessageFilter}. * @param listener - An event listener function. */ - unsubscribe(filter: MessageFilter, listener?: messageCallback): void; + unsubscribe(filter: MessageFilter, listener?: messageCallback): void; /** * Deregisters the given listener (for any/all event names). This removes an earlier subscription. * * @param listener - An event listener function. */ - unsubscribe(listener: messageCallback): void; + unsubscribe(listener: messageCallback): void; /** * Deregisters all listeners to messages on this channel. This removes all earlier subscriptions. */ @@ -2179,12 +2179,12 @@ declare namespace Types { */ detach(): Promise; /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link Message} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. + * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link InboundMessage} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. * * @param params - A set of parameters which are used to specify which presence members should be retrieved. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Message} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link InboundMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - history(params?: RealtimeHistoryParams): Promise>; + history(params?: RealtimeHistoryParams): Promise>; /** * Sets the {@link ChannelOptions} for the channel. * @@ -2199,7 +2199,7 @@ declare namespace Types { * @param listener - An event listener function. * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. */ - subscribe(event: string, listener?: messageCallback): Promise; + subscribe(event: string, listener?: messageCallback): Promise; /** * Registers a listener for messages on this channel for multiple event name values. * @@ -2207,7 +2207,7 @@ declare namespace Types { * @param listener - An event listener function. * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. */ - subscribe(events: Array, listener?: messageCallback): Promise; + subscribe(events: Array, listener?: messageCallback): Promise; /** * {@label WITH_MESSAGE_FILTER} * @@ -2217,14 +2217,14 @@ declare namespace Types { * @param listener - An event listener function. * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. */ - subscribe(filter: MessageFilter, listener?: messageCallback): Promise; + subscribe(filter: MessageFilter, listener?: messageCallback): Promise; /** * Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel. * * @param callback - An event listener function. * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. */ - subscribe(callback: messageCallback): Promise; + subscribe(callback: messageCallback): Promise; /** * Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. * @@ -2239,14 +2239,14 @@ declare namespace Types { * @param messages - An array of {@link Message} objects. * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. */ - publish(messages: any[]): Promise; + publish(messages: Message[]): Promise; /** * Publish a message to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. * * @param message - A {@link Message} object. * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. */ - publish(message: any): Promise; + publish(message: Message): Promise; /** * Returns a promise which is resolved when the channel reaches the specified {@link ChannelState}. If the channel is already in the specified state, the promise is resolved immediately. * @@ -2329,7 +2329,7 @@ declare namespace Types { /** * Contains an individual message that is sent to, or received from, Ably. */ - class Message { + interface Message { /** * The client ID of the publisher of this message. */ @@ -2353,7 +2353,7 @@ declare namespace Types { /** * Unique ID assigned by Ably to this message. */ - id: string; + id?: string; /** * The event name. */ @@ -2361,29 +2361,34 @@ declare namespace Types { /** * Timestamp of when the message was received by Ably, as milliseconds since the Unix epoch. */ - timestamp: number; + timestamp?: number; } + /** + * A message received from Ably. + */ + type InboundMessage = Message & Required>; + /** * Static utilities related to messages. */ interface MessageStatic { /** - * A static factory method to create a `Message` object from a deserialized Message-like object encoded using Ably's wire protocol. + * A static factory method to create an `InboundMessage` object from a deserialized InboundMessage-like object encoded using Ably's wire protocol. * - * @param JsonObject - A `Message`-like deserialized object. + * @param JsonObject - A `InboundMessage`-like deserialized object. * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A promise which will be fulfilled with a `Message` object. + * @returns A promise which will be fulfilled with an `InboundMessage` object. */ - fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; + fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; /** - * A static factory method to create an array of `Message` objects from an array of deserialized Message-like object encoded using Ably's wire protocol. + * A static factory method to create an array of `InboundMessage` objects from an array of deserialized InboundMessage-like object encoded using Ably's wire protocol. * - * @param JsonArray - An array of `Message`-like deserialized objects. + * @param JsonArray - An array of `InboundMessage`-like deserialized objects. * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A promise which will be fulfilled with an array of {@link Message} objects. + * @returns A promise which will be fulfilled with an array of {@link InboundMessage} objects. */ - fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; + fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; } /** @@ -2601,7 +2606,7 @@ declare namespace Types { */ class PaginatedResult { /** - * Contains the current page of results; for example, an array of {@link Message} or {@link PresenceMessage} objects for a channel history request. + * Contains the current page of results; for example, an array of {@link InboundMessage} or {@link PresenceMessage} objects for a channel history request. */ items: T[]; /** diff --git a/test/package/browser/template/src/index-default.ts b/test/package/browser/template/src/index-default.ts index 865bc21e60..87d5270c0c 100644 --- a/test/package/browser/template/src/index-default.ts +++ b/test/package/browser/template/src/index-default.ts @@ -14,12 +14,19 @@ globalThis.testAblyPackage = async function () { const channel = realtime.channels.get('channel'); await attachChannel(channel); - const receivedMessagePromise = new Promise((resolve) => { - channel.subscribe(() => { - resolve(); - }); + const receivedMessagePromise = new Promise((resolve) => { + channel.subscribe(resolve); }); + // Check that we can use the TypeScript overload that accepts name and data as separate arguments await channel.publish('message', { foo: 'bar' }); - await receivedMessagePromise; + const receivedMessage = await receivedMessagePromise; + + // Check that id and timestamp of a message received from Ably can be assigned to non-optional types + const { id: string, timestamp: number } = receivedMessage; + + channel.unsubscribe(); + + // Check that we can use the TypeScript overload that accepts a Message object + await channel.publish({ name: 'message', data: { foo: 'bar' } }); }; diff --git a/test/package/browser/template/src/index-modules.ts b/test/package/browser/template/src/index-modules.ts index 07a7751e3d..9ad31d69c1 100644 --- a/test/package/browser/template/src/index-modules.ts +++ b/test/package/browser/template/src/index-modules.ts @@ -22,13 +22,21 @@ globalThis.testAblyPackage = async function () { const channel = realtime.channels.get('channel'); await attachChannel(channel); - const receivedMessagePromise = new Promise((resolve) => { - channel.subscribe(() => { - resolve(); - }); + const receivedMessagePromise = new Promise((resolve) => { + channel.subscribe(resolve); }); + // Check that we can use the TypeScript overload that accepts name and data as separate arguments await channel.publish('message', { foo: 'bar' }); - await receivedMessagePromise; + const receivedMessage = await receivedMessagePromise; + + // Check that id and timestamp of a message received from Ably can be assigned to non-optional types + const { id: string, timestamp: number } = receivedMessage; + await checkStandaloneFunction(); + + channel.unsubscribe(); + + // Check that we can use the TypeScript overload that accepts a Message object + await channel.publish({ name: 'message', data: { foo: 'bar' } }); }; From fecf2dfacaa6e849686a32145c8a715163d34d4a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 30 Nov 2023 13:32:38 -0300 Subject: [PATCH 227/468] Correct tsc module settings for test:package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I noticed that, when I tried changing the structure of our package to put the type declarations in a subdirectory (not part of this commit), test:package started failing, claiming that `ably/modules` didn’t exist. Changing the module settings per [1] fixes this, and sounds like it’s the way it should have been configured in the first place: > Use `esnext` with `--moduleResolution bundler` for bundlers, Bun, and > tsx. (Not sure _why_ putting the types in a subdirectory triggered this…) I noticed that making this change caused VS Code to start displaying the following error against the Ably import in `index-default.ts` (although weirdly, not causing `tsc` to fail): > Could not find a declaration file for module 'ably'. > '/Users/lawrence/code/work/ably/ably-js/test/package/browser/build/node_modules/ably/build/ably.js' > implicitly has an 'any' type. > > There are types at > '/Users/lawrence/code/work/ably/ably-js/test/package/browser/build/node_modules/ably/ably.d.ts', > but this result could not be resolved when respecting package.json > "exports". The 'ably' library may need to update its package.json or > typings.ts (7016) So I’ve added typings against the "." package.json export. I think I should have done this in de5ddfa. [1] https://www.typescriptlang.org/docs/handbook/modules/reference.html#summary-1 --- package.json | 1 + test/package/browser/template/package.json | 2 +- test/package/browser/template/src/tsconfig.json | 9 +++++++++ test/package/browser/template/tsconfig.json | 2 -- 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 test/package/browser/template/src/tsconfig.json diff --git a/package.json b/package.json index 299aa4f043..3446ecb1f8 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "exports": { ".": { + "types": "./ably.d.ts", "node": "./build/ably-node.js", "react-native": "./build/ably-reactnative.js", "default": "./build/ably.js" diff --git a/test/package/browser/template/package.json b/test/package/browser/template/package.json index 1c91a386eb..8ee502bb86 100644 --- a/test/package/browser/template/package.json +++ b/test/package/browser/template/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "build": "esbuild --bundle src/index-default.ts --outdir=dist && esbuild --bundle src/index-modules.ts --outdir=dist", - "typecheck": "tsc -noEmit", + "typecheck": "tsc --project src -noEmit", "test-support:server": "ts-node server/server.ts", "test": "playwright test", "test:install-deps": "playwright install chromium" diff --git a/test/package/browser/template/src/tsconfig.json b/test/package/browser/template/src/tsconfig.json new file mode 100644 index 0000000000..cca98c96fa --- /dev/null +++ b/test/package/browser/template/src/tsconfig.json @@ -0,0 +1,9 @@ +{ + "include": ["**/*.ts"], + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler" + } +} diff --git a/test/package/browser/template/tsconfig.json b/test/package/browser/template/tsconfig.json index 4928ea369e..2f98042715 100644 --- a/test/package/browser/template/tsconfig.json +++ b/test/package/browser/template/tsconfig.json @@ -1,7 +1,5 @@ { - "include": ["src/**/*.ts"], "compilerOptions": { - "resolveJsonModule": true, "esModuleInterop": true } } From ae0ea991eb4c54655be72c991430c88ec2a61672 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 12 Dec 2023 22:10:42 +0000 Subject: [PATCH 228/468] Makes modular variant of the library available to Web Workers Removes dedicated Web Worker build from webpack. Now automatically detects whether we are running in a Web Worker context where needed. Resolves #1514 --- .github/workflows/check.yml | 2 +- README.md | 6 +-- package.json | 1 - src/common/lib/util/utils.ts | 10 ++++ src/common/types/IPlatformConfig.d.ts | 1 - src/platform/web/config-webworker.ts | 5 -- src/platform/web/index-webworker.ts | 4 -- .../web/lib/http/request/fetchrequest.ts | 4 +- test/support/browser_file_list.js | 1 - webpack.config.js | 51 ------------------- 10 files changed, 14 insertions(+), 71 deletions(-) delete mode 100644 src/platform/web/config-webworker.ts delete mode 100644 src/platform/web/index-webworker.ts diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f131a149da..1c20b7623e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -21,5 +21,5 @@ jobs: - run: npm ci - run: npm run lint - run: npm run format:check - - run: npx tsc --noEmit ably.d.ts modules.d.ts build/ably-webworker.min.d.ts + - run: npx tsc --noEmit ably.d.ts modules.d.ts - run: npm audit --production diff --git a/README.md b/README.md index 309bae8705..8052723151 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,7 @@ This SDK supports the following platforms: **TypeScript:** see [below](#typescript) -**WebWorkers**: We build a separate bundle which supports running in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) context. You can import it like this: - -```js -import Ably from 'ably/build/ably-webworker.min'; -``` +**WebWorkers:** Browser bundle supports running in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) context. You can also use a [modular variant](#modular-tree-shakable-variant) of the library in Web Workers. We regression-test the library against a selection of those (which will change over time, but usually consists of the versions that are supported upstream, plus old versions of IE). diff --git a/package.json b/package.json index 3446ecb1f8..d97404440b 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", "chai": "^4.2.0", - "copy-webpack-plugin": "^11.0.0", "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 9388ad2db2..90af710dcb 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -571,3 +571,13 @@ export function arrEquals(a: any[], b: any[]) { export function throwMissingModuleError(moduleName: keyof ModulesMap): never { throw new ErrorInfo(`${moduleName} module not provided`, 40019, 400); } + +// from: https://stackoverflow.com/a/18002694 +export function isWebWorkerContext(): boolean { + // run this in global scope of window or worker. since window.self = window, we're ok + if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { + return true; + } else { + return false; + } +} diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 9d678f23bb..50263c7016 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -27,5 +27,4 @@ export interface IPlatformConfig { byteLength: number, callback: (err: Error | null, result: ArrayBuffer | null) => void ) => void; - isWebworker?: boolean; } diff --git a/src/platform/web/config-webworker.ts b/src/platform/web/config-webworker.ts deleted file mode 100644 index 45a1aa43ba..0000000000 --- a/src/platform/web/config-webworker.ts +++ /dev/null @@ -1,5 +0,0 @@ -import Config from './config'; - -Config.isWebworker = true; - -export default Config; diff --git a/src/platform/web/index-webworker.ts b/src/platform/web/index-webworker.ts deleted file mode 100644 index de3be9b971..0000000000 --- a/src/platform/web/index-webworker.ts +++ /dev/null @@ -1,4 +0,0 @@ -import './config-webworker'; -import Ably from './index'; - -export default Ably; diff --git a/src/platform/web/lib/http/request/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts index 68a9febf0f..33214e048f 100644 --- a/src/platform/web/lib/http/request/fetchrequest.ts +++ b/src/platform/web/lib/http/request/fetchrequest.ts @@ -5,7 +5,7 @@ import { RequestCallback, RequestCallbackHeaders, RequestParams } from 'common/t import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import * as Utils from 'common/lib/util/utils'; -import { getGlobalObject } from 'common/lib/util/utils'; +import { getGlobalObject, isWebWorkerContext } from 'common/lib/util/utils'; function isAblyError(responseBody: unknown, headers: Headers): responseBody is { error?: ErrorInfo } { return !!headers.get('x-ably-errorcode'); @@ -55,7 +55,7 @@ export default function fetchRequest( body: body as any, }; - if (!Platform.Config.isWebworker) { + if (!isWebWorkerContext()) { requestInit.credentials = fetchHeaders.has('authorization') ? 'include' : 'same-origin'; } diff --git a/test/support/browser_file_list.js b/test/support/browser_file_list.js index 7350578e0a..748ab836ad 100644 --- a/test/support/browser_file_list.js +++ b/test/support/browser_file_list.js @@ -3,7 +3,6 @@ window.__testFiles__.files = { 'build/ably-nativescript.js': true, 'build/ably-node.js': true, 'build/ably-reactnative.js': true, - 'build/ably-webworker.min.js': true, 'build/ably.js': true, 'build/ably.min.js': true, 'browser/lib/util/base64.js': true, diff --git a/webpack.config.js b/webpack.config.js index c569d64272..e6c9c04536 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,6 @@ const path = require('path'); const { BannerPlugin } = require('webpack'); const banner = require('./src/fragments/license'); -const CopyPlugin = require('copy-webpack-plugin'); // This is needed for baseUrl to resolve correctly from tsconfig const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); @@ -64,26 +63,6 @@ const nodeConfig = { }, }; -const browserConfig = { - ...baseConfig, - output: { - ...baseConfig.output, - filename: 'ably.js', - }, - entry: { - index: platformPath('web'), - }, - resolve: { - ...baseConfig.resolve, - fallback: { - crypto: false, - }, - }, - optimization: { - minimize: false, - }, -}; - const nativeScriptConfig = { ...baseConfig, output: { @@ -137,38 +116,8 @@ const reactNativeConfig = { }, }; -const webworkerConfig = { - target: ['webworker', 'es5'], - ...browserConfig, - entry: { - index: platformPath('web', 'index-webworker.ts'), - }, - output: { - ...baseConfig.output, - filename: 'ably-webworker.min.js', - globalObject: 'this', - }, - optimization: { - minimize: true, - }, - performance: { - hints: 'warning', - }, - plugins: [ - new CopyPlugin({ - patterns: [ - { - from: path.resolve(__dirname, 'src', 'fragments', 'ably.d.ts'), - to: path.resolve(__dirname, 'build', 'ably-webworker.min.d.ts'), - }, - ], - }), - ], -}; - module.exports = { node: nodeConfig, - webworker: webworkerConfig, nativeScript: nativeScriptConfig, reactNative: reactNativeConfig, }; From e31317a7bff9fccac4581bddc851a5e287675f0f Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 13 Dec 2023 14:42:28 +0000 Subject: [PATCH 229/468] Housekeeping --- README.md | 2 +- package-lock.json | 274 ------------------ .../web/lib/http/request/fetchrequest.ts | 5 +- 3 files changed, 3 insertions(+), 278 deletions(-) diff --git a/README.md b/README.md index 8052723151..02dd0b3080 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This SDK supports the following platforms: **Node.js:** version 8.17 or newer. (1.1.x versions work on Node.js 4.5 or newer). We do not currently provide an ESM bundle, please [contact us](https://www.ably.com/contact) if you would would like to use ably-js in a NodeJS ESM project. -**React (release candidate)** We offer a set of React Hooks which make it seamless to use ably-js in your React application. See the [React Hooks documentation](./docs/react.md) for more details. +**React (release candidate):** We offer a set of React Hooks which make it seamless to use ably-js in your React application. See the [React Hooks documentation](./docs/react.md) for more details. **React Native:** We aim to support all platforms supported by React Native. If you find any issues please raise an issue or [contact us](https://www.ably.com/contact). diff --git a/package-lock.json b/package-lock.json index 58689871d8..6363b1c4fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", "chai": "^4.2.0", - "copy-webpack-plugin": "^11.0.0", "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", @@ -2005,45 +2004,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -2839,61 +2799,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "dev": true, - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -4953,18 +4858,6 @@ "node": "*" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -8497,59 +8390,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -12036,35 +11876,6 @@ "uri-js": "^4.2.2" } }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "requires": { - "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -12662,41 +12473,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, - "copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "dev": true, - "requires": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "dependencies": { - "globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "requires": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - } - }, - "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true - } - } - }, "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -14220,15 +13996,6 @@ } } }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -16773,47 +16540,6 @@ "loose-envify": "^1.1.0" } }, - "schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", diff --git a/src/platform/web/lib/http/request/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts index 33214e048f..abe39d7820 100644 --- a/src/platform/web/lib/http/request/fetchrequest.ts +++ b/src/platform/web/lib/http/request/fetchrequest.ts @@ -5,7 +5,6 @@ import { RequestCallback, RequestCallbackHeaders, RequestParams } from 'common/t import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import * as Utils from 'common/lib/util/utils'; -import { getGlobalObject, isWebWorkerContext } from 'common/lib/util/utils'; function isAblyError(responseBody: unknown, headers: Headers): responseBody is { error?: ErrorInfo } { return !!headers.get('x-ably-errorcode'); @@ -55,11 +54,11 @@ export default function fetchRequest( body: body as any, }; - if (!isWebWorkerContext()) { + if (!Utils.isWebWorkerContext()) { requestInit.credentials = fetchHeaders.has('authorization') ? 'include' : 'same-origin'; } - getGlobalObject() + Utils.getGlobalObject() .fetch(uri + '?' + new URLSearchParams(params || {}), requestInit) .then((res) => { clearTimeout(timeout); From e4fac867c3d5435cde715860e64d9d5d5a380485 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 30 Nov 2023 14:36:12 -0300 Subject: [PATCH 230/468] Implement flattened stats API This implements the API changes of spec commit a731d12, and the protocol version bump from spec commit dfe9476. Documentation taken from sdk-api-reference commit 570728b. Resolves #1269. --- ably.d.ts | 152 ++-------------- src/common/lib/types/stats.ts | 300 ++------------------------------ src/common/lib/util/defaults.ts | 2 +- test/realtime/init.test.js | 2 +- test/rest/http.test.js | 2 +- test/rest/request.test.js | 4 +- test/rest/stats.test.js | 73 +++++--- 7 files changed, 72 insertions(+), 463 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index ff61ca4534..fb3bf2dac0 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -751,122 +751,6 @@ declare namespace Types { constructor(message: string, code: number, statusCode: number, cause?: string | Error | ErrorInfo); } - /** - * Contains the aggregate counts for messages and data transferred. - */ - interface StatsMessageCount { - /** - * The count of all messages. - */ - count: number; - /** - * The total number of bytes transferred for all messages. - */ - data: number; - } - - /** - * Contains a breakdown of summary stats data for different (channel vs presence) message types. - */ - interface StatsMessageTypes { - /** - * A {@link StatsMessageCount} object containing the count and byte value of messages and presence messages. - */ - all: StatsMessageCount; - /** - * A {@link StatsMessageCount} object containing the count and byte value of messages. - */ - messages: StatsMessageCount; - /** - * A {@link StatsMessageCount} object containing the count and byte value of presence messages. - */ - presence: StatsMessageCount; - } - - /** - * Contains the aggregate counts for requests made. - */ - interface StatsRequestCount { - /** - * The number of requests that failed. - */ - failed: number; - /** - * The number of requests that were refused, typically as a result of permissions or a limit being exceeded. - */ - refused: number; - /** - * The number of requests that succeeded. - */ - succeeded: number; - } - - /** - * Contains the aggregate data for usage of a resource in a specific scope. - */ - interface StatsResourceCount { - /** - * The average number of resources of this type used for this period. - */ - mean: number; - /** - * The minimum total resources of this type used for this period. - */ - min: number; - /** - * The total number of resources opened of this type. - */ - opened: number; - /** - * The peak number of resources of this type used for this period. - */ - peak: number; - /** - * The number of resource requests refused within this period. - */ - refused: number; - } - - /** - * Contains a breakdown of summary stats data for different (TLS vs non-TLS) connection types. - */ - interface StatsConnectionTypes { - /** - * A {@link StatsResourceCount} object containing a breakdown of usage by scope over TLS connections (both TLS and non-TLS). - */ - all: StatsResourceCount; - /** - * A {@link StatsResourceCount} object containing a breakdown of usage by scope over non-TLS connections. - */ - plain: StatsResourceCount; - /** - * A {@link StatsResourceCount} object containing a breakdown of usage by scope over TLS connections. - */ - tls: StatsResourceCount; - } - - /** - * Contains a breakdown of summary stats data for traffic over various transport types. - */ - interface StatsMessageTraffic { - /** - * A {@link StatsMessageTypes} object containing a breakdown of usage by message type for all messages (includes `realtime`, `rest` and `webhook` messages). - */ - all: StatsMessageTypes; - /** - * A {@link StatsMessageTypes} object containing a breakdown of usage by message type for messages transferred over a realtime transport such as WebSocket. - */ - realtime: StatsMessageTypes; - /** - * A {@link StatsMessageTypes} object containing a breakdown of usage by message type for messages transferred over a rest transport such as WebSocket. - */ - rest: StatsMessageTypes; - /** - * A {@link StatsMessageTypes} object containing a breakdown of usage by message type for messages delivered using webhooks. - */ - webhook: StatsMessageTypes; - } - /** * Contains an Ably Token and its associated metadata. */ @@ -2563,42 +2447,26 @@ declare namespace Types { * Contains application statistics for a specified time interval and time period. */ class Stats { - /** - * A {@link StatsMessageTypes} object containing the aggregate count of all message stats. - */ - all: StatsMessageTypes; - /** - * A {@link StatsRequestCount} object containing a breakdown of API Requests. - */ - apiRequests: StatsRequestCount; - /** - * A {@link StatsResourceCount} object containing a breakdown of channels. - */ - channels: StatsResourceCount; - /** - * A {@link StatsConnectionTypes} object containing a breakdown of connection related stats, such as min, mean and peak connections. - */ - connections: StatsConnectionTypes; - /** - * A {@link StatsMessageTraffic} object containing the aggregate count of inbound message stats. - */ - inbound: StatsMessageTraffic; /** * The UTC time at which the time period covered begins. If `unit` is set to `minute` this will be in the format `YYYY-mm-dd:HH:MM`, if `hour` it will be `YYYY-mm-dd:HH`, if `day` it will be `YYYY-mm-dd:00` and if `month` it will be `YYYY-mm-01:00`. */ intervalId: string; /** - * A {@link StatsMessageTraffic} object containing the aggregate count of outbound message stats. + * For entries that are still in progress, such as the current month: the last sub-interval included in this entry (in format yyyy-mm-dd:hh:mm:ss), else undefined. + */ + inProgress?: string; + /** + * The statistics for this time interval and time period. See the JSON schema which the {@link Stats.schema | `schema`} property points to for more information. */ - outbound: StatsMessageTraffic; + entries: Partial>; /** - * A {@link StatsMessageTypes} object containing the aggregate count of persisted message stats. + * The URL of a [JSON Schema](https://json-schema.org/) which describes the structure of this `Stats` object. */ - persisted: StatsMessageTypes; + schema: string; /** - * A {@link StatsRequestCount} object containing a breakdown of Ably Token requests. + * The ID of the Ably application the statistics are for. */ - tokenRequests: StatsRequestCount; + appId: string; } /** diff --git a/src/common/lib/types/stats.ts b/src/common/lib/types/stats.ts index 8905a8a7ba..e9811f4d45 100644 --- a/src/common/lib/types/stats.ts +++ b/src/common/lib/types/stats.ts @@ -1,304 +1,24 @@ -import * as Utils from '../util/utils'; - -type MessageValues = { - count?: number; - data?: number; - uncompressedData?: number; - failed?: number; - refused?: number; - category?: Record; -}; - -type ResourceValues = { - peak?: number; - min?: number; - mean?: number; - opened?: number; - refused?: number; -}; - -type RequestValues = { - succeeded?: number; - failed?: number; - refused?: number; -}; - -type ConnectionTypesValues = { - plain?: ResourceValues; - tls?: ResourceValues; - all?: ResourceValues; -}; - -type MessageTypesValues = { - messages?: MessageValues; - presence?: MessageValues; - all?: MessageValues; -}; - -type MessageTrafficValues = { - realtime?: MessageTypesValues; - rest?: MessageTypesValues; - webhook?: MessageTypesValues; - sharedQueue?: MessageTypesValues; - externalQueue?: MessageTypesValues; - httpEvent?: MessageTypesValues; - push?: MessageTypesValues; - all?: MessageTypesValues; -}; - -type MessageDirectionsValues = { - all?: MessageTypesValues; - inbound?: MessageTrafficValues; - outbound?: MessageTrafficValues; -}; - -type XchgMessagesValues = { - all?: MessageTypesValues; - producerPaid?: MessageDirectionsValues; - consumerPaid?: MessageDirectionsValues; -}; - -type NotificationsValues = { - invalid?: number; - attempted?: number; - successful?: number; - failed?: number; -}; - -type PushValues = { - messages?: number; - notifications?: NotificationsValues; - directPublishes?: number; -}; - -type ProcessedCountValues = { - succeeded?: number; - skipped?: number; - failed?: number; -}; - -type ProcessedMessagesValues = { - delta?: Record; -}; - type StatsValues = { - all?: MessageTypesValues; - inbound?: MessageTrafficValues; - outbound?: MessageTrafficValues; - persisted?: MessageTypesValues; - connections?: ConnectionTypesValues; - channels?: ResourceValues; - apiRequests?: RequestValues; - tokenRequests?: RequestValues; - xchgProducer?: XchgMessagesValues; - xchgConsumer?: XchgMessagesValues; - pushStats?: PushValues; - processed?: ProcessedMessagesValues; + entries?: Partial>; + schema?: string; + appId?: string; inProgress?: never; unit?: never; intervalId?: never; }; -class MessageCount { - count?: number; - data?: number; - uncompressedData?: number; - failed?: number; - refused?: number; - - constructor(values?: MessageValues) { - this.count = (values && values.count) || 0; - this.data = (values && values.data) || 0; - this.uncompressedData = (values && values.uncompressedData) || 0; - this.failed = (values && values.failed) || 0; - this.refused = (values && values.refused) || 0; - } -} - -class MessageCategory extends MessageCount { - category?: Record; - constructor(values?: MessageValues) { - super(values); - if (values && values.category) { - this.category = {}; - Utils.forInOwnNonNullProperties(values.category, (prop: string) => { - (this.category as Record)[prop] = new MessageCount( - (values.category as Record)[prop] - ); - }); - } - } -} - -class ResourceCount { - peak?: number; - min?: number; - mean?: number; - opened?: number; - refused?: number; - - constructor(values?: ResourceValues) { - this.peak = (values && values.peak) || 0; - this.min = (values && values.min) || 0; - this.mean = (values && values.mean) || 0; - this.opened = (values && values.opened) || 0; - this.refused = (values && values.refused) || 0; - } -} - -class RequestCount { - succeeded?: number; - failed?: number; - refused?: number; - - constructor(values?: RequestValues) { - this.succeeded = (values && values.succeeded) || 0; - this.failed = (values && values.failed) || 0; - this.refused = (values && values.refused) || 0; - } -} - -class ConnectionTypes { - plain?: ResourceCount; - tls?: ResourceCount; - all?: ResourceCount; - - constructor(values?: ConnectionTypesValues) { - this.plain = new ResourceCount(values && values.plain); - this.tls = new ResourceCount(values && values.tls); - this.all = new ResourceCount(values && values.all); - } -} - -class MessageTypes { - messages?: MessageCategory; - presence?: MessageCategory; - all?: MessageCategory; - - constructor(values?: MessageTypesValues) { - this.messages = new MessageCategory(values && values.messages); - this.presence = new MessageCategory(values && values.presence); - this.all = new MessageCategory(values && values.all); - } -} - -class MessageTraffic { - realtime?: MessageTypes; - rest?: MessageTypes; - webhook?: MessageTypes; - sharedQueue?: MessageTypes; - externalQueue?: MessageTypes; - httpEvent?: MessageTypes; - push?: MessageTypes; - all?: MessageTypes; - - constructor(values?: MessageTrafficValues) { - this.realtime = new MessageTypes(values && values.realtime); - this.rest = new MessageTypes(values && values.rest); - this.webhook = new MessageTypes(values && values.webhook); - this.sharedQueue = new MessageTypes(values && values.sharedQueue); - this.externalQueue = new MessageTypes(values && values.externalQueue); - this.httpEvent = new MessageTypes(values && values.httpEvent); - this.push = new MessageTypes(values && values.push); - this.all = new MessageTypes(values && values.all); - } -} - -class MessageDirections { - all?: MessageTypes; - inbound?: MessageTraffic; - outbound?: MessageTraffic; - - constructor(values?: MessageDirectionsValues) { - this.all = new MessageTypes(values && values.all); - this.inbound = new MessageTraffic(values && values.inbound); - this.outbound = new MessageTraffic(values && values.outbound); - } -} - -class XchgMessages { - all?: MessageTypes; - producerPaid?: MessageDirections; - consumerPaid?: MessageDirections; - - constructor(values?: XchgMessagesValues) { - this.all = new MessageTypes(values && values.all); - this.producerPaid = new MessageDirections(values && values.producerPaid); - this.consumerPaid = new MessageDirections(values && values.consumerPaid); - } -} - -class PushStats { - messages?: number; - notifications?: NotificationsValues; - directPublishes?: number; - - constructor(values?: PushValues) { - this.messages = (values && values.messages) || 0; - const notifications = values && values.notifications; - this.notifications = { - invalid: (notifications && notifications.invalid) || 0, - attempted: (notifications && notifications.attempted) || 0, - successful: (notifications && notifications.successful) || 0, - failed: (notifications && notifications.failed) || 0, - }; - this.directPublishes = (values && values.directPublishes) || 0; - } -} - -class ProcessedCount { - succeeded?: number; - skipped?: number; - failed?: number; - - constructor(values: ProcessedCountValues) { - this.succeeded = (values && values.succeeded) || 0; - this.skipped = (values && values.skipped) || 0; - this.failed = (values && values.failed) || 0; - } -} - -class ProcessedMessages { - delta?: Record; - - constructor(values?: ProcessedMessagesValues) { - this.delta = undefined; - if (values && values.delta) { - this.delta = {}; - Utils.forInOwnNonNullProperties(values.delta, (prop: string) => { - (this.delta as Record)[prop] = new ProcessedCount( - (values.delta as Record)[prop] - ); - }); - } - } -} - -class Stats extends MessageDirections { - persisted?: MessageTypes; - connections?: ConnectionTypes; - channels?: ResourceCount; - apiRequests?: RequestCount; - tokenRequests?: RequestCount; - xchgProducer?: XchgMessages; - xchgConsumer?: XchgMessages; - push?: PushStats; - processed?: ProcessedMessages; +class Stats { + entries?: Partial>; + schema?: string; + appId?: string; inProgress?: never; unit?: never; intervalId?: never; constructor(values?: StatsValues) { - super(values as MessageDirectionsValues); - this.persisted = new MessageTypes(values && values.persisted); - this.connections = new ConnectionTypes(values && values.connections); - this.channels = new ResourceCount(values && values.channels); - this.apiRequests = new RequestCount(values && values.apiRequests); - this.tokenRequests = new RequestCount(values && values.tokenRequests); - this.xchgProducer = new XchgMessages(values && values.xchgProducer); - this.xchgConsumer = new XchgMessages(values && values.xchgConsumer); - this.push = new PushStats(values && values.pushStats); - this.processed = new ProcessedMessages(values && values.processed); + this.entries = (values && values.entries) || undefined; + this.schema = (values && values.schema) || undefined; + this.appId = (values && values.appId) || undefined; this.inProgress = (values && values.inProgress) || undefined; this.unit = (values && values.unit) || undefined; this.intervalId = (values && values.intervalId) || undefined; diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 489d651307..2be603152a 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -81,7 +81,7 @@ const Defaults = { maxMessageSize: 65536, version, - protocolVersion: 2, + protocolVersion: 3, agent, getHost, getPort, diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index f617628641..9b056eb6eb 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -35,7 +35,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var transport = realtime.connection.connectionManager.activeProtocol.transport; var connectUri = helper.isWebsocket(transport) ? transport.uri : transport.recvRequest.uri; try { - expect(connectUri.indexOf('v=2') > -1, 'Check uri includes v=2').to.be.ok; + expect(connectUri.indexOf('v=3') > -1, 'Check uri includes v=3').to.be.ok; } catch (err) { closeAndFinish(done, realtime, err); return; diff --git a/test/rest/http.test.js b/test/rest/http.test.js index dcd7ee8262..f02ed488de 100644 --- a/test/rest/http.test.js +++ b/test/rest/http.test.js @@ -31,7 +31,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { // This test should not directly validate version against Defaults.version, as // ultimately the version header has been derived from that value. - expect(headers['X-Ably-Version']).to.equal('2', 'Verify current version number'); + expect(headers['X-Ably-Version']).to.equal('3', 'Verify current version number'); expect(headers['Ably-Agent'].indexOf('ably-js/' + Defaults.version) > -1, 'Verify agent').to.be.ok; expect(headers['Ably-Agent'].indexOf('custom-agent/0.1.2') > -1, 'Verify custom agent').to.be.ok; diff --git a/test/rest/request.test.js b/test/rest/request.test.js index c123e94606..cfeb081b33 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -125,7 +125,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async restTestOnJsonMsgpack('request_batch_api_success', async function (rest, name) { var body = { channels: [name + '1', name + '2'], messages: { data: 'foo' } }; - const res = await rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}); + const res = await rest.request('POST', '/messages', 2, {}, body, {}); expect(res.success).to.equal(true, 'Check res.success is true for a success'); expect(res.statusCode).to.equal(201, 'Check res.statusCode is 201 for a success'); expect(res.errorCode).to.equal(null, 'Check res.errorCode is null for a success'); @@ -145,7 +145,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async restTestOnJsonMsgpack.skip('request_batch_api_partial_success', async function (rest, name) { var body = { channels: [name, '[invalid', ''], messages: { data: 'foo' } }; - var res = await rest.request('POST', '/messages', Defaults.protocolVersion, {}, body, {}); + var res = await rest.request('POST', '/messages', 2, {}, body, {}); expect(res.success).to.equal(false, 'Check res.success is false for a partial failure'); expect(res.statusCode).to.equal(400, 'Check HPR.statusCode is 400 for a partial failure'); expect(res.errorCode).to.equal(40020, 'Check HPR.errorCode is 40020 for a partial failure'); diff --git a/test/rest/stats.test.js b/test/rest/stats.test.js index bda82edfb5..171a69730a 100644 --- a/test/rest/stats.test.js +++ b/test/rest/stats.test.js @@ -75,6 +75,27 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); }); + it('contains expected fields', async () => { + // To provoke a non-undefined `inProgress` in the response, we publish a message and fetch stats for the current hour. (I wasn’t able to provoke a non-undefined `inProgress` using stats API fixtures.) + const now = new Date(await rest.time()); + // If the hour is about to turn, wait for it to turn (with a 5-second extra wait to hopefully account for clock differences between Ably servers). + if (now.getUTCMinutes() === 59 && now.getUTCSeconds() > 45) { + await new Promise((resolve) => setTimeout(resolve, 1000 * (5 + (60 - now.getUTCSeconds())))); + } + await rest.channels.get('channel').publish('message', 'data'); + // ably.com documentation says "The most recent statistics are delayed by up to six seconds." + await new Promise((resolve) => setTimeout(resolve, 6000 + 4000 /* a bit of extra tolerance */)); + + const stats = (await rest.stats({ end: Date.now(), unit: 'hour' })).items[0]; + + expect(stats.entries).to.be.a('object'); + expect(stats.schema).to.be.a('string'); + expect(stats.appId).to.be.a('string'); + expect(stats.inProgress).to.be.a('string'); + expect(stats.unit).to.be.a('string'); + expect(stats.intervalId).to.be.a('string'); + }); + /** * Using an interval ID string format, check minute-level inbound and outbound stats match fixture data (forwards) * @spec : (RSC6b4) @@ -91,8 +112,8 @@ define(['shared_helper', 'chai'], function (helper, chai) { var totalInbound = 0, totalOutbound = 0; for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; + totalInbound += stats[i].entries['messages.inbound.all.messages.count']; + totalOutbound += stats[i].entries['messages.outbound.all.messages.count']; } expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); @@ -115,8 +136,8 @@ define(['shared_helper', 'chai'], function (helper, chai) { var totalInbound = 0, totalOutbound = 0; for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; + totalInbound += stats[i].entries['messages.inbound.all.messages.count']; + totalOutbound += stats[i].entries['messages.outbound.all.messages.count']; } expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); @@ -140,8 +161,8 @@ define(['shared_helper', 'chai'], function (helper, chai) { var totalInbound = 0, totalOutbound = 0; for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; + totalInbound += stats[i].entries['messages.inbound.all.messages.count']; + totalOutbound += stats[i].entries['messages.outbound.all.messages.count']; } expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); @@ -164,8 +185,8 @@ define(['shared_helper', 'chai'], function (helper, chai) { var totalInbound = 0, totalOutbound = 0; for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; + totalInbound += stats[i].entries['messages.inbound.all.messages.count']; + totalOutbound += stats[i].entries['messages.outbound.all.messages.count']; } expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); @@ -188,8 +209,8 @@ define(['shared_helper', 'chai'], function (helper, chai) { var totalInbound = 0, totalOutbound = 0; for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; + totalInbound += stats[i].entries['messages.inbound.all.messages.count']; + totalOutbound += stats[i].entries['messages.outbound.all.messages.count']; } expect(totalInbound).to.equal(50 + 60 + 70, 'Verify all inbound messages found'); @@ -212,8 +233,8 @@ define(['shared_helper', 'chai'], function (helper, chai) { var totalInbound = 0, totalOutbound = 0; for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; + totalInbound += stats[i].entries['messages.inbound.all.messages.count']; + totalOutbound += stats[i].entries['messages.outbound.all.messages.count']; } expect(totalInbound).to.equal(60, 'Verify all inbound messages found'); @@ -236,8 +257,8 @@ define(['shared_helper', 'chai'], function (helper, chai) { var totalInbound = 0, totalOutbound = 0; for (var i = 0; i < stats.length; i++) { - totalInbound += stats[i].inbound.all.messages.count; - totalOutbound += stats[i].outbound.all.messages.count; + totalInbound += stats[i].entries['messages.inbound.all.messages.count']; + totalOutbound += stats[i].entries['messages.outbound.all.messages.count']; } expect(totalInbound).to.equal(50, 'Verify all inbound messages found'); @@ -257,7 +278,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(7000, 'Verify all published message data found'); /* get next page */ @@ -266,7 +287,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(6000, 'Verify all published message data found'); /* get next page */ @@ -275,7 +296,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(5000, 'Verify all published message data found'); /* verify no further pages */ @@ -284,7 +305,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var page = await page.first(); var totalData = 0; var stats = page.items; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(7000, 'Verify all published message data found'); }); @@ -301,7 +322,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(5000, 'Verify all published message data found'); /* get next page */ @@ -310,7 +331,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(6000, 'Verify all published message data found'); /* get next page */ @@ -319,7 +340,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(7000, 'Verify all published message data found'); /* verify no further pages */ @@ -328,7 +349,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var page = await page.first(); var totalData = 0; var stats = page.items; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(5000, 'Verify all published message data found'); }); @@ -344,7 +365,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(7000, 'Verify all published message data found'); /* get next page */ @@ -353,7 +374,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(6000, 'Verify all published message data found'); /* get next page */ @@ -362,7 +383,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var stats = page.items; expect(stats.length == 1, 'Verify exactly one stats record found').to.be.ok; var totalData = 0; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(5000, 'Verify all published message data found'); /* verify no further pages */ @@ -371,7 +392,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { var page = await page.first(); var totalData = 0; var stats = page.items; - for (var i = 0; i < stats.length; i++) totalData += stats[i].inbound.all.messages.data; + for (var i = 0; i < stats.length; i++) totalData += stats[i].entries['messages.inbound.all.messages.data']; expect(totalData).to.equal(7000, 'Verify all published message data found'); }); }); From 350d2fdc3c5779aaba7807df074db1dc5ff481be Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 4 Dec 2023 10:22:50 -0300 Subject: [PATCH 231/468] Remove newBatchResponse parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per spec commit c48464a (it’s no longer needed in protocol v3). --- src/common/lib/client/rest.ts | 40 ++++++++++++++--------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index c9a26f0061..6c1fa7eae5 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -210,31 +210,23 @@ export class Rest { if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); - Resource.post( - this.client, - '/messages', - requestBody, - headers, - { newBatchResponse: 'true' }, - null, - (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } + Resource.post(this.client, '/messages', requestBody, headers, {}, null, (err, body, headers, unpacked) => { + if (err) { + callback(err); + return; + } - const batchResults = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as BatchPublishResult[]; + const batchResults = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as BatchPublishResult[]; - // I don't love the below type assertions for `callback` but not sure how to avoid them - if (singleSpecMode) { - (callback as StandardCallback)(null, batchResults[0]); - } else { - (callback as StandardCallback)(null, batchResults); - } + // I don't love the below type assertions for `callback` but not sure how to avoid them + if (singleSpecMode) { + (callback as StandardCallback)(null, batchResults[0]); + } else { + (callback as StandardCallback)(null, batchResults); } - ); + }); } batchPresence(channels: string[]): Promise; @@ -257,7 +249,7 @@ export class Rest { this.client, '/presence', headers, - { newBatchResponse: 'true', channels: channelsParam }, + { channels: channelsParam }, null, (err, body, headers, unpacked) => { if (err) { @@ -304,7 +296,7 @@ export class Rest { `/keys/${keyName}/revokeTokens`, requestBody, headers, - { newBatchResponse: 'true' }, + {}, null, (err, body, headers, unpacked) => { if (err) { From dbe2ae2ce7c347c2f4e26719ab461b5fb1cc0b20 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 15 Dec 2023 15:12:23 +0000 Subject: [PATCH 232/468] Update README.md Co-authored-by: Lawrence Forooghian <53756884+lawrence-forooghian@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02dd0b3080..dac81d617c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This SDK supports the following platforms: **TypeScript:** see [below](#typescript) -**WebWorkers:** Browser bundle supports running in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) context. You can also use a [modular variant](#modular-tree-shakable-variant) of the library in Web Workers. +**WebWorkers:** The browser bundle supports running in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) context. You can also use the [modular variant](#modular-tree-shakable-variant) of the library in Web Workers. We regression-test the library against a selection of those (which will change over time, but usually consists of the versions that are supported upstream, plus old versions of IE). From 8901db90bb807d65933b1e9c7d32f9d412e144e0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 3 Jan 2024 15:44:53 +0000 Subject: [PATCH 233/468] Fix copy-and-paste error introduced in 48ede7f --- typedoc/landing-pages/modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typedoc/landing-pages/modules.md b/typedoc/landing-pages/modules.md index 2a9ff63dea..4f4f7be061 100644 --- a/typedoc/landing-pages/modules.md +++ b/typedoc/landing-pages/modules.md @@ -1,6 +1,6 @@ # Ably JavaScript Client Library SDK API Reference (modular variant) -You are currently viewing the modular (tree-shakable) variant of the Ably JavaScript Client Library SDK. View the callback-based variant [here](../default/index.html). +You are currently viewing the modular (tree-shakable) variant of the Ably JavaScript Client Library SDK. View the default variant [here](../default/index.html). To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. From 1a76ec03e05ad9d342e10e6129ebcc93f8367fb7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 30 Nov 2023 09:15:32 -0300 Subject: [PATCH 234/468] Add ErrorInfo export to modules.d.ts Missed this in b839715. --- modules.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules.d.ts b/modules.d.ts index 76f54a5aee..b4782e5502 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -1,4 +1,4 @@ -import { Types } from './ably'; +import { Types, ErrorInfo } from './ably'; export declare const generateRandomKey: Types.Crypto['generateRandomKey']; export declare const getDefaultCryptoParams: Types.Crypto['getDefaultParams']; @@ -276,4 +276,4 @@ export declare class BaseRealtime extends Types.Realtime { constructor(options: Types.ClientOptions, modules: ModulesMap); } -export { Types }; +export { Types, ErrorInfo }; From 1289df7ae07abdd84391eedc63f68359177ed704 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 30 Nov 2023 09:29:36 -0300 Subject: [PATCH 235/468] Remove Types.ErrorInfo To avoid a clash with the top-level class of the same name when we remove the Types namespace in #909. --- ably.d.ts | 91 +++++++++---------- src/common/lib/types/errorinfo.ts | 2 +- .../react-hooks/sample-app/src/App.tsx | 6 +- .../react-hooks/src/AblyReactHooks.ts | 6 +- .../react-hooks/src/hooks/useChannel.test.tsx | 6 +- .../react-hooks/src/hooks/useChannel.ts | 6 +- .../src/hooks/usePresence.test.tsx | 6 +- .../react-hooks/src/hooks/usePresence.ts | 6 +- .../react-hooks/src/hooks/useStateErrors.ts | 6 +- 9 files changed, 65 insertions(+), 70 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index fb3bf2dac0..3e30600a0a 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -719,38 +719,6 @@ declare namespace Types { mode: string; } - /** - * A generic Ably error object that contains an Ably-specific status code, and a generic status code. Errors returned from the Ably server are compatible with the `ErrorInfo` structure and should result in errors that inherit from `ErrorInfo`. - */ - class ErrorInfo extends Error { - /** - * Ably [error code](https://github.com/ably/ably-common/blob/main/protocol/errors.json). - */ - code: number; - /** - * Additional message information, where available. - */ - message: string; - /** - * HTTP Status Code corresponding to this error, where applicable. - */ - statusCode: number; - /** - * The underlying cause of the error, where applicable. - */ - cause?: string | Error | ErrorInfo; - - /** - * Construct an ErrorInfo object. - * - * @param message - A string describing the error. - * @param code - Ably [error code](https://github.com/ably/ably-common/blob/main/protocol/errors.json). - * @param statusCode - HTTP Status Code corresponding to this error. - * @param cause - The underlying cause of the error. - */ - constructor(message: string, code: number, statusCode: number, cause?: string | Error | ErrorInfo); - } - /** * Contains an Ably Token and its associated metadata. */ @@ -1592,7 +1560,7 @@ declare namespace Types { * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. - * @returns A promise which, upon success, will be fulfilled with an {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with an {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ request( method: string, @@ -1606,13 +1574,13 @@ declare namespace Types { * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). * * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link Types.StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link Types.StatsParams} interface. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ stats(params?: StatsParams | any): Promise>; /** * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link Types.TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link Types.ClientOptions.queryTime} property instead of this method. * - * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ time(): Promise; @@ -1620,14 +1588,14 @@ declare namespace Types { * Publishes a {@link Types.BatchPublishSpec} object to one or more channels, up to a maximum of 100 channels. * * @param spec - A {@link Types.BatchPublishSpec} object. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ batchPublish(spec: BatchPublishSpec): Promise>; /** * Publishes one or more {@link Types.BatchPublishSpec} objects to one or more channels, up to a maximum of 100 channels. * * @param specs - An array of {@link Types.BatchPublishSpec} objects. - * @returns A promise which, upon success, will be fulfilled with an array of {@link Types.BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link Types.BatchPublishSpec}. This array is in the same order as the provided {@link Types.BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with an array of {@link Types.BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link Types.BatchPublishSpec}. This array is in the same order as the provided {@link Types.BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ batchPublish( specs: BatchPublishSpec[] @@ -1636,7 +1604,7 @@ declare namespace Types { * Retrieves the presence state for one or more channels, up to a maximum of 100 channels. Presence state includes the `clientId` of members and their current {@link Types.PresenceAction}. * * @param channels - An array of one or more channel names, up to a maximum of 100 channels. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ batchPresence(channels: string[]): Promise[]>; /** @@ -1683,7 +1651,7 @@ declare namespace Types { * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. - * @returns A promise which, upon success, will be fulfilled with the {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with the {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ request( method: string, @@ -1697,27 +1665,27 @@ declare namespace Types { * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). * * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link Types.StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link Types.StatsParams} interface. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ stats(params?: StatsParams | any): Promise>; /** * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link Types.TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link Types.ClientOptions.queryTime} property instead of this method. * - * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ time(): Promise; /** * Publishes a {@link Types.BatchPublishSpec} object to one or more channels, up to a maximum of 100 channels. * * @param spec - A {@link Types.BatchPublishSpec} object. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ batchPublish(spec: BatchPublishSpec): Promise>; /** * Publishes one or more {@link Types.BatchPublishSpec} objects to one or more channels, up to a maximum of 100 channels. * * @param specs - An array of {@link Types.BatchPublishSpec} objects. - * @returns A promise which, upon success, will be fulfilled with an array of {@link Types.BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link Types.BatchPublishSpec}. This array is in the same order as the provided {@link Types.BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with an array of {@link Types.BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link Types.BatchPublishSpec}. This array is in the same order as the provided {@link Types.BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ batchPublish( specs: BatchPublishSpec[] @@ -1726,7 +1694,7 @@ declare namespace Types { * Retrieves the presence state for one or more channels, up to a maximum of 100 channels. Presence state includes the `clientId` of members and their current {@link Types.PresenceAction}. * * @param channels - An array of one or more channel names, up to a maximum of 100 channels. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ batchPresence(channels: string[]): Promise[]>; /** @@ -1757,7 +1725,7 @@ declare namespace Types { * * @param tokenParams - A {@link TokenParams} object. * @param authOptions - An {@link AuthOptions} object. - * @returns A promise which, upon success, will be fulfilled with a {@link TokenRequest} object. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link TokenRequest} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ createTokenRequest(tokenParams?: TokenParams, authOptions?: AuthOptions): Promise; /** @@ -1773,7 +1741,7 @@ declare namespace Types { * * @param specifiers - An array of {@link TokenRevocationTargetSpecifier} objects. * @param options - A set of options which are used to modify the revocation request. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} containing information about the result of the token revocation request for each provided [`TokenRevocationTargetSpecifier`]{@link TokenRevocationTargetSpecifier}. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} containing information about the result of the token revocation request for each provided [`TokenRevocationTargetSpecifier`]{@link TokenRevocationTargetSpecifier}. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ revokeTokens( specifiers: TokenRevocationTargetSpecifier[], @@ -2380,7 +2348,7 @@ declare namespace Types { * Generates a random key to be used in the encryption of the channel. If the language cryptographic randomness primitives are blocking or async, a callback is used. The callback returns a generated binary key. * * @param keyLength - The length of the key, in bits, to be generated. If not specified, this is equal to the default `keyLength` of the default algorithm: for AES this is 256 bits. - * @returns A promise which, upon success, will be fulfilled with the generated key as a binary, for example, a byte array. Upon failure, the promise will be rejected with an {@link Types.ErrorInfo} object which explains the error. + * @returns A promise which, upon success, will be fulfilled with the generated key as a binary, for example, a byte array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ generateRandomKey(keyLength?: number): Promise; /** @@ -2725,4 +2693,31 @@ export declare class Realtime extends Types.Realtime { /** * A generic Ably error object that contains an Ably-specific status code, and a generic status code. Errors returned from the Ably server are compatible with the `ErrorInfo` structure and should result in errors that inherit from `ErrorInfo`. */ -export declare class ErrorInfo extends Types.ErrorInfo {} +export declare class ErrorInfo { + /** + * Ably [error code](https://github.com/ably/ably-common/blob/main/protocol/errors.json). + */ + code: number; + /** + * Additional message information, where available. + */ + message: string; + /** + * HTTP Status Code corresponding to this error, where applicable. + */ + statusCode: number; + /** + * The underlying cause of the error, where applicable. + */ + cause?: string | Error | ErrorInfo; + + /** + * Construct an ErrorInfo object. + * + * @param message - A string describing the error. + * @param code - Ably [error code](https://github.com/ably/ably-common/blob/main/protocol/errors.json). + * @param statusCode - HTTP Status Code corresponding to this error. + * @param cause - The underlying cause of the error. + */ + constructor(message: string, code: number, statusCode: number, cause?: string | Error | ErrorInfo); +} diff --git a/src/common/lib/types/errorinfo.ts b/src/common/lib/types/errorinfo.ts index f7ed5193b7..86b267159e 100644 --- a/src/common/lib/types/errorinfo.ts +++ b/src/common/lib/types/errorinfo.ts @@ -26,7 +26,7 @@ export interface IConvertibleToErrorInfo { statusCode: number; } -export default class ErrorInfo extends Error implements IPartialErrorInfo, API.Types.ErrorInfo { +export default class ErrorInfo extends Error implements IPartialErrorInfo, API.ErrorInfo { code: number; statusCode: number; cause?: string | Error | ErrorInfo; diff --git a/src/platform/react-hooks/sample-app/src/App.tsx b/src/platform/react-hooks/sample-app/src/App.tsx index fafebf8d3a..aafceb74ec 100644 --- a/src/platform/react-hooks/sample-app/src/App.tsx +++ b/src/platform/react-hooks/sample-app/src/App.tsx @@ -1,4 +1,4 @@ -import { Types } from 'ably'; +import { Types, ErrorInfo } from 'ably'; import React, { useState } from 'react'; import { useChannel, @@ -33,7 +33,7 @@ function App() { const [ablyErr, setAblyErr] = useState(''); const [channelState, setChannelState] = useState(channel.state); const [previousChannelState, setPreviousChannelState] = useState(); - const [channelStateReason, setChannelStateReason] = useState(); + const [channelStateReason, setChannelStateReason] = useState(); useChannelStateListener('your-channel-name', (stateChange) => { setAblyErr(JSON.stringify(stateChange.reason)); @@ -106,7 +106,7 @@ function App() { function ConnectionState() { const ably = useAbly(); const [connectionState, setConnectionState] = useState(ably.connection.state); - const [connectionError, setConnectionError] = useState(null); + const [connectionError, setConnectionError] = useState(null); useConnectionStateListener((stateChange) => { console.log(stateChange); setConnectionState(stateChange.current); diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 2742f29a7e..1d45ae9ccc 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../ably.js'; +import { Types, ErrorInfo } from '../../../../ably.js'; export type ChannelNameAndOptions = { channelName: string; @@ -7,8 +7,8 @@ export type ChannelNameAndOptions = { subscribeOnly?: boolean; skip?: boolean; - onConnectionError?: (error: Types.ErrorInfo) => unknown; - onChannelError?: (error: Types.ErrorInfo) => unknown; + onConnectionError?: (error: ErrorInfo) => unknown; + onChannelError?: (error: ErrorInfo) => unknown; }; export type ChannelNameAndId = { diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 741d064576..a1f66dcc34 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -3,7 +3,7 @@ import { it, beforeEach, describe, expect, vi } from 'vitest'; import { useChannel } from './useChannel.js'; import { render, screen, waitFor } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; -import { Types } from '../../../../../ably.js'; +import { Types, ErrorInfo } from '../../../../../ably.js'; import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; @@ -202,8 +202,8 @@ const UseChannelComponent = ({ skip }: { skip?: boolean }) => { }; interface UseChannelStateErrorsComponentProps { - onConnectionError?: (err: Types.ErrorInfo) => unknown; - onChannelError?: (err: Types.ErrorInfo) => unknown; + onConnectionError?: (err: ErrorInfo) => unknown; + onChannelError?: (err: ErrorInfo) => unknown; } const UseChannelStateErrorsComponent = ({ onConnectionError, onChannelError }: UseChannelStateErrorsComponentProps) => { diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index a92958c08e..f6becb3afd 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../../ably.js'; +import { Types, ErrorInfo } from '../../../../../ably.js'; import { useEffect, useMemo, useRef } from 'react'; import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; @@ -9,8 +9,8 @@ export type AblyMessageCallback = Types.messageCallback; export interface ChannelResult { channel: Types.RealtimeChannel; ably: Types.Realtime; - connectionError: Types.ErrorInfo | null; - channelError: Types.ErrorInfo | null; + connectionError: ErrorInfo | null; + channelError: ErrorInfo | null; } type SubscribeArgs = [string, AblyMessageCallback] | [AblyMessageCallback]; diff --git a/src/platform/react-hooks/src/hooks/usePresence.test.tsx b/src/platform/react-hooks/src/hooks/usePresence.test.tsx index 8553832a30..659163ba92 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.test.tsx +++ b/src/platform/react-hooks/src/hooks/usePresence.test.tsx @@ -4,7 +4,7 @@ import { usePresence } from './usePresence.js'; import { render, screen, act } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; import { AblyProvider } from '../AblyProvider.js'; -import { Types } from '../../../../../ably.js'; +import { Types, ErrorInfo } from '../../../../../ably.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { return render({children}); @@ -215,8 +215,8 @@ const UsePresenceComponentMultipleClients = () => { }; interface UsePresenceStateErrorsComponentProps { - onConnectionError?: (err: Types.ErrorInfo) => unknown; - onChannelError?: (err: Types.ErrorInfo) => unknown; + onConnectionError?: (err: ErrorInfo) => unknown; + onChannelError?: (err: ErrorInfo) => unknown; } const UsePresenceStateErrorsComponent = ({ diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index c328a6055b..944480203d 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,4 +1,4 @@ -import { Types } from '../../../../../ably.js'; +import { Types, ErrorInfo } from '../../../../../ably.js'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; @@ -7,8 +7,8 @@ import { useStateErrors } from './useStateErrors.js'; export interface PresenceResult { presenceData: PresenceMessage[]; updateStatus: (messageOrPresenceObject: T) => void; - connectionError: Types.ErrorInfo | null; - channelError: Types.ErrorInfo | null; + connectionError: ErrorInfo | null; + channelError: ErrorInfo | null; } export type OnPresenceMessageReceived = (presenceData: PresenceMessage) => void; diff --git a/src/platform/react-hooks/src/hooks/useStateErrors.ts b/src/platform/react-hooks/src/hooks/useStateErrors.ts index ff991bb55e..4387bb225e 100644 --- a/src/platform/react-hooks/src/hooks/useStateErrors.ts +++ b/src/platform/react-hooks/src/hooks/useStateErrors.ts @@ -1,12 +1,12 @@ -import { Types } from '../../../../../ably.js'; +import { ErrorInfo } from '../../../../../ably.js'; import { useState } from 'react'; import { useConnectionStateListener } from './useConnectionStateListener.js'; import { useChannelStateListener } from './useChannelStateListener.js'; import { ChannelNameAndOptions } from '../AblyReactHooks.js'; export function useStateErrors(params: ChannelNameAndOptions) { - const [connectionError, setConnectionError] = useState(null); - const [channelError, setChannelError] = useState(null); + const [connectionError, setConnectionError] = useState(null); + const [channelError, setChannelError] = useState(null); useConnectionStateListener( ['suspended', 'failed', 'disconnected'], From 312298a7a95370f149b63ac8751caf8b73e76b2a Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 29 Nov 2023 16:01:58 -0300 Subject: [PATCH 236/468] Add "Abstract" prefix to Types.{Rest, Realtime} names To avoid a clash with the top-level classes of the same name when we remove the Types namespace in #909. --- ably.d.ts | 16 ++++++++-------- modules.d.ts | 4 ++-- src/platform/react-hooks/src/AblyProvider.tsx | 4 ++-- src/platform/react-hooks/src/hooks/useAbly.ts | 4 ++-- .../react-hooks/src/hooks/useChannel.test.tsx | 4 ++-- src/platform/react-hooks/src/hooks/useChannel.ts | 2 +- .../src/hooks/useChannelStateListener.test.tsx | 2 +- .../hooks/useConnectionStateListener.test.tsx | 2 +- .../react-hooks/src/hooks/usePresence.test.tsx | 4 ++-- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 3e30600a0a..e85bbacc52 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -364,7 +364,7 @@ declare namespace Types { } /** - * Passes additional client-specific properties to the REST {@link Rest.constructor | `constructor()`} or the Realtime {@link Realtime.constructor | `constructor()`}. + * Passes additional client-specific properties to the REST {@link AbstractRest.constructor | `constructor()`} or the Realtime {@link AbstractRealtime.constructor | `constructor()`}. */ interface ClientOptions extends AuthOptions { /** @@ -1220,8 +1220,8 @@ declare namespace Types { /** * The `StatsParams` interface describes the parameters accepted by the following methods: * - * - {@link Rest.stats} - * - {@link Realtime.stats} + * - {@link AbstractRest.stats} + * - {@link AbstractRealtime.stats} */ interface StatsParams { /** @@ -1542,7 +1542,7 @@ declare namespace Types { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ - abstract class Rest { + abstract class AbstractRest { /** * An {@link Types.Auth} object. */ @@ -1614,9 +1614,9 @@ declare namespace Types { } /** - * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. + * A client that extends the functionality of {@link AbstractRest} and provides additional realtime-specific features. */ - abstract class Realtime { + abstract class AbstractRealtime { /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. */ @@ -2633,7 +2633,7 @@ declare namespace Types { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare class Rest extends Types.Rest { +export declare class Rest extends Types.AbstractRest { /** * Construct a client object using an Ably {@link Types.ClientOptions} object. * @@ -2663,7 +2663,7 @@ export declare class Rest extends Types.Rest { /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ -export declare class Realtime extends Types.Realtime { +export declare class Realtime extends Types.AbstractRealtime { /** * Construct a client object using an Ably {@link Types.ClientOptions} object. * diff --git a/modules.d.ts b/modules.d.ts index b4782e5502..c621879024 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -242,7 +242,7 @@ export interface ModulesMap { * * `BaseRest` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Rest`](../../default/classes/Rest.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRest extends Types.Rest { +export declare class BaseRest extends Types.AbstractRest { /** * Construct a client object using an Ably {@link Types.ClientOptions} object. * @@ -261,7 +261,7 @@ export declare class BaseRest extends Types.Rest { * * `BaseRealtime` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Realtime`](../../default/classes/Realtime.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRealtime extends Types.Realtime { +export declare class BaseRealtime extends Types.AbstractRealtime { /** * Construct a client object using an Ably {@link Types.ClientOptions} object. * diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index a7ef61b879..855de7a0d2 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -8,11 +8,11 @@ const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'func interface AblyProviderProps { children?: React.ReactNode | React.ReactNode[] | null; - client?: Ably.Types.Realtime; + client?: Ably.Types.AbstractRealtime; id?: string; } -type AblyContextType = React.Context; +type AblyContextType = React.Context; // An object is appended to `React.createContext` which stores all contexts // indexed by id, which is used by useAbly to find the correct context when an diff --git a/src/platform/react-hooks/src/hooks/useAbly.ts b/src/platform/react-hooks/src/hooks/useAbly.ts index be2910857b..12728583fb 100644 --- a/src/platform/react-hooks/src/hooks/useAbly.ts +++ b/src/platform/react-hooks/src/hooks/useAbly.ts @@ -2,8 +2,8 @@ import React from 'react'; import { getContext } from '../AblyProvider.js'; import * as API from '../../../../../ably.js'; -export function useAbly(id = 'default'): API.Types.Realtime { - const client = React.useContext(getContext(id)) as API.Types.Realtime; +export function useAbly(id = 'default'): API.Types.AbstractRealtime { + const client = React.useContext(getContext(id)) as API.Types.AbstractRealtime; if (!client) { throw new Error( diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index a1f66dcc34..91db939496 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useChannel', () => { @@ -57,7 +57,7 @@ describe('useChannel', () => { it('useChannel works with multiple clients', async () => { renderInCtxProvider( ablyClient, - + ); diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index f6becb3afd..7ef289de5f 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -8,7 +8,7 @@ export type AblyMessageCallback = Types.messageCallback; export interface ChannelResult { channel: Types.RealtimeChannel; - ably: Types.Realtime; + ably: Types.AbstractRealtime; connectionError: ErrorInfo | null; channelError: ErrorInfo | null; } diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx index eee3e4d1fe..d3026cdb96 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useChannelStateListener', () => { diff --git a/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx b/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx index 068449d54e..7f47ba9239 100644 --- a/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx @@ -9,7 +9,7 @@ import { AblyProvider } from '../AblyProvider.js'; import { useConnectionStateListener } from './useConnectionStateListener.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useConnectionStateListener', () => { diff --git a/src/platform/react-hooks/src/hooks/usePresence.test.tsx b/src/platform/react-hooks/src/hooks/usePresence.test.tsx index 659163ba92..7651fceb96 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.test.tsx +++ b/src/platform/react-hooks/src/hooks/usePresence.test.tsx @@ -7,7 +7,7 @@ import { AblyProvider } from '../AblyProvider.js'; import { Types, ErrorInfo } from '../../../../../ably.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } const testChannelName = 'testChannel'; @@ -97,7 +97,7 @@ describe('usePresence', () => { it('usePresence works with multiple clients', async () => { renderInCtxProvider( ablyClient, - + ); From c3a2937daf66bf58529dcb8a90774abeb16078bf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 3 Jan 2024 14:47:02 +0000 Subject: [PATCH 237/468] Only export types from default variant of library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we remove the Types namespace in #909, the RealtimePresence class defined in ably.d.ts will clash with the variable of the same name declared in modules.d.ts. Owen indicated that his preference for resolving this issue is to export the common types (i.e. those currently contained in the Types namespace) from only the default variant and not from the modular variant, so that’s what we do here. Users of the modular variant who wish to refer to the common types will need to import them from the default variant. To maintain TypeDoc’s ability to generate hyperlinks from the references which are contained in the modular variant but which refer to the common types (e.g. some @link tags, BaseRest’s inheritance from AbstractRest), we switch to using a single TypeDoc project to generate documentation for both variants. For reasons that I don’t understand, TypeDoc is unable to resolve the aforementioned @link tags unless I explicitly add a module source [1]. I’d have thought that it’d be sufficient to simply `import` the types that these tags refer to, but doing so makes no difference. So, I’ve added these module sources. Unfortunately, IntelliSense in VS Code doesn’t know what to do with these and the link is not clickable. (I also tried experimenting with TSDoc’s syntax for declaration references [2] but it made no difference). I think we’ll have to live with this; luckily there are only a few such tags. [1] https://typedoc.org/guides/declaration-references/#module-source [2] https://tsdoc.org/pages/tags/link/ --- README.md | 2 +- ably.d.ts | 8 ++++ modules.d.ts | 42 +++++++++++++++---- package.json | 2 +- .../browser/template/src/index-modules.ts | 3 +- typedoc.json | 3 ++ typedoc/landing-page.md | 12 ++++++ typedoc/landing-pages/choose-library.html | 31 -------------- typedoc/landing-pages/default.md | 5 --- typedoc/landing-pages/modules.md | 21 ---------- 10 files changed, 60 insertions(+), 69 deletions(-) create mode 100644 typedoc/landing-page.md delete mode 100644 typedoc/landing-pages/choose-library.html delete mode 100644 typedoc/landing-pages/default.md delete mode 100644 typedoc/landing-pages/modules.md diff --git a/README.md b/README.md index 309bae8705..ada6eefe60 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ You must provide: `BaseRealtime` offers the same API as the `Realtime` class described in the rest of this `README`. This means that you can develop an application using the default variant of the SDK and switch to the modular version when you wish to optimize your bundle size. -For more information, see the [generated documentation](https://sdk.ably.com/builds/ably/ably-js/main/typedoc/modules/index.html) (this link points to the documentation for the `main` branch). +For more information, see the [generated documentation](https://sdk.ably.com/builds/ably/ably-js/main/typedoc/modules/modules.html) (this link points to the documentation for the `main` branch). ### TypeScript diff --git a/ably.d.ts b/ably.d.ts index e85bbacc52..8439685718 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -3,6 +3,14 @@ // Definitions by: Ably // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/** + * You are currently viewing the default variant of the Ably JavaScript Client Library SDK. View the modular variant {@link modules | here}. + * + * To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. + * + * @module + */ + /** * Type definitions for Ably Realtime and REST client library. */ diff --git a/modules.d.ts b/modules.d.ts index c621879024..6b7679ca15 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -1,3 +1,27 @@ +/** + * You are currently viewing the modular (tree-shakable) variant of the Ably JavaScript Client Library SDK. View the default variant {@link ably | here}. + * + * To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. + * + * ## No `static` class functionality + * + * In contrast to the default variant of the SDK, the modular variant does not expose any functionality via `static` class properties or methods, since they cannot be tree-shaken by module bundlers. Instead, it exports free-standing functions which provide the same functionality. These are: + * + * | `static` version | Replacement in modular variant | + * | ------------------------------------------ | ---------------------------------------------------------------------------------- | + * | `Crypto.generateRandomKey()` | [`generateRandomKey()`](../functions/modules.generateRandomKey.html) | + * | `Crypto.getDefaultParams()` | [`getDefaultCryptoParams()`](../functions/modules.getDefaultCryptoParams.html) | + * | `MessageStatic.fromEncoded()` | [`decodeMessage()`](../functions/modules.decodeMessage.html) | + * | `MessageStatic.fromEncoded()` | [`decodeEncryptedMessage()`](../functions/modules.decodeEncryptedMessage.html) | + * | `MessageStatic.fromEncodedArray()` | [`decodeMessages()`](../functions/modules.decodeMessages.html) | + * | `MessageStatic.fromEncodedArray()` | [`decodeEncryptedMessages()`](../functions/modules.decodeEncryptedMessages.html) | + * | `PresenceMessageStatic.fromEncoded()` | [`decodePresenceMessage()`](../functions/modules.decodePresenceMessage.html) | + * | `PresenceMessageStatic.fromEncodedArray()` | [`decodePresenceMessages()`](../functions/modules.decodePresenceMessages.html) | + * | `PresenceMessageStatic.fromValues()` | [`constructPresenceMessage()`](../functions/modules.constructPresenceMessage.html) | + * + * @module + */ + import { Types, ErrorInfo } from './ably'; export declare const generateRandomKey: Types.Crypto['generateRandomKey']; @@ -22,22 +46,22 @@ export declare const constructPresenceMessage: Types.PresenceMessageStatic['from * * When provided, the following functionality becomes available: * - * - { @link Types.Push | push admin } + * - { @link ably!Types.Push | push admin } * - { @link BaseRealtime.time | retrieving Ably service time } * - { @link BaseRealtime.stats | retrieving your application’s usage statistics } * - { @link BaseRealtime.request | making arbitrary REST requests } * - { @link BaseRealtime.batchPublish | batch publishing of messages } * - { @link BaseRealtime.batchPresence | batch retrieval of channel presence state } - * - { @link Types.Auth.revokeTokens | requesting the revocation of tokens } - * - { @link Types.RealtimeChannel.history | retrieving the message history of a channel } - * - { @link Types.RealtimePresence.history | retrieving the presence history of a channel } + * - { @link ably!Types.Auth.revokeTokens | requesting the revocation of tokens } + * - { @link ably!Types.RealtimeChannel.history | retrieving the message history of a channel } + * - { @link ably!Types.RealtimePresence.history | retrieving the presence history of a channel } * * If this module is not provided, then trying to use the above functionality will cause a runtime error. */ export declare const Rest: unknown; /** - * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to encrypt and decrypt {@link Types.Message} payloads. + * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to encrypt and decrypt {@link ably!Types.Message} payloads. * * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: * @@ -46,7 +70,7 @@ export declare const Rest: unknown; * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, Crypto }); * ``` * - * When provided, you can configure message encryption on a channel via the {@link Types.ChannelOptions.cipher} property of the `ChannelOptions` that you pass when {@link Types.Channels.get | fetching a channel}. If this module is not provided, then passing a `ChannelOptions` with a `cipher` property will cause a runtime error. + * When provided, you can configure message encryption on a channel via the {@link ably!Types.ChannelOptions.cipher} property of the `ChannelOptions` that you pass when {@link ably!Types.Channels.get | fetching a channel}. If this module is not provided, then passing a `ChannelOptions` with a `cipher` property will cause a runtime error. */ export declare const Crypto: unknown; @@ -74,7 +98,7 @@ export declare const MsgPack: unknown; * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, RealtimePresence }); * ``` * - * If you do not provide this module, then attempting to access a channel’s {@link Types.RealtimeChannel.presence} property will cause a runtime error. + * If you do not provide this module, then attempting to access a channel’s {@link ably!Types.RealtimeChannel.presence} property will cause a runtime error. */ export declare const RealtimePresence: unknown; @@ -150,7 +174,7 @@ export declare const XHRRequest: unknown; export declare const FetchRequest: unknown; /** - * Provides a {@link BaseRealtime} instance with the ability to filter channel subscriptions at runtime using { @link Types.RealtimeChannel.subscribe:WITH_MESSAGE_FILTER | the overload of `subscribe()` that accepts a `MessageFilter` }. + * Provides a {@link BaseRealtime} instance with the ability to filter channel subscriptions at runtime using { @link ably!Types.RealtimeChannel.subscribe:WITH_MESSAGE_FILTER | the overload of `subscribe()` that accepts a `MessageFilter` }. * * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: * @@ -276,4 +300,4 @@ export declare class BaseRealtime extends Types.AbstractRealtime { constructor(options: Types.ClientOptions, modules: ModulesMap); } -export { Types, ErrorInfo }; +export { ErrorInfo }; diff --git a/package.json b/package.json index 3446ecb1f8..f527c35209 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,6 @@ "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s", "sourcemap": "source-map-explorer build/ably.min.js", "modulereport": "tsc --noEmit scripts/moduleReport.ts && esr scripts/moduleReport.ts", - "docs": "typedoc --entryPoints ably.d.ts --out typedoc/generated/default --readme typedoc/landing-pages/default.md && typedoc --entryPoints modules.d.ts --out typedoc/generated/modules --name \"ably (modular version)\" --readme typedoc/landing-pages/modules.md && cp typedoc/landing-pages/choose-library.html typedoc/generated/index.html" + "docs": "typedoc" } } diff --git a/test/package/browser/template/src/index-modules.ts b/test/package/browser/template/src/index-modules.ts index 9ad31d69c1..594835eaee 100644 --- a/test/package/browser/template/src/index-modules.ts +++ b/test/package/browser/template/src/index-modules.ts @@ -1,4 +1,5 @@ -import { BaseRealtime, Types, WebSocketTransport, FetchRequest, generateRandomKey } from 'ably/modules'; +import { BaseRealtime, WebSocketTransport, FetchRequest, generateRandomKey } from 'ably/modules'; +import { Types } from 'ably'; import { createSandboxAblyAPIKey } from './sandbox'; // This function exists to check that we can import the Types namespace and refer to its types. diff --git a/typedoc.json b/typedoc.json index 5a43b8897c..f7a58f1ec8 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,5 +1,8 @@ { "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["ably.d.ts", "modules.d.ts"], + "out": "typedoc/generated", + "readme": "typedoc/landing-page.md", "treatWarningsAsErrors": true, "includeVersion": true, "validation": true, diff --git a/typedoc/landing-page.md b/typedoc/landing-page.md new file mode 100644 index 0000000000..f52d4093a7 --- /dev/null +++ b/typedoc/landing-page.md @@ -0,0 +1,12 @@ +# Ably JavaScript Client Library SDK API Reference + +The JavaScript Client Library SDK supports a realtime and a REST interface. The JavaScript API references are generated from the [Ably JavaScript Client Library SDK source code](https://github.com/ably/ably-js/) using [TypeDoc](https://typedoc.org) and structured by classes. + +The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels. The REST interface is stateless and typically implemented server-side. It is used to make requests such as retrieving statistics, token authentication and publishing to a channel. + +There are two variants of the Ably JavaScript Client Library SDK: + +- [Default variant](modules/ably.html): This variant of the SDK always creates a fully-featured Ably client. +- [Modular (tree-shakable) variant](modules/modules.html): Aimed at those who are concerned about their app’s bundle size, this allows you to create a client which has only the functionality that you choose. + +View the [Ably docs](https://ably.com/docs/) for conceptual information on using Ably, and for API references featuring all languages. The combined [API references](https://ably.com/docs/api/) are organized by features and split between the [realtime](https://ably.com/docs/api/realtime-sdk) and [REST](https://ably.com/docs/api/rest-sdk) interfaces. diff --git a/typedoc/landing-pages/choose-library.html b/typedoc/landing-pages/choose-library.html deleted file mode 100644 index d6a91bb42e..0000000000 --- a/typedoc/landing-pages/choose-library.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - Ably JavaScript Client Library SDK API Reference - - -

Ably JavaScript Client Library SDK API Reference

- -

- The JavaScript Client Library SDK supports a realtime and a REST interface. The JavaScript API references are generated from the Ably JavaScript Client Library SDK source code using TypeDoc and structured by classes. -

- -

- The realtime interface enables a client to maintain a persistent connection to Ably and publish, subscribe and be present on channels. The REST interface is stateless and typically implemented server-side. It is used to make requests such as retrieving statistics, token authentication and publishing to a channel. -

- -

- There are two variants of the Ably JavaScript Client Library SDK: - -

    -
  • Default variant: This variant of the SDK always creates a fully-featured Ably client.
  • -
  • Modular (tree-shakable) variant: Aimed at those who are concerned about their app’s bundle size, this allows you to create a client which has only the functionality that you choose.
  • -
-

- -

- View the Ably docs for conceptual information on using Ably, and for API references featuring all languages. The combined API references are organized by features and split between the realtime and REST interfaces. -

- - diff --git a/typedoc/landing-pages/default.md b/typedoc/landing-pages/default.md deleted file mode 100644 index 9f2e9698e8..0000000000 --- a/typedoc/landing-pages/default.md +++ /dev/null @@ -1,5 +0,0 @@ -# Ably JavaScript Client Library SDK API Reference - -You are currently viewing the default variant of the Ably JavaScript Client Library SDK. View the modular variant [here](../modules/index.html). - -To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. diff --git a/typedoc/landing-pages/modules.md b/typedoc/landing-pages/modules.md deleted file mode 100644 index 4f4f7be061..0000000000 --- a/typedoc/landing-pages/modules.md +++ /dev/null @@ -1,21 +0,0 @@ -# Ably JavaScript Client Library SDK API Reference (modular variant) - -You are currently viewing the modular (tree-shakable) variant of the Ably JavaScript Client Library SDK. View the default variant [here](../default/index.html). - -To get started with the Ably JavaScript Client Library SDK, follow the [Quickstart Guide](https://ably.com/docs/quick-start-guide) or view the introductions to the [realtime](https://ably.com/docs/realtime/usage) and [REST](https://ably.com/docs/rest/usage) interfaces. - -## No `static` class functionality - -In contrast to the default variant of the SDK, the modular variant does not expose any functionality via `static` class properties or methods, since they cannot be tree-shaken by module bundlers. Instead, it exports free-standing functions which provide the same functionality. These are: - -| `static` version | Replacement in modular variant | -| ------------------------------------------ | ----------------------------------------------------------------------- | -| `Crypto.generateRandomKey()` | [`generateRandomKey()`](functions/generateRandomKey.html) | -| `Crypto.getDefaultParams()` | [`getDefaultCryptoParams()`](functions/getDefaultCryptoParams.html) | -| `MessageStatic.fromEncoded()` | [`decodeMessage()`](functions/decodeMessage.html) | -| `MessageStatic.fromEncoded()` | [`decodeEncryptedMessage()`](functions/decodeEncryptedMessage.html) | -| `MessageStatic.fromEncodedArray()` | [`decodeMessages()`](functions/decodeMessages.html) | -| `MessageStatic.fromEncodedArray()` | [`decodeEncryptedMessages()`](functions/decodeEncryptedMessages.html) | -| `PresenceMessageStatic.fromEncoded()` | [`decodePresenceMessage()`](functions/decodePresenceMessage.html) | -| `PresenceMessageStatic.fromEncodedArray()` | [`decodePresenceMessages()`](functions/decodePresenceMessages.html) | -| `PresenceMessageStatic.fromValues()` | [`constructPresenceMessage()`](functions/constructPresenceMessage.html) | From 30f267d8f9f377bde2917cd3f61dd598bbeadb21 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 30 Nov 2023 09:37:46 -0300 Subject: [PATCH 238/468] Remove the Types namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re removing it because we believe that it’s more consistent with other JS libraries for users to write `import * as Ably from 'ably'` and then access types on the `Ably` namespace. The triggering of @typescript-eslint/no-redeclare (and hence the added eslint-disable-next-line statements) seems to be related to [1]. I’m not sure why it wasn’t complaining before, though. Resolves #909. [1] https://github.com/typescript-eslint/typescript-eslint/issues/2818 --- README.md | 4 +- ably.d.ts | 4711 +++++++++-------- modules.d.ts | 62 +- src/common/lib/client/auth.ts | 86 +- src/common/lib/client/baseclient.ts | 12 +- src/common/lib/client/baserealtime.ts | 4 +- .../lib/client/filteredsubscriptions.ts | 40 +- src/common/lib/client/realtimechannel.ts | 25 +- src/common/lib/client/rest.ts | 20 +- src/common/lib/client/restchannel.ts | 2 +- src/common/lib/client/restchannelmixin.ts | 13 +- src/common/lib/transport/comettransport.ts | 2 +- src/common/lib/transport/connectionmanager.ts | 2 +- src/common/lib/transport/transport.ts | 2 +- src/common/lib/types/defaultmessage.ts | 4 +- .../lib/types/defaultpresencemessage.ts | 4 +- src/common/lib/types/message.ts | 6 +- src/common/lib/types/presencemessage.ts | 4 +- src/common/lib/types/protocolmessage.ts | 6 +- src/common/types/ClientOptions.ts | 2 +- src/common/types/ICryptoStatic.ts | 8 +- src/common/types/channel.d.ts | 2 +- src/platform/nodejs/lib/util/crypto.ts | 12 +- .../react-hooks/sample-app/src/App.tsx | 10 +- src/platform/react-hooks/src/AblyProvider.tsx | 5 +- .../react-hooks/src/AblyReactHooks.ts | 10 +- src/platform/react-hooks/src/fakes/ably.ts | 26 +- src/platform/react-hooks/src/hooks/useAbly.ts | 4 +- .../react-hooks/src/hooks/useChannel.test.tsx | 14 +- .../react-hooks/src/hooks/useChannel.ts | 16 +- .../hooks/useChannelStateListener.test.tsx | 10 +- .../src/hooks/useChannelStateListener.ts | 12 +- .../hooks/useConnectionStateListener.test.tsx | 10 +- .../src/hooks/useConnectionStateListener.ts | 10 +- .../react-hooks/src/hooks/useEventListener.ts | 8 +- .../src/hooks/usePresence.test.tsx | 10 +- .../react-hooks/src/hooks/usePresence.ts | 12 +- src/platform/web/lib/util/crypto.ts | 14 +- src/platform/web/modules/crypto.ts | 4 +- src/platform/web/modules/message.ts | 8 +- src/platform/web/modules/presencemessage.ts | 6 +- .../browser/template/src/index-default.ts | 10 +- .../browser/template/src/index-modules.ts | 8 +- 43 files changed, 2612 insertions(+), 2628 deletions(-) diff --git a/README.md b/README.md index ada6eefe60..f6888fa806 100644 --- a/README.md +++ b/README.md @@ -119,9 +119,9 @@ The TypeScript typings are included in the package and so all you have to do is: ```typescript import * as Ably from 'ably'; -let options: Ably.Types.ClientOptions = { key: 'foo' }; +let options: Ably.ClientOptions = { key: 'foo' }; let client = new Ably.Realtime(options); /* inferred type Ably.Realtime */ -let channel = client.channels.get('feed'); /* inferred type Ably.Types.RealtimeChannel */ +let channel = client.channels.get('feed'); /* inferred type Ably.RealtimeChannel */ ``` Intellisense in IDEs with TypeScript support is supported: diff --git a/ably.d.ts b/ably.d.ts index 8439685718..28a3f9808b 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -12,2642 +12,2645 @@ */ /** - * Type definitions for Ably Realtime and REST client library. + * The `ChannelState` namespace describes the possible values of the {@link ChannelState:type} type. */ -declare namespace Types { +declare namespace ChannelState { /** - * The `ChannelState` namespace describes the possible values of the {@link ChannelState:type} type. + * The channel has been initialized but no attach has yet been attempted. */ - namespace ChannelState { - /** - * The channel has been initialized but no attach has yet been attempted. - */ - type INITIALIZED = 'initialized'; - /** - * An attach has been initiated by sending a request to Ably. This is a transient state, followed either by a transition to `ATTACHED`, `SUSPENDED`, or `FAILED`. - */ - type ATTACHING = 'attaching'; - /** - * The attach has succeeded. In the `ATTACHED` state a client may publish and subscribe to messages, or be present on the channel. - */ - type ATTACHED = 'attached'; - /** - * A detach has been initiated on an `ATTACHED` channel by sending a request to Ably. This is a transient state, followed either by a transition to `DETACHED` or `FAILED`. - */ - type DETACHING = 'detaching'; - /** - * The channel, having previously been `ATTACHED`, has been detached by the user. - */ - type DETACHED = 'detached'; - /** - * The channel, having previously been `ATTACHED`, has lost continuity, usually due to the client being disconnected from Ably for longer than two minutes. It will automatically attempt to reattach as soon as connectivity is restored. - */ - type SUSPENDED = 'suspended'; - /** - * An indefinite failure condition. This state is entered if a channel error has been received from the Ably service, such as an attempt to attach without the necessary access rights. - */ - type FAILED = 'failed'; - } + type INITIALIZED = 'initialized'; + /** + * An attach has been initiated by sending a request to Ably. This is a transient state, followed either by a transition to `ATTACHED`, `SUSPENDED`, or `FAILED`. + */ + type ATTACHING = 'attaching'; /** - * Describes the possible states of a {@link Channel} or {@link RealtimeChannel} object. + * The attach has succeeded. In the `ATTACHED` state a client may publish and subscribe to messages, or be present on the channel. */ - type ChannelState = - | ChannelState.FAILED - | ChannelState.INITIALIZED - | ChannelState.SUSPENDED - | ChannelState.ATTACHED - | ChannelState.ATTACHING - | ChannelState.DETACHED - | ChannelState.DETACHING; + type ATTACHED = 'attached'; + /** + * A detach has been initiated on an `ATTACHED` channel by sending a request to Ably. This is a transient state, followed either by a transition to `DETACHED` or `FAILED`. + */ + type DETACHING = 'detaching'; + /** + * The channel, having previously been `ATTACHED`, has been detached by the user. + */ + type DETACHED = 'detached'; + /** + * The channel, having previously been `ATTACHED`, has lost continuity, usually due to the client being disconnected from Ably for longer than two minutes. It will automatically attempt to reattach as soon as connectivity is restored. + */ + type SUSPENDED = 'suspended'; + /** + * An indefinite failure condition. This state is entered if a channel error has been received from the Ably service, such as an attempt to attach without the necessary access rights. + */ + type FAILED = 'failed'; +} +/** + * Describes the possible states of a {@link Channel} or {@link RealtimeChannel} object. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ChannelState = + | ChannelState.FAILED + | ChannelState.INITIALIZED + | ChannelState.SUSPENDED + | ChannelState.ATTACHED + | ChannelState.ATTACHING + | ChannelState.DETACHED + | ChannelState.DETACHING; +/** + * The `ChannelEvent` namespace describes the possible values of the {@link ChannelEvent:type} type. + */ +declare namespace ChannelEvent { /** - * The `ChannelEvent` namespace describes the possible values of the {@link ChannelEvent:type} type. + * The channel has been initialized but no attach has yet been attempted. */ - namespace ChannelEvent { - /** - * The channel has been initialized but no attach has yet been attempted. - */ - type INITIALIZED = 'initialized'; - /** - * An attach has been initiated by sending a request to Ably. This is a transient state, followed either by a transition to `ATTACHED`, `SUSPENDED`, or `FAILED`. - */ - type ATTACHING = 'attaching'; - /** - * The attach has succeeded. In the `ATTACHED` state a client may publish and subscribe to messages, or be present on the channel. - */ - type ATTACHED = 'attached'; - /** - * A detach has been initiated on an `ATTACHED` channel by sending a request to Ably. This is a transient state, followed either by a transition to `DETACHED` or `FAILED`. - */ - type DETACHING = 'detaching'; - /** - * The channel, having previously been `ATTACHED`, has been detached by the user. - */ - type DETACHED = 'detached'; - /** - * The channel, having previously been `ATTACHED`, has lost continuity, usually due to the client being disconnected from Ably for longer than two minutes. It will automatically attempt to reattach as soon as connectivity is restored. - */ - type SUSPENDED = 'suspended'; - /** - * An indefinite failure condition. This state is entered if a channel error has been received from the Ably service, such as an attempt to attach without the necessary access rights. - */ - type FAILED = 'failed'; - /** - * An event for changes to channel conditions that do not result in a change in {@link ChannelState}. - */ - type UPDATE = 'update'; - } + type INITIALIZED = 'initialized'; + /** + * An attach has been initiated by sending a request to Ably. This is a transient state, followed either by a transition to `ATTACHED`, `SUSPENDED`, or `FAILED`. + */ + type ATTACHING = 'attaching'; + /** + * The attach has succeeded. In the `ATTACHED` state a client may publish and subscribe to messages, or be present on the channel. + */ + type ATTACHED = 'attached'; + /** + * A detach has been initiated on an `ATTACHED` channel by sending a request to Ably. This is a transient state, followed either by a transition to `DETACHED` or `FAILED`. + */ + type DETACHING = 'detaching'; + /** + * The channel, having previously been `ATTACHED`, has been detached by the user. + */ + type DETACHED = 'detached'; + /** + * The channel, having previously been `ATTACHED`, has lost continuity, usually due to the client being disconnected from Ably for longer than two minutes. It will automatically attempt to reattach as soon as connectivity is restored. + */ + type SUSPENDED = 'suspended'; + /** + * An indefinite failure condition. This state is entered if a channel error has been received from the Ably service, such as an attempt to attach without the necessary access rights. + */ + type FAILED = 'failed'; /** - * Describes the events emitted by a {@link Channel} or {@link RealtimeChannel} object. An event is either an `UPDATE` or a {@link ChannelState}. + * An event for changes to channel conditions that do not result in a change in {@link ChannelState}. */ - type ChannelEvent = - | ChannelEvent.FAILED - | ChannelEvent.INITIALIZED - | ChannelEvent.SUSPENDED - | ChannelEvent.ATTACHED - | ChannelEvent.ATTACHING - | ChannelEvent.DETACHED - | ChannelEvent.DETACHING - | ChannelEvent.UPDATE; + type UPDATE = 'update'; +} +/** + * Describes the events emitted by a {@link Channel} or {@link RealtimeChannel} object. An event is either an `UPDATE` or a {@link ChannelState}. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ChannelEvent = + | ChannelEvent.FAILED + | ChannelEvent.INITIALIZED + | ChannelEvent.SUSPENDED + | ChannelEvent.ATTACHED + | ChannelEvent.ATTACHING + | ChannelEvent.DETACHED + | ChannelEvent.DETACHING + | ChannelEvent.UPDATE; +/** + * The `ConnectionState` namespace describes the possible values of the {@link ConnectionState:type} type. + */ +declare namespace ConnectionState { /** - * The `ConnectionState` namespace describes the possible values of the {@link ConnectionState:type} type. + * A connection with this state has been initialized but no connection has yet been attempted. */ - namespace ConnectionState { - /** - * A connection with this state has been initialized but no connection has yet been attempted. - */ - type INITIALIZED = 'initialized'; - /** - * A connection attempt has been initiated. The connecting state is entered as soon as the library has completed initialization, and is reentered each time connection is re-attempted following disconnection. - */ - type CONNECTING = 'connecting'; - /** - * A connection exists and is active. - */ - type CONNECTED = 'connected'; - /** - * A temporary failure condition. No current connection exists because there is no network connectivity or no host is available. The disconnected state is entered if an established connection is dropped, or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt to open a new connection (approximately every 15 seconds), anticipating that the connection will be re-established soon and thus connection and channel continuity will be possible. In this state, developers can continue to publish messages as they are automatically placed in a local queue, to be sent as soon as a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection, so long as the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible and the connection will move to the `SUSPENDED` state. - */ - type DISCONNECTED = 'disconnected'; - /** - * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link Connection.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). - */ - type SUSPENDED = 'suspended'; - /** - * An explicit request by the developer to close the connection has been sent to the Ably service. If a reply is not received from Ably within a short period of time, the connection is forcibly terminated and the connection state becomes `CLOSED`. - */ - type CLOSING = 'closing'; - /** - * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}, which results in a new connection. - */ - type CLOSED = 'closed'; - /** - * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}. - */ - type FAILED = 'failed'; - } + type INITIALIZED = 'initialized'; + /** + * A connection attempt has been initiated. The connecting state is entered as soon as the library has completed initialization, and is reentered each time connection is re-attempted following disconnection. + */ + type CONNECTING = 'connecting'; /** - * Describes the realtime {@link Connection} object states. + * A connection exists and is active. */ - type ConnectionState = - | ConnectionState.INITIALIZED - | ConnectionState.CONNECTED - | ConnectionState.CONNECTING - | ConnectionState.DISCONNECTED - | ConnectionState.SUSPENDED - | ConnectionState.CLOSED - | ConnectionState.CLOSING - | ConnectionState.FAILED; + type CONNECTED = 'connected'; + /** + * A temporary failure condition. No current connection exists because there is no network connectivity or no host is available. The disconnected state is entered if an established connection is dropped, or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt to open a new connection (approximately every 15 seconds), anticipating that the connection will be re-established soon and thus connection and channel continuity will be possible. In this state, developers can continue to publish messages as they are automatically placed in a local queue, to be sent as soon as a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection, so long as the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible and the connection will move to the `SUSPENDED` state. + */ + type DISCONNECTED = 'disconnected'; + /** + * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link Connection.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). + */ + type SUSPENDED = 'suspended'; + /** + * An explicit request by the developer to close the connection has been sent to the Ably service. If a reply is not received from Ably within a short period of time, the connection is forcibly terminated and the connection state becomes `CLOSED`. + */ + type CLOSING = 'closing'; + /** + * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}, which results in a new connection. + */ + type CLOSED = 'closed'; + /** + * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}. + */ + type FAILED = 'failed'; +} +/** + * Describes the realtime {@link Connection} object states. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ConnectionState = + | ConnectionState.INITIALIZED + | ConnectionState.CONNECTED + | ConnectionState.CONNECTING + | ConnectionState.DISCONNECTED + | ConnectionState.SUSPENDED + | ConnectionState.CLOSED + | ConnectionState.CLOSING + | ConnectionState.FAILED; +/** + * The `ConnectionEvent` namespace describes the possible values of the {@link ConnectionEvent:type} type. + */ +declare namespace ConnectionEvent { /** - * The `ConnectionEvent` namespace describes the possible values of the {@link ConnectionEvent:type} type. + * A connection with this state has been initialized but no connection has yet been attempted. */ - namespace ConnectionEvent { - /** - * A connection with this state has been initialized but no connection has yet been attempted. - */ - type INITIALIZED = 'initialized'; - /** - * A connection attempt has been initiated. The connecting state is entered as soon as the library has completed initialization, and is reentered each time connection is re-attempted following disconnection. - */ - type CONNECTING = 'connecting'; - /** - * A connection exists and is active. - */ - type CONNECTED = 'connected'; - /** - * A temporary failure condition. No current connection exists because there is no network connectivity or no host is available. The disconnected state is entered if an established connection is dropped, or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt to open a new connection (approximately every 15 seconds), anticipating that the connection will be re-established soon and thus connection and channel continuity will be possible. In this state, developers can continue to publish messages as they are automatically placed in a local queue, to be sent as soon as a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection, so long as the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible and the connection will move to the `SUSPENDED` state. - */ - type DISCONNECTED = 'disconnected'; - /** - * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link Connection.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). - */ - type SUSPENDED = 'suspended'; - /** - * An explicit request by the developer to close the connection has been sent to the Ably service. If a reply is not received from Ably within a short period of time, the connection is forcibly terminated and the connection state becomes `CLOSED`. - */ - type CLOSING = 'closing'; - /** - * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}, which results in a new connection. - */ - type CLOSED = 'closed'; - /** - * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}. - */ - type FAILED = 'failed'; - /** - * An event for changes to connection conditions for which the {@link ConnectionState} does not change. - */ - type UPDATE = 'update'; - } + type INITIALIZED = 'initialized'; + /** + * A connection attempt has been initiated. The connecting state is entered as soon as the library has completed initialization, and is reentered each time connection is re-attempted following disconnection. + */ + type CONNECTING = 'connecting'; + /** + * A connection exists and is active. + */ + type CONNECTED = 'connected'; + /** + * A temporary failure condition. No current connection exists because there is no network connectivity or no host is available. The disconnected state is entered if an established connection is dropped, or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt to open a new connection (approximately every 15 seconds), anticipating that the connection will be re-established soon and thus connection and channel continuity will be possible. In this state, developers can continue to publish messages as they are automatically placed in a local queue, to be sent as soon as a connection is reestablished. Messages published by other clients while this client is disconnected will be delivered to it upon reconnection, so long as the connection was resumed within 2 minutes. After 2 minutes have elapsed, recovery is no longer possible and the connection will move to the `SUSPENDED` state. + */ + type DISCONNECTED = 'disconnected'; + /** + * A long term failure condition. No current connection exists because there is no network connectivity or no host is available. The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to {@link Connection.connect | `connect()`}. Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the [History API](https://ably.com/docs/realtime/history). + */ + type SUSPENDED = 'suspended'; + /** + * An explicit request by the developer to close the connection has been sent to the Ably service. If a reply is not received from Ably within a short period of time, the connection is forcibly terminated and the connection state becomes `CLOSED`. + */ + type CLOSING = 'closing'; + /** + * The connection has been explicitly closed by the client. In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}, which results in a new connection. + */ + type CLOSED = 'closed'; + /** + * This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service, for example an attempt to connect with an incorrect API key, or a local terminal error, for example the token in use has expired and the library does not have any way to renew it. In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to {@link Connection.connect | `connect()`}. + */ + type FAILED = 'failed'; /** - * Describes the events emitted by a {@link Connection} object. An event is either an `UPDATE` or a {@link ConnectionState}. + * An event for changes to connection conditions for which the {@link ConnectionState} does not change. */ - type ConnectionEvent = - | ConnectionEvent.INITIALIZED - | ConnectionEvent.CONNECTED - | ConnectionEvent.CONNECTING - | ConnectionEvent.DISCONNECTED - | ConnectionEvent.SUSPENDED - | ConnectionEvent.CLOSED - | ConnectionEvent.CLOSING - | ConnectionEvent.FAILED - | ConnectionEvent.UPDATE; + type UPDATE = 'update'; +} +/** + * Describes the events emitted by a {@link Connection} object. An event is either an `UPDATE` or a {@link ConnectionState}. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ConnectionEvent = + | ConnectionEvent.INITIALIZED + | ConnectionEvent.CONNECTED + | ConnectionEvent.CONNECTING + | ConnectionEvent.DISCONNECTED + | ConnectionEvent.SUSPENDED + | ConnectionEvent.CLOSED + | ConnectionEvent.CLOSING + | ConnectionEvent.FAILED + | ConnectionEvent.UPDATE; +/** + * The `PresenceAction` namespace describes the possible values of the {@link PresenceAction:type} type. + */ +declare namespace PresenceAction { /** - * The `PresenceAction` namespace describes the possible values of the {@link PresenceAction:type} type. + * A member is not present in the channel. */ - namespace PresenceAction { - /** - * A member is not present in the channel. - */ - type ABSENT = 'absent'; - /** - * When subscribing to presence events on a channel that already has members present, this event is emitted for every member already present on the channel before the subscribe listener was registered. - */ - type PRESENT = 'present'; - /** - * A new member has entered the channel. - */ - type ENTER = 'enter'; - /** - * A member who was present has now left the channel. This may be a result of an explicit request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a leave event as the client is no longer present. - */ - type LEAVE = 'leave'; - /** - * An already present member has updated their member data. Being notified of member data updates can be very useful, for example, it can be used to update the status of a user when they are typing a message. - */ - type UPDATE = 'update'; - } + type ABSENT = 'absent'; + /** + * When subscribing to presence events on a channel that already has members present, this event is emitted for every member already present on the channel before the subscribe listener was registered. + */ + type PRESENT = 'present'; /** - * Describes the possible actions members in the presence set can emit. + * A new member has entered the channel. */ - type PresenceAction = - | PresenceAction.ABSENT - | PresenceAction.PRESENT - | PresenceAction.ENTER - | PresenceAction.LEAVE - | PresenceAction.UPDATE; + type ENTER = 'enter'; + /** + * A member who was present has now left the channel. This may be a result of an explicit request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a leave event as the client is no longer present. + */ + type LEAVE = 'leave'; + /** + * An already present member has updated their member data. Being notified of member data updates can be very useful, for example, it can be used to update the status of a user when they are typing a message. + */ + type UPDATE = 'update'; +} +/** + * Describes the possible actions members in the presence set can emit. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type PresenceAction = + | PresenceAction.ABSENT + | PresenceAction.PRESENT + | PresenceAction.ENTER + | PresenceAction.LEAVE + | PresenceAction.UPDATE; +/** + * The `StatsIntervalGranularity` namespace describes the possible values of the {@link StatsIntervalGranularity:type} type. + */ +declare namespace StatsIntervalGranularity { /** - * The `StatsIntervalGranularity` namespace describes the possible values of the {@link StatsIntervalGranularity:type} type. + * Interval unit over which statistics are gathered as minutes. */ - namespace StatsIntervalGranularity { - /** - * Interval unit over which statistics are gathered as minutes. - */ - type MINUTE = 'minute'; - /** - * Interval unit over which statistics are gathered as hours. - */ - type HOUR = 'hour'; - /** - * Interval unit over which statistics are gathered as days. - */ - type DAY = 'day'; - /** - * Interval unit over which statistics are gathered as months. - */ - type MONTH = 'month'; - } + type MINUTE = 'minute'; + /** + * Interval unit over which statistics are gathered as hours. + */ + type HOUR = 'hour'; + /** + * Interval unit over which statistics are gathered as days. + */ + type DAY = 'day'; /** - * Describes the interval unit over which statistics are gathered. + * Interval unit over which statistics are gathered as months. */ - type StatsIntervalGranularity = - | StatsIntervalGranularity.MINUTE - | StatsIntervalGranularity.HOUR - | StatsIntervalGranularity.DAY - | StatsIntervalGranularity.MONTH; + type MONTH = 'month'; +} +/** + * Describes the interval unit over which statistics are gathered. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type StatsIntervalGranularity = + | StatsIntervalGranularity.MINUTE + | StatsIntervalGranularity.HOUR + | StatsIntervalGranularity.DAY + | StatsIntervalGranularity.MONTH; +/** + * HTTP Methods, used internally. + */ +declare namespace HTTPMethods { /** - * HTTP Methods, used internally. + * Represents a HTTP POST request. */ - namespace HTTPMethods { - /** - * Represents a HTTP POST request. - */ - type POST = 'POST'; - /** - * Represents a HTTP GET request. - */ - type GET = 'GET'; - } + type POST = 'POST'; /** - * HTTP Methods, used internally. + * Represents a HTTP GET request. */ - type HTTPMethods = HTTPMethods.GET | HTTPMethods.POST; + type GET = 'GET'; +} +/** + * HTTP Methods, used internally. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type HTTPMethods = HTTPMethods.GET | HTTPMethods.POST; + +/** + * A type which specifies the valid transport names. [See here](https://faqs.ably.com/which-transports-are-supported) for more information. + */ +export type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'comet'; +/** + * Contains the details of a {@link Channel} or {@link RealtimeChannel} object such as its ID and {@link ChannelStatus}. + */ +export interface ChannelDetails { + /** + * The identifier of the channel. + */ + channelId: string; /** - * A type which specifies the valid transport names. [See here](https://faqs.ably.com/which-transports-are-supported) for more information. + * A {@link ChannelStatus} object. */ - type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'comet'; + status: ChannelStatus; +} +/** + * Contains the status of a {@link Channel} or {@link RealtimeChannel} object such as whether it is active and its {@link ChannelOccupancy}. + */ +export interface ChannelStatus { /** - * Contains the details of a {@link Channel} or {@link RealtimeChannel} object such as its ID and {@link ChannelStatus}. + * If `true`, the channel is active, otherwise `false`. */ - interface ChannelDetails { - /** - * The identifier of the channel. - */ - channelId: string; - /** - * A {@link ChannelStatus} object. - */ - status: ChannelStatus; - } + isActive: boolean; + /** + * A {@link ChannelOccupancy} object. + */ + occupancy: ChannelOccupancy; +} +/** + * Contains the metrics of a {@link Channel} or {@link RealtimeChannel} object. + */ +export interface ChannelOccupancy { /** - * Contains the status of a {@link Channel} or {@link RealtimeChannel} object such as whether it is active and its {@link ChannelOccupancy}. + * A {@link ChannelMetrics} object. */ - interface ChannelStatus { - /** - * If `true`, the channel is active, otherwise `false`. - */ - isActive: boolean; - /** - * A {@link ChannelOccupancy} object. - */ - occupancy: ChannelOccupancy; - } + metrics: ChannelMetrics; +} +/** + * Contains the metrics associated with a {@link Channel} or {@link RealtimeChannel}, such as the number of publishers, subscribers and connections it has. + */ +export interface ChannelMetrics { /** - * Contains the metrics of a {@link Channel} or {@link RealtimeChannel} object. + * The number of realtime connections attached to the channel. */ - interface ChannelOccupancy { - /** - * A {@link ChannelMetrics} object. - */ - metrics: ChannelMetrics; - } + connections: number; + /** + * The number of realtime connections attached to the channel with permission to enter the presence set, regardless of whether or not they have entered it. This requires the `presence` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PRESENCE}. + */ + presenceConnections: number; + /** + * The number of members in the presence set of the channel. + */ + presenceMembers: number; + /** + * The number of realtime attachments receiving presence messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PRESENCE_SUBSCRIBE}. + */ + presenceSubscribers: number; + /** + * The number of realtime attachments permitted to publish messages to the channel. This requires the `publish` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PUBLISH}. + */ + publishers: number; + /** + * The number of realtime attachments receiving messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.SUBSCRIBE}. + */ + subscribers: number; +} +/** + * Passes additional client-specific properties to the REST {@link AbstractRest.constructor | `constructor()`} or the Realtime {@link AbstractRealtime.constructor | `constructor()`}. + */ +export interface ClientOptions extends AuthOptions { /** - * Contains the metrics associated with a {@link Channel} or {@link RealtimeChannel}, such as the number of publishers, subscribers and connections it has. + * When `true`, the client connects to Ably as soon as it is instantiated. You can set this to `false` and explicitly connect to Ably using the {@link Connection.connect | `connect()`} method. The default is `true`. + * + * @defaultValue `true` */ - interface ChannelMetrics { - /** - * The number of realtime connections attached to the channel. - */ - connections: number; - /** - * The number of realtime connections attached to the channel with permission to enter the presence set, regardless of whether or not they have entered it. This requires the `presence` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PRESENCE}. - */ - presenceConnections: number; - /** - * The number of members in the presence set of the channel. - */ - presenceMembers: number; - /** - * The number of realtime attachments receiving presence messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PRESENCE_SUBSCRIBE}. - */ - presenceSubscribers: number; - /** - * The number of realtime attachments permitted to publish messages to the channel. This requires the `publish` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PUBLISH}. - */ - publishers: number; - /** - * The number of realtime attachments receiving messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.SUBSCRIBE}. - */ - subscribers: number; - } + autoConnect?: boolean; /** - * Passes additional client-specific properties to the REST {@link AbstractRest.constructor | `constructor()`} or the Realtime {@link AbstractRealtime.constructor | `constructor()`}. + * When a {@link TokenParams} object is provided, it overrides the client library defaults when issuing new Ably Tokens or Ably {@link TokenRequest | `TokenRequest`s}. */ - interface ClientOptions extends AuthOptions { - /** - * When `true`, the client connects to Ably as soon as it is instantiated. You can set this to `false` and explicitly connect to Ably using the {@link Connection.connect | `connect()`} method. The default is `true`. - * - * @defaultValue `true` - */ - autoConnect?: boolean; + defaultTokenParams?: TokenParams; - /** - * When a {@link TokenParams} object is provided, it overrides the client library defaults when issuing new Ably Tokens or Ably {@link TokenRequest | `TokenRequest`s}. - */ - defaultTokenParams?: TokenParams; + /** + * If `false`, prevents messages originating from this connection being echoed back on the same connection. The default is `true`. + * + * @defaultValue `true` + */ + echoMessages?: boolean; - /** - * If `false`, prevents messages originating from this connection being echoed back on the same connection. The default is `true`. - * - * @defaultValue `true` - */ - echoMessages?: boolean; + /** + * Enables a [custom environment](https://ably.com/docs/platform-customization) to be used with the Ably service. + */ + environment?: string; - /** - * Enables a [custom environment](https://ably.com/docs/platform-customization) to be used with the Ably service. - */ - environment?: string; + /** + * Controls the verbosity of the logs output from the library. Valid values are: 0 (no logs), 1 (errors only), 2 (errors plus connection and channel state changes), 3 (high-level debug output), and 4 (full debug output). + */ + logLevel?: number; - /** - * Controls the verbosity of the logs output from the library. Valid values are: 0 (no logs), 1 (errors only), 2 (errors plus connection and channel state changes), 3 (high-level debug output), and 4 (full debug output). - */ - logLevel?: number; + /** + * Controls the log output of the library. This is a function to handle each line of log output. If you do not set this value, then `console.log` will be used. + * + * @param msg - The log message emitted by the library. + */ + logHandler?: (msg: string) => void; - /** - * Controls the log output of the library. This is a function to handle each line of log output. If you do not set this value, then `console.log` will be used. - * - * @param msg - The log message emitted by the library. - */ - logHandler?: (msg: string) => void; - - /** - * Enables a non-default Ably port to be specified. For development environments only. The default value is 80. - * - * @defaultValue 80 - */ - port?: number; - - /** - * If `false`, this disables the default behavior whereby the library queues messages on a connection in the disconnected or connecting states. The default behavior enables applications to submit messages immediately upon instantiating the library without having to wait for the connection to be established. Applications may use this option to disable queueing if they wish to have application-level control over the queueing. The default is `true`. - * - * @defaultValue `true` - */ - queueMessages?: boolean; - - /** - * Enables a non-default Ably host to be specified. For development environments only. The default value is `rest.ably.io`. - * - * @defaultValue `"rest.ably.io"` - */ - restHost?: string; - - /** - * Enables a non-default Ably host to be specified for realtime connections. For development environments only. The default value is `realtime.ably.io`. - * - * @defaultValue `"realtime.ably.io"` - */ - realtimeHost?: string; - - /** - * An array of fallback hosts to be used in the case of an error necessitating the use of an alternative host. If you have been provided a set of custom fallback hosts by Ably, please specify them here. - * - * @defaultValue `['a.ably-realtime.com', 'b.ably-realtime.com', 'c.ably-realtime.com', 'd.ably-realtime.com', 'e.ably-realtime.com']`` - */ - fallbackHosts?: string[]; - - /** - * Set of configurable options to set on the HTTP(S) agent used for REST requests. - * - * See the [NodeJS docs](https://nodejs.org/api/http.html#new-agentoptions) for descriptions of these options. - */ - restAgentOptions?: { - /** - * See [NodeJS docs](https://nodejs.org/api/http.html#new-agentoptions) - */ - maxSockets?: number; - /** - * See [NodeJS docs](https://nodejs.org/api/http.html#new-agentoptions) - */ - keepAlive?: boolean; - }; - - /** - * Enables a connection to inherit the state of a previous connection that may have existed under a different instance of the Realtime library. This might typically be used by clients of the browser library to ensure connection state can be preserved when the user refreshes the page. A recovery key string can be explicitly provided, or alternatively if a callback function is provided, the client library will automatically persist the recovery key between page reloads and call the callback when the connection is recoverable. The callback is then responsible for confirming whether the connection should be recovered or not. See [connection state recovery](https://ably.com/docs/realtime/connection/#connection-state-recovery) for further information. - */ - recover?: string | recoverConnectionCallback; - - /** - * When `false`, the client will use an insecure connection. The default is `true`, meaning a TLS connection will be used to connect to Ably. - * - * @defaultValue `true` - */ - tls?: boolean; - - /** - * Enables a non-default Ably TLS port to be specified. For development environments only. The default value is 443. - * - * @defaultValue 443 - */ - tlsPort?: number; - - /** - * When `true`, the more efficient MsgPack binary encoding is used. When `false`, JSON text encoding is used. The default is `true`. - * - * @defaultValue `true` - */ - useBinaryProtocol?: boolean; - - /** - * Override the URL used by the realtime client to check if the internet is available. - * - * In the event of a failure to connect to the primary endpoint, the client will send a - * GET request to this URL to check if the internet is available. If this request returns - * a success response the client will attempt to connect to a fallback host. - */ - connectivityCheckUrl?: string; - - /** - * Disable the check used by the realtime client to check if the internet - * is available before connecting to a fallback host. - */ - disableConnectivityCheck?: boolean; - - /** - * If the connection is still in the {@link ConnectionState.DISCONNECTED} state after this delay in milliseconds, the client library will attempt to reconnect automatically. The default is 15 seconds. - * - * @defaultValue 15s - */ - disconnectedRetryTimeout?: number; - - /** - * When the connection enters the {@link ConnectionState.SUSPENDED} state, after this delay in milliseconds, if the state is still {@link ConnectionState.SUSPENDED | `SUSPENDED`}, the client library attempts to reconnect automatically. The default is 30 seconds. - * - * @defaultValue 30s - */ - suspendedRetryTimeout?: number; - - /** - * When `true`, the client library will automatically send a close request to Ably whenever the `window` [`beforeunload` event](https://developer.mozilla.org/en-US/docs/Web/API/BeforeUnloadEvent) fires. By enabling this option, the close request sent to Ably ensures the connection state will not be retained and all channels associated with the channel will be detached. This is commonly used by developers who want presence leave events to fire immediately (that is, if a user navigates to another page or closes their browser, then enabling this option will result in the presence member leaving immediately). Without this option or an explicit call to the `close` method of the `Connection` object, Ably expects that the abruptly disconnected connection could later be recovered and therefore does not immediately remove the user from presence. Instead, to avoid “twitchy” presence behaviour an abruptly disconnected client is removed from channels in which they are present after 15 seconds, and the connection state is retained for two minutes. Defaults to `true`. - */ - closeOnUnload?: boolean; - - /** - * When `true`, enables idempotent publishing by assigning a unique message ID client-side, allowing the Ably servers to discard automatic publish retries following a failure such as a network fault. The default is `true`. - * - * @defaultValue `true` - */ - idempotentRestPublishing?: boolean; + /** + * Enables a non-default Ably port to be specified. For development environments only. The default value is 80. + * + * @defaultValue 80 + */ + port?: number; - /** - * A set of key-value pairs that can be used to pass in arbitrary connection parameters, such as [`heartbeatInterval`](https://ably.com/docs/realtime/connection#heartbeats) or [`remainPresentFor`](https://ably.com/docs/realtime/presence#unstable-connections). - */ - transportParams?: { [k: string]: string | number }; + /** + * If `false`, this disables the default behavior whereby the library queues messages on a connection in the disconnected or connecting states. The default behavior enables applications to submit messages immediately upon instantiating the library without having to wait for the connection to be established. Applications may use this option to disable queueing if they wish to have application-level control over the queueing. The default is `true`. + * + * @defaultValue `true` + */ + queueMessages?: boolean; - /** - * An array of transports to use, in descending order of preference. In the browser environment the available transports are: `web_socket` and `xhr`. - */ - transports?: Transport[]; + /** + * Enables a non-default Ably host to be specified. For development environments only. The default value is `rest.ably.io`. + * + * @defaultValue `"rest.ably.io"` + */ + restHost?: string; - /** - * The maximum number of fallback hosts to use as a fallback when an HTTP request to the primary host is unreachable or indicates that it is unserviceable. The default value is 3. - * - * @defaultValue 3 - */ - httpMaxRetryCount?: number; + /** + * Enables a non-default Ably host to be specified for realtime connections. For development environments only. The default value is `realtime.ably.io`. + * + * @defaultValue `"realtime.ably.io"` + */ + realtimeHost?: string; - /** - * The maximum elapsed time in milliseconds in which fallback host retries for HTTP requests will be attempted. The default is 15 seconds. - * - * @defaultValue 15s - */ - httpMaxRetryDuration?: number; + /** + * An array of fallback hosts to be used in the case of an error necessitating the use of an alternative host. If you have been provided a set of custom fallback hosts by Ably, please specify them here. + * + * @defaultValue `['a.ably-realtime.com', 'b.ably-realtime.com', 'c.ably-realtime.com', 'd.ably-realtime.com', 'e.ably-realtime.com']`` + */ + fallbackHosts?: string[]; + /** + * Set of configurable options to set on the HTTP(S) agent used for REST requests. + * + * See the [NodeJS docs](https://nodejs.org/api/http.html#new-agentoptions) for descriptions of these options. + */ + restAgentOptions?: { /** - * Timeout in milliseconds for opening a connection to Ably to initiate an HTTP request. The default is 4 seconds. - * - * @defaultValue 4s + * See [NodeJS docs](https://nodejs.org/api/http.html#new-agentoptions) */ - httpOpenTimeout?: number; - + maxSockets?: number; /** - * Timeout in milliseconds for a client performing a complete HTTP request to Ably, including the connection phase. The default is 10 seconds. - * - * @defaultValue 10s + * See [NodeJS docs](https://nodejs.org/api/http.html#new-agentoptions) */ - httpRequestTimeout?: number; + keepAlive?: boolean; + }; - /** - * Timeout for the wait of acknowledgement for operations performed via a realtime connection, before the client library considers a request failed and triggers a failure condition. Operations include establishing a connection with Ably, or sending a `HEARTBEAT`, `CONNECT`, `ATTACH`, `DETACH` or `CLOSE` request. It is the equivalent of `httpRequestTimeout` but for realtime operations, rather than REST. The default is 10 seconds. - * - * @defaultValue 10s - */ - realtimeRequestTimeout?: number; - } + /** + * Enables a connection to inherit the state of a previous connection that may have existed under a different instance of the Realtime library. This might typically be used by clients of the browser library to ensure connection state can be preserved when the user refreshes the page. A recovery key string can be explicitly provided, or alternatively if a callback function is provided, the client library will automatically persist the recovery key between page reloads and call the callback when the connection is recoverable. The callback is then responsible for confirming whether the connection should be recovered or not. See [connection state recovery](https://ably.com/docs/realtime/connection/#connection-state-recovery) for further information. + */ + recover?: string | recoverConnectionCallback; /** - * Passes authentication-specific properties in authentication requests to Ably. Properties set using `AuthOptions` are used instead of the default values set when the client library is instantiated, as opposed to being merged with them. + * When `false`, the client will use an insecure connection. The default is `true`, meaning a TLS connection will be used to connect to Ably. + * + * @defaultValue `true` */ - interface AuthOptions { - /** - * Called when a new token is required. The role of the callback is to obtain a fresh token, one of: an Ably Token string (in plain text format); a signed {@link TokenRequest}; a {@link TokenDetails} (in JSON format); an [Ably JWT](https://ably.com/docs/core-features/authentication.ably-jwt). See [the authentication documentation](https://ably.com/docs/realtime/authentication) for details of the Ably {@link TokenRequest} format and associated API calls. - * - * @param data - The parameters that should be used to generate the token. - * @param callback - A function which, upon success, the `authCallback` should call with one of: an Ably Token string (in plain text format); a signed `TokenRequest`; a `TokenDetails` (in JSON format); an [Ably JWT](https://ably.com/docs/core-features/authentication#ably-jwt). Upon failure, the `authCallback` should call this function with information about the error. - */ - authCallback?( - data: TokenParams, - /** - * A function which, upon success, the `authCallback` should call with one of: an Ably Token string (in plain text format); a signed `TokenRequest`; a `TokenDetails` (in JSON format); an [Ably JWT](https://ably.com/docs/core-features/authentication#ably-jwt). Upon failure, the `authCallback` should call this function with information about the error. - * - * @param error - Should be `null` if the auth request completed successfully, or containing details of the error if not. - * @param tokenRequestOrDetails - A valid `TokenRequest`, `TokenDetails` or Ably JWT to be used for authentication. - */ - callback: ( - error: ErrorInfo | string | null, - tokenRequestOrDetails: TokenDetails | TokenRequest | string | null - ) => void - ): void; + tls?: boolean; - /** - * A set of key-value pair headers to be added to any request made to the `authUrl`. Useful when an application requires these to be added to validate the request or implement the response. If the `authHeaders` object contains an `authorization` key, then `withCredentials` is set on the XHR request. - */ - authHeaders?: { [index: string]: string }; + /** + * Enables a non-default Ably TLS port to be specified. For development environments only. The default value is 443. + * + * @defaultValue 443 + */ + tlsPort?: number; - /** - * The HTTP verb to use for any request made to the `authUrl`, either `GET` or `POST`. The default value is `GET`. - * - * @defaultValue `HTTPMethods.GET` - */ - authMethod?: HTTPMethods; + /** + * When `true`, the more efficient MsgPack binary encoding is used. When `false`, JSON text encoding is used. The default is `true`. + * + * @defaultValue `true` + */ + useBinaryProtocol?: boolean; - /** - * A set of key-value pair params to be added to any request made to the `authUrl`. When the `authMethod` is `GET`, query params are added to the URL, whereas when `authMethod` is `POST`, the params are sent as URL encoded form data. Useful when an application requires these to be added to validate the request or implement the response. - */ - authParams?: { [index: string]: string }; + /** + * Override the URL used by the realtime client to check if the internet is available. + * + * In the event of a failure to connect to the primary endpoint, the client will send a + * GET request to this URL to check if the internet is available. If this request returns + * a success response the client will attempt to connect to a fallback host. + */ + connectivityCheckUrl?: string; - /** - * A URL that the library may use to obtain a token string (in plain text format), or a signed {@link TokenRequest} or {@link TokenDetails} (in JSON format) from. - */ - authUrl?: string; + /** + * Disable the check used by the realtime client to check if the internet + * is available before connecting to a fallback host. + */ + disableConnectivityCheck?: boolean; - /** - * The full API key string, as obtained from the [Ably dashboard](https://ably.com/dashboard). Use this option if you wish to use Basic authentication, or wish to be able to issue Ably Tokens without needing to defer to a separate entity to sign Ably {@link TokenRequest | `TokenRequest`s}. Read more about [Basic authentication](https://ably.com/docs/core-features/authentication#basic-authentication). - */ - key?: string; + /** + * If the connection is still in the {@link ConnectionState.DISCONNECTED} state after this delay in milliseconds, the client library will attempt to reconnect automatically. The default is 15 seconds. + * + * @defaultValue 15s + */ + disconnectedRetryTimeout?: number; - /** - * If `true`, the library queries the Ably servers for the current time when issuing {@link TokenRequest | `TokenRequest`s} instead of relying on a locally-available time of day. Knowing the time accurately is needed to create valid signed Ably {@link TokenRequest | `TokenRequest`s}, so this option is useful for library instances on auth servers where for some reason the server clock cannot be kept synchronized through normal means, such as an [NTP daemon](https://en.wikipedia.org/wiki/Ntpd). The server is queried for the current time once per client library instance (which stores the offset from the local clock), so if using this option you should avoid instancing a new version of the library for each request. The default is `false`. - * - * @defaultValue `false` - */ - queryTime?: boolean; + /** + * When the connection enters the {@link ConnectionState.SUSPENDED} state, after this delay in milliseconds, if the state is still {@link ConnectionState.SUSPENDED | `SUSPENDED`}, the client library attempts to reconnect automatically. The default is 30 seconds. + * + * @defaultValue 30s + */ + suspendedRetryTimeout?: number; - /** - * An authenticated token. This can either be a {@link TokenDetails} object or token string (obtained from the `token` property of a {@link TokenDetails} component of an Ably {@link TokenRequest} response, or a JSON Web Token satisfying [the Ably requirements for JWTs](https://ably.com/docs/core-features/authentication#ably-jwt)). This option is mostly useful for testing: since tokens are short-lived, in production you almost always want to use an authentication method that enables the client library to renew the token automatically when the previous one expires, such as `authUrl` or `authCallback`. Read more about [Token authentication](https://ably.com/docs/core-features/authentication#token-authentication). - */ - token?: TokenDetails | string; + /** + * When `true`, the client library will automatically send a close request to Ably whenever the `window` [`beforeunload` event](https://developer.mozilla.org/en-US/docs/Web/API/BeforeUnloadEvent) fires. By enabling this option, the close request sent to Ably ensures the connection state will not be retained and all channels associated with the channel will be detached. This is commonly used by developers who want presence leave events to fire immediately (that is, if a user navigates to another page or closes their browser, then enabling this option will result in the presence member leaving immediately). Without this option or an explicit call to the `close` method of the `Connection` object, Ably expects that the abruptly disconnected connection could later be recovered and therefore does not immediately remove the user from presence. Instead, to avoid “twitchy” presence behaviour an abruptly disconnected client is removed from channels in which they are present after 15 seconds, and the connection state is retained for two minutes. Defaults to `true`. + */ + closeOnUnload?: boolean; - /** - * An authenticated {@link TokenDetails} object (most commonly obtained from an Ably Token Request response). This option is mostly useful for testing: since tokens are short-lived, in production you almost always want to use an authentication method that enables the client library to renew the token automatically when the previous one expires, such as `authUrl` or `authCallback`. Use this option if you wish to use Token authentication. Read more about [Token authentication](https://ably.com/docs/core-features/authentication#token-authentication). - */ - tokenDetails?: TokenDetails; + /** + * When `true`, enables idempotent publishing by assigning a unique message ID client-side, allowing the Ably servers to discard automatic publish retries following a failure such as a network fault. The default is `true`. + * + * @defaultValue `true` + */ + idempotentRestPublishing?: boolean; - /** - * When `true`, forces token authentication to be used by the library. If a `clientId` is not specified in the {@link ClientOptions} or {@link TokenParams}, then the Ably Token issued is [anonymous](https://ably.com/docs/core-features/authentication#identified-clients). - */ - useTokenAuth?: boolean; + /** + * A set of key-value pairs that can be used to pass in arbitrary connection parameters, such as [`heartbeatInterval`](https://ably.com/docs/realtime/connection#heartbeats) or [`remainPresentFor`](https://ably.com/docs/realtime/presence#unstable-connections). + */ + transportParams?: { [k: string]: string | number }; - /** - * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [client identities](https://ably.com/documentation/how-ably-works#client-identity). - */ - clientId?: string; - } + /** + * An array of transports to use, in descending order of preference. In the browser environment the available transports are: `web_socket` and `xhr`. + */ + transports?: Transport[]; /** - * Capabilities which are available for use within {@link TokenParams}. + * The maximum number of fallback hosts to use as a fallback when an HTTP request to the primary host is unreachable or indicates that it is unserviceable. The default value is 3. + * + * @defaultValue 3 */ - type capabilityOp = - | 'publish' - | 'subscribe' - | 'presence' - | 'history' - | 'stats' - | 'channel-metadata' - | 'push-subscribe' - | 'push-admin'; + httpMaxRetryCount?: number; + /** - * Capabilities which are available for use within {@link TokenParams}. + * The maximum elapsed time in milliseconds in which fallback host retries for HTTP requests will be attempted. The default is 15 seconds. + * + * @defaultValue 15s */ - type CapabilityOp = capabilityOp; + httpMaxRetryDuration?: number; /** - * Defines the properties of an Ably Token. + * Timeout in milliseconds for opening a connection to Ably to initiate an HTTP request. The default is 4 seconds. + * + * @defaultValue 4s */ - interface TokenParams { - /** - * The capabilities associated with this Ably Token. The capabilities value is a JSON-encoded representation of the resource paths and associated operations. Read more about capabilities in the [capabilities docs](https://ably.com/docs/core-features/authentication/#capabilities-explained). - * - * @defaultValue `'{"*":["*"]}'` - */ - capability?: { [key: string]: capabilityOp[] } | string; - /** - * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error is raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [identified clients](https://ably.com/docs/core-features/authentication#identified-clients). - */ - clientId?: string; - /** - * A cryptographically secure random string of at least 16 characters, used to ensure the {@link TokenRequest} cannot be reused. - */ - nonce?: string; - /** - * The timestamp of this request as milliseconds since the Unix epoch. Timestamps, in conjunction with the `nonce`, are used to prevent requests from being replayed. `timestamp` is a "one-time" value, and is valid in a request, but is not validly a member of any default token params such as `ClientOptions.defaultTokenParams`. - */ - timestamp?: number; - /** - * Requested time to live for the token in milliseconds. The default is 60 minutes. - * - * @defaultValue 60min - */ - ttl?: number; - } + httpOpenTimeout?: number; /** - * Sets the properties to configure encryption for a {@link Channel} or {@link RealtimeChannel} object. + * Timeout in milliseconds for a client performing a complete HTTP request to Ably, including the connection phase. The default is 10 seconds. + * + * @defaultValue 10s */ - interface CipherParams { - /** - * The algorithm to use for encryption. Only `AES` is supported and is the default value. - * - * @defaultValue `"AES"` - */ - algorithm: string; - /** - * The private key used to encrypt and decrypt payloads. You should not set this value directly; rather, you should pass a `key` of type {@link Types.CipherKeyParam} to {@link Crypto.getDefaultParams}. - */ - key: unknown; - /** - * The length of the key in bits; either 128 or 256. - */ - keyLength: number; - /** - * The cipher mode. Only `CBC` is supported and is the default value. - * - * @defaultValue `"CBC"` - */ - mode: string; - } + httpRequestTimeout?: number; /** - * Contains an Ably Token and its associated metadata. + * Timeout for the wait of acknowledgement for operations performed via a realtime connection, before the client library considers a request failed and triggers a failure condition. Operations include establishing a connection with Ably, or sending a `HEARTBEAT`, `CONNECT`, `ATTACH`, `DETACH` or `CLOSE` request. It is the equivalent of `httpRequestTimeout` but for realtime operations, rather than REST. The default is 10 seconds. + * + * @defaultValue 10s */ - interface TokenDetails { - /** - * The capabilities associated with this Ably Token. The capabilities value is a JSON-encoded representation of the resource paths and associated operations. Read more about capabilities in the [capabilities docs](https://ably.com/docs/core-features/authentication/#capabilities-explained). - */ - capability: string; - /** - * The client ID, if any, bound to this Ably Token. If a client ID is included, then the Ably Token authenticates its bearer as that client ID, and the Ably Token may only be used to perform operations on behalf of that client ID. The client is then considered to be an [identified client](https://ably.com/docs/core-features/authentication#identified-clients). - */ - clientId?: string; - /** - * The timestamp at which this token expires as milliseconds since the Unix epoch. - */ - expires: number; - /** - * The timestamp at which this token was issued as milliseconds since the Unix epoch. - */ - issued: number; - /** - * The [Ably Token](https://ably.com/docs/core-features/authentication#ably-tokens) itself. A typical Ably Token string appears with the form `xVLyHw.A-pwh7wicf3afTfgiw4k2Ku33kcnSA7z6y8FjuYpe3QaNRTEo4`. - */ - token: string; - } + realtimeRequestTimeout?: number; +} +/** + * Passes authentication-specific properties in authentication requests to Ably. Properties set using `AuthOptions` are used instead of the default values set when the client library is instantiated, as opposed to being merged with them. + */ +export interface AuthOptions { /** - * Contains the properties of a request for a token to Ably. Tokens are generated using {@link Auth.requestToken}. + * Called when a new token is required. The role of the callback is to obtain a fresh token, one of: an Ably Token string (in plain text format); a signed {@link TokenRequest}; a {@link TokenDetails} (in JSON format); an [Ably JWT](https://ably.com/docs/core-features/authentication.ably-jwt). See [the authentication documentation](https://ably.com/docs/realtime/authentication) for details of the Ably {@link TokenRequest} format and associated API calls. + * + * @param data - The parameters that should be used to generate the token. + * @param callback - A function which, upon success, the `authCallback` should call with one of: an Ably Token string (in plain text format); a signed `TokenRequest`; a `TokenDetails` (in JSON format); an [Ably JWT](https://ably.com/docs/core-features/authentication#ably-jwt). Upon failure, the `authCallback` should call this function with information about the error. */ - interface TokenRequest { + authCallback?( + data: TokenParams, /** - * Capability of the requested Ably Token. If the Ably `TokenRequest` is successful, the capability of the returned Ably Token will be the intersection of this capability with the capability of the issuing key. The capabilities value is a JSON-encoded representation of the resource paths and associated operations. Read more about capabilities in the [capabilities docs](https://ably.com/docs/realtime/authentication). - */ - capability: string; - /** - * The client ID to associate with the requested Ably Token. When provided, the Ably Token may only be used to perform operations on behalf of that client ID. - */ - clientId?: string; - /** - * The name of the key against which this request is made. The key name is public, whereas the key secret is private. - */ - keyName: string; - /** - * The Message Authentication Code for this request. - */ - mac: string; - /** - * A cryptographically secure random string of at least 16 characters, used to ensure the `TokenRequest` cannot be reused. - */ - nonce: string; - /** - * The timestamp of this request as milliseconds since the Unix epoch. - */ - timestamp: number; - /** - * Requested time to live for the Ably Token in milliseconds. If the Ably `TokenRequest` is successful, the TTL of the returned Ably Token is less than or equal to this value, depending on application settings and the attributes of the issuing key. The default is 60 minutes. + * A function which, upon success, the `authCallback` should call with one of: an Ably Token string (in plain text format); a signed `TokenRequest`; a `TokenDetails` (in JSON format); an [Ably JWT](https://ably.com/docs/core-features/authentication#ably-jwt). Upon failure, the `authCallback` should call this function with information about the error. * - * @defaultValue 60min + * @param error - Should be `null` if the auth request completed successfully, or containing details of the error if not. + * @param tokenRequestOrDetails - A valid `TokenRequest`, `TokenDetails` or Ably JWT to be used for authentication. */ - ttl?: number; - } + callback: ( + error: ErrorInfo | string | null, + tokenRequestOrDetails: TokenDetails | TokenRequest | string | null + ) => void + ): void; /** - * [Channel Parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) used within {@link ChannelOptions}. + * A set of key-value pair headers to be added to any request made to the `authUrl`. Useful when an application requires these to be added to validate the request or implement the response. If the `authHeaders` object contains an `authorization` key, then `withCredentials` is set on the XHR request. */ - type ChannelParams = { [key: string]: string }; + authHeaders?: { [index: string]: string }; /** - * The `ChannelMode` namespace describes the possible values of the {@link ChannelMode:type} type. + * The HTTP verb to use for any request made to the `authUrl`, either `GET` or `POST`. The default value is `GET`. + * + * @defaultValue `HTTPMethods.GET` */ - namespace ChannelMode { - /** - * The client can publish messages. - */ - type PUBLISH = 'PUBLISH'; - /** - * The client can subscribe to messages. - */ - type SUBSCRIBE = 'SUBSCRIBE'; - /** - * The client can enter the presence set. - */ - type PRESENCE = 'PRESENCE'; - /** - * The client can receive presence messages. - */ - type PRESENCE_SUBSCRIBE = 'PRESENCE_SUBSCRIBE'; - /** - * The client is resuming an existing connection. - */ - type ATTACH_RESUME = 'ATTACH_RESUME'; - } + authMethod?: HTTPMethods; /** - * Describes the possible flags used to configure client capabilities, using {@link ChannelOptions}. + * A set of key-value pair params to be added to any request made to the `authUrl`. When the `authMethod` is `GET`, query params are added to the URL, whereas when `authMethod` is `POST`, the params are sent as URL encoded form data. Useful when an application requires these to be added to validate the request or implement the response. */ - type ChannelMode = - | ChannelMode.PUBLISH - | ChannelMode.SUBSCRIBE - | ChannelMode.PRESENCE - | ChannelMode.PRESENCE_SUBSCRIBE - | ChannelMode.ATTACH_RESUME; + authParams?: { [index: string]: string }; + /** - * Describes the possible flags used to configure client capabilities, using {@link ChannelOptions}. + * A URL that the library may use to obtain a token string (in plain text format), or a signed {@link TokenRequest} or {@link TokenDetails} (in JSON format) from. */ - type ChannelModes = Array; + authUrl?: string; /** - * Passes additional properties to a {@link Channel} or {@link RealtimeChannel} object, such as encryption, {@link ChannelMode} and channel parameters. + * The full API key string, as obtained from the [Ably dashboard](https://ably.com/dashboard). Use this option if you wish to use Basic authentication, or wish to be able to issue Ably Tokens without needing to defer to a separate entity to sign Ably {@link TokenRequest | `TokenRequest`s}. Read more about [Basic authentication](https://ably.com/docs/core-features/authentication#basic-authentication). */ - interface ChannelOptions { - /** - * Requests encryption for this channel when not null, and specifies encryption-related parameters (such as algorithm, chaining mode, key length and key). See [an example](https://ably.com/docs/realtime/encryption#getting-started). When running in a browser, encryption is only available when the current environment is a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). - */ - cipher?: CipherParamOptions | CipherParams; - /** - * [Channel Parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) that configure the behavior of the channel. - */ - params?: ChannelParams; - /** - * An array of {@link ChannelMode} objects. - */ - modes?: ChannelModes; - } + key?: string; /** - * Passes additional properties to a {@link RealtimeChannel} name to produce a new derived channel + * If `true`, the library queries the Ably servers for the current time when issuing {@link TokenRequest | `TokenRequest`s} instead of relying on a locally-available time of day. Knowing the time accurately is needed to create valid signed Ably {@link TokenRequest | `TokenRequest`s}, so this option is useful for library instances on auth servers where for some reason the server clock cannot be kept synchronized through normal means, such as an [NTP daemon](https://en.wikipedia.org/wiki/Ntpd). The server is queried for the current time once per client library instance (which stores the offset from the local clock), so if using this option you should avoid instancing a new version of the library for each request. The default is `false`. + * + * @defaultValue `false` */ - interface DeriveOptions { - /** - * The JMESPath Query filter string to be used to derive new channel. - */ - filter?: string; - } + queryTime?: boolean; /** - * The `RestHistoryParams` interface describes the parameters accepted by the following methods: - * - * - {@link Presence.history} - * - {@link Channel.history} + * An authenticated token. This can either be a {@link TokenDetails} object or token string (obtained from the `token` property of a {@link TokenDetails} component of an Ably {@link TokenRequest} response, or a JSON Web Token satisfying [the Ably requirements for JWTs](https://ably.com/docs/core-features/authentication#ably-jwt)). This option is mostly useful for testing: since tokens are short-lived, in production you almost always want to use an authentication method that enables the client library to renew the token automatically when the previous one expires, such as `authUrl` or `authCallback`. Read more about [Token authentication](https://ably.com/docs/core-features/authentication#token-authentication). */ - interface RestHistoryParams { - /** - * The time from which messages are retrieved, specified as milliseconds since the Unix epoch. - */ - start?: number; - /** - * The time until messages are retrieved, specified as milliseconds since the Unix epoch. - * - * @defaultValue The current time. - */ - end?: number; - /** - * The order for which messages are returned in. Valid values are `'backwards'` which orders messages from most recent to oldest, or `'forwards'` which orders messages from oldest to most recent. The default is `'backwards'`. - * - * @defaultValue `'backwards'` - */ - direction?: 'forwards' | 'backwards'; - /** - * An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. - * - * @defaultValue 100 - */ - limit?: number; - } + token?: TokenDetails | string; /** - * The `RestPresenceParams` interface describes the parameters accepted by {@link Presence.get}. + * An authenticated {@link TokenDetails} object (most commonly obtained from an Ably Token Request response). This option is mostly useful for testing: since tokens are short-lived, in production you almost always want to use an authentication method that enables the client library to renew the token automatically when the previous one expires, such as `authUrl` or `authCallback`. Use this option if you wish to use Token authentication. Read more about [Token authentication](https://ably.com/docs/core-features/authentication#token-authentication). */ - interface RestPresenceParams { - /** - * An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. - * - * @defaultValue 100 - */ - limit?: number; - /** - * Filters the list of returned presence members by a specific client using its ID. - */ - clientId?: string; - /** - * Filters the list of returned presence members by a specific connection using its ID. - */ - connectionId?: string; - } + tokenDetails?: TokenDetails; /** - * The `RealtimePresenceParams` interface describes the parameters accepted by {@link RealtimePresence.get}. + * When `true`, forces token authentication to be used by the library. If a `clientId` is not specified in the {@link ClientOptions} or {@link TokenParams}, then the Ably Token issued is [anonymous](https://ably.com/docs/core-features/authentication#identified-clients). */ - interface RealtimePresenceParams { - /** - * Sets whether to wait for a full presence set synchronization between Ably and the clients on the channel to complete before returning the results. Synchronization begins as soon as the channel is {@link ChannelState.ATTACHED}. When set to `true` the results will be returned as soon as the sync is complete. When set to `false` the current list of members will be returned without the sync completing. The default is `true`. - * - * @defaultValue `true` - */ - waitForSync?: boolean; - /** - * Filters the array of returned presence members by a specific client using its ID. - */ - clientId?: string; - /** - * Filters the array of returned presence members by a specific connection using its ID. - */ - connectionId?: string; - } + useTokenAuth?: boolean; /** - * The `RealtimeHistoryParams` interface describes the parameters accepted by the following methods: - * - * - {@link RealtimePresence.history} - * - {@link RealtimeChannel.history} + * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [client identities](https://ably.com/documentation/how-ably-works#client-identity). */ - interface RealtimeHistoryParams { - /** - * The time from which messages are retrieved, specified as milliseconds since the Unix epoch. - */ - start?: number; - /** - * The time until messages are retrieved, specified as milliseconds since the Unix epoch. - * - * @defaultValue The current time. - */ - end?: number; - /** - * The order for which messages are returned in. Valid values are `'backwards'` which orders messages from most recent to oldest, or `'forwards'` which orders messages from oldest to most recent. The default is `'backwards'`. - * - * @defaultValue `'backwards'` - */ - direction?: 'forwards' | 'backwards'; - /** - * An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. - * - * @defaultValue 100 - */ - limit?: number; - /** - * When `true`, ensures message history is up until the point of the channel being attached. See [continuous history](https://ably.com/docs/realtime/history#continuous-history) for more info. Requires the `direction` to be `backwards`. If the channel is not attached, or if `direction` is set to `forwards`, this option results in an error. - */ - untilAttach?: boolean; - } + clientId?: string; +} + +/** + * Capabilities which are available for use within {@link TokenParams}. + */ +export type capabilityOp = + | 'publish' + | 'subscribe' + | 'presence' + | 'history' + | 'stats' + | 'channel-metadata' + | 'push-subscribe' + | 'push-admin'; +/** + * Capabilities which are available for use within {@link TokenParams}. + */ +export type CapabilityOp = capabilityOp; +/** + * Defines the properties of an Ably Token. + */ +export interface TokenParams { /** - * Contains state change information emitted by {@link Channel} and {@link RealtimeChannel} objects. + * The capabilities associated with this Ably Token. The capabilities value is a JSON-encoded representation of the resource paths and associated operations. Read more about capabilities in the [capabilities docs](https://ably.com/docs/core-features/authentication/#capabilities-explained). + * + * @defaultValue `'{"*":["*"]}'` */ - interface ChannelStateChange { - /** - * The new current {@link ChannelState}. - */ - current: ChannelState; - /** - * The previous state. For the {@link ChannelEvent.UPDATE} event, this is equal to the `current` {@link ChannelState}. - */ - previous: ChannelState; - /** - * An {@link ErrorInfo} object containing any information relating to the transition. - */ - reason?: ErrorInfo; - /** - * Indicates whether message continuity on this channel is preserved, see [Nonfatal channel errors](https://ably.com/docs/realtime/channels#nonfatal-errors) for more info. - */ - resumed: boolean; - /** - * Indicates whether the client can expect a backlog of messages from a rewind or resume. - */ - hasBacklog?: boolean; - } - + capability?: { [key: string]: capabilityOp[] } | string; /** - * Contains {@link ConnectionState} change information emitted by the {@link Connection} object. + * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error is raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [identified clients](https://ably.com/docs/core-features/authentication#identified-clients). */ - interface ConnectionStateChange { - /** - * The new {@link ConnectionState}. - */ - current: ConnectionState; - /** - * The previous {@link ConnectionState}. For the {@link ConnectionEvent.UPDATE} event, this is equal to the current {@link ConnectionState}. - */ - previous: ConnectionState; - /** - * An {@link ErrorInfo} object containing any information relating to the transition. - */ - reason?: ErrorInfo; - /** - * Duration in milliseconds, after which the client retries a connection where applicable. - */ - retryIn?: number; - } - + clientId?: string; /** - * The `DevicePlatform` namespace describes the possible values of the {@link DevicePlatform:type} type. + * A cryptographically secure random string of at least 16 characters, used to ensure the {@link TokenRequest} cannot be reused. */ - namespace DevicePlatform { - /** - * The device platform is Android. - */ - type ANDROID = 'android'; - /** - * The device platform is iOS. - */ - type IOS = 'ios'; - /** - * The device platform is a web browser. - */ - type BROWSER = 'browser'; - } - + nonce?: string; /** - * Describes the device receiving push notifications. + * The timestamp of this request as milliseconds since the Unix epoch. Timestamps, in conjunction with the `nonce`, are used to prevent requests from being replayed. `timestamp` is a "one-time" value, and is valid in a request, but is not validly a member of any default token params such as `ClientOptions.defaultTokenParams`. */ - type DevicePlatform = DevicePlatform.ANDROID | DevicePlatform.IOS | DevicePlatform.BROWSER; - + timestamp?: number; /** - * The `DeviceFormFactor` namespace describes the possible values of the {@link DeviceFormFactor:type} type. + * Requested time to live for the token in milliseconds. The default is 60 minutes. + * + * @defaultValue 60min */ - namespace DeviceFormFactor { - /** - * The device is a phone. - */ - type PHONE = 'phone'; - /** - * The device is tablet. - */ - type TABLET = 'tablet'; - /** - * The device is a desktop. - */ - type DESKTOP = 'desktop'; - /** - * The device is a TV. - */ - type TV = 'tv'; - /** - * The device is a watch. - */ - type WATCH = 'watch'; - /** - * The device is a car. - */ - type CAR = 'car'; - /** - * The device is embedded. - */ - type EMBEDDED = 'embedded'; - /** - * The device is other. - */ - type OTHER = 'other'; - } + ttl?: number; +} +/** + * Sets the properties to configure encryption for a {@link Channel} or {@link RealtimeChannel} object. + */ +export interface CipherParams { /** - * Describes the type of device receiving a push notification. + * The algorithm to use for encryption. Only `AES` is supported and is the default value. + * + * @defaultValue `"AES"` */ - type DeviceFormFactor = - | DeviceFormFactor.PHONE - | DeviceFormFactor.TABLET - | DeviceFormFactor.DESKTOP - | DeviceFormFactor.TV - | DeviceFormFactor.WATCH - | DeviceFormFactor.CAR - | DeviceFormFactor.EMBEDDED - | DeviceFormFactor.OTHER; - + algorithm: string; /** - * Contains the properties of a device registered for push notifications. + * The private key used to encrypt and decrypt payloads. You should not set this value directly; rather, you should pass a `key` of type {@link CipherKeyParam} to {@link Crypto.getDefaultParams}. */ - interface DeviceDetails { - /** - * A unique ID generated by the device. - */ - id: string; - /** - * The client ID the device is connected to Ably with. - */ - clientId?: string; - /** - * The {@link DevicePlatform} associated with the device. Describes the platform the device uses, such as `android` or `ios`. - */ - platform: DevicePlatform; - /** - * The {@link DeviceFormFactor} object associated with the device. Describes the type of the device, such as `phone` or `tablet`. - */ - formFactor: DeviceFormFactor; - /** - * A JSON object of key-value pairs that contains metadata for the device. - */ - metadata?: any; - /** - * A unique device secret generated by the Ably SDK. - */ - deviceSecret?: string; - /** - * The {@link DevicePushDetails} object associated with the device. Describes the details of the push registration of the device. - */ - push: DevicePushDetails; - } - + key: unknown; /** - * Contains the subscriptions of a device, or a group of devices sharing the same `clientId`, has to a channel in order to receive push notifications. + * The length of the key in bits; either 128 or 256. */ - interface PushChannelSubscription { - /** - * The channel the push notification subscription is for. - */ - channel: string; - /** - * The unique ID of the device. - */ - deviceId?: string; - /** - * The ID of the client the device, or devices are associated to. - */ - clientId?: string; - } - + keyLength: number; /** - * Valid states which a Push device may be in. + * The cipher mode. Only `CBC` is supported and is the default value. + * + * @defaultValue `"CBC"` */ - type DevicePushState = 'ACTIVE' | 'FAILING' | 'FAILED'; + mode: string; +} +/** + * Contains an Ably Token and its associated metadata. + */ +export interface TokenDetails { /** - * Contains the details of the push registration of a device. + * The capabilities associated with this Ably Token. The capabilities value is a JSON-encoded representation of the resource paths and associated operations. Read more about capabilities in the [capabilities docs](https://ably.com/docs/core-features/authentication/#capabilities-explained). */ - interface DevicePushDetails { - /** - * A JSON object of key-value pairs that contains of the push transport and address. - */ - recipient: any; - /** - * The current state of the push registration. - */ - state?: DevicePushState; - /** - * An {@link ErrorInfo} object describing the most recent error when the `state` is `Failing` or `Failed`. - */ - error?: ErrorInfo; - } - + capability: string; /** - * The `DeviceRegistrationParams` interface describes the parameters accepted by the following methods: - * - * - {@link PushDeviceRegistrations.list} - * - {@link PushDeviceRegistrations.removeWhere} + * The client ID, if any, bound to this Ably Token. If a client ID is included, then the Ably Token authenticates its bearer as that client ID, and the Ably Token may only be used to perform operations on behalf of that client ID. The client is then considered to be an [identified client](https://ably.com/docs/core-features/authentication#identified-clients). */ - interface DeviceRegistrationParams { - /** - * Filter to restrict to devices associated with a client ID. - */ - clientId?: string; - /** - * Filter to restrict by the unique ID of the device. - */ - deviceId?: string; - /** - * A limit on the number of devices returned, up to 1,000. - */ - limit?: number; - /** - * Filter by the state of the device. - */ - state?: DevicePushState; - } - + clientId?: string; /** - * The `PushChannelSubscriptionParams` interface describes the parameters accepted by the following methods: - * - * - {@link PushChannelSubscriptions.list} - * - {@link PushChannelSubscriptions.removeWhere} + * The timestamp at which this token expires as milliseconds since the Unix epoch. */ - interface PushChannelSubscriptionParams { - /** - * Filter to restrict to subscriptions associated with the given channel. - */ - channel?: string; - /** - * Filter to restrict to devices associated with the given client identifier. Cannot be used with a deviceId param. - */ - clientId?: string; - /** - * Filter to restrict to devices associated with that device identifier. Cannot be used with a clientId param. - */ - deviceId?: string; - /** - * A limit on the number of devices returned, up to 1,000. - */ - limit?: number; - } - + expires: number; /** - * The `PushChannelsParams` interface describes the parameters accepted by {@link PushChannelSubscriptions.listChannels}. + * The timestamp at which this token was issued as milliseconds since the Unix epoch. */ - interface PushChannelsParams { - /** - * A limit on the number of channels returned, up to 1,000. - */ - limit?: number; - } + issued: number; + /** + * The [Ably Token](https://ably.com/docs/core-features/authentication#ably-tokens) itself. A typical Ably Token string appears with the form `xVLyHw.A-pwh7wicf3afTfgiw4k2Ku33kcnSA7z6y8FjuYpe3QaNRTEo4`. + */ + token: string; +} +/** + * Contains the properties of a request for a token to Ably. Tokens are generated using {@link Auth.requestToken}. + */ +export interface TokenRequest { /** - * The `StatsParams` interface describes the parameters accepted by the following methods: + * Capability of the requested Ably Token. If the Ably `TokenRequest` is successful, the capability of the returned Ably Token will be the intersection of this capability with the capability of the issuing key. The capabilities value is a JSON-encoded representation of the resource paths and associated operations. Read more about capabilities in the [capabilities docs](https://ably.com/docs/realtime/authentication). + */ + capability: string; + /** + * The client ID to associate with the requested Ably Token. When provided, the Ably Token may only be used to perform operations on behalf of that client ID. + */ + clientId?: string; + /** + * The name of the key against which this request is made. The key name is public, whereas the key secret is private. + */ + keyName: string; + /** + * The Message Authentication Code for this request. + */ + mac: string; + /** + * A cryptographically secure random string of at least 16 characters, used to ensure the `TokenRequest` cannot be reused. + */ + nonce: string; + /** + * The timestamp of this request as milliseconds since the Unix epoch. + */ + timestamp: number; + /** + * Requested time to live for the Ably Token in milliseconds. If the Ably `TokenRequest` is successful, the TTL of the returned Ably Token is less than or equal to this value, depending on application settings and the attributes of the issuing key. The default is 60 minutes. * - * - {@link AbstractRest.stats} - * - {@link AbstractRealtime.stats} + * @defaultValue 60min */ - interface StatsParams { - /** - * The time from which stats are retrieved, specified as milliseconds since the Unix epoch. - * - * @defaultValue The Unix epoch. - */ - start?: number; - /** - * The time until stats are retrieved, specified as milliseconds since the Unix epoch. - * - * @defaultValue The current time. - */ - end?: number; - /** - * The order for which stats are returned in. Valid values are `'backwards'` which orders stats from most recent to oldest, or `'forwards'` which orders stats from oldest to most recent. The default is `'backwards'`. - * - * @defaultValue `'backwards'` - */ - direction?: 'backwards' | 'forwards'; - /** - * An upper limit on the number of stats returned. The default is 100, and the maximum is 1000. - * - * @defaultValue 100 - */ - limit?: number; - /** - * Based on the unit selected, the given `start` or `end` times are rounded down to the start of the relevant interval depending on the unit granularity of the query. - * - * @defaultValue `StatsIntervalGranularity.MINUTE` - */ - unit?: StatsIntervalGranularity; - } + ttl?: number; +} +/** + * [Channel Parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) used within {@link ChannelOptions}. + */ +export type ChannelParams = { [key: string]: string }; + +/** + * The `ChannelMode` namespace describes the possible values of the {@link ChannelMode:type} type. + */ +declare namespace ChannelMode { /** - * Contains information about the results of a batch operation. + * The client can publish messages. */ - interface BatchResult { - /** - * The number of successful operations in the request. - */ - successCount: number; - /** - * The number of unsuccessful operations in the request. - */ - failureCount: number; - /** - * An array of results for the batch operation. - */ - results: T[]; - } - + type PUBLISH = 'PUBLISH'; /** - * Describes the messages that should be published by a batch publish operation, and the channels to which they should be published. + * The client can subscribe to messages. */ - interface BatchPublishSpec { - /** - * The names of the channels to publish the `messages` to. - */ - channels: string[]; - /** - * An array of {@link Message} objects. - */ - messages: Message[]; - } - + type SUBSCRIBE = 'SUBSCRIBE'; /** - * Contains information about the result of successful publishes to a channel requested by a single {@link Types.BatchPublishSpec}. + * The client can enter the presence set. */ - interface BatchPublishSuccessResult { - /** - * The name of the channel the message(s) was published to. - */ - channel: string; - /** - * A unique ID prefixed to the {@link Message.id} of each published message. - */ - messageId: string; - } - + type PRESENCE = 'PRESENCE'; /** - * Contains information about the result of unsuccessful publishes to a channel requested by a single {@link Types.BatchPublishSpec}. + * The client can receive presence messages. */ - interface BatchPublishFailureResult { - /** - * The name of the channel the message(s) failed to be published to. - */ - channel: string; - /** - * Describes the reason for which the message(s) failed to publish to the channel as an {@link ErrorInfo} object. - */ - error: ErrorInfo; - } - + type PRESENCE_SUBSCRIBE = 'PRESENCE_SUBSCRIBE'; /** - * Contains information about the result of a successful batch presence request for a single channel. + * The client is resuming an existing connection. */ - interface BatchPresenceSuccessResult { - /** - * The channel name the presence state was retrieved for. - */ - channel: string; - /** - * An array of {@link PresenceMessage}s describing members present on the channel. - */ - presence: PresenceMessage[]; - } + type ATTACH_RESUME = 'ATTACH_RESUME'; +} + +/** + * Describes the possible flags used to configure client capabilities, using {@link ChannelOptions}. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ChannelMode = + | ChannelMode.PUBLISH + | ChannelMode.SUBSCRIBE + | ChannelMode.PRESENCE + | ChannelMode.PRESENCE_SUBSCRIBE + | ChannelMode.ATTACH_RESUME; +/** + * Describes the possible flags used to configure client capabilities, using {@link ChannelOptions}. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type ChannelModes = Array; +/** + * Passes additional properties to a {@link Channel} or {@link RealtimeChannel} object, such as encryption, {@link ChannelMode} and channel parameters. + */ +export interface ChannelOptions { /** - * Contains information about the result of an unsuccessful batch presence request for a single channel. + * Requests encryption for this channel when not null, and specifies encryption-related parameters (such as algorithm, chaining mode, key length and key). See [an example](https://ably.com/docs/realtime/encryption#getting-started). When running in a browser, encryption is only available when the current environment is a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). */ - interface BatchPresenceFailureResult { - /** - * The channel name the presence state failed to be retrieved for. - */ - channel: string; - /** - * Describes the reason for which presence state could not be retrieved for the channel as an {@link ErrorInfo} object. - */ - error: ErrorInfo; - } - + cipher?: CipherParamOptions | CipherParams; /** - * The `TokenRevocationOptions` interface describes the additional options accepted by {@link Auth.revokeTokens}. + * [Channel Parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) that configure the behavior of the channel. */ - interface TokenRevocationOptions { - /** - * A Unix timestamp in milliseconds where only tokens issued before this time are revoked. The default is the current time. Requests with an `issuedBefore` in the future, or more than an hour in the past, will be rejected. - */ - issuedBefore?: number; - /** - * If true, permits a token renewal cycle to take place without needing established connections to be dropped, by postponing enforcement to 30 seconds in the future, and sending any existing connections a hint to obtain (and upgrade the connection to use) a new token. The default is `false`, meaning that the effect is near-immediate. - */ - allowReauthMargin?: boolean; - } - + params?: ChannelParams; /** - * Describes which tokens should be affected by a token revocation request. + * An array of {@link ChannelMode} objects. */ - interface TokenRevocationTargetSpecifier { - /** - * The type of token revocation target specifier. Valid values include `clientId`, `revocationKey` and `channel`. - */ - type: string; - /** - * The value of the token revocation target specifier. - */ - value: string; - } + modes?: ChannelModes; +} +/** + * Passes additional properties to a {@link RealtimeChannel} name to produce a new derived channel + */ +export interface DeriveOptions { /** - * Contains information about the result of a successful token revocation request for a single target specifier. + * The JMESPath Query filter string to be used to derive new channel. */ - interface TokenRevocationSuccessResult { - /** - * The target specifier. - */ - target: string; - /** - * The time at which the token revocation will take effect, as a Unix timestamp in milliseconds. - */ - appliesAt: number; - /** - * A Unix timestamp in milliseconds. Only tokens issued earlier than this time will be revoked. - */ - issuedBefore: number; - } + filter?: string; +} +/** + * The `RestHistoryParams` interface describes the parameters accepted by the following methods: + * + * - {@link Presence.history} + * - {@link Channel.history} + */ +export interface RestHistoryParams { /** - * Contains information about the result of an unsuccessful token revocation request for a single target specifier. + * The time from which messages are retrieved, specified as milliseconds since the Unix epoch. */ - interface TokenRevocationFailureResult { - /** - * The target specifier. - */ - target: string; - /** - * Describes the reason for which token revocation failed for the given `target` as an {@link ErrorInfo} object. - */ - error: ErrorInfo; - } - - // Common Listeners + start?: number; /** - * A callback which returns only a single argument, used for {@link RealtimeChannel} subscriptions. + * The time until messages are retrieved, specified as milliseconds since the Unix epoch. * - * @param message - The message which triggered the callback. + * @defaultValue The current time. */ - type messageCallback = (message: T) => void; + end?: number; /** - * The callback used for the events emitted by {@link RealtimeChannel}. + * The order for which messages are returned in. Valid values are `'backwards'` which orders messages from most recent to oldest, or `'forwards'` which orders messages from oldest to most recent. The default is `'backwards'`. * - * @param changeStateChange - The state change that occurred. + * @defaultValue `'backwards'` */ - type channelEventCallback = (changeStateChange: ChannelStateChange) => void; + direction?: 'forwards' | 'backwards'; /** - * The callback used for the events emitted by {@link Connection}. + * An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. * - * @param connectionStateChange - The state change that occurred. + * @defaultValue 100 */ - type connectionEventCallback = (connectionStateChange: ConnectionStateChange) => void; + limit?: number; +} + +/** + * The `RestPresenceParams` interface describes the parameters accepted by {@link Presence.get}. + */ +export interface RestPresenceParams { /** - * The callback used by {@link recoverConnectionCallback}. + * An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. * - * @param shouldRecover - Whether the connection should be recovered. + * @defaultValue 100 */ - type recoverConnectionCompletionCallback = (shouldRecover: boolean) => void; + limit?: number; /** - * Used in {@link ClientOptions} to configure connection recovery behaviour. - * - * @param lastConnectionDetails - Details of the connection used by the connection recovery process. - * @param callback - A callback which is called when a connection recovery attempt is complete. + * Filters the list of returned presence members by a specific client using its ID. */ - type recoverConnectionCallback = ( - lastConnectionDetails: { - /** - * The recovery key can be used by another client to recover this connection’s state in the `recover` client options property. See [connection state recover options](https://ably.com/documentation/realtime/connection/#connection-state-recover-options) for more information. - */ - recoveryKey: string; - /** - * The time at which the previous client was abruptly disconnected before the page was unloaded. This is represented as milliseconds since Unix epoch. - */ - disconnectedAt: number; - /** - * A clone of the `location` object of the previous page’s document object before the page was unloaded. A common use case for this attribute is to ensure that the previous page URL is the same as the current URL before allowing the connection to be recovered. For example, you may want the connection to be recovered only for page reloads, but not when a user navigates to a different page. - */ - location: string; - /** - * The `clientId` of the client’s `Auth` object before the page was unloaded. A common use case for this attribute is to ensure that the current logged in user’s `clientId` matches the previous connection’s `clientId` before allowing the connection to be recovered. Ably prohibits changing a `clientId` for an existing connection, so any mismatch in `clientId` during a recover will result in the connection moving to the failed state. - */ - clientId: string | null; - }, - callback: recoverConnectionCompletionCallback - ) => void; + clientId?: string; + /** + * Filters the list of returned presence members by a specific connection using its ID. + */ + connectionId?: string; +} - // Internal Classes +/** + * The `RealtimePresenceParams` interface describes the parameters accepted by {@link RealtimePresence.get}. + */ +export interface RealtimePresenceParams { + /** + * Sets whether to wait for a full presence set synchronization between Ably and the clients on the channel to complete before returning the results. Synchronization begins as soon as the channel is {@link ChannelState.ATTACHED}. When set to `true` the results will be returned as soon as the sync is complete. When set to `false` the current list of members will be returned without the sync completing. The default is `true`. + * + * @defaultValue `true` + */ + waitForSync?: boolean; + /** + * Filters the array of returned presence members by a specific client using its ID. + */ + clientId?: string; + /** + * Filters the array of returned presence members by a specific connection using its ID. + */ + connectionId?: string; +} - // To allow a uniform (callback) interface between on and once even in the - // promisified version of the lib, but still allow once to be used in a way - // that returns a Promise if desired, EventEmitter uses method overloading to - // present both methods +/** + * The `RealtimeHistoryParams` interface describes the parameters accepted by the following methods: + * + * - {@link RealtimePresence.history} + * - {@link RealtimeChannel.history} + */ +export interface RealtimeHistoryParams { /** - * A generic interface for event registration and delivery used in a number of the types in the Realtime client library. For example, the {@link Connection} object emits events for connection state using the `EventEmitter` pattern. + * The time from which messages are retrieved, specified as milliseconds since the Unix epoch. */ - class EventEmitter { - /** - * Registers the provided listener for the specified event. If `on()` is called more than once with the same listener and event, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `on()`, and an event is emitted once, the listener would be invoked twice. - * - * @param event - The named event to listen for. - * @param callback - The event listener. - */ - on(event: EventType, callback: CallbackType): void; - /** - * Registers the provided listener for the specified events. If `on()` is called more than once with the same listener and event, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `on()`, and an event is emitted once, the listener would be invoked twice. - * - * @param events - The named events to listen for. - * @param callback - The event listener. - */ - on(events: EventType[], callback: CallbackType): void; - /** - * Registers the provided listener all events. If `on()` is called more than once with the same listener and event, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `on()`, and an event is emitted once, the listener would be invoked twice. - * - * @param callback - The event listener. - */ - on(callback: CallbackType): void; - /** - * Registers the provided listener for the first occurrence of a single named event specified as the `Event` argument. If `once` is called more than once with the same listener, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `once`, and an event is emitted once, the listener would be invoked twice. However, all subsequent events emitted would not invoke the listener as `once` ensures that each registration is only invoked once. - * - * @param event - The named event to listen for. - * @param callback - The event listener. - */ - once(event: EventType, callback: CallbackType): void; - /** - * Registers the provided listener for the first event that is emitted. If `once()` is called more than once with the same listener, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `once()`, and an event is emitted once, the listener would be invoked twice. However, all subsequent events emitted would not invoke the listener as `once()` ensures that each registration is only invoked once. - * - * @param callback - The event listener. - */ - once(callback: CallbackType): void; - /** - * Returns a promise which resolves upon the first occurrence of a single named event specified as the `Event` argument. - * - * @param event - The named event to listen for. - * @returns A promise which resolves upon the first occurrence of the named event. - */ - once(event: EventType): Promise; - /** - * Returns a promise which resolves upon the first occurrence of an event. - * - * @returns A promise which resolves upon the first occurrence of an event. - */ - once(): Promise; - /** - * Removes all registrations that match both the specified listener and the specified event. - * - * @param event - The named event. - * @param callback - The event listener. - */ - off(event: EventType, callback: CallbackType): void; - /** - * Deregisters the specified listener. Removes all registrations matching the given listener, regardless of whether they are associated with an event or not. - * - * @param callback - The event listener. - */ - off(callback: CallbackType): void; - /** - * Deregisters all registrations, for all events and listeners. - */ - off(): void; - /** - * Returns the listeners for a specified `EventType`. - * - * @param eventName - The event name to retrieve the listeners for. - */ - listeners(eventName?: EventType): CallbackType[] | null; - } + start?: number; + /** + * The time until messages are retrieved, specified as milliseconds since the Unix epoch. + * + * @defaultValue The current time. + */ + end?: number; + /** + * The order for which messages are returned in. Valid values are `'backwards'` which orders messages from most recent to oldest, or `'forwards'` which orders messages from oldest to most recent. The default is `'backwards'`. + * + * @defaultValue `'backwards'` + */ + direction?: 'forwards' | 'backwards'; + /** + * An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. + * + * @defaultValue 100 + */ + limit?: number; + /** + * When `true`, ensures message history is up until the point of the channel being attached. See [continuous history](https://ably.com/docs/realtime/history#continuous-history) for more info. Requires the `direction` to be `backwards`. If the channel is not attached, or if `direction` is set to `forwards`, this option results in an error. + */ + untilAttach?: boolean; +} - // Classes +/** + * Contains state change information emitted by {@link Channel} and {@link RealtimeChannel} objects. + */ +export interface ChannelStateChange { /** - * A client that offers a simple stateless API to interact directly with Ably's REST API. + * The new current {@link ChannelState}. */ - abstract class AbstractRest { - /** - * An {@link Types.Auth} object. - */ - auth: Types.Auth; - /** - * A {@link Types.Channels} object. - */ - channels: Types.Channels; - /** - * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. - * - * @param method - The request method to use, such as `GET`, `POST`. - * @param path - The request path. - * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. - * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. - * @param body - The JSON body of the request. - * @param headers - Additional HTTP headers to include in the request. - * @returns A promise which, upon success, will be fulfilled with an {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - request( - method: string, - path: string, - version: number, - params?: any, - body?: any[] | any, - headers?: any - ): Promise>; - /** - * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). - * - * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link Types.StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link Types.StatsParams} interface. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - stats(params?: StatsParams | any): Promise>; - /** - * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link Types.TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link Types.ClientOptions.queryTime} property instead of this method. - * - * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - time(): Promise; + current: ChannelState; + /** + * The previous state. For the {@link ChannelEvent.UPDATE} event, this is equal to the `current` {@link ChannelState}. + */ + previous: ChannelState; + /** + * An {@link ErrorInfo} object containing any information relating to the transition. + */ + reason?: ErrorInfo; + /** + * Indicates whether message continuity on this channel is preserved, see [Nonfatal channel errors](https://ably.com/docs/realtime/channels#nonfatal-errors) for more info. + */ + resumed: boolean; + /** + * Indicates whether the client can expect a backlog of messages from a rewind or resume. + */ + hasBacklog?: boolean; +} - /** - * Publishes a {@link Types.BatchPublishSpec} object to one or more channels, up to a maximum of 100 channels. - * - * @param spec - A {@link Types.BatchPublishSpec} object. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - batchPublish(spec: BatchPublishSpec): Promise>; - /** - * Publishes one or more {@link Types.BatchPublishSpec} objects to one or more channels, up to a maximum of 100 channels. - * - * @param specs - An array of {@link Types.BatchPublishSpec} objects. - * @returns A promise which, upon success, will be fulfilled with an array of {@link Types.BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link Types.BatchPublishSpec}. This array is in the same order as the provided {@link Types.BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - batchPublish( - specs: BatchPublishSpec[] - ): Promise[]>; - /** - * Retrieves the presence state for one or more channels, up to a maximum of 100 channels. Presence state includes the `clientId` of members and their current {@link Types.PresenceAction}. - * - * @param channels - An array of one or more channel names, up to a maximum of 100 channels. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - batchPresence(channels: string[]): Promise[]>; - /** - * A {@link Types.Push} object. - */ - push: Types.Push; - } +/** + * Contains {@link ConnectionState} change information emitted by the {@link Connection} object. + */ +export interface ConnectionStateChange { + /** + * The new {@link ConnectionState}. + */ + current: ConnectionState; + /** + * The previous {@link ConnectionState}. For the {@link ConnectionEvent.UPDATE} event, this is equal to the current {@link ConnectionState}. + */ + previous: ConnectionState; + /** + * An {@link ErrorInfo} object containing any information relating to the transition. + */ + reason?: ErrorInfo; + /** + * Duration in milliseconds, after which the client retries a connection where applicable. + */ + retryIn?: number; +} +/** + * The `DevicePlatform` namespace describes the possible values of the {@link DevicePlatform:type} type. + */ +declare namespace DevicePlatform { /** - * A client that extends the functionality of {@link AbstractRest} and provides additional realtime-specific features. + * The device platform is Android. */ - abstract class AbstractRealtime { - /** - * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. - */ - clientId: string; - /** - * Calls {@link Types.Connection.close | `connection.close()`} and causes the connection to close, entering the closing state. Once closed, the library will not attempt to re-establish the connection without an explicit call to {@link Types.Connection.connect | `connect()`}. - */ - close(): void; - /** - * Calls {@link Types.Connection.connect | `connection.connect()`} and causes the connection to open, entering the connecting state. Explicitly calling `connect()` is unnecessary unless the {@link Types.ClientOptions.autoConnect} property is disabled. - */ - connect(): void; + type ANDROID = 'android'; + /** + * The device platform is iOS. + */ + type IOS = 'ios'; + /** + * The device platform is a web browser. + */ + type BROWSER = 'browser'; +} - /** - * An {@link Types.Auth} object. - */ - auth: Types.Auth; - /** - * A {@link Types.Channels} object. - */ - channels: Types.Channels; - /** - * A {@link Types.Connection} object. - */ - connection: Types.Connection; - /** - * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. - * - * @param method - The request method to use, such as `GET`, `POST`. - * @param path - The request path. - * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. - * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. - * @param body - The JSON body of the request. - * @param headers - Additional HTTP headers to include in the request. - * @returns A promise which, upon success, will be fulfilled with the {@link Types.HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - request( - method: string, - path: string, - version: number, - params?: any, - body?: any[] | any, - headers?: any - ): Promise>; - /** - * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link Types.PaginatedResult} object, containing an array of {@link Types.Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). - * - * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link Types.StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link Types.StatsParams} interface. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link Types.Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - stats(params?: StatsParams | any): Promise>; - /** - * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link Types.TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link Types.ClientOptions.queryTime} property instead of this method. - * - * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - time(): Promise; - /** - * Publishes a {@link Types.BatchPublishSpec} object to one or more channels, up to a maximum of 100 channels. - * - * @param spec - A {@link Types.BatchPublishSpec} object. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - batchPublish(spec: BatchPublishSpec): Promise>; - /** - * Publishes one or more {@link Types.BatchPublishSpec} objects to one or more channels, up to a maximum of 100 channels. - * - * @param specs - An array of {@link Types.BatchPublishSpec} objects. - * @returns A promise which, upon success, will be fulfilled with an array of {@link Types.BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link Types.BatchPublishSpec}. This array is in the same order as the provided {@link Types.BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - batchPublish( - specs: BatchPublishSpec[] - ): Promise[]>; - /** - * Retrieves the presence state for one or more channels, up to a maximum of 100 channels. Presence state includes the `clientId` of members and their current {@link Types.PresenceAction}. - * - * @param channels - An array of one or more channel names, up to a maximum of 100 channels. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - batchPresence(channels: string[]): Promise[]>; - /** - * A {@link Types.Push} object. - */ - push: Types.Push; - } +/** + * Describes the device receiving push notifications. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type DevicePlatform = DevicePlatform.ANDROID | DevicePlatform.IOS | DevicePlatform.BROWSER; +/** + * The `DeviceFormFactor` namespace describes the possible values of the {@link DeviceFormFactor:type} type. + */ +declare namespace DeviceFormFactor { /** - * Creates Ably {@link TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients. + * The device is a phone. */ - class Auth { - /** - * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error is raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [identified clients](https://ably.com/docs/core-features/authentication#identified-clients). - */ - clientId: string; + type PHONE = 'phone'; + /** + * The device is tablet. + */ + type TABLET = 'tablet'; + /** + * The device is a desktop. + */ + type DESKTOP = 'desktop'; + /** + * The device is a TV. + */ + type TV = 'tv'; + /** + * The device is a watch. + */ + type WATCH = 'watch'; + /** + * The device is a car. + */ + type CAR = 'car'; + /** + * The device is embedded. + */ + type EMBEDDED = 'embedded'; + /** + * The device is other. + */ + type OTHER = 'other'; +} - /** - * Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token has been obtained. Also stores any {@link TokenParams} and {@link AuthOptions} passed in as the new defaults, to be used for all subsequent implicit or explicit token requests. Any {@link TokenParams} and {@link AuthOptions} objects passed in entirely replace, as opposed to being merged with, the current client library saved values. - * - * @param tokenParams - A {@link TokenParams} object. - * @param authOptions - An {@link AuthOptions} object. - * @returns A promise which, upon success, will be fulfilled with a {@link TokenDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - authorize(tokenParams?: TokenParams, authOptions?: AuthOptions): Promise; - /** - * Creates and signs an Ably {@link TokenRequest} based on the specified (or if none specified, the client library stored) {@link TokenParams} and {@link AuthOptions}. Note this can only be used when the API `key` value is available locally. Otherwise, the Ably {@link TokenRequest} must be obtained from the key owner. Use this to generate an Ably {@link TokenRequest} in order to implement an Ably Token request callback for use by other clients. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param tokenParams - A {@link TokenParams} object. - * @param authOptions - An {@link AuthOptions} object. - * @returns A promise which, upon success, will be fulfilled with a {@link TokenRequest} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - createTokenRequest(tokenParams?: TokenParams, authOptions?: AuthOptions): Promise; - /** - * Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams} and {@link AuthOptions}. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). - * - * @param TokenParams - A {@link TokenParams} object. - * @param authOptions - An {@link AuthOptions} object. - * @returns A promise which, upon success, will be fulfilled with a {@link TokenDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - requestToken(TokenParams?: TokenParams, authOptions?: AuthOptions): Promise; - /** - * Revokes the tokens specified by the provided array of {@link TokenRevocationTargetSpecifier}s. Only tokens issued by an API key that had revocable tokens enabled before the token was issued can be revoked. See the [token revocation docs](https://ably.com/docs/core-features/authentication#token-revocation) for more information. - * - * @param specifiers - An array of {@link TokenRevocationTargetSpecifier} objects. - * @param options - A set of options which are used to modify the revocation request. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.BatchResult} containing information about the result of the token revocation request for each provided [`TokenRevocationTargetSpecifier`]{@link TokenRevocationTargetSpecifier}. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - revokeTokens( - specifiers: TokenRevocationTargetSpecifier[], - options?: TokenRevocationOptions - ): Promise>; - } +/** + * Describes the type of device receiving a push notification. + */ +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type DeviceFormFactor = + | DeviceFormFactor.PHONE + | DeviceFormFactor.TABLET + | DeviceFormFactor.DESKTOP + | DeviceFormFactor.TV + | DeviceFormFactor.WATCH + | DeviceFormFactor.CAR + | DeviceFormFactor.EMBEDDED + | DeviceFormFactor.OTHER; +/** + * Contains the properties of a device registered for push notifications. + */ +export interface DeviceDetails { /** - * Enables the retrieval of the current and historic presence set for a channel. + * A unique ID generated by the device. */ - class Presence { - /** - * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns a {@link Types.PaginatedResult} object, containing an array of {@link PresenceMessage} objects. - * - * @param params - A set of parameters which are used to specify which presence members should be retrieved. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - get(params?: RestPresenceParams): Promise>; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which messages should be retrieved. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - history(params?: RestHistoryParams): Promise>; - } - + id: string; /** - * Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. + * The client ID the device is connected to Ably with. */ - class RealtimePresence { - /** - * Indicates whether the presence set synchronization between Ably and the clients on the channel has been completed. Set to `true` when the sync is complete. - */ - syncComplete: boolean; - /** - * Deregisters a specific listener that is registered to receive {@link PresenceMessage} on the channel for a given {@link PresenceAction}. - * - * @param presence - A specific {@link PresenceAction} to deregister the listener for. - * @param listener - An event listener function. - */ - unsubscribe(presence: PresenceAction, listener: messageCallback): void; - /** - * Deregisters a specific listener that is registered to receive {@link PresenceMessage} on the channel for a given array of {@link PresenceAction} objects. - * - * @param presence - An array of {@link PresenceAction} objects to deregister the listener for. - * @param listener - An event listener function. - */ - unsubscribe(presence: Array, listener: messageCallback): void; - /** - * Deregisters any listener that is registered to receive {@link PresenceMessage} on the channel for a specific {@link PresenceAction} - * - * @param presence - A specific {@link PresenceAction} to deregister the listeners for. - */ - unsubscribe(presence: PresenceAction): void; - /** - * Deregisters any listener that is registered to receive {@link PresenceMessage} on the channel for an array of {@link PresenceAction} objects - * - * @param presence - An array of {@link PresenceAction} objects to deregister the listeners for. - */ - unsubscribe(presence: Array): void; - /** - * Deregisters a specific listener that is registered to receive {@link PresenceMessage} on the channel. - * - * @param listener - An event listener function. - */ - unsubscribe(listener: messageCallback): void; - /** - * Deregisters all listeners currently receiving {@link PresenceMessage} for the channel. - */ - unsubscribe(): void; - - /** - * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns an array of {@link PresenceMessage} objects. - * - * @param params - A set of parameters which are used to specify which presence members should be retrieved. - * @returns A promise which, upon success, will be fulfilled with an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - get(params?: RealtimePresenceParams): Promise; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which presence messages should be retrieved. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - history(params?: RealtimeHistoryParams): Promise>; - /** - * Registers a listener that is called each time a {@link PresenceMessage} matching a given {@link PresenceAction}, or an action within an array of {@link PresenceAction | `PresenceAction`s}, is received on the channel, such as a new member entering the presence set. - * - * @param action - A {@link PresenceAction} or an array of {@link PresenceAction | `PresenceAction`s} to register the listener for. - * @param listener - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - subscribe( - action: PresenceAction | Array, - listener?: messageCallback - ): Promise; - /** - * Registers a listener that is called each time a {@link PresenceMessage} is received on the channel, such as a new member entering the presence set. - * - * @param listener - An event listener function. - * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - subscribe(listener?: messageCallback): Promise; - /** - * Enters the presence set for the channel, optionally passing a `data` payload. A `clientId` is required to be present on a channel. - * - * @param data - The payload associated with the presence member. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - enter(data?: any): Promise; - /** - * Updates the `data` payload for a presence member. If called before entering the presence set, this is treated as an {@link PresenceAction.ENTER} event. - * - * @param data - The payload to update for the presence member. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - update(data?: any): Promise; - /** - * Leaves the presence set for the channel. A client must have previously entered the presence set before they can leave it. - * - * @param data - The payload associated with the presence member. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - leave(data?: any): Promise; - /** - * Enters the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to enter into the presence set. - * @param data - The payload associated with the presence member. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - enterClient(clientId: string, data?: any): Promise; - /** - * Updates the `data` payload for a presence member using a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to update in the presence set. - * @param data - The payload to update for the presence member. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - updateClient(clientId: string, data?: any): Promise; - /** - * Leaves the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. - * - * @param clientId - The ID of the client to leave the presence set for. - * @param data - The payload associated with the presence member. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - leaveClient(clientId: string, data?: any): Promise; - } + clientId?: string; + /** + * The {@link DevicePlatform} associated with the device. Describes the platform the device uses, such as `android` or `ios`. + */ + platform: DevicePlatform; + /** + * The {@link DeviceFormFactor} object associated with the device. Describes the type of the device, such as `phone` or `tablet`. + */ + formFactor: DeviceFormFactor; + /** + * A JSON object of key-value pairs that contains metadata for the device. + */ + metadata?: any; + /** + * A unique device secret generated by the Ably SDK. + */ + deviceSecret?: string; + /** + * The {@link DevicePushDetails} object associated with the device. Describes the details of the push registration of the device. + */ + push: DevicePushDetails; +} +/** + * Contains the subscriptions of a device, or a group of devices sharing the same `clientId`, has to a channel in order to receive push notifications. + */ +export interface PushChannelSubscription { /** - * Enables messages to be published and historic messages to be retrieved for a channel. + * The channel the push notification subscription is for. */ - class Channel { - /** - * The channel name. - */ - name: string; + channel: string; + /** + * The unique ID of the device. + */ + deviceId?: string; + /** + * The ID of the client the device, or devices are associated to. + */ + clientId?: string; +} - /** - * A {@link Presence} object. - */ - presence: Presence; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link InboundMessage} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which messages should be retrieved. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link InboundMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - history(params?: RestHistoryParams): Promise>; - /** - * Publishes an array of messages to the channel. - * - * @param messages - An array of {@link Message} objects. - * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - publish(messages: Message[], options?: PublishOptions): Promise; - /** - * Publishes a message to the channel. - * - * @param message - A {@link Message} object. - * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - publish(message: Message, options?: PublishOptions): Promise; - /** - * Publishes a single message to the channel with the given event name and payload. - * - * @param name - The name of the message. - * @param data - The payload of the message. - * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - publish(name: string, data: any, options?: PublishOptions): Promise; - /** - * Retrieves a {@link ChannelDetails} object for the channel, which includes status and occupancy metrics. - * - * @returns A promise which, upon success, will be fulfilled a {@link ChannelDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - status(): Promise; - } +/** + * Valid states which a Push device may be in. + */ +export type DevicePushState = 'ACTIVE' | 'FAILING' | 'FAILED'; +/** + * Contains the details of the push registration of a device. + */ +export interface DevicePushDetails { /** - * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresence} object of a channel. + * A JSON object of key-value pairs that contains of the push transport and address. */ - class RealtimeChannel extends EventEmitter { - /** - * The channel name. - */ - readonly name: string; - /** - * An {@link ErrorInfo} object describing the last error which occurred on the channel, if any. - */ - errorReason: ErrorInfo; - /** - * The current {@link ChannelState} of the channel. - */ - readonly state: ChannelState; - /** - * Optional [channel parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) that configure the behavior of the channel. - */ - params: ChannelParams; - /** - * An array of {@link ChannelMode} objects. - */ - modes: ChannelModes; - /** - * Deregisters the given listener for the specified event name. This removes an earlier event-specific subscription. - * - * @param event - The event name. - * @param listener - An event listener function. - */ - unsubscribe(event: string, listener: messageCallback): void; - /** - * Deregisters the given listener from all event names in the array. - * - * @param events - An array of event names. - * @param listener - An event listener function. - */ - unsubscribe(events: Array, listener: messageCallback): void; - /** - * Deregisters all listeners for the given event name. - * - * @param event - The event name. - */ - unsubscribe(event: string): void; - /** - * Deregisters all listeners for all event names in the array. - * - * @param events - An array of event names. - */ - unsubscribe(events: Array): void; - /** - * Deregisters all listeners to messages on this channel that match the supplied filter. - * - * @param filter - A {@link MessageFilter}. - * @param listener - An event listener function. - */ - unsubscribe(filter: MessageFilter, listener?: messageCallback): void; - /** - * Deregisters the given listener (for any/all event names). This removes an earlier subscription. - * - * @param listener - An event listener function. - */ - unsubscribe(listener: messageCallback): void; - /** - * Deregisters all listeners to messages on this channel. This removes all earlier subscriptions. - */ - unsubscribe(): void; + recipient: any; + /** + * The current state of the push registration. + */ + state?: DevicePushState; + /** + * An {@link ErrorInfo} object describing the most recent error when the `state` is `Failing` or `Failed`. + */ + error?: ErrorInfo; +} +/** + * The `DeviceRegistrationParams` interface describes the parameters accepted by the following methods: + * + * - {@link PushDeviceRegistrations.list} + * - {@link PushDeviceRegistrations.removeWhere} + */ +export interface DeviceRegistrationParams { + /** + * Filter to restrict to devices associated with a client ID. + */ + clientId?: string; + /** + * Filter to restrict by the unique ID of the device. + */ + deviceId?: string; + /** + * A limit on the number of devices returned, up to 1,000. + */ + limit?: number; + /** + * Filter by the state of the device. + */ + state?: DevicePushState; +} + +/** + * The `PushChannelSubscriptionParams` interface describes the parameters accepted by the following methods: + * + * - {@link PushChannelSubscriptions.list} + * - {@link PushChannelSubscriptions.removeWhere} + */ +export interface PushChannelSubscriptionParams { + /** + * Filter to restrict to subscriptions associated with the given channel. + */ + channel?: string; + /** + * Filter to restrict to devices associated with the given client identifier. Cannot be used with a deviceId param. + */ + clientId?: string; + /** + * Filter to restrict to devices associated with that device identifier. Cannot be used with a clientId param. + */ + deviceId?: string; + /** + * A limit on the number of devices returned, up to 1,000. + */ + limit?: number; +} + +/** + * The `PushChannelsParams` interface describes the parameters accepted by {@link PushChannelSubscriptions.listChannels}. + */ +export interface PushChannelsParams { + /** + * A limit on the number of channels returned, up to 1,000. + */ + limit?: number; +} + +/** + * The `StatsParams` interface describes the parameters accepted by the following methods: + * + * - {@link AbstractRest.stats} + * - {@link AbstractRealtime.stats} + */ +export interface StatsParams { + /** + * The time from which stats are retrieved, specified as milliseconds since the Unix epoch. + * + * @defaultValue The Unix epoch. + */ + start?: number; + /** + * The time until stats are retrieved, specified as milliseconds since the Unix epoch. + * + * @defaultValue The current time. + */ + end?: number; + /** + * The order for which stats are returned in. Valid values are `'backwards'` which orders stats from most recent to oldest, or `'forwards'` which orders stats from oldest to most recent. The default is `'backwards'`. + * + * @defaultValue `'backwards'` + */ + direction?: 'backwards' | 'forwards'; + /** + * An upper limit on the number of stats returned. The default is 100, and the maximum is 1000. + * + * @defaultValue 100 + */ + limit?: number; + /** + * Based on the unit selected, the given `start` or `end` times are rounded down to the start of the relevant interval depending on the unit granularity of the query. + * + * @defaultValue `StatsIntervalGranularity.MINUTE` + */ + unit?: StatsIntervalGranularity; +} + +/** + * Contains information about the results of a batch operation. + */ +export interface BatchResult { + /** + * The number of successful operations in the request. + */ + successCount: number; + /** + * The number of unsuccessful operations in the request. + */ + failureCount: number; + /** + * An array of results for the batch operation. + */ + results: T[]; +} + +/** + * Describes the messages that should be published by a batch publish operation, and the channels to which they should be published. + */ +export interface BatchPublishSpec { + /** + * The names of the channels to publish the `messages` to. + */ + channels: string[]; + /** + * An array of {@link Message} objects. + */ + messages: Message[]; +} + +/** + * Contains information about the result of successful publishes to a channel requested by a single {@link BatchPublishSpec}. + */ +export interface BatchPublishSuccessResult { + /** + * The name of the channel the message(s) was published to. + */ + channel: string; + /** + * A unique ID prefixed to the {@link Message.id} of each published message. + */ + messageId: string; +} + +/** + * Contains information about the result of unsuccessful publishes to a channel requested by a single {@link BatchPublishSpec}. + */ +export interface BatchPublishFailureResult { + /** + * The name of the channel the message(s) failed to be published to. + */ + channel: string; + /** + * Describes the reason for which the message(s) failed to publish to the channel as an {@link ErrorInfo} object. + */ + error: ErrorInfo; +} + +/** + * Contains information about the result of a successful batch presence request for a single channel. + */ +export interface BatchPresenceSuccessResult { + /** + * The channel name the presence state was retrieved for. + */ + channel: string; + /** + * An array of {@link PresenceMessage}s describing members present on the channel. + */ + presence: PresenceMessage[]; +} + +/** + * Contains information about the result of an unsuccessful batch presence request for a single channel. + */ +export interface BatchPresenceFailureResult { + /** + * The channel name the presence state failed to be retrieved for. + */ + channel: string; + /** + * Describes the reason for which presence state could not be retrieved for the channel as an {@link ErrorInfo} object. + */ + error: ErrorInfo; +} + +/** + * The `TokenRevocationOptions` interface describes the additional options accepted by {@link Auth.revokeTokens}. + */ +export interface TokenRevocationOptions { + /** + * A Unix timestamp in milliseconds where only tokens issued before this time are revoked. The default is the current time. Requests with an `issuedBefore` in the future, or more than an hour in the past, will be rejected. + */ + issuedBefore?: number; + /** + * If true, permits a token renewal cycle to take place without needing established connections to be dropped, by postponing enforcement to 30 seconds in the future, and sending any existing connections a hint to obtain (and upgrade the connection to use) a new token. The default is `false`, meaning that the effect is near-immediate. + */ + allowReauthMargin?: boolean; +} + +/** + * Describes which tokens should be affected by a token revocation request. + */ +export interface TokenRevocationTargetSpecifier { + /** + * The type of token revocation target specifier. Valid values include `clientId`, `revocationKey` and `channel`. + */ + type: string; + /** + * The value of the token revocation target specifier. + */ + value: string; +} + +/** + * Contains information about the result of a successful token revocation request for a single target specifier. + */ +export interface TokenRevocationSuccessResult { + /** + * The target specifier. + */ + target: string; + /** + * The time at which the token revocation will take effect, as a Unix timestamp in milliseconds. + */ + appliesAt: number; + /** + * A Unix timestamp in milliseconds. Only tokens issued earlier than this time will be revoked. + */ + issuedBefore: number; +} + +/** + * Contains information about the result of an unsuccessful token revocation request for a single target specifier. + */ +export interface TokenRevocationFailureResult { + /** + * The target specifier. + */ + target: string; + /** + * Describes the reason for which token revocation failed for the given `target` as an {@link ErrorInfo} object. + */ + error: ErrorInfo; +} + +// Common Listeners +/** + * A callback which returns only a single argument, used for {@link RealtimeChannel} subscriptions. + * + * @param message - The message which triggered the callback. + */ +export type messageCallback = (message: T) => void; +/** + * The callback used for the events emitted by {@link RealtimeChannel}. + * + * @param changeStateChange - The state change that occurred. + */ +export type channelEventCallback = (changeStateChange: ChannelStateChange) => void; +/** + * The callback used for the events emitted by {@link Connection}. + * + * @param connectionStateChange - The state change that occurred. + */ +export type connectionEventCallback = (connectionStateChange: ConnectionStateChange) => void; +/** + * The callback used by {@link recoverConnectionCallback}. + * + * @param shouldRecover - Whether the connection should be recovered. + */ +export type recoverConnectionCompletionCallback = (shouldRecover: boolean) => void; +/** + * Used in {@link ClientOptions} to configure connection recovery behaviour. + * + * @param lastConnectionDetails - Details of the connection used by the connection recovery process. + * @param callback - A callback which is called when a connection recovery attempt is complete. + */ +export type recoverConnectionCallback = ( + lastConnectionDetails: { /** - * A {@link RealtimePresence} object. - */ - presence: RealtimePresence; - /** - * Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel are received by any channel listeners registered using {@link RealtimeChannel.subscribe | `subscribe()`}. Any resulting channel state change will be emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. As a convenience, `attach()` is called implicitly if {@link RealtimeChannel.subscribe | `subscribe()`} for the channel is called, or {@link RealtimePresence.enter | `enter()`} or {@link RealtimePresence.subscribe | `subscribe()`} are called on the {@link RealtimePresence} object for this channel. - * - * @returns A promise which, upon success, if the channel became attached will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be fulfilled with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. - */ - attach(): Promise; - /** - * Detach from this channel. Any resulting channel state change is emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. Once all clients globally have detached from the channel, the channel will be released in the Ably service within two minutes. - * - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - detach(): Promise; - /** - * Retrieves a {@link Types.PaginatedResult} object, containing an array of historical {@link InboundMessage} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. - * - * @param params - A set of parameters which are used to specify which presence members should be retrieved. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link InboundMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - history(params?: RealtimeHistoryParams): Promise>; - /** - * Sets the {@link ChannelOptions} for the channel. - * - * @param options - A {@link ChannelOptions} object. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - setOptions(options: ChannelOptions): Promise; - /** - * Registers a listener for messages with a given event name on this channel. The caller supplies a listener function, which is called each time one or more matching messages arrives on the channel. - * - * @param event - The event name. - * @param listener - An event listener function. - * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. - */ - subscribe(event: string, listener?: messageCallback): Promise; - /** - * Registers a listener for messages on this channel for multiple event name values. - * - * @param events - An array of event names. - * @param listener - An event listener function. - * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. - */ - subscribe(events: Array, listener?: messageCallback): Promise; - /** - * {@label WITH_MESSAGE_FILTER} - * - * Registers a listener for messages on this channel that match the supplied filter. - * - * @param filter - A {@link MessageFilter}. - * @param listener - An event listener function. - * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. - */ - subscribe(filter: MessageFilter, listener?: messageCallback): Promise; - /** - * Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel. - * - * @param callback - An event listener function. - * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. - */ - subscribe(callback: messageCallback): Promise; - /** - * Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. - * - * @param name - The event name. - * @param data - The message payload. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + * The recovery key can be used by another client to recover this connection’s state in the `recover` client options property. See [connection state recover options](https://ably.com/documentation/realtime/connection/#connection-state-recover-options) for more information. */ - publish(name: string, data: any): Promise; + recoveryKey: string; /** - * Publishes an array of messages to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. - * - * @param messages - An array of {@link Message} objects. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + * The time at which the previous client was abruptly disconnected before the page was unloaded. This is represented as milliseconds since Unix epoch. */ - publish(messages: Message[]): Promise; + disconnectedAt: number; /** - * Publish a message to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. - * - * @param message - A {@link Message} object. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + * A clone of the `location` object of the previous page’s document object before the page was unloaded. A common use case for this attribute is to ensure that the previous page URL is the same as the current URL before allowing the connection to be recovered. For example, you may want the connection to be recovered only for page reloads, but not when a user navigates to a different page. */ - publish(message: Message): Promise; + location: string; /** - * Returns a promise which is resolved when the channel reaches the specified {@link ChannelState}. If the channel is already in the specified state, the promise is resolved immediately. - * - * @param targetState - The state which should be reached. + * The `clientId` of the client’s `Auth` object before the page was unloaded. A common use case for this attribute is to ensure that the current logged in user’s `clientId` matches the previous connection’s `clientId` before allowing the connection to be recovered. Ably prohibits changing a `clientId` for an existing connection, so any mismatch in `clientId` during a recover will result in the connection moving to the failed state. */ - whenState(targetState: ChannelState): Promise; - } + clientId: string | null; + }, + callback: recoverConnectionCompletionCallback +) => void; + +// Internal Classes + +// To allow a uniform (callback) interface between on and once even in the +// promisified version of the lib, but still allow once to be used in a way +// that returns a Promise if desired, EventEmitter uses method overloading to +// present both methods +/** + * A generic interface for event registration and delivery used in a number of the types in the Realtime client library. For example, the {@link Connection} object emits events for connection state using the `EventEmitter` pattern. + */ +export declare class EventEmitter { + /** + * Registers the provided listener for the specified event. If `on()` is called more than once with the same listener and event, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `on()`, and an event is emitted once, the listener would be invoked twice. + * + * @param event - The named event to listen for. + * @param callback - The event listener. + */ + on(event: EventType, callback: CallbackType): void; + /** + * Registers the provided listener for the specified events. If `on()` is called more than once with the same listener and event, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `on()`, and an event is emitted once, the listener would be invoked twice. + * + * @param events - The named events to listen for. + * @param callback - The event listener. + */ + on(events: EventType[], callback: CallbackType): void; + /** + * Registers the provided listener all events. If `on()` is called more than once with the same listener and event, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `on()`, and an event is emitted once, the listener would be invoked twice. + * + * @param callback - The event listener. + */ + on(callback: CallbackType): void; + /** + * Registers the provided listener for the first occurrence of a single named event specified as the `Event` argument. If `once` is called more than once with the same listener, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `once`, and an event is emitted once, the listener would be invoked twice. However, all subsequent events emitted would not invoke the listener as `once` ensures that each registration is only invoked once. + * + * @param event - The named event to listen for. + * @param callback - The event listener. + */ + once(event: EventType, callback: CallbackType): void; + /** + * Registers the provided listener for the first event that is emitted. If `once()` is called more than once with the same listener, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `once()`, and an event is emitted once, the listener would be invoked twice. However, all subsequent events emitted would not invoke the listener as `once()` ensures that each registration is only invoked once. + * + * @param callback - The event listener. + */ + once(callback: CallbackType): void; + /** + * Returns a promise which resolves upon the first occurrence of a single named event specified as the `Event` argument. + * + * @param event - The named event to listen for. + * @returns A promise which resolves upon the first occurrence of the named event. + */ + once(event: EventType): Promise; + /** + * Returns a promise which resolves upon the first occurrence of an event. + * + * @returns A promise which resolves upon the first occurrence of an event. + */ + once(): Promise; + /** + * Removes all registrations that match both the specified listener and the specified event. + * + * @param event - The named event. + * @param callback - The event listener. + */ + off(event: EventType, callback: CallbackType): void; + /** + * Deregisters the specified listener. Removes all registrations matching the given listener, regardless of whether they are associated with an event or not. + * + * @param callback - The event listener. + */ + off(callback: CallbackType): void; + /** + * Deregisters all registrations, for all events and listeners. + */ + off(): void; + /** + * Returns the listeners for a specified `EventType`. + * + * @param eventName - The event name to retrieve the listeners for. + */ + listeners(eventName?: EventType): CallbackType[] | null; +} + +// Classes +/** + * A client that offers a simple stateless API to interact directly with Ably's REST API. + */ +export declare abstract class AbstractRest { + /** + * An {@link Auth} object. + */ + auth: Auth; + /** + * A {@link Channels} object. + */ + channels: Channels; + /** + * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. + * + * @param method - The request method to use, such as `GET`, `POST`. + * @param path - The request path. + * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. + * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. + * @param body - The JSON body of the request. + * @param headers - Additional HTTP headers to include in the request. + * @returns A promise which, upon success, will be fulfilled with an {@link HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + request( + method: string, + path: string, + version: number, + params?: any, + body?: any[] | any, + headers?: any + ): Promise>; + /** + * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link PaginatedResult} object, containing an array of {@link Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). + * + * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link StatsParams} interface. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + stats(params?: StatsParams | any): Promise>; + /** + * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link ClientOptions.queryTime} property instead of this method. + * + * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + time(): Promise; + + /** + * Publishes a {@link BatchPublishSpec} object to one or more channels, up to a maximum of 100 channels. + * + * @param spec - A {@link BatchPublishSpec} object. + * @returns A promise which, upon success, will be fulfilled with a {@link BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + batchPublish(spec: BatchPublishSpec): Promise>; + /** + * Publishes one or more {@link BatchPublishSpec} objects to one or more channels, up to a maximum of 100 channels. + * + * @param specs - An array of {@link BatchPublishSpec} objects. + * @returns A promise which, upon success, will be fulfilled with an array of {@link BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link BatchPublishSpec}. This array is in the same order as the provided {@link BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + batchPublish( + specs: BatchPublishSpec[] + ): Promise[]>; + /** + * Retrieves the presence state for one or more channels, up to a maximum of 100 channels. Presence state includes the `clientId` of members and their current {@link PresenceAction}. + * + * @param channels - An array of one or more channel names, up to a maximum of 100 channels. + * @returns A promise which, upon success, will be fulfilled with a {@link BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + batchPresence(channels: string[]): Promise[]>; + /** + * A {@link Push} object. + */ + push: Push; +} + +/** + * A client that extends the functionality of {@link AbstractRest} and provides additional realtime-specific features. + */ +export declare abstract class AbstractRealtime { + /** + * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. + */ + clientId: string; + /** + * Calls {@link Connection.close | `connection.close()`} and causes the connection to close, entering the closing state. Once closed, the library will not attempt to re-establish the connection without an explicit call to {@link Connection.connect | `connect()`}. + */ + close(): void; + /** + * Calls {@link Connection.connect | `connection.connect()`} and causes the connection to open, entering the connecting state. Explicitly calling `connect()` is unnecessary unless the {@link ClientOptions.autoConnect} property is disabled. + */ + connect(): void; + + /** + * An {@link Auth} object. + */ + auth: Auth; + /** + * A {@link Channels} object. + */ + channels: Channels; + /** + * A {@link Connection} object. + */ + connection: Connection; + /** + * Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API functionality that is either not documented or is not yet included in the public API, without having to directly handle features such as authentication, paging, fallback hosts, MsgPack and JSON support. + * + * @param method - The request method to use, such as `GET`, `POST`. + * @param path - The request path. + * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. + * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. + * @param body - The JSON body of the request. + * @param headers - Additional HTTP headers to include in the request. + * @returns A promise which, upon success, will be fulfilled with the {@link HttpPaginatedResponse} response object returned by the HTTP request. This response object will contain an empty or JSON-encodable object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + request( + method: string, + path: string, + version: number, + params?: any, + body?: any[] | any, + headers?: any + ): Promise>; + /** + * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link PaginatedResult} object, containing an array of {@link Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). + * + * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link StatsParams} interface. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + stats(params?: StatsParams | any): Promise>; + /** + * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link ClientOptions.queryTime} property instead of this method. + * + * @returns A promise which, upon success, will be fulfilled with the time as milliseconds since the Unix epoch. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + time(): Promise; + /** + * Publishes a {@link BatchPublishSpec} object to one or more channels, up to a maximum of 100 channels. + * + * @param spec - A {@link BatchPublishSpec} object. + * @returns A promise which, upon success, will be fulfilled with a {@link BatchResult} object containing information about the result of the batch publish for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + batchPublish(spec: BatchPublishSpec): Promise>; + /** + * Publishes one or more {@link BatchPublishSpec} objects to one or more channels, up to a maximum of 100 channels. + * + * @param specs - An array of {@link BatchPublishSpec} objects. + * @returns A promise which, upon success, will be fulfilled with an array of {@link BatchResult} objects containing information about the result of the batch publish for each requested channel for each provided {@link BatchPublishSpec}. This array is in the same order as the provided {@link BatchPublishSpec} array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + batchPublish( + specs: BatchPublishSpec[] + ): Promise[]>; + /** + * Retrieves the presence state for one or more channels, up to a maximum of 100 channels. Presence state includes the `clientId` of members and their current {@link PresenceAction}. + * + * @param channels - An array of one or more channel names, up to a maximum of 100 channels. + * @returns A promise which, upon success, will be fulfilled with a {@link BatchResult} object containing information about the result of the batch presence request for each requested channel. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + batchPresence(channels: string[]): Promise[]>; + /** + * A {@link Push} object. + */ + push: Push; +} + +/** + * Creates Ably {@link TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients. + */ +export declare class Auth { + /** + * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error is raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [identified clients](https://ably.com/docs/core-features/authentication#identified-clients). + */ + clientId: string; + + /** + * Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token has been obtained. Also stores any {@link TokenParams} and {@link AuthOptions} passed in as the new defaults, to be used for all subsequent implicit or explicit token requests. Any {@link TokenParams} and {@link AuthOptions} objects passed in entirely replace, as opposed to being merged with, the current client library saved values. + * + * @param tokenParams - A {@link TokenParams} object. + * @param authOptions - An {@link AuthOptions} object. + * @returns A promise which, upon success, will be fulfilled with a {@link TokenDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + authorize(tokenParams?: TokenParams, authOptions?: AuthOptions): Promise; + /** + * Creates and signs an Ably {@link TokenRequest} based on the specified (or if none specified, the client library stored) {@link TokenParams} and {@link AuthOptions}. Note this can only be used when the API `key` value is available locally. Otherwise, the Ably {@link TokenRequest} must be obtained from the key owner. Use this to generate an Ably {@link TokenRequest} in order to implement an Ably Token request callback for use by other clients. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). + * + * @param tokenParams - A {@link TokenParams} object. + * @param authOptions - An {@link AuthOptions} object. + * @returns A promise which, upon success, will be fulfilled with a {@link TokenRequest} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + createTokenRequest(tokenParams?: TokenParams, authOptions?: AuthOptions): Promise; + /** + * Calls the `requestToken` REST API endpoint to obtain an Ably Token according to the specified {@link TokenParams} and {@link AuthOptions}. Both {@link TokenParams} and {@link AuthOptions} are optional. When omitted or `null`, the default token parameters and authentication options for the client library are used, as specified in the {@link ClientOptions} when the client library was instantiated, or later updated with an explicit `authorize` request. Values passed in are used instead of, rather than being merged with, the default values. To understand why an Ably {@link TokenRequest} may be issued to clients in favor of a token, see [Token Authentication explained](https://ably.com/docs/core-features/authentication/#token-authentication). + * + * @param TokenParams - A {@link TokenParams} object. + * @param authOptions - An {@link AuthOptions} object. + * @returns A promise which, upon success, will be fulfilled with a {@link TokenDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + requestToken(TokenParams?: TokenParams, authOptions?: AuthOptions): Promise; + /** + * Revokes the tokens specified by the provided array of {@link TokenRevocationTargetSpecifier}s. Only tokens issued by an API key that had revocable tokens enabled before the token was issued can be revoked. See the [token revocation docs](https://ably.com/docs/core-features/authentication#token-revocation) for more information. + * + * @param specifiers - An array of {@link TokenRevocationTargetSpecifier} objects. + * @param options - A set of options which are used to modify the revocation request. + * @returns A promise which, upon success, will be fulfilled with a {@link BatchResult} containing information about the result of the token revocation request for each provided [`TokenRevocationTargetSpecifier`]{@link TokenRevocationTargetSpecifier}. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + revokeTokens( + specifiers: TokenRevocationTargetSpecifier[], + options?: TokenRevocationOptions + ): Promise>; +} + +/** + * Enables the retrieval of the current and historic presence set for a channel. + */ +export declare class Presence { + /** + * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns a {@link PaginatedResult} object, containing an array of {@link PresenceMessage} objects. + * + * @param params - A set of parameters which are used to specify which presence members should be retrieved. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + get(params?: RestPresenceParams): Promise>; + /** + * Retrieves a {@link PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. + * + * @param params - A set of parameters which are used to specify which messages should be retrieved. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + history(params?: RestHistoryParams): Promise>; +} + +/** + * Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. + */ +export declare class RealtimePresence { + /** + * Indicates whether the presence set synchronization between Ably and the clients on the channel has been completed. Set to `true` when the sync is complete. + */ + syncComplete: boolean; + /** + * Deregisters a specific listener that is registered to receive {@link PresenceMessage} on the channel for a given {@link PresenceAction}. + * + * @param presence - A specific {@link PresenceAction} to deregister the listener for. + * @param listener - An event listener function. + */ + unsubscribe(presence: PresenceAction, listener: messageCallback): void; + /** + * Deregisters a specific listener that is registered to receive {@link PresenceMessage} on the channel for a given array of {@link PresenceAction} objects. + * + * @param presence - An array of {@link PresenceAction} objects to deregister the listener for. + * @param listener - An event listener function. + */ + unsubscribe(presence: Array, listener: messageCallback): void; + /** + * Deregisters any listener that is registered to receive {@link PresenceMessage} on the channel for a specific {@link PresenceAction} + * + * @param presence - A specific {@link PresenceAction} to deregister the listeners for. + */ + unsubscribe(presence: PresenceAction): void; + /** + * Deregisters any listener that is registered to receive {@link PresenceMessage} on the channel for an array of {@link PresenceAction} objects + * + * @param presence - An array of {@link PresenceAction} objects to deregister the listeners for. + */ + unsubscribe(presence: Array): void; + /** + * Deregisters a specific listener that is registered to receive {@link PresenceMessage} on the channel. + * + * @param listener - An event listener function. + */ + unsubscribe(listener: messageCallback): void; + /** + * Deregisters all listeners currently receiving {@link PresenceMessage} for the channel. + */ + unsubscribe(): void; + + /** + * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns an array of {@link PresenceMessage} objects. + * + * @param params - A set of parameters which are used to specify which presence members should be retrieved. + * @returns A promise which, upon success, will be fulfilled with an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + get(params?: RealtimePresenceParams): Promise; + /** + * Retrieves a {@link PaginatedResult} object, containing an array of historical {@link PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past. + * + * @param params - A set of parameters which are used to specify which presence messages should be retrieved. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link PresenceMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + history(params?: RealtimeHistoryParams): Promise>; + /** + * Registers a listener that is called each time a {@link PresenceMessage} matching a given {@link PresenceAction}, or an action within an array of {@link PresenceAction | `PresenceAction`s}, is received on the channel, such as a new member entering the presence set. + * + * @param action - A {@link PresenceAction} or an array of {@link PresenceAction | `PresenceAction`s} to register the listener for. + * @param listener - An event listener function. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + subscribe(action: PresenceAction | Array, listener?: messageCallback): Promise; + /** + * Registers a listener that is called each time a {@link PresenceMessage} is received on the channel, such as a new member entering the presence set. + * + * @param listener - An event listener function. + * @returns A promise which resolves upon success of the channel {@link RealtimeChannel.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + subscribe(listener?: messageCallback): Promise; + /** + * Enters the presence set for the channel, optionally passing a `data` payload. A `clientId` is required to be present on a channel. + * + * @param data - The payload associated with the presence member. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + enter(data?: any): Promise; + /** + * Updates the `data` payload for a presence member. If called before entering the presence set, this is treated as an {@link PresenceAction.ENTER} event. + * + * @param data - The payload to update for the presence member. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + update(data?: any): Promise; + /** + * Leaves the presence set for the channel. A client must have previously entered the presence set before they can leave it. + * + * @param data - The payload associated with the presence member. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + leave(data?: any): Promise; + /** + * Enters the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. + * + * @param clientId - The ID of the client to enter into the presence set. + * @param data - The payload associated with the presence member. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + enterClient(clientId: string, data?: any): Promise; + /** + * Updates the `data` payload for a presence member using a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. + * + * @param clientId - The ID of the client to update in the presence set. + * @param data - The payload to update for the presence member. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + updateClient(clientId: string, data?: any): Promise; + /** + * Leaves the presence set of the channel for a given `clientId`. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard `clientId`. + * + * @param clientId - The ID of the client to leave the presence set for. + * @param data - The payload associated with the presence member. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + leaveClient(clientId: string, data?: any): Promise; +} + +/** + * Enables messages to be published and historic messages to be retrieved for a channel. + */ +export declare class Channel { + /** + * The channel name. + */ + name: string; + + /** + * A {@link Presence} object. + */ + presence: Presence; + /** + * Retrieves a {@link PaginatedResult} object, containing an array of historical {@link InboundMessage} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. + * + * @param params - A set of parameters which are used to specify which messages should be retrieved. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link InboundMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + history(params?: RestHistoryParams): Promise>; + /** + * Publishes an array of messages to the channel. + * + * @param messages - An array of {@link Message} objects. + * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + publish(messages: Message[], options?: PublishOptions): Promise; + /** + * Publishes a message to the channel. + * + * @param message - A {@link Message} object. + * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + publish(message: Message, options?: PublishOptions): Promise; + /** + * Publishes a single message to the channel with the given event name and payload. + * + * @param name - The name of the message. + * @param data - The payload of the message. + * @param options - Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + publish(name: string, data: any, options?: PublishOptions): Promise; + /** + * Retrieves a {@link ChannelDetails} object for the channel, which includes status and occupancy metrics. + * + * @returns A promise which, upon success, will be fulfilled a {@link ChannelDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + status(): Promise; +} + +/** + * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresence} object of a channel. + */ +export declare class RealtimeChannel extends EventEmitter { + /** + * The channel name. + */ + readonly name: string; + /** + * An {@link ErrorInfo} object describing the last error which occurred on the channel, if any. + */ + errorReason: ErrorInfo; + /** + * The current {@link ChannelState} of the channel. + */ + readonly state: ChannelState; + /** + * Optional [channel parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) that configure the behavior of the channel. + */ + params: ChannelParams; + /** + * An array of {@link ChannelMode} objects. + */ + modes: ChannelModes; + /** + * Deregisters the given listener for the specified event name. This removes an earlier event-specific subscription. + * + * @param event - The event name. + * @param listener - An event listener function. + */ + unsubscribe(event: string, listener: messageCallback): void; + /** + * Deregisters the given listener from all event names in the array. + * + * @param events - An array of event names. + * @param listener - An event listener function. + */ + unsubscribe(events: Array, listener: messageCallback): void; + /** + * Deregisters all listeners for the given event name. + * + * @param event - The event name. + */ + unsubscribe(event: string): void; + /** + * Deregisters all listeners for all event names in the array. + * + * @param events - An array of event names. + */ + unsubscribe(events: Array): void; + /** + * Deregisters all listeners to messages on this channel that match the supplied filter. + * + * @param filter - A {@link MessageFilter}. + * @param listener - An event listener function. + */ + unsubscribe(filter: MessageFilter, listener?: messageCallback): void; + /** + * Deregisters the given listener (for any/all event names). This removes an earlier subscription. + * + * @param listener - An event listener function. + */ + unsubscribe(listener: messageCallback): void; + /** + * Deregisters all listeners to messages on this channel. This removes all earlier subscriptions. + */ + unsubscribe(): void; + + /** + * A {@link RealtimePresence} object. + */ + presence: RealtimePresence; + /** + * Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel are received by any channel listeners registered using {@link RealtimeChannel.subscribe | `subscribe()`}. Any resulting channel state change will be emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. As a convenience, `attach()` is called implicitly if {@link RealtimeChannel.subscribe | `subscribe()`} for the channel is called, or {@link RealtimePresence.enter | `enter()`} or {@link RealtimePresence.subscribe | `subscribe()`} are called on the {@link RealtimePresence} object for this channel. + * + * @returns A promise which, upon success, if the channel became attached will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be fulfilled with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. + */ + attach(): Promise; + /** + * Detach from this channel. Any resulting channel state change is emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. Once all clients globally have detached from the channel, the channel will be released in the Ably service within two minutes. + * + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + detach(): Promise; + /** + * Retrieves a {@link PaginatedResult} object, containing an array of historical {@link InboundMessage} objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. + * + * @param params - A set of parameters which are used to specify which presence members should be retrieved. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link InboundMessage} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + history(params?: RealtimeHistoryParams): Promise>; + /** + * Sets the {@link ChannelOptions} for the channel. + * + * @param options - A {@link ChannelOptions} object. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + setOptions(options: ChannelOptions): Promise; + /** + * Registers a listener for messages with a given event name on this channel. The caller supplies a listener function, which is called each time one or more matching messages arrives on the channel. + * + * @param event - The event name. + * @param listener - An event listener function. + * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. + */ + subscribe(event: string, listener?: messageCallback): Promise; + /** + * Registers a listener for messages on this channel for multiple event name values. + * + * @param events - An array of event names. + * @param listener - An event listener function. + * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. + */ + subscribe(events: Array, listener?: messageCallback): Promise; + /** + * {@label WITH_MESSAGE_FILTER} + * + * Registers a listener for messages on this channel that match the supplied filter. + * + * @param filter - A {@link MessageFilter}. + * @param listener - An event listener function. + * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. + */ + subscribe(filter: MessageFilter, listener?: messageCallback): Promise; + /** + * Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel. + * + * @param callback - An event listener function. + * @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object. + */ + subscribe(callback: messageCallback): Promise; + /** + * Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. + * + * @param name - The event name. + * @param data - The message payload. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + publish(name: string, data: any): Promise; + /** + * Publishes an array of messages to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. + * + * @param messages - An array of {@link Message} objects. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + publish(messages: Message[]): Promise; + /** + * Publish a message to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. + * + * @param message - A {@link Message} object. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + publish(message: Message): Promise; + /** + * Returns a promise which is resolved when the channel reaches the specified {@link ChannelState}. If the channel is already in the specified state, the promise is resolved immediately. + * + * @param targetState - The state which should be reached. + */ + whenState(targetState: ChannelState): Promise; +} + +/** + * Optional parameters for message publishing. + */ +export type PublishOptions = { + /** + * See [here](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes). + */ + quickAck?: boolean; +}; + +/** + * Contains properties to filter messages with when calling {@link RealtimeChannel.subscribe | `RealtimeChannel.subscribe()`}. + */ +export type MessageFilter = { + /** + * Filters messages by a specific message `name`. + */ + name?: string; + /** + * Filters messages by a specific `extras.ref.timeserial` value. + */ + refTimeserial?: string; + /** + * Filters messages by a specific `extras.ref.type` value. + */ + refType?: string; + /** + * Filters messages based on whether they contain an `extras.ref`. + */ + isRef?: boolean; + /** + * Filters messages by a specific message `clientId`. + */ + clientId: string; +}; + +/** + * Creates and destroys {@link Channel} and {@link RealtimeChannel} objects. + */ +export declare class Channels { + /** + * Creates a new {@link Channel} or {@link RealtimeChannel} object, with the specified {@link ChannelOptions}, or returns the existing channel object. + * + * @param name - The channel name. + * @param channelOptions - A {@link ChannelOptions} object. + * @returns A {@link Channel} or {@link RealtimeChannel} object. + */ + get(name: string, channelOptions?: ChannelOptions): T; + /** + * @experimental This is a preview feature and may change in a future non-major release. + * This experimental method allows you to create custom realtime data feeds by selectively subscribing + * to receive only part of the data from the channel. + * See the [announcement post](https://pages.ably.com/subscription-filters-preview) for more information. + * + * Creates a new {@link Channel} or {@link RealtimeChannel} object, with the specified channel {@link DeriveOptions} + * and {@link ChannelOptions}, or returns the existing channel object. + * + * @param name - The channel name. + * @param deriveOptions - A {@link DeriveOptions} object. + * @param channelOptions - A {@link ChannelOptions} object. + * @returns A {@link RealtimeChannel} object. + */ + getDerived(name: string, deriveOptions: DeriveOptions, channelOptions?: ChannelOptions): T; + /** + * Releases a {@link Channel} or {@link RealtimeChannel} object, deleting it, and enabling it to be garbage collected. It also removes any listeners associated with the channel. To release a channel, the {@link ChannelState} must be `INITIALIZED`, `DETACHED`, or `FAILED`. + * + * @param name - The channel name. + */ + release(name: string): void; +} + +/** + * Contains an individual message that is sent to, or received from, Ably. + */ +export interface Message { + /** + * The client ID of the publisher of this message. + */ + clientId?: string; + /** + * The connection ID of the publisher of this message. + */ + connectionId?: string; + /** + * The message payload, if provided. + */ + data?: any; + /** + * This is typically empty, as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute contains the remaining transformations not applied to the `data` payload. + */ + encoding?: string; + /** + * A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads. Valid payloads include `push`, `delta`, `ref` and `headers`. + */ + extras?: any; + /** + * Unique ID assigned by Ably to this message. + */ + id?: string; + /** + * The event name. + */ + name?: string; + /** + * Timestamp of when the message was received by Ably, as milliseconds since the Unix epoch. + */ + timestamp?: number; +} + +/** + * A message received from Ably. + */ +export type InboundMessage = Message & Required>; + +/** + * Static utilities related to messages. + */ +export interface MessageStatic { + /** + * A static factory method to create an `InboundMessage` object from a deserialized InboundMessage-like object encoded using Ably's wire protocol. + * + * @param JsonObject - A `InboundMessage`-like deserialized object. + * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. + * @returns A promise which will be fulfilled with an `InboundMessage` object. + */ + fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; + /** + * A static factory method to create an array of `InboundMessage` objects from an array of deserialized InboundMessage-like object encoded using Ably's wire protocol. + * + * @param JsonArray - An array of `InboundMessage`-like deserialized objects. + * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. + * @returns A promise which will be fulfilled with an array of {@link InboundMessage} objects. + */ + fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; +} + +/** + * Contains an individual presence update sent to, or received from, Ably. + */ +export declare class PresenceMessage { + /** + * The type of {@link PresenceAction} the `PresenceMessage` is for. + */ + action: PresenceAction; + /** + * The ID of the client that published the `PresenceMessage`. + */ + clientId: string; + /** + * The ID of the connection associated with the client that published the `PresenceMessage`. + */ + connectionId: string; + /** + * The payload of the `PresenceMessage`. + */ + data: any; + /** + * This will typically be empty as all presence messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the data payload. + */ + encoding: string; + /** + * A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads. Valid payloads include `headers`. + */ + extras: any; + /** + * A unique ID assigned to each `PresenceMessage` by Ably. + */ + id: string; + /** + * The time the `PresenceMessage` was received by Ably, as milliseconds since the Unix epoch. + */ + timestamp: number; +} + +/** + * Static utilities related to presence messages. + */ +export interface PresenceMessageStatic { + /** + * Decodes and decrypts a deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. + * + * @param JsonObject - The deserialized `PresenceMessage`-like object to decode and decrypt. + * @param channelOptions - A {@link ChannelOptions} object containing the cipher. + */ + fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; + /** + * Decodes and decrypts an array of deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. + * + * @param JsonArray - An array of deserialized `PresenceMessage`-like objects to decode and decrypt. + * @param channelOptions - A {@link ChannelOptions} object containing the cipher. + */ + fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; + + /** + * Initialises a `PresenceMessage` from a `PresenceMessage`-like object. + * + * @param values - The values to intialise the `PresenceMessage` from. + * @param stringifyAction - Whether to convert the `action` field from a number to a string. + */ + fromValues(values: PresenceMessage | Record, stringifyAction?: boolean): PresenceMessage; +} + +/** + * Cipher Key used in {@link CipherParamOptions}. If set to a `string`, the value must be base64 encoded. + */ +export type CipherKeyParam = ArrayBuffer | Uint8Array | string; // if string must be base64-encoded +/** + * The type of the key returned by {@link Crypto.generateRandomKey}. Typed differently depending on platform (`Buffer` in Node.js, `ArrayBuffer` elsewhere). + */ +export type CipherKey = ArrayBuffer | Buffer; + +/** + * Contains the properties used to generate a {@link CipherParams} object. + */ +export type CipherParamOptions = { + /** + * The private key used to encrypt and decrypt payloads. + */ + key: CipherKeyParam; + /** + * The algorithm to use for encryption. Only `AES` is supported. + */ + algorithm?: 'aes'; + /** + * The length of the key in bits; for example 128 or 256. + */ + keyLength?: number; + /** + * The cipher mode. Only `CBC` is supported. + */ + mode?: 'cbc'; +}; + +/** + * Contains the properties required to configure the encryption of {@link Message} payloads. + */ +export interface Crypto { + /** + * Generates a random key to be used in the encryption of the channel. If the language cryptographic randomness primitives are blocking or async, a callback is used. The callback returns a generated binary key. + * + * @param keyLength - The length of the key, in bits, to be generated. If not specified, this is equal to the default `keyLength` of the default algorithm: for AES this is 256 bits. + * @returns A promise which, upon success, will be fulfilled with the generated key as a binary, for example, a byte array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + generateRandomKey(keyLength?: number): Promise; + /** + * Returns a {@link CipherParams} object, using the default values for any fields not supplied by the {@link CipherParamOptions} object. + * + * @param params - A {@link CipherParamOptions} object. + * @returns A {@link CipherParams} object, using the default values for any fields not supplied. + */ + getDefaultParams(params: CipherParamOptions): CipherParams; +} + +/** + * Enables the management of a connection to Ably. + */ +export declare class Connection extends EventEmitter { + /** + * An {@link ErrorInfo} object describing the last error received if a connection failure occurs. + */ + errorReason: ErrorInfo; + /** + * A unique public identifier for this connection, used to identify this member. + */ + id?: string; + /** + * A unique private connection key used to recover or resume a connection, assigned by Ably. When recovering a connection explicitly, the `recoveryKey` is used in the recover client options as it contains both the key and the last message serial. This private connection key can also be used by other REST clients to publish on behalf of this client. See the [publishing over REST on behalf of a realtime client docs](https://ably.com/docs/rest/channels#publish-on-behalf) for more info. + */ + key?: string; + /** + * The recovery key string can be used by another client to recover this connection's state in the recover client options property. See [connection state recover options](https://ably.com/docs/realtime/connection#connection-state-recover-options) for more information. + */ + recoveryKey: string | null; + /** + * The serial number of the last message to be received on this connection, used automatically by the library when recovering or resuming a connection. When recovering a connection explicitly, the `recoveryKey` is used in the recover client options as it contains both the key and the last message serial. + */ + serial: number; + /** + * The current {@link ConnectionState} of the connection. + */ + readonly state: ConnectionState; + /** + * Causes the connection to close, entering the {@link ConnectionState.CLOSING} state. Once closed, the library does not attempt to re-establish the connection without an explicit call to {@link Connection.connect | `connect()`}. + */ + close(): void; + /** + * Explicitly calling `connect()` is unnecessary unless the `autoConnect` attribute of the {@link ClientOptions} object is `false`. Unless already connected or connecting, this method causes the connection to open, entering the {@link ConnectionState.CONNECTING} state. + */ + connect(): void; + + /** + * When connected, sends a heartbeat ping to the Ably server and executes the callback with any error and the response time in milliseconds when a heartbeat ping request is echoed from the server. This can be useful for measuring true round-trip latency to the connected Ably server. + * + * @returns A promise which, upon success, will be fulfilled with the response time in milliseconds. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + ping(): Promise; + /** + * Returns a promise which is resolved when the connection reaches the specified {@link ConnectionState}. If the connection is already in the specified state, the promise is resolved immediately. + * + * @param targetState - The state which should be reached. + */ + whenState(targetState: ConnectionState): Promise; +} + +/** + * Contains application statistics for a specified time interval and time period. + */ +export declare class Stats { + /** + * The UTC time at which the time period covered begins. If `unit` is set to `minute` this will be in the format `YYYY-mm-dd:HH:MM`, if `hour` it will be `YYYY-mm-dd:HH`, if `day` it will be `YYYY-mm-dd:00` and if `month` it will be `YYYY-mm-01:00`. + */ + intervalId: string; + /** + * For entries that are still in progress, such as the current month: the last sub-interval included in this entry (in format yyyy-mm-dd:hh:mm:ss), else undefined. + */ + inProgress?: string; + /** + * The statistics for this time interval and time period. See the JSON schema which the {@link Stats.schema | `schema`} property points to for more information. + */ + entries: Partial>; + /** + * The URL of a [JSON Schema](https://json-schema.org/) which describes the structure of this `Stats` object. + */ + schema: string; + /** + * The ID of the Ably application the statistics are for. + */ + appId: string; +} +/** + * Contains a page of results for message or presence history, stats, or REST presence requests. A `PaginatedResult` response from a REST API paginated query is also accompanied by metadata that indicates the relative queries available to the `PaginatedResult` object. + */ +export declare class PaginatedResult { /** - * Optional parameters for message publishing. + * Contains the current page of results; for example, an array of {@link InboundMessage} or {@link PresenceMessage} objects for a channel history request. */ - type PublishOptions = { - /** - * See [here](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes). - */ - quickAck?: boolean; - }; - + items: T[]; /** - * Contains properties to filter messages with when calling {@link RealtimeChannel.subscribe | `RealtimeChannel.subscribe()`}. + * Returns a new `PaginatedResult` for the first page of results. + * + * @returns A promise which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - type MessageFilter = { - /** - * Filters messages by a specific message `name`. - */ - name?: string; - /** - * Filters messages by a specific `extras.ref.timeserial` value. - */ - refTimeserial?: string; - /** - * Filters messages by a specific `extras.ref.type` value. - */ - refType?: string; - /** - * Filters messages based on whether they contain an `extras.ref`. - */ - isRef?: boolean; - /** - * Filters messages by a specific message `clientId`. - */ - clientId: string; - }; - + first(): Promise>; /** - * Creates and destroys {@link Channel} and {@link RealtimeChannel} objects. + * Returns a new `PaginatedResult` loaded with the next page of results. If there are no further pages, then `null` is returned. + * + * @returns A promise which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - class Channels { - /** - * Creates a new {@link Channel} or {@link RealtimeChannel} object, with the specified {@link ChannelOptions}, or returns the existing channel object. - * - * @param name - The channel name. - * @param channelOptions - A {@link ChannelOptions} object. - * @returns A {@link Channel} or {@link RealtimeChannel} object. - */ - get(name: string, channelOptions?: ChannelOptions): T; - /** - * @experimental This is a preview feature and may change in a future non-major release. - * This experimental method allows you to create custom realtime data feeds by selectively subscribing - * to receive only part of the data from the channel. - * See the [announcement post](https://pages.ably.com/subscription-filters-preview) for more information. - * - * Creates a new {@link Channel} or {@link RealtimeChannel} object, with the specified channel {@link DeriveOptions} - * and {@link ChannelOptions}, or returns the existing channel object. - * - * @param name - The channel name. - * @param deriveOptions - A {@link DeriveOptions} object. - * @param channelOptions - A {@link ChannelOptions} object. - * @returns A {@link RealtimeChannel} object. - */ - getDerived(name: string, deriveOptions: DeriveOptions, channelOptions?: ChannelOptions): T; - /** - * Releases a {@link Channel} or {@link RealtimeChannel} object, deleting it, and enabling it to be garbage collected. It also removes any listeners associated with the channel. To release a channel, the {@link ChannelState} must be `INITIALIZED`, `DETACHED`, or `FAILED`. - * - * @param name - The channel name. - */ - release(name: string): void; - } - + next(): Promise>; /** - * Contains an individual message that is sent to, or received from, Ably. + * Returns the `PaginatedResult` for the current page of results. */ - interface Message { - /** - * The client ID of the publisher of this message. - */ - clientId?: string; - /** - * The connection ID of the publisher of this message. - */ - connectionId?: string; - /** - * The message payload, if provided. - */ - data?: any; - /** - * This is typically empty, as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute contains the remaining transformations not applied to the `data` payload. - */ - encoding?: string; - /** - * A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads. Valid payloads include `push`, `delta`, `ref` and `headers`. - */ - extras?: any; - /** - * Unique ID assigned by Ably to this message. - */ - id?: string; - /** - * The event name. - */ - name?: string; - /** - * Timestamp of when the message was received by Ably, as milliseconds since the Unix epoch. - */ - timestamp?: number; - } - + current(): Promise>; /** - * A message received from Ably. + * Returns `true` if there are more pages available by calling next and returns `false` if this page is the last page available. + * + * @returns Whether or not there are more pages of results. */ - type InboundMessage = Message & Required>; - + hasNext(): boolean; /** - * Static utilities related to messages. + * Returns `true` if this page is the last page and returns `false` if there are more pages available by calling next available. + * + * @returns Whether or not this is the last page of results. */ - interface MessageStatic { - /** - * A static factory method to create an `InboundMessage` object from a deserialized InboundMessage-like object encoded using Ably's wire protocol. - * - * @param JsonObject - A `InboundMessage`-like deserialized object. - * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A promise which will be fulfilled with an `InboundMessage` object. - */ - fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; - /** - * A static factory method to create an array of `InboundMessage` objects from an array of deserialized InboundMessage-like object encoded using Ably's wire protocol. - * - * @param JsonArray - An array of `InboundMessage`-like deserialized objects. - * @param channelOptions - A {@link ChannelOptions} object. If you have an encrypted channel, use this to allow the library to decrypt the data. - * @returns A promise which will be fulfilled with an array of {@link InboundMessage} objects. - */ - fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; - } + isLast(): boolean; +} +/** + * A superset of {@link PaginatedResult} which represents a page of results plus metadata indicating the relative queries available to it. `HttpPaginatedResponse` additionally carries information about the response to an HTTP request. + */ +export declare class HttpPaginatedResponse extends PaginatedResult { /** - * Contains an individual presence update sent to, or received from, Ably. + * The HTTP status code of the response. */ - class PresenceMessage { - /** - * The type of {@link PresenceAction} the `PresenceMessage` is for. - */ - action: PresenceAction; - /** - * The ID of the client that published the `PresenceMessage`. - */ - clientId: string; - /** - * The ID of the connection associated with the client that published the `PresenceMessage`. - */ - connectionId: string; - /** - * The payload of the `PresenceMessage`. - */ - data: any; - /** - * This will typically be empty as all presence messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the data payload. - */ - encoding: string; - /** - * A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads. Valid payloads include `headers`. - */ - extras: any; - /** - * A unique ID assigned to each `PresenceMessage` by Ably. - */ - id: string; - /** - * The time the `PresenceMessage` was received by Ably, as milliseconds since the Unix epoch. - */ - timestamp: number; - } - + statusCode: number; /** - * Static utilities related to presence messages. + * Whether `statusCode` indicates success. This is equivalent to `200 <= statusCode < 300`. */ - interface PresenceMessageStatic { - /** - * Decodes and decrypts a deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. - * - * @param JsonObject - The deserialized `PresenceMessage`-like object to decode and decrypt. - * @param channelOptions - A {@link ChannelOptions} object containing the cipher. - */ - fromEncoded: (JsonObject: any, channelOptions?: ChannelOptions) => Promise; - /** - * Decodes and decrypts an array of deserialized `PresenceMessage`-like object using the cipher in {@link ChannelOptions}. Any residual transforms that cannot be decoded or decrypted will be in the `encoding` property. Intended for users receiving messages from a source other than a REST or Realtime channel (for example a queue) to avoid having to parse the encoding string. - * - * @param JsonArray - An array of deserialized `PresenceMessage`-like objects to decode and decrypt. - * @param channelOptions - A {@link ChannelOptions} object containing the cipher. - */ - fromEncodedArray: (JsonArray: any[], channelOptions?: ChannelOptions) => Promise; - - /** - * Initialises a `PresenceMessage` from a `PresenceMessage`-like object. - * - * @param values - The values to intialise the `PresenceMessage` from. - * @param stringifyAction - Whether to convert the `action` field from a number to a string. - */ - fromValues(values: PresenceMessage | Record, stringifyAction?: boolean): PresenceMessage; - } - + success: boolean; /** - * Cipher Key used in {@link CipherParamOptions}. If set to a `string`, the value must be base64 encoded. + * The error code if the `X-Ably-Errorcode` HTTP header is sent in the response. */ - type CipherKeyParam = ArrayBuffer | Uint8Array | string; // if string must be base64-encoded + errorCode: number; /** - * The type of the key returned by {@link Crypto.generateRandomKey}. Typed differently depending on platform (`Buffer` in Node.js, `ArrayBuffer` elsewhere). + * The error message if the `X-Ably-Errormessage` HTTP header is sent in the response. */ - type CipherKey = ArrayBuffer | Buffer; - + errorMessage: string; /** - * Contains the properties used to generate a {@link CipherParams} object. + * The headers of the response. */ - type CipherParamOptions = { - /** - * The private key used to encrypt and decrypt payloads. - */ - key: CipherKeyParam; - /** - * The algorithm to use for encryption. Only `AES` is supported. - */ - algorithm?: 'aes'; - /** - * The length of the key in bits; for example 128 or 256. - */ - keyLength?: number; - /** - * The cipher mode. Only `CBC` is supported. - */ - mode?: 'cbc'; - }; + headers: any; +} +/** + * Enables a device to be registered and deregistered from receiving push notifications. + */ +export declare class Push { /** - * Contains the properties required to configure the encryption of {@link Message} payloads. + * A {@link PushAdmin} object. */ - interface Crypto { - /** - * Generates a random key to be used in the encryption of the channel. If the language cryptographic randomness primitives are blocking or async, a callback is used. The callback returns a generated binary key. - * - * @param keyLength - The length of the key, in bits, to be generated. If not specified, this is equal to the default `keyLength` of the default algorithm: for AES this is 256 bits. - * @returns A promise which, upon success, will be fulfilled with the generated key as a binary, for example, a byte array. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - generateRandomKey(keyLength?: number): Promise; - /** - * Returns a {@link CipherParams} object, using the default values for any fields not supplied by the {@link CipherParamOptions} object. - * - * @param params - A {@link CipherParamOptions} object. - * @returns A {@link CipherParams} object, using the default values for any fields not supplied. - */ - getDefaultParams(params: CipherParamOptions): CipherParams; - } + admin: PushAdmin; +} +/** + * Enables the management of device registrations and push notification subscriptions. Also enables the publishing of push notifications to devices. + */ +export declare class PushAdmin { /** - * Enables the management of a connection to Ably. + * A {@link PushDeviceRegistrations} object. */ - class Connection extends EventEmitter { - /** - * An {@link ErrorInfo} object describing the last error received if a connection failure occurs. - */ - errorReason: ErrorInfo; - /** - * A unique public identifier for this connection, used to identify this member. - */ - id?: string; - /** - * A unique private connection key used to recover or resume a connection, assigned by Ably. When recovering a connection explicitly, the `recoveryKey` is used in the recover client options as it contains both the key and the last message serial. This private connection key can also be used by other REST clients to publish on behalf of this client. See the [publishing over REST on behalf of a realtime client docs](https://ably.com/docs/rest/channels#publish-on-behalf) for more info. - */ - key?: string; - /** - * The recovery key string can be used by another client to recover this connection's state in the recover client options property. See [connection state recover options](https://ably.com/docs/realtime/connection#connection-state-recover-options) for more information. - */ - recoveryKey: string | null; - /** - * The serial number of the last message to be received on this connection, used automatically by the library when recovering or resuming a connection. When recovering a connection explicitly, the `recoveryKey` is used in the recover client options as it contains both the key and the last message serial. - */ - serial: number; - /** - * The current {@link ConnectionState} of the connection. - */ - readonly state: ConnectionState; - /** - * Causes the connection to close, entering the {@link ConnectionState.CLOSING} state. Once closed, the library does not attempt to re-establish the connection without an explicit call to {@link Connection.connect | `connect()`}. - */ - close(): void; - /** - * Explicitly calling `connect()` is unnecessary unless the `autoConnect` attribute of the {@link ClientOptions} object is `false`. Unless already connected or connecting, this method causes the connection to open, entering the {@link ConnectionState.CONNECTING} state. - */ - connect(): void; - - /** - * When connected, sends a heartbeat ping to the Ably server and executes the callback with any error and the response time in milliseconds when a heartbeat ping request is echoed from the server. This can be useful for measuring true round-trip latency to the connected Ably server. - * - * @returns A promise which, upon success, will be fulfilled with the response time in milliseconds. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - ping(): Promise; - /** - * Returns a promise which is resolved when the connection reaches the specified {@link ConnectionState}. If the connection is already in the specified state, the promise is resolved immediately. - * - * @param targetState - The state which should be reached. - */ - whenState(targetState: ConnectionState): Promise; - } - + deviceRegistrations: PushDeviceRegistrations; /** - * Contains application statistics for a specified time interval and time period. + * A {@link PushChannelSubscriptions} object. */ - class Stats { - /** - * The UTC time at which the time period covered begins. If `unit` is set to `minute` this will be in the format `YYYY-mm-dd:HH:MM`, if `hour` it will be `YYYY-mm-dd:HH`, if `day` it will be `YYYY-mm-dd:00` and if `month` it will be `YYYY-mm-01:00`. - */ - intervalId: string; - /** - * For entries that are still in progress, such as the current month: the last sub-interval included in this entry (in format yyyy-mm-dd:hh:mm:ss), else undefined. - */ - inProgress?: string; - /** - * The statistics for this time interval and time period. See the JSON schema which the {@link Stats.schema | `schema`} property points to for more information. - */ - entries: Partial>; - /** - * The URL of a [JSON Schema](https://json-schema.org/) which describes the structure of this `Stats` object. - */ - schema: string; - /** - * The ID of the Ably application the statistics are for. - */ - appId: string; - } - + channelSubscriptions: PushChannelSubscriptions; /** - * Contains a page of results for message or presence history, stats, or REST presence requests. A `PaginatedResult` response from a REST API paginated query is also accompanied by metadata that indicates the relative queries available to the `PaginatedResult` object. + * Sends a push notification directly to a device, or a group of devices sharing the same `clientId`. + * + * @param recipient - A JSON object containing the recipient details using `clientId`, `deviceId` or the underlying notifications service. + * @param payload - A JSON object containing the push notification payload. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. */ - class PaginatedResult { - /** - * Contains the current page of results; for example, an array of {@link InboundMessage} or {@link PresenceMessage} objects for a channel history request. - */ - items: T[]; - /** - * Returns a new `PaginatedResult` for the first page of results. - * - * @returns A promise which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - first(): Promise>; - /** - * Returns a new `PaginatedResult` loaded with the next page of results. If there are no further pages, then `null` is returned. - * - * @returns A promise which, upon success, will be fulfilled with a page of results for message and presence history, stats, and REST presence requests. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - next(): Promise>; - /** - * Returns the `PaginatedResult` for the current page of results. - */ - current(): Promise>; - /** - * Returns `true` if there are more pages available by calling next and returns `false` if this page is the last page available. - * - * @returns Whether or not there are more pages of results. - */ - hasNext(): boolean; - /** - * Returns `true` if this page is the last page and returns `false` if there are more pages available by calling next available. - * - * @returns Whether or not this is the last page of results. - */ - isLast(): boolean; - } + publish(recipient: any, payload: any): Promise; +} +/** + * Enables the management of push notification registrations with Ably. + */ +export declare class PushDeviceRegistrations { /** - * A superset of {@link Types.PaginatedResult} which represents a page of results plus metadata indicating the relative queries available to it. `HttpPaginatedResponse` additionally carries information about the response to an HTTP request. + * Registers or updates a {@link DeviceDetails} object with Ably. Returns the new, or updated {@link DeviceDetails} object. + * + * @param deviceDetails - The {@link DeviceDetails} object to create or update. + * @returns A promise which, upon success, will be fulfilled with a {@link DeviceDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - class HttpPaginatedResponse extends PaginatedResult { - /** - * The HTTP status code of the response. - */ - statusCode: number; - /** - * Whether `statusCode` indicates success. This is equivalent to `200 <= statusCode < 300`. - */ - success: boolean; - /** - * The error code if the `X-Ably-Errorcode` HTTP header is sent in the response. - */ - errorCode: number; - /** - * The error message if the `X-Ably-Errormessage` HTTP header is sent in the response. - */ - errorMessage: string; - /** - * The headers of the response. - */ - headers: any; - } - + save(deviceDetails: DeviceDetails): Promise; /** - * Enables a device to be registered and deregistered from receiving push notifications. + * Retrieves the {@link DeviceDetails} of a device registered to receive push notifications using its `deviceId`. + * + * @param deviceId - The unique ID of the device. + * @returns A promise which, upon success, will be fulfilled with a {@link DeviceDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - class Push { - /** - * A {@link PushAdmin} object. - */ - admin: PushAdmin; - } - + get(deviceId: string): Promise; /** - * Enables the management of device registrations and push notification subscriptions. Also enables the publishing of push notifications to devices. + * Retrieves the {@link DeviceDetails} of a device registered to receive push notifications using the `id` property of a {@link DeviceDetails} object. + * + * @param deviceDetails - The {@link DeviceDetails} object containing the `id` property of the device. + * @returns A promise which, upon success, will be fulfilled with a {@link DeviceDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - class PushAdmin { - /** - * A {@link PushDeviceRegistrations} object. - */ - deviceRegistrations: PushDeviceRegistrations; - /** - * A {@link PushChannelSubscriptions} object. - */ - channelSubscriptions: PushChannelSubscriptions; - /** - * Sends a push notification directly to a device, or a group of devices sharing the same `clientId`. - * - * @param recipient - A JSON object containing the recipient details using `clientId`, `deviceId` or the underlying notifications service. - * @param payload - A JSON object containing the push notification payload. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - publish(recipient: any, payload: any): Promise; - } - + get(deviceDetails: DeviceDetails): Promise; /** - * Enables the management of push notification registrations with Ably. + * Retrieves all devices matching the filter `params` provided. Returns a {@link PaginatedResult} object, containing an array of {@link DeviceDetails} objects. + * + * @param params - An object containing key-value pairs to filter devices by. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link DeviceDetails} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - class PushDeviceRegistrations { - /** - * Registers or updates a {@link DeviceDetails} object with Ably. Returns the new, or updated {@link DeviceDetails} object. - * - * @param deviceDetails - The {@link DeviceDetails} object to create or update. - * @returns A promise which, upon success, will be fulfilled with a {@link DeviceDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - save(deviceDetails: DeviceDetails): Promise; - /** - * Retrieves the {@link DeviceDetails} of a device registered to receive push notifications using its `deviceId`. - * - * @param deviceId - The unique ID of the device. - * @returns A promise which, upon success, will be fulfilled with a {@link DeviceDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - get(deviceId: string): Promise; - /** - * Retrieves the {@link DeviceDetails} of a device registered to receive push notifications using the `id` property of a {@link DeviceDetails} object. - * - * @param deviceDetails - The {@link DeviceDetails} object containing the `id` property of the device. - * @returns A promise which, upon success, will be fulfilled with a {@link DeviceDetails} object. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - get(deviceDetails: DeviceDetails): Promise; - /** - * Retrieves all devices matching the filter `params` provided. Returns a {@link Types.PaginatedResult} object, containing an array of {@link DeviceDetails} objects. - * - * @param params - An object containing key-value pairs to filter devices by. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link DeviceDetails} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - list(params: DeviceRegistrationParams): Promise>; - /** - * Removes a device registered to receive push notifications from Ably using its `deviceId`. - * - * @param deviceId - The unique ID of the device. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - remove(deviceId: string): Promise; - /** - * Removes a device registered to receive push notifications from Ably using the `id` property of a {@link DeviceDetails} object. - * - * @param deviceDetails - The {@link DeviceDetails} object containing the `id` property of the device. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - remove(deviceDetails: DeviceDetails): Promise; - /** - * Removes all devices registered to receive push notifications from Ably matching the filter `params` provided. - * - * @param params - An object containing key-value pairs to filter devices by. This object’s {@link DeviceRegistrationParams.limit} property will be ignored. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - removeWhere(params: DeviceRegistrationParams): Promise; - } + list(params: DeviceRegistrationParams): Promise>; + /** + * Removes a device registered to receive push notifications from Ably using its `deviceId`. + * + * @param deviceId - The unique ID of the device. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + remove(deviceId: string): Promise; + /** + * Removes a device registered to receive push notifications from Ably using the `id` property of a {@link DeviceDetails} object. + * + * @param deviceDetails - The {@link DeviceDetails} object containing the `id` property of the device. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + remove(deviceDetails: DeviceDetails): Promise; + /** + * Removes all devices registered to receive push notifications from Ably matching the filter `params` provided. + * + * @param params - An object containing key-value pairs to filter devices by. This object’s {@link DeviceRegistrationParams.limit} property will be ignored. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + removeWhere(params: DeviceRegistrationParams): Promise; +} +/** + * Enables device push channel subscriptions. + */ +export declare class PushChannelSubscriptions { /** - * Enables device push channel subscriptions. + * Subscribes a device, or a group of devices sharing the same `clientId` to push notifications on a channel. Returns a {@link PushChannelSubscription} object. + * + * @param subscription - A {@link PushChannelSubscription} object. + * @returns A promise which, upon success, will be fulfilled with a {@link PushChannelSubscription} object describing the new or updated subscriptions. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - class PushChannelSubscriptions { - /** - * Subscribes a device, or a group of devices sharing the same `clientId` to push notifications on a channel. Returns a {@link PushChannelSubscription} object. - * - * @param subscription - A {@link PushChannelSubscription} object. - * @returns A promise which, upon success, will be fulfilled with a {@link PushChannelSubscription} object describing the new or updated subscriptions. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - save(subscription: PushChannelSubscription): Promise; - /** - * Retrieves all push channel subscriptions matching the filter `params` provided. Returns a {@link Types.PaginatedResult} object, containing an array of {@link PushChannelSubscription} objects. - * - * @param params - An object containing key-value pairs to filter subscriptions by. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of {@link PushChannelSubscription} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - list(params: PushChannelSubscriptionParams): Promise>; - /** - * Retrieves all channels with at least one device subscribed to push notifications. Returns a {@link Types.PaginatedResult} object, containing an array of channel names. - * - * @param params - An object containing key-value pairs to filter channels by. - * @returns A promise which, upon success, will be fulfilled with a {@link Types.PaginatedResult} object containing an array of channel names. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. - */ - listChannels(params: PushChannelsParams): Promise>; - /** - * Unsubscribes a device, or a group of devices sharing the same `clientId` from receiving push notifications on a channel. - * - * @param subscription - A {@link PushChannelSubscription} object. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - remove(subscription: PushChannelSubscription): Promise; - /** - * Unsubscribes all devices from receiving push notifications on a channel that match the filter `params` provided. - * - * @param params - An object containing key-value pairs to filter subscriptions by. Can contain `channel`, and optionally either `clientId` or `deviceId`. - * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. - */ - removeWhere(params: PushChannelSubscriptionParams): Promise; - } + save(subscription: PushChannelSubscription): Promise; + /** + * Retrieves all push channel subscriptions matching the filter `params` provided. Returns a {@link PaginatedResult} object, containing an array of {@link PushChannelSubscription} objects. + * + * @param params - An object containing key-value pairs to filter subscriptions by. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link PushChannelSubscription} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + list(params: PushChannelSubscriptionParams): Promise>; + /** + * Retrieves all channels with at least one device subscribed to push notifications. Returns a {@link PaginatedResult} object, containing an array of channel names. + * + * @param params - An object containing key-value pairs to filter channels by. + * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of channel names. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. + */ + listChannels(params: PushChannelsParams): Promise>; + /** + * Unsubscribes a device, or a group of devices sharing the same `clientId` from receiving push notifications on a channel. + * + * @param subscription - A {@link PushChannelSubscription} object. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + remove(subscription: PushChannelSubscription): Promise; + /** + * Unsubscribes all devices from receiving push notifications on a channel that match the filter `params` provided. + * + * @param params - An object containing key-value pairs to filter subscriptions by. Can contain `channel`, and optionally either `clientId` or `deviceId`. + * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. + */ + removeWhere(params: PushChannelSubscriptionParams): Promise; } /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare class Rest extends Types.AbstractRest { +export declare class Rest extends AbstractRest { /** - * Construct a client object using an Ably {@link Types.ClientOptions} object. + * Construct a client object using an Ably {@link ClientOptions} object. * - * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + * @param options - A {@link ClientOptions} object to configure the client connection to Ably. */ - constructor(options: Types.ClientOptions); + constructor(options: ClientOptions); /** * Constructs a client object using an Ably API key or token string. * @@ -2657,27 +2660,27 @@ export declare class Rest extends Types.AbstractRest { /** * The cryptographic functions available in the library. */ - static Crypto: Types.Crypto; + static Crypto: Crypto; /** * Static utilities related to messages. */ - static Message: Types.MessageStatic; + static Message: MessageStatic; /** * Static utilities related to presence messages. */ - static PresenceMessage: Types.PresenceMessageStatic; + static PresenceMessage: PresenceMessageStatic; } /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ -export declare class Realtime extends Types.AbstractRealtime { +export declare class Realtime extends AbstractRealtime { /** - * Construct a client object using an Ably {@link Types.ClientOptions} object. + * Construct a client object using an Ably {@link ClientOptions} object. * - * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + * @param options - A {@link ClientOptions} object to configure the client connection to Ably. */ - constructor(options: Types.ClientOptions); + constructor(options: ClientOptions); /** * Constructs a client object using an Ably API key or token string. * @@ -2687,15 +2690,15 @@ export declare class Realtime extends Types.AbstractRealtime { /** * The cryptographic functions available in the library. */ - static Crypto: Types.Crypto; + static Crypto: Crypto; /** * Static utilities related to messages. */ - static Message: Types.MessageStatic; + static Message: MessageStatic; /** * Static utilities related to presence messages. */ - static PresenceMessage: Types.PresenceMessageStatic; + static PresenceMessage: PresenceMessageStatic; } /** diff --git a/modules.d.ts b/modules.d.ts index 6b7679ca15..e238fcf677 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -22,17 +22,25 @@ * @module */ -import { Types, ErrorInfo } from './ably'; +import { + ErrorInfo, + AbstractRest, + ClientOptions, + Crypto as CryptoClass, + MessageStatic, + PresenceMessageStatic, + AbstractRealtime, +} from './ably'; -export declare const generateRandomKey: Types.Crypto['generateRandomKey']; -export declare const getDefaultCryptoParams: Types.Crypto['getDefaultParams']; -export declare const decodeMessage: Types.MessageStatic['fromEncoded']; -export declare const decodeEncryptedMessage: Types.MessageStatic['fromEncoded']; -export declare const decodeMessages: Types.MessageStatic['fromEncodedArray']; -export declare const decodeEncryptedMessages: Types.MessageStatic['fromEncodedArray']; -export declare const decodePresenceMessage: Types.PresenceMessageStatic['fromEncoded']; -export declare const decodePresenceMessages: Types.PresenceMessageStatic['fromEncodedArray']; -export declare const constructPresenceMessage: Types.PresenceMessageStatic['fromValues']; +export declare const generateRandomKey: CryptoClass['generateRandomKey']; +export declare const getDefaultCryptoParams: CryptoClass['getDefaultParams']; +export declare const decodeMessage: MessageStatic['fromEncoded']; +export declare const decodeEncryptedMessage: MessageStatic['fromEncoded']; +export declare const decodeMessages: MessageStatic['fromEncodedArray']; +export declare const decodeEncryptedMessages: MessageStatic['fromEncodedArray']; +export declare const decodePresenceMessage: PresenceMessageStatic['fromEncoded']; +export declare const decodePresenceMessages: PresenceMessageStatic['fromEncodedArray']; +export declare const constructPresenceMessage: PresenceMessageStatic['fromValues']; /** * Provides REST-related functionality to a {@link BaseRealtime} client. @@ -46,22 +54,22 @@ export declare const constructPresenceMessage: Types.PresenceMessageStatic['from * * When provided, the following functionality becomes available: * - * - { @link ably!Types.Push | push admin } + * - { @link ably!Push | push admin } * - { @link BaseRealtime.time | retrieving Ably service time } * - { @link BaseRealtime.stats | retrieving your application’s usage statistics } * - { @link BaseRealtime.request | making arbitrary REST requests } * - { @link BaseRealtime.batchPublish | batch publishing of messages } * - { @link BaseRealtime.batchPresence | batch retrieval of channel presence state } - * - { @link ably!Types.Auth.revokeTokens | requesting the revocation of tokens } - * - { @link ably!Types.RealtimeChannel.history | retrieving the message history of a channel } - * - { @link ably!Types.RealtimePresence.history | retrieving the presence history of a channel } + * - { @link ably!Auth.revokeTokens | requesting the revocation of tokens } + * - { @link ably!RealtimeChannel.history | retrieving the message history of a channel } + * - { @link ably!RealtimePresence.history | retrieving the presence history of a channel } * * If this module is not provided, then trying to use the above functionality will cause a runtime error. */ export declare const Rest: unknown; /** - * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to encrypt and decrypt {@link ably!Types.Message} payloads. + * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to encrypt and decrypt {@link ably!Message} payloads. * * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: * @@ -70,7 +78,7 @@ export declare const Rest: unknown; * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, Crypto }); * ``` * - * When provided, you can configure message encryption on a channel via the {@link ably!Types.ChannelOptions.cipher} property of the `ChannelOptions` that you pass when {@link ably!Types.Channels.get | fetching a channel}. If this module is not provided, then passing a `ChannelOptions` with a `cipher` property will cause a runtime error. + * When provided, you can configure message encryption on a channel via the {@link ably!ChannelOptions.cipher} property of the `ChannelOptions` that you pass when {@link ably!Channels.get | fetching a channel}. If this module is not provided, then passing a `ChannelOptions` with a `cipher` property will cause a runtime error. */ export declare const Crypto: unknown; @@ -84,7 +92,7 @@ export declare const Crypto: unknown; * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, MsgPack }); * ``` * - * When provided, you can control whether the client uses MessagePack via the {@link Types.ClientOptions.useBinaryProtocol} client option. If you do not provide this module, then the library will always JSON format for encoding messages. + * When provided, you can control whether the client uses MessagePack via the {@link ClientOptions.useBinaryProtocol} client option. If you do not provide this module, then the library will always JSON format for encoding messages. */ export declare const MsgPack: unknown; @@ -98,7 +106,7 @@ export declare const MsgPack: unknown; * const realtime = new BaseRealtime(options, { WebSocketTransport, FetchRequest, RealtimePresence }); * ``` * - * If you do not provide this module, then attempting to access a channel’s {@link ably!Types.RealtimeChannel.presence} property will cause a runtime error. + * If you do not provide this module, then attempting to access a channel’s {@link ably!RealtimeChannel.presence} property will cause a runtime error. */ export declare const RealtimePresence: unknown; @@ -174,7 +182,7 @@ export declare const XHRRequest: unknown; export declare const FetchRequest: unknown; /** - * Provides a {@link BaseRealtime} instance with the ability to filter channel subscriptions at runtime using { @link ably!Types.RealtimeChannel.subscribe:WITH_MESSAGE_FILTER | the overload of `subscribe()` that accepts a `MessageFilter` }. + * Provides a {@link BaseRealtime} instance with the ability to filter channel subscriptions at runtime using { @link ably!RealtimeChannel.subscribe:WITH_MESSAGE_FILTER | the overload of `subscribe()` that accepts a `MessageFilter` }. * * To create a client that includes this module, include it in the `ModulesMap` that you pass to the {@link BaseRealtime.constructor}: * @@ -266,18 +274,18 @@ export interface ModulesMap { * * `BaseRest` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Rest`](../../default/classes/Rest.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRest extends Types.AbstractRest { +export declare class BaseRest extends AbstractRest { /** - * Construct a client object using an Ably {@link Types.ClientOptions} object. + * Construct a client object using an Ably {@link ClientOptions} object. * - * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + * @param options - A {@link ClientOptions} object to configure the client connection to Ably. * @param modules - An object which describes which functionality the client should offer. See the documentation for {@link ModulesMap}. * * You must provide at least one HTTP request implementation; that is, one of {@link FetchRequest} or {@link XHRRequest}. For minimum bundle size, favour `FetchRequest`. * * The {@link Rest} module is always implicitly included. */ - constructor(options: Types.ClientOptions, modules: ModulesMap); + constructor(options: ClientOptions, modules: ModulesMap); } /** @@ -285,11 +293,11 @@ export declare class BaseRest extends Types.AbstractRest { * * `BaseRealtime` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Realtime`](../../default/classes/Realtime.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRealtime extends Types.AbstractRealtime { +export declare class BaseRealtime extends AbstractRealtime { /** - * Construct a client object using an Ably {@link Types.ClientOptions} object. + * Construct a client object using an Ably {@link ClientOptions} object. * - * @param options - A {@link Types.ClientOptions} object to configure the client connection to Ably. + * @param options - A {@link ClientOptions} object to configure the client connection to Ably. * @param modules - An object which describes which functionality the client should offer. See the documentation for {@link ModulesMap}. * * You must provide: @@ -297,7 +305,7 @@ export declare class BaseRealtime extends Types.AbstractRealtime { * - at least one HTTP request implementation; that is, one of {@link FetchRequest} or {@link XHRRequest} — for minimum bundle size, favour `FetchRequest`; * - at least one realtime transport implementation; that is, one of {@link WebSocketTransport}, {@link XHRStreaming}, or {@link XHRPolling} — for minimum bundle size, favour `WebSocketTransport`. */ - constructor(options: Types.ClientOptions, modules: ModulesMap); + constructor(options: ClientOptions, modules: ModulesMap); } export { ErrorInfo }; diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index c2a0ef0382..b7b9f56002 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -13,11 +13,11 @@ import HttpStatusCodes from 'common/constants/HttpStatusCodes'; import Platform from '../../platform'; import Defaults from '../util/defaults'; -type BatchResult = API.Types.BatchResult; -type TokenRevocationTargetSpecifier = API.Types.TokenRevocationTargetSpecifier; -type TokenRevocationOptions = API.Types.TokenRevocationOptions; -type TokenRevocationSuccessResult = API.Types.TokenRevocationSuccessResult; -type TokenRevocationFailureResult = API.Types.TokenRevocationFailureResult; +type BatchResult = API.BatchResult; +type TokenRevocationTargetSpecifier = API.TokenRevocationTargetSpecifier; +type TokenRevocationOptions = API.TokenRevocationOptions; +type TokenRevocationSuccessResult = API.TokenRevocationSuccessResult; +type TokenRevocationFailureResult = API.TokenRevocationFailureResult; type TokenRevocationResult = BatchResult; const MAX_TOKEN_LENGTH = Math.pow(2, 17); @@ -74,7 +74,7 @@ function c14n(capability?: string | Record>) { return JSON.stringify(c14nCapability); } -function logAndValidateTokenAuthMethod(authOptions: API.Types.AuthOptions) { +function logAndValidateTokenAuthMethod(authOptions: API.AuthOptions) { if (authOptions.authCallback) { Logger.logAction(Logger.LOG_MINOR, 'Auth()', 'using token auth with authCallback'); } else if (authOptions.authUrl) { @@ -114,12 +114,12 @@ function getTokenRequestId() { class Auth { client: BaseClient; - tokenParams: API.Types.TokenParams; + tokenParams: API.TokenParams; currentTokenRequestId: number | null; waitingForTokenRequest: ReturnType | null; // This initialization is always overwritten and only used to prevent a TypeScript compiler error - authOptions: API.Types.AuthOptions = {} as API.Types.AuthOptions; - tokenDetails?: API.Types.TokenDetails | null; + authOptions: API.AuthOptions = {} as API.AuthOptions; + tokenDetails?: API.TokenDetails | null; method?: string; key?: string; basicKey?: string; @@ -141,7 +141,7 @@ class Auth { 'Warning: library initialized with a token literal without any way to renew the token when it expires (no authUrl, authCallback, or key). See https://help.ably.io/error/40171 for help' ); } - this._saveTokenOptions(options.defaultTokenParams as API.Types.TokenDetails, options); + this._saveTokenOptions(options.defaultTokenParams as API.TokenDetails, options); logAndValidateTokenAuthMethod(this.authOptions); } else { /* Basic auth */ @@ -189,7 +189,7 @@ class Auth { * * @param callback (err, tokenDetails) */ - authorize(tokenParams: API.Types.TokenParams | null, callback: Function): void; + authorize(tokenParams: API.TokenParams | null, callback: Function): void; /** * Instructs the library to get a token immediately and ensures Token Auth @@ -245,18 +245,14 @@ class Auth { * * @param callback (err, tokenDetails) */ - authorize( - tokenParams: API.Types.TokenParams | null, - authOptions: API.Types.AuthOptions | null, - callback: Function - ): void; + authorize(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null, callback: Function): void; authorize( tokenParams: Record | Function | null, - authOptions?: API.Types.AuthOptions | null | Function, + authOptions?: API.AuthOptions | null | Function, callback?: Function ): void | Promise { - let _authOptions: API.Types.AuthOptions | null; + let _authOptions: API.AuthOptions | null; /* shuffle and normalise arguments as necessary */ if (typeof tokenParams == 'function' && !callback) { callback = tokenParams; @@ -265,7 +261,7 @@ class Auth { callback = authOptions; _authOptions = null; } else { - _authOptions = authOptions as API.Types.AuthOptions; + _authOptions = authOptions as API.AuthOptions; } if (!callback) { return Utils.promisify(this, 'authorize', arguments); @@ -278,9 +274,9 @@ class Auth { } this._forceNewToken( - tokenParams as API.Types.TokenParams, + tokenParams as API.TokenParams, _authOptions, - (err: ErrorInfo, tokenDetails: API.Types.TokenDetails) => { + (err: ErrorInfo, tokenDetails: API.TokenDetails) => { if (err) { if ((this.client as BaseRealtime).connection && err.statusCode === HttpStatusCodes.Forbidden) { /* Per RSA4d & RSA4d1, if the auth server explicitly repudiates our right to @@ -309,11 +305,7 @@ class Auth { /* For internal use, eg by connectionManager - useful when want to call back * as soon as we have the new token, rather than waiting for it to take * effect on the connection as #authorize does */ - _forceNewToken( - tokenParams: API.Types.TokenParams | null, - authOptions: API.Types.AuthOptions | null, - callback: Function - ) { + _forceNewToken(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null, callback: Function) { /* get rid of current token even if still valid */ this.tokenDetails = null; @@ -324,7 +316,7 @@ class Auth { logAndValidateTokenAuthMethod(this.authOptions); - this._ensureValidAuthCredentials(true, (err: ErrorInfo | null, tokenDetails?: API.Types.TokenDetails) => { + this._ensureValidAuthCredentials(true, (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) => { /* RSA10g */ delete this.tokenParams.timestamp; delete this.authOptions.queryTime; @@ -336,7 +328,7 @@ class Auth { * Request an access token * @param callback (err, tokenDetails) */ - requestToken(callback: StandardCallback): void; + requestToken(callback: StandardCallback): void; /** * Request an access token @@ -358,7 +350,7 @@ class Auth { * * @param callback (err, tokenDetails) */ - requestToken(tokenParams: API.Types.TokenParams | null, callback: StandardCallback): void; + requestToken(tokenParams: API.TokenParams | null, callback: StandardCallback): void; /** * Request an access token @@ -407,15 +399,15 @@ class Auth { * @param callback (err, tokenDetails) */ requestToken( - tokenParams: API.Types.TokenParams | null, - authOptions: API.Types.AuthOptions, - callback: StandardCallback + tokenParams: API.TokenParams | null, + authOptions: API.AuthOptions, + callback: StandardCallback ): void; requestToken( - tokenParams: API.Types.TokenParams | StandardCallback | null, - authOptions?: any | StandardCallback, - callback?: StandardCallback + tokenParams: API.TokenParams | StandardCallback | null, + authOptions?: any | StandardCallback, + callback?: StandardCallback ): void | Promise { /* shuffle and normalise arguments as necessary */ if (typeof tokenParams == 'function' && !callback) { @@ -645,7 +637,7 @@ class Auth { ) ); } else { - _callback(null, { token: tokenRequestOrDetails } as API.Types.TokenDetails); + _callback(null, { token: tokenRequestOrDetails } as API.TokenDetails); } return; } @@ -685,7 +677,7 @@ class Auth { tokenRequestOrDetails, function ( err?: ErrorInfo | ErrnoException | null, - tokenResponse?: API.Types.TokenDetails | string, + tokenResponse?: API.TokenDetails | string, headers?: Record, unpacked?: boolean ) { @@ -700,7 +692,7 @@ class Auth { } if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); - _callback(null, tokenResponse as API.Types.TokenDetails); + _callback(null, tokenResponse as API.TokenDetails); } ); }); @@ -741,7 +733,7 @@ class Auth { * * @param callback */ - createTokenRequest(tokenParams: API.Types.TokenParams | null, authOptions: any, callback: Function) { + createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any, callback: Function) { /* shuffle and normalise arguments as necessary */ if (typeof tokenParams == 'function' && !callback) { callback = tokenParams; @@ -756,7 +748,7 @@ class Auth { /* RSA9h: if authOptions passed in, they're used instead of stored, don't merge them */ authOptions = authOptions || this.authOptions; - tokenParams = tokenParams || Utils.copy(this.tokenParams); + tokenParams = tokenParams || Utils.copy(this.tokenParams); const key = authOptions.key; if (!key) { @@ -830,7 +822,7 @@ class Auth { getAuthParams(callback: Function) { if (this.method == 'basic') callback(null, { key: this.key }); else - this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.Types.TokenDetails) { + this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { if (err) { callback(err); return; @@ -850,7 +842,7 @@ class Auth { if (this.method == 'basic') { callback(null, { authorization: 'Basic ' + this.basicKey }); } else { - this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.Types.TokenDetails) { + this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { if (err) { callback(err); return; @@ -885,7 +877,7 @@ class Auth { return this.client.serverTimeOffset !== null; } - _saveBasicOptions(authOptions: API.Types.AuthOptions) { + _saveBasicOptions(authOptions: API.AuthOptions) { this.method = 'basic'; this.key = authOptions.key; this.basicKey = Utils.toBase64(authOptions.key as string); @@ -895,7 +887,7 @@ class Auth { } } - _saveTokenOptions(tokenParams: API.Types.TokenParams | null, authOptions: API.Types.AuthOptions | null) { + _saveTokenOptions(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null) { this.method = 'token'; if (tokenParams) { @@ -911,7 +903,7 @@ class Auth { /* options.token may contain a token string or, for convenience, a TokenDetails */ authOptions.tokenDetails = typeof authOptions.token === 'string' - ? ({ token: authOptions.token } as API.Types.TokenDetails) + ? ({ token: authOptions.token } as API.TokenDetails) : authOptions.token; } @@ -931,7 +923,7 @@ class Auth { * progress, making all pending callbacks wait for the new one */ _ensureValidAuthCredentials( forceSupersede: boolean, - callback: (err: ErrorInfo | null, token?: API.Types.TokenDetails) => void + callback: (err: ErrorInfo | null, token?: API.TokenDetails) => void ) { const token = this.tokenDetails; @@ -967,7 +959,7 @@ class Auth { /* Request a new token */ const tokenRequestId = (this.currentTokenRequestId = getTokenRequestId()); - this.requestToken(this.tokenParams, this.authOptions, (err: Function, tokenResponse?: API.Types.TokenDetails) => { + this.requestToken(this.tokenParams, this.authOptions, (err: Function, tokenResponse?: API.TokenDetails) => { if ((this.currentTokenRequestId as number) > tokenRequestId) { Logger.logAction( Logger.LOG_MINOR, diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 70eb307bff..2ee0ed41d3 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -18,13 +18,13 @@ import { MsgPack } from 'common/types/msgpack'; import { HTTPRequestImplementations } from 'platform/web/lib/http/http'; import { FilteredSubscriptions } from './filteredsubscriptions'; -type BatchResult = API.Types.BatchResult; -type BatchPublishSpec = API.Types.BatchPublishSpec; -type BatchPublishSuccessResult = API.Types.BatchPublishSuccessResult; -type BatchPublishFailureResult = API.Types.BatchPublishFailureResult; +type BatchResult = API.BatchResult; +type BatchPublishSpec = API.BatchPublishSpec; +type BatchPublishSuccessResult = API.BatchPublishSuccessResult; +type BatchPublishFailureResult = API.BatchPublishFailureResult; type BatchPublishResult = BatchResult; -type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult; -type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; +type BatchPresenceSuccessResult = API.BatchPresenceSuccessResult; +type BatchPresenceFailureResult = API.BatchPresenceFailureResult; type BatchPresenceResult = BatchResult; /** diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 821b86b6e5..745d7bec3e 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -143,7 +143,7 @@ class Channels extends EventEmitter { * events) imply connection state changes for any channel which is either * attached, pending, or will attempt to become attached in the future */ propogateConnectionInterruption(connectionState: string, reason: ErrorInfo) { - const connectionStateToChannelState: Record = { + const connectionStateToChannelState: Record = { closing: 'detached', closed: 'detached', failed: 'failed', @@ -178,7 +178,7 @@ class Channels extends EventEmitter { return channel; } - getDerived(name: string, deriveOptions: API.Types.DeriveOptions, channelOptions?: ChannelOptions) { + getDerived(name: string, deriveOptions: API.DeriveOptions, channelOptions?: ChannelOptions) { if (deriveOptions.filter) { const filter = Utils.toBase64(deriveOptions.filter); const match = Utils.matchDerivedChannel(name); diff --git a/src/common/lib/client/filteredsubscriptions.ts b/src/common/lib/client/filteredsubscriptions.ts index 1588fa4962..dbe4087613 100644 --- a/src/common/lib/client/filteredsubscriptions.ts +++ b/src/common/lib/client/filteredsubscriptions.ts @@ -3,13 +3,9 @@ import RealtimeChannel from './realtimechannel'; import Message from '../types/message'; export class FilteredSubscriptions { - static subscribeFilter( - channel: RealtimeChannel, - filter: API.Types.MessageFilter, - listener: API.Types.messageCallback - ) { + static subscribeFilter(channel: RealtimeChannel, filter: API.MessageFilter, listener: API.messageCallback) { const filteredListener = (m: Message) => { - const mapping: { [key in keyof API.Types.MessageFilter]: any } = { + const mapping: { [key in keyof API.MessageFilter]: any } = { name: m.name, refTimeserial: m.extras?.ref?.timeserial, refType: m.extras?.ref?.type, @@ -19,7 +15,7 @@ export class FilteredSubscriptions { // Check if any values are defined in the filter and if they match the value in the message object if ( Object.entries(filter).find(([key, value]) => - value !== undefined ? mapping[key as keyof API.Types.MessageFilter] !== value : false + value !== undefined ? mapping[key as keyof API.MessageFilter] !== value : false ) ) { return; @@ -33,36 +29,36 @@ export class FilteredSubscriptions { // Adds a new filtered subscription static addFilteredSubscription( channel: RealtimeChannel, - filter: API.Types.MessageFilter, - realListener: API.Types.messageCallback, - filteredListener: API.Types.messageCallback + filter: API.MessageFilter, + realListener: API.messageCallback, + filteredListener: API.messageCallback ) { if (!channel.filteredSubscriptions) { channel.filteredSubscriptions = new Map< - API.Types.messageCallback, - Map[]> + API.messageCallback, + Map[]> >(); } if (channel.filteredSubscriptions.has(realListener)) { const realListenerMap = channel.filteredSubscriptions.get(realListener) as Map< - API.Types.MessageFilter, - API.Types.messageCallback[] + API.MessageFilter, + API.messageCallback[] >; // Add the filtered listener to the map, or append to the array if this filter has already been used realListenerMap.set(filter, realListenerMap?.get(filter)?.concat(filteredListener) || [filteredListener]); } else { channel.filteredSubscriptions.set( realListener, - new Map[]>([[filter, [filteredListener]]]) + new Map[]>([[filter, [filteredListener]]]) ); } } static getAndDeleteFilteredSubscriptions( channel: RealtimeChannel, - filter: API.Types.MessageFilter | undefined, - realListener: API.Types.messageCallback | undefined - ): API.Types.messageCallback[] { + filter: API.MessageFilter | undefined, + realListener: API.messageCallback | undefined + ): API.messageCallback[] { // No filtered subscriptions map means there has been no filtered subscriptions yet, so return nothing if (!channel.filteredSubscriptions) { return []; @@ -82,9 +78,9 @@ export class FilteredSubscriptions { return listenerMaps; }) .reduce( - (prev, cur) => (cur ? (prev as API.Types.messageCallback[]).concat(...cur) : prev), + (prev, cur) => (cur ? (prev as API.messageCallback[]).concat(...cur) : prev), [] - ) as API.Types.messageCallback[]; + ) as API.messageCallback[]; } // No subscriptions for this listener @@ -92,8 +88,8 @@ export class FilteredSubscriptions { return []; } const realListenerMap = channel.filteredSubscriptions.get(realListener) as Map< - API.Types.MessageFilter, - API.Types.messageCallback[] + API.MessageFilter, + API.messageCallback[] >; // If no filter is specified return all listeners using that function if (!filter) { diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 27d409ea38..1b2ba59f45 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -39,7 +39,7 @@ interface RealtimeHistoryParams { const noop = function () {}; -function validateChannelOptions(options?: API.Types.ChannelOptions) { +function validateChannelOptions(options?: API.ChannelOptions) { if (options && 'params' in options && !Utils.isObject(options.params)) { return new ErrorInfo('options.params must be an object', 40000, 400); } @@ -72,19 +72,16 @@ class RealtimeChannel extends EventEmitter { return this._presence; } connectionManager: ConnectionManager; - state: API.Types.ChannelState; + state: API.ChannelState; subscriptions: EventEmitter; - filteredSubscriptions?: Map< - API.Types.messageCallback, - Map[]> - >; + filteredSubscriptions?: Map, Map[]>>; syncChannelSerial?: string | null; properties: { attachSerial: string | null | undefined; channelSerial: string | null | undefined; }; errorReason: ErrorInfo | string | null; - _requestedFlags: Array | null; + _requestedFlags: Array | null; _mode?: null | number; _attachResume: boolean; _decodingContext: EncodingDecodingContext; @@ -100,7 +97,7 @@ class RealtimeChannel extends EventEmitter { retryTimer?: number | NodeJS.Timeout | null; retryCount: number = 0; - constructor(client: BaseRealtime, name: string, options?: API.Types.ChannelOptions) { + constructor(client: BaseRealtime, name: string, options?: API.ChannelOptions) { super(); Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel()', 'started; name = ' + name); this.name = name; @@ -156,7 +153,7 @@ class RealtimeChannel extends EventEmitter { return args; } - setOptions(options?: API.Types.ChannelOptions, callback?: ErrCallback): void | Promise { + setOptions(options?: API.ChannelOptions, callback?: ErrCallback): void | Promise { if (!callback) { return Utils.promisify(this, 'setOptions', arguments); } @@ -204,7 +201,7 @@ class RealtimeChannel extends EventEmitter { } } - _shouldReattachToSetOptions(options?: API.Types.ChannelOptions) { + _shouldReattachToSetOptions(options?: API.ChannelOptions) { if (!(this.state === 'attached' || this.state === 'attaching')) { return false; } @@ -376,7 +373,7 @@ class RealtimeChannel extends EventEmitter { if (this._requestedFlags) { attachMsg.encodeModesToFlags(this._requestedFlags); } else if (this.channelOptions.modes) { - attachMsg.encodeModesToFlags(Utils.allToUpperCase(this.channelOptions.modes) as API.Types.ChannelMode[]); + attachMsg.encodeModesToFlags(Utils.allToUpperCase(this.channelOptions.modes) as API.ChannelMode[]); } if (this._attachResume) { attachMsg.setFlag('ATTACH_RESUME'); @@ -720,7 +717,7 @@ class RealtimeChannel extends EventEmitter { } notifyState( - state: API.Types.ChannelState, + state: API.ChannelState, reason?: ErrorInfo | null, resumed?: boolean, hasPresence?: boolean, @@ -780,7 +777,7 @@ class RealtimeChannel extends EventEmitter { this.emit(state, change); } - requestState(state: API.Types.ChannelState, reason?: ErrorInfo | null): void { + requestState(state: API.ChannelState, reason?: ErrorInfo | null): void { Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel.requestState', 'name = ' + this.name + ', state = ' + state); this.notifyState(state, reason); /* send the event and await response */ @@ -958,7 +955,7 @@ class RealtimeChannel extends EventEmitter { } } - status(callback?: StandardCallback): void | Promise { + status(callback?: StandardCallback): void | Promise { return this.client.rest.channelMixin.status(this, callback); } } diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 6c1fa7eae5..c61c22215d 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -19,20 +19,20 @@ import { useTokenAuth } from './auth'; import { RestChannelMixin } from './restchannelmixin'; import { RestPresenceMixin } from './restpresencemixin'; -type BatchResult = API.Types.BatchResult; +type BatchResult = API.BatchResult; -type BatchPublishSpec = API.Types.BatchPublishSpec; -type BatchPublishSuccessResult = API.Types.BatchPublishSuccessResult; -type BatchPublishFailureResult = API.Types.BatchPublishFailureResult; +type BatchPublishSpec = API.BatchPublishSpec; +type BatchPublishSuccessResult = API.BatchPublishSuccessResult; +type BatchPublishFailureResult = API.BatchPublishFailureResult; type BatchPublishResult = BatchResult; -type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult; -type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; +type BatchPresenceSuccessResult = API.BatchPresenceSuccessResult; +type BatchPresenceFailureResult = API.BatchPresenceFailureResult; type BatchPresenceResult = BatchResult; -type TokenRevocationTargetSpecifier = API.Types.TokenRevocationTargetSpecifier; -type TokenRevocationOptions = API.Types.TokenRevocationOptions; -type TokenRevocationSuccessResult = API.Types.TokenRevocationSuccessResult; -type TokenRevocationFailureResult = API.Types.TokenRevocationFailureResult; +type TokenRevocationTargetSpecifier = API.TokenRevocationTargetSpecifier; +type TokenRevocationOptions = API.TokenRevocationOptions; +type TokenRevocationSuccessResult = API.TokenRevocationSuccessResult; +type TokenRevocationFailureResult = API.TokenRevocationFailureResult; type TokenRevocationResult = BatchResult; const noop = function () {}; diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 95fe706227..08fda3be76 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -153,7 +153,7 @@ class RestChannel { ); } - status(callback?: StandardCallback): void | Promise { + status(callback?: StandardCallback): void | Promise { return this.client.rest.channelMixin.status(this, callback); } } diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts index 2e3ffc84f1..967ef19b0e 100644 --- a/src/common/lib/client/restchannelmixin.ts +++ b/src/common/lib/client/restchannelmixin.ts @@ -46,8 +46,8 @@ export class RestChannelMixin { static status( channel: RestChannel | RealtimeChannel, - callback?: StandardCallback - ): void | Promise { + callback?: StandardCallback + ): void | Promise { if (typeof callback !== 'function') { return Utils.promisify(this, 'status', [channel]); } @@ -55,13 +55,6 @@ export class RestChannelMixin { const format = channel.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; const headers = Defaults.defaultPostHeaders(channel.client.options, { format }); - Resource.get( - channel.client, - this.basePath(channel), - headers, - {}, - format, - callback || noop - ); + Resource.get(channel.client, this.basePath(channel), headers, {}, format, callback || noop); } } diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index af1f9a020d..02420ea393 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -382,7 +382,7 @@ abstract class CometTransport extends Transport { * Now that we’ve dropped JSONP support, we may be able to revisit the above; * see https://github.com/ably/ably-js/issues/1214. */ - onAuthUpdated = (tokenDetails: API.Types.TokenDetails): void => { + onAuthUpdated = (tokenDetails: API.TokenDetails): void => { this.authParams = { access_token: tokenDetails.token }; }; } diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 3b4484fde4..3f30237475 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1748,7 +1748,7 @@ class ConnectionManager extends EventEmitter { this.notifyState({ state: 'closed' }); } - onAuthUpdated(tokenDetails: API.Types.TokenDetails, callback: Function): void { + onAuthUpdated(tokenDetails: API.TokenDetails, callback: Function): void { switch (this.state.state) { case 'connected': { Logger.logAction( diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 76e9b49f94..1042e303ef 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -321,7 +321,7 @@ abstract class Transport extends EventEmitter { transport.connect(); } - onAuthUpdated?: (tokenDetails: API.Types.TokenDetails) => void; + onAuthUpdated?: (tokenDetails: API.TokenDetails) => void; } export default Transport; diff --git a/src/common/lib/types/defaultmessage.ts b/src/common/lib/types/defaultmessage.ts index 34135869fd..9350098c84 100644 --- a/src/common/lib/types/defaultmessage.ts +++ b/src/common/lib/types/defaultmessage.ts @@ -15,11 +15,11 @@ import { ChannelOptions } from 'common/types/channel'; `DefaultMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `Message` static property. It introduces the static methods described in the `MessageStatic` interface of the public API of the non tree-shakable version of the library. */ export class DefaultMessage extends Message { - static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { + static async fromEncoded(encoded: unknown, inputOptions?: API.ChannelOptions): Promise { return fromEncoded(Platform.Crypto, encoded, inputOptions); } - static async fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Promise { + static async fromEncodedArray(encodedArray: Array, options?: API.ChannelOptions): Promise { return fromEncodedArray(Platform.Crypto, encodedArray, options); } diff --git a/src/common/lib/types/defaultpresencemessage.ts b/src/common/lib/types/defaultpresencemessage.ts index 5f1ceb6575..5cc5c57c62 100644 --- a/src/common/lib/types/defaultpresencemessage.ts +++ b/src/common/lib/types/defaultpresencemessage.ts @@ -5,13 +5,13 @@ import PresenceMessage, { fromEncoded, fromEncodedArray, fromValues } from './pr `DefaultPresenceMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `PresenceMessage` static property. It introduces the static methods described in the `PresenceMessageStatic` interface of the public API of the non tree-shakable version of the library. */ export class DefaultPresenceMessage extends PresenceMessage { - static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { + static async fromEncoded(encoded: unknown, inputOptions?: API.ChannelOptions): Promise { return fromEncoded(encoded, inputOptions); } static async fromEncodedArray( encodedArray: Array, - options?: API.Types.ChannelOptions + options?: API.ChannelOptions ): Promise { return fromEncodedArray(encodedArray, options); } diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index aafba56ca9..828cba66d9 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -42,7 +42,7 @@ function normaliseContext(context: CipherOptions | EncodingDecodingContext | Cha function normalizeCipherOptions( Crypto: IUntypedCryptoStatic | null, - options: API.Types.ChannelOptions | null + options: API.ChannelOptions | null ): ChannelOptions { if (options && options.cipher) { if (!Crypto) Utils.throwMissingModuleError('Crypto'); @@ -75,7 +75,7 @@ function getMessageSize(msg: Message) { export async function fromEncoded( Crypto: IUntypedCryptoStatic | null, encoded: unknown, - inputOptions?: API.Types.ChannelOptions + inputOptions?: API.ChannelOptions ): Promise { const msg = fromValues(encoded); const options = normalizeCipherOptions(Crypto, inputOptions ?? null); @@ -92,7 +92,7 @@ export async function fromEncoded( export async function fromEncodedArray( Crypto: IUntypedCryptoStatic | null, encodedArray: Array, - options?: API.Types.ChannelOptions + options?: API.ChannelOptions ): Promise { return Promise.all( encodedArray.map(function (encoded) { diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index b9969df241..80a1b13fd5 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -11,7 +11,7 @@ function toActionValue(actionString: string) { return actions.indexOf(actionString); } -export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { +export async function fromEncoded(encoded: unknown, options?: API.ChannelOptions): Promise { const msg = fromValues(encoded as PresenceMessage | Record, true); /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ @@ -25,7 +25,7 @@ export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelO export async function fromEncodedArray( encodedArray: unknown[], - options?: API.Types.ChannelOptions + options?: API.ChannelOptions ): Promise { return Promise.all( encodedArray.map(function (encoded) { diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index 945d09378d..34f59bd386 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -1,5 +1,5 @@ import { MsgPack } from 'common/types/msgpack'; -import { Types } from '../../../../ably'; +import * as API from '../../../../ably'; import { PresenceMessageModule } from '../client/modulesmap'; import * as Utils from '../util/utils'; import ErrorInfo from './errorinfo'; @@ -159,7 +159,7 @@ class ProtocolMessage { return ((this.flags as number) & flags[flag]) > 0; }; - setFlag(flag: Types.ChannelMode): number { + setFlag(flag: API.ChannelMode): number { return (this.flags = (this.flags as number) | flags[flag]); } @@ -167,7 +167,7 @@ class ProtocolMessage { return this.flags && this.flags & flags.MODE_ALL; } - encodeModesToFlags(modes: Types.ChannelMode[]): void { + encodeModesToFlags(modes: API.ChannelMode[]): void { modes.forEach((mode) => this.setFlag(mode)); } diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index 98c96f046d..ec4a310443 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -6,7 +6,7 @@ export type RestAgentOptions = { maxSockets: number; }; -export default interface ClientOptions extends API.Types.ClientOptions { +export default interface ClientOptions extends API.ClientOptions { restAgentOptions?: RestAgentOptions; pushFullWait?: boolean; agents?: Record; diff --git a/src/common/types/ICryptoStatic.ts b/src/common/types/ICryptoStatic.ts index 4115c5d248..b4b6b7223a 100644 --- a/src/common/types/ICryptoStatic.ts +++ b/src/common/types/ICryptoStatic.ts @@ -1,14 +1,14 @@ import * as API from '../../../ably'; import ICipher from './ICipher'; -export type IGetCipherParams = (API.Types.CipherParams | API.Types.CipherParamOptions) & { iv?: IV }; +export type IGetCipherParams = (API.CipherParams | API.CipherParamOptions) & { iv?: IV }; export interface IGetCipherReturnValue { cipher: Cipher; - cipherParams: API.Types.CipherParams; + cipherParams: API.CipherParams; } export default interface ICryptoStatic - extends API.Types.Crypto { + extends API.Crypto { getCipher( params: IGetCipherParams ): IGetCipherReturnValue>; @@ -18,6 +18,6 @@ export default interface ICryptoStatic { + static async generateRandomKey(keyLength?: number): Promise { return new Promise((resolve, reject) => { generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { if (err) { diff --git a/src/platform/react-hooks/sample-app/src/App.tsx b/src/platform/react-hooks/sample-app/src/App.tsx index aafceb74ec..3ca2e06a5b 100644 --- a/src/platform/react-hooks/sample-app/src/App.tsx +++ b/src/platform/react-hooks/sample-app/src/App.tsx @@ -1,4 +1,4 @@ -import { Types, ErrorInfo } from 'ably'; +import * as Ably from 'ably'; import React, { useState } from 'react'; import { useChannel, @@ -10,7 +10,7 @@ import { import './App.css'; function App() { - const [messages, updateMessages] = useState([]); + const [messages, updateMessages] = useState([]); const [skip, setSkip] = useState(false); const { channel, ably } = useChannel({ channelName: 'your-channel-name', skip }, (message) => { updateMessages((prev) => [...prev, message]); @@ -32,8 +32,8 @@ function App() { const [ablyErr, setAblyErr] = useState(''); const [channelState, setChannelState] = useState(channel.state); - const [previousChannelState, setPreviousChannelState] = useState(); - const [channelStateReason, setChannelStateReason] = useState(); + const [previousChannelState, setPreviousChannelState] = useState(); + const [channelStateReason, setChannelStateReason] = useState(); useChannelStateListener('your-channel-name', (stateChange) => { setAblyErr(JSON.stringify(stateChange.reason)); @@ -106,7 +106,7 @@ function App() { function ConnectionState() { const ably = useAbly(); const [connectionState, setConnectionState] = useState(ably.connection.state); - const [connectionError, setConnectionError] = useState(null); + const [connectionError, setConnectionError] = useState(null); useConnectionStateListener((stateChange) => { console.log(stateChange); setConnectionState(stateChange.current); diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index 855de7a0d2..cfd2ef50e7 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -1,18 +1,17 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import * as Ably from 'ably'; -import { Types } from '../../../../ably.js'; import React, { useMemo } from 'react'; const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'function'; interface AblyProviderProps { children?: React.ReactNode | React.ReactNode[] | null; - client?: Ably.Types.AbstractRealtime; + client?: Ably.Ably.AbstractRealtime; id?: string; } -type AblyContextType = React.Context; +type AblyContextType = React.Context; // An object is appended to `React.createContext` which stores all contexts // indexed by id, which is used by useAbly to find the correct context when an diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 1d45ae9ccc..e5a36689aa 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -1,14 +1,14 @@ -import { Types, ErrorInfo } from '../../../../ably.js'; +import * as Ably from '../../../../ably.js'; export type ChannelNameAndOptions = { channelName: string; - options?: Types.ChannelOptions; + options?: Ably.ChannelOptions; id?: string; subscribeOnly?: boolean; skip?: boolean; - onConnectionError?: (error: ErrorInfo) => unknown; - onChannelError?: (error: ErrorInfo) => unknown; + onConnectionError?: (error: Ably.ErrorInfo) => unknown; + onChannelError?: (error: Ably.ErrorInfo) => unknown; }; export type ChannelNameAndId = { @@ -19,7 +19,7 @@ export type ChannelParameters = string | ChannelNameAndOptions; export const version = '1.2.47'; -export function channelOptionsWithAgent(options?: Types.ChannelOptions) { +export function channelOptionsWithAgent(options?: Ably.ChannelOptions) { return { ...options, params: { diff --git a/src/platform/react-hooks/src/fakes/ably.ts b/src/platform/react-hooks/src/fakes/ably.ts index 6f0695b100..1c265bfa34 100644 --- a/src/platform/react-hooks/src/fakes/ably.ts +++ b/src/platform/react-hooks/src/fakes/ably.ts @@ -1,4 +1,4 @@ -import { Types } from 'ably'; +import * as Ably from 'ably'; export class FakeAblySdk { public clientId: string; @@ -79,7 +79,7 @@ class EventEmitter { } class Connection extends EventEmitter { - state: Types.ConnectionState; + state: Ably.ConnectionState; constructor() { super(); @@ -125,16 +125,16 @@ export class ClientSingleChannelConnection extends EventEmitter { this.state = 'attached'; } - publish(messages: any, callback?: Types.errorCallback): void; - publish(name: string, messages: any, callback?: Types.errorCallback): void; - publish(name: string, messages: any, options?: Types.PublishOptions, callback?: Types.errorCallback): void; + publish(messages: any, callback?: Ably.errorCallback): void; + publish(name: string, messages: any, callback?: Ably.errorCallback): void; + publish(name: string, messages: any, options?: Ably.PublishOptions, callback?: Ably.errorCallback): void; public publish(...rest: any[]) { this.channel.publish(this.client.clientId, rest); } public async subscribe( - eventOrCallback: Types.messageCallback | string | Array, - listener?: Types.messageCallback + eventOrCallback: Ably.messageCallback | string | Array, + listener?: Ably.messageCallback ) { this.channel.subscribe(this.client.clientId, eventOrCallback, listener); } @@ -222,14 +222,14 @@ export class Channel { this.presence = new ChannelPresence(this); } - publish(clientId: string, messages: any, callback?: Types.errorCallback): void; - publish(clientId: string, name: string, messages: any, callback?: Types.errorCallback): void; + publish(clientId: string, messages: any, callback?: Ably.errorCallback): void; + publish(clientId: string, name: string, messages: any, callback?: Ably.errorCallback): void; publish( clientId: string, name: string, messages: any, - options?: Types.PublishOptions, - callback?: Types.errorCallback + options?: Ably.PublishOptions, + callback?: Ably.errorCallback ): void; public publish(clientId: string, ...rest: any[]) { const name = rest.length <= 2 ? '' : rest[0]; @@ -257,8 +257,8 @@ export class Channel { public async subscribe( clientId: string, - eventOrCallback: Types.messageCallback | string | Array, - listener?: Types.messageCallback + eventOrCallback: Ably.messageCallback | string | Array, + listener?: Ably.messageCallback ) { if (!this.subscriptionsPerClient.has(clientId)) { this.subscriptionsPerClient.set(clientId, new Map()); diff --git a/src/platform/react-hooks/src/hooks/useAbly.ts b/src/platform/react-hooks/src/hooks/useAbly.ts index 12728583fb..470bbc24c1 100644 --- a/src/platform/react-hooks/src/hooks/useAbly.ts +++ b/src/platform/react-hooks/src/hooks/useAbly.ts @@ -2,8 +2,8 @@ import React from 'react'; import { getContext } from '../AblyProvider.js'; import * as API from '../../../../../ably.js'; -export function useAbly(id = 'default'): API.Types.AbstractRealtime { - const client = React.useContext(getContext(id)) as API.Types.AbstractRealtime; +export function useAbly(id = 'default'): API.AbstractRealtime { + const client = React.useContext(getContext(id)) as API.AbstractRealtime; if (!client) { throw new Error( diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 91db939496..fea775df45 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -3,12 +3,12 @@ import { it, beforeEach, describe, expect, vi } from 'vitest'; import { useChannel } from './useChannel.js'; import { render, screen, waitFor } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; -import { Types, ErrorInfo } from '../../../../../ably.js'; +import * as Ably from '../../../../../ably.js'; import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useChannel', () => { @@ -57,7 +57,7 @@ describe('useChannel', () => { it('useChannel works with multiple clients', async () => { renderInCtxProvider( ablyClient, - + ); @@ -177,7 +177,7 @@ describe('useChannel', () => { }); const UseChannelComponentMultipleClients = () => { - const [messages, updateMessages] = useState([]); + const [messages, updateMessages] = useState([]); useChannel({ channelName: 'blah' }, (message) => { updateMessages((prev) => [...prev, message]); }); @@ -191,7 +191,7 @@ const UseChannelComponentMultipleClients = () => { }; const UseChannelComponent = ({ skip }: { skip?: boolean }) => { - const [messages, updateMessages] = useState([]); + const [messages, updateMessages] = useState([]); useChannel({ channelName: 'blah', skip }, (message) => { updateMessages((prev) => [...prev, message]); }); @@ -202,8 +202,8 @@ const UseChannelComponent = ({ skip }: { skip?: boolean }) => { }; interface UseChannelStateErrorsComponentProps { - onConnectionError?: (err: ErrorInfo) => unknown; - onChannelError?: (err: ErrorInfo) => unknown; + onConnectionError?: (err: Ably.ErrorInfo) => unknown; + onChannelError?: (err: Ably.ErrorInfo) => unknown; } const UseChannelStateErrorsComponent = ({ onConnectionError, onChannelError }: UseChannelStateErrorsComponentProps) => { diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index 7ef289de5f..47218f7320 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -1,16 +1,16 @@ -import { Types, ErrorInfo } from '../../../../../ably.js'; +import * as Ably from '../../../../../ably.js'; import { useEffect, useMemo, useRef } from 'react'; import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; -export type AblyMessageCallback = Types.messageCallback; +export type AblyMessageCallback = Ably.messageCallback; export interface ChannelResult { - channel: Types.RealtimeChannel; - ably: Types.AbstractRealtime; - connectionError: ErrorInfo | null; - channelError: ErrorInfo | null; + channel: Ably.RealtimeChannel; + ably: Ably.AbstractRealtime; + connectionError: Ably.ErrorInfo | null; + channelError: Ably.ErrorInfo | null; } type SubscribeArgs = [string, AblyMessageCallback] | [AblyMessageCallback]; @@ -88,11 +88,11 @@ export function useChannel( return { channel, ably, connectionError, channelError }; } -async function handleChannelMount(channel: Types.RealtimeChannel, ...subscribeArgs: SubscribeArgs) { +async function handleChannelMount(channel: Ably.RealtimeChannel, ...subscribeArgs: SubscribeArgs) { await (channel.subscribe as any)(...subscribeArgs); } -async function handleChannelUnmount(channel: Types.RealtimeChannel, ...subscribeArgs: SubscribeArgs) { +async function handleChannelUnmount(channel: Ably.RealtimeChannel, ...subscribeArgs: SubscribeArgs) { await (channel.unsubscribe as any)(...subscribeArgs); setTimeout(async () => { diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx index d3026cdb96..3187087a57 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx @@ -4,12 +4,12 @@ import { useChannelStateListener } from './useChannelStateListener.js'; import { useState } from 'react'; import { render, screen } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; -import { Types } from 'ably'; +import * as Ably from 'ably'; import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useChannelStateListener', () => { @@ -73,7 +73,7 @@ describe('useChannelStateListener', () => { }); const UseChannelStateListenerComponent = () => { - const [channelState, setChannelState] = useState('initialized'); + const [channelState, setChannelState] = useState('initialized'); useChannelStateListener('blah', (stateChange) => { setChannelState(stateChange.current); @@ -83,11 +83,11 @@ const UseChannelStateListenerComponent = () => { }; interface UseChannelStateListenerComponentNamedEventsProps { - event: Types.ChannelState | Types.ChannelState[]; + event: Ably.ChannelState | Ably.ChannelState[]; } const UseChannelStateListenerComponentNamedEvents = ({ event }: UseChannelStateListenerComponentNamedEventsProps) => { - const [channelState, setChannelState] = useState('initialized'); + const [channelState, setChannelState] = useState('initialized'); useChannelStateListener('blah', event, (stateChange) => { setChannelState(stateChange.current); diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts index 4c38c92a4a..06638172c6 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts @@ -1,23 +1,23 @@ import { useEffect, useRef } from 'react'; -import { Types } from '../../../../../ably.js'; +import * as Ably from '../../../../../ably.js'; import { ChannelNameAndId, ChannelNameAndOptions, channelOptionsWithAgent } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; -type ChannelStateListener = (stateChange: Types.ChannelStateChange) => any; +type ChannelStateListener = (stateChange: Ably.ChannelStateChange) => any; export function useChannelStateListener(channelName: string, listener?: ChannelStateListener); export function useChannelStateListener( options: ChannelNameAndOptions | string, - state?: Types.ChannelState | Types.ChannelState[], + state?: Ably.ChannelState | Ably.ChannelState[], listener?: ChannelStateListener ); export function useChannelStateListener( channelNameOrNameAndId: ChannelNameAndOptions | string, - stateOrListener?: Types.ChannelState | Types.ChannelState[] | ChannelStateListener, - listener?: (stateChange: Types.ChannelStateChange) => any + stateOrListener?: Ably.ChannelState | Ably.ChannelState[] | ChannelStateListener, + listener?: (stateChange: Ably.ChannelStateChange) => any ) { const channelHookOptions = typeof channelNameOrNameAndId === 'object' ? channelNameOrNameAndId : { channelName: channelNameOrNameAndId }; @@ -48,5 +48,5 @@ export function useChannelStateListener( const state = typeof stateOrListener !== 'function' ? stateOrListener : undefined; - useEventListener(channel, _listener, state); + useEventListener(channel, _listener, state); } diff --git a/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx b/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx index 7f47ba9239..b96c5e2b2b 100644 --- a/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx @@ -3,13 +3,13 @@ import { it, beforeEach, describe, expect } from 'vitest'; import { useState } from 'react'; import { render, screen } from '@testing-library/react'; import { FakeAblySdk } from '../fakes/ably.js'; -import { Types } from 'ably'; +import * as Ably from 'ably'; import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; import { useConnectionStateListener } from './useConnectionStateListener.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useConnectionStateListener', () => { @@ -71,7 +71,7 @@ describe('useConnectionStateListener', () => { }); const UseConnectionStateListenerComponent = () => { - const [connectionState, setConnectionState] = useState('initialized'); + const [connectionState, setConnectionState] = useState('initialized'); useConnectionStateListener((stateChange) => { setConnectionState(stateChange.current); @@ -81,13 +81,13 @@ const UseConnectionStateListenerComponent = () => { }; interface UseConnectionStateListenerComponentNamedEventsProps { - event: Types.ConnectionState | Types.ConnectionState[]; + event: Ably.ConnectionState | Ably.ConnectionState[]; } const UseConnectionStateListenerComponentNamedEvents = ({ event, }: UseConnectionStateListenerComponentNamedEventsProps) => { - const [channelState, setChannelState] = useState('initialized'); + const [channelState, setChannelState] = useState('initialized'); useConnectionStateListener(event, (stateChange) => { setChannelState(stateChange.current); diff --git a/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts b/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts index 3df2ac16b1..d862970791 100644 --- a/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts @@ -1,19 +1,19 @@ -import { Types } from '../../../../../ably.js'; +import * as Ably from '../../../../../ably.js'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; -type ConnectionStateListener = (stateChange: Types.ConnectionStateChange) => any; +type ConnectionStateListener = (stateChange: Ably.ConnectionStateChange) => any; export function useConnectionStateListener(listener: ConnectionStateListener, id?: string); export function useConnectionStateListener( - state: Types.ConnectionState | Types.ConnectionState[], + state: Ably.ConnectionState | Ably.ConnectionState[], listener: ConnectionStateListener, id?: string ); export function useConnectionStateListener( - stateOrListener?: Types.ConnectionState | Types.ConnectionState[] | ConnectionStateListener, + stateOrListener?: Ably.ConnectionState | Ably.ConnectionState[] | ConnectionStateListener, listenerOrId?: string | ConnectionStateListener, id = 'default' ) { @@ -23,5 +23,5 @@ export function useConnectionStateListener( const listener = typeof listenerOrId === 'function' ? listenerOrId : (stateOrListener as ConnectionStateListener); const state = typeof stateOrListener !== 'function' ? stateOrListener : undefined; - useEventListener(ably.connection, listener, state); + useEventListener(ably.connection, listener, state); } diff --git a/src/platform/react-hooks/src/hooks/useEventListener.ts b/src/platform/react-hooks/src/hooks/useEventListener.ts index 8c3d487819..44e93094e9 100644 --- a/src/platform/react-hooks/src/hooks/useEventListener.ts +++ b/src/platform/react-hooks/src/hooks/useEventListener.ts @@ -1,12 +1,12 @@ -import { Types } from '../../../../../ably.js'; +import * as Ably from '../../../../../ably.js'; import { useEffect, useRef } from 'react'; type EventListener = (stateChange: T) => any; export function useEventListener< - S extends Types.ConnectionState | Types.ChannelState, - C extends Types.ConnectionStateChange | Types.ChannelStateChange ->(emitter: Types.EventEmitter, C, S>, listener: EventListener, event?: S | S[]) { + S extends Ably.ConnectionState | Ably.ChannelState, + C extends Ably.ConnectionStateChange | Ably.ChannelStateChange +>(emitter: Ably.EventEmitter, C, S>, listener: EventListener, event?: S | S[]) { const savedListener = useRef(listener); useEffect(() => { diff --git a/src/platform/react-hooks/src/hooks/usePresence.test.tsx b/src/platform/react-hooks/src/hooks/usePresence.test.tsx index 7651fceb96..f22dbe048b 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.test.tsx +++ b/src/platform/react-hooks/src/hooks/usePresence.test.tsx @@ -4,10 +4,10 @@ import { usePresence } from './usePresence.js'; import { render, screen, act } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; import { AblyProvider } from '../AblyProvider.js'; -import { Types, ErrorInfo } from '../../../../../ably.js'; +import * as Ably from '../../../../../ably.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } const testChannelName = 'testChannel'; @@ -97,7 +97,7 @@ describe('usePresence', () => { it('usePresence works with multiple clients', async () => { renderInCtxProvider( ablyClient, - + ); @@ -215,8 +215,8 @@ const UsePresenceComponentMultipleClients = () => { }; interface UsePresenceStateErrorsComponentProps { - onConnectionError?: (err: ErrorInfo) => unknown; - onChannelError?: (err: ErrorInfo) => unknown; + onConnectionError?: (err: Ably.ErrorInfo) => unknown; + onChannelError?: (err: Ably.ErrorInfo) => unknown; } const UsePresenceStateErrorsComponent = ({ diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index 944480203d..c763702c6c 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,4 +1,4 @@ -import { Types, ErrorInfo } from '../../../../../ably.js'; +import * as Ably from '../../../../../ably.js'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; @@ -7,12 +7,12 @@ import { useStateErrors } from './useStateErrors.js'; export interface PresenceResult { presenceData: PresenceMessage[]; updateStatus: (messageOrPresenceObject: T) => void; - connectionError: ErrorInfo | null; - channelError: ErrorInfo | null; + connectionError: Ably.ErrorInfo | null; + channelError: Ably.ErrorInfo | null; } export type OnPresenceMessageReceived = (presenceData: PresenceMessage) => void; -export type UseStatePresenceUpdate = (presenceData: Types.PresenceMessage[]) => void; +export type UseStatePresenceUpdate = (presenceData: Ably.PresenceMessage[]) => void; export function usePresence( channelNameOrNameAndOptions: ChannelParameters, @@ -48,7 +48,7 @@ export function usePresence( const [presenceData, updatePresenceData] = useState>>([]); - const updatePresence = async (message?: Types.PresenceMessage) => { + const updatePresence = async (message?: Ably.PresenceMessage) => { const snapshot = await channel.presence.get(); updatePresenceData(snapshot); @@ -104,7 +104,7 @@ export function usePresence( } interface PresenceMessage { - action: Types.PresenceAction; + action: Ably.PresenceAction; clientId: string; connectionId: string; data: T; diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index eff091f094..ecc70ebf90 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -64,7 +64,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B * Internal: checks that the cipherParams are a valid combination. Currently * just checks that the calculated keyLength is a valid one for aes-cbc */ - function validateCipherParams(params: API.Types.CipherParams) { + function validateCipherParams(params: API.CipherParams) { if (params.algorithm === 'aes' && params.mode === 'cbc') { if (params.keyLength === 128 || params.keyLength === 256) { return; @@ -82,10 +82,8 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B return string.replace('_', '/').replace('-', '+'); } - function isCipherParams( - params: API.Types.CipherParams | API.Types.CipherParamOptions - ): params is API.Types.CipherParams { - // Although API.Types.CipherParams is an interface, the documentation for its `key` property makes it clear that the only valid way to form one is by using getDefaultParams. The implementation of getDefaultParams returns an instance of CipherParams. + function isCipherParams(params: API.CipherParams | API.CipherParamOptions): params is API.CipherParams { + // Although API.CipherParams is an interface, the documentation for its `key` property makes it clear that the only valid way to form one is by using getDefaultParams. The implementation of getDefaultParams returns an instance of CipherParams. return params instanceof CipherParams; } @@ -100,7 +98,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B * Crypto.getDefaultParams helper, which will fill in any fields not supplied * with default values and validation the result. */ - class CipherParams implements API.Types.CipherParams { + class CipherParams implements API.CipherParams { algorithm: string; keyLength: number; mode: string; @@ -144,7 +142,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B * May optionally also contain: algorithm (defaults to AES), * mode (defaults to 'cbc') */ - static getDefaultParams(params: API.Types.CipherParamOptions) { + static getDefaultParams(params: API.CipherParamOptions) { var key: ArrayBuffer; if (!params.key) { @@ -182,7 +180,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B * default keyLength if none supplied) as an ArrayBuffer * @param keyLength (optional) the required keyLength in bits */ - static async generateRandomKey(keyLength?: number): Promise { + static async generateRandomKey(keyLength?: number): Promise { return new Promise((resolve, reject) => { generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { if (err) { diff --git a/src/platform/web/modules/crypto.ts b/src/platform/web/modules/crypto.ts index 2c65d23eb1..9f9704f7ee 100644 --- a/src/platform/web/modules/crypto.ts +++ b/src/platform/web/modules/crypto.ts @@ -5,10 +5,10 @@ import * as API from '../../../../ably'; export const Crypto = /* @__PURE__@ */ createCryptoClass(Config, BufferUtils); -export const generateRandomKey: API.Types.Crypto['generateRandomKey'] = (keyLength) => { +export const generateRandomKey: API.Crypto['generateRandomKey'] = (keyLength) => { return Crypto.generateRandomKey(keyLength); }; -export const getDefaultCryptoParams: API.Types.Crypto['getDefaultParams'] = (params) => { +export const getDefaultCryptoParams: API.Crypto['getDefaultParams'] = (params) => { return Crypto.getDefaultParams(params); }; diff --git a/src/platform/web/modules/message.ts b/src/platform/web/modules/message.ts index de8b2ab4f9..18c5d1396b 100644 --- a/src/platform/web/modules/message.ts +++ b/src/platform/web/modules/message.ts @@ -6,16 +6,16 @@ import { fromEncoded, fromEncodedArray } from '../../../common/lib/types/message export const decodeMessage = ((obj, options) => { return fromEncoded(null, obj, options); -}) as API.Types.MessageStatic['fromEncoded']; +}) as API.MessageStatic['fromEncoded']; export const decodeEncryptedMessage = ((obj, options) => { return fromEncoded(Crypto, obj, options); -}) as API.Types.MessageStatic['fromEncoded']; +}) as API.MessageStatic['fromEncoded']; export const decodeMessages = ((obj, options) => { return fromEncodedArray(null, obj, options); -}) as API.Types.MessageStatic['fromEncodedArray']; +}) as API.MessageStatic['fromEncodedArray']; export const decodeEncryptedMessages = ((obj, options) => { return fromEncodedArray(Crypto, obj, options); -}) as API.Types.MessageStatic['fromEncodedArray']; +}) as API.MessageStatic['fromEncodedArray']; diff --git a/src/platform/web/modules/presencemessage.ts b/src/platform/web/modules/presencemessage.ts index d90d2b42b5..60e5351e9c 100644 --- a/src/platform/web/modules/presencemessage.ts +++ b/src/platform/web/modules/presencemessage.ts @@ -3,6 +3,6 @@ import { fromEncoded, fromEncodedArray, fromValues } from '../../../common/lib/t // The type assertions for the functions below are due to https://github.com/ably/ably-js/issues/1421 -export const decodePresenceMessage = fromEncoded as API.Types.PresenceMessageStatic['fromEncoded']; -export const decodePresenceMessages = fromEncodedArray as API.Types.PresenceMessageStatic['fromEncodedArray']; -export const constructPresenceMessage = fromValues as API.Types.PresenceMessageStatic['fromValues']; +export const decodePresenceMessage = fromEncoded as API.PresenceMessageStatic['fromEncoded']; +export const decodePresenceMessages = fromEncodedArray as API.PresenceMessageStatic['fromEncodedArray']; +export const constructPresenceMessage = fromValues as API.PresenceMessageStatic['fromValues']; diff --git a/test/package/browser/template/src/index-default.ts b/test/package/browser/template/src/index-default.ts index 87d5270c0c..cda822d21b 100644 --- a/test/package/browser/template/src/index-default.ts +++ b/test/package/browser/template/src/index-default.ts @@ -1,20 +1,20 @@ -import { Realtime, Types } from 'ably'; +import * as Ably from 'ably'; import { createSandboxAblyAPIKey } from './sandbox'; -// This function exists to check that we can import the Types namespace and refer to its types. -async function attachChannel(channel: Types.RealtimeChannel) { +// This function exists to check that we can refer to the types exported by Ably. +async function attachChannel(channel: Ably.RealtimeChannel) { await channel.attach(); } globalThis.testAblyPackage = async function () { const key = await createSandboxAblyAPIKey(); - const realtime = new Realtime({ key, environment: 'sandbox' }); + const realtime = new Ably.Realtime({ key, environment: 'sandbox' }); const channel = realtime.channels.get('channel'); await attachChannel(channel); - const receivedMessagePromise = new Promise((resolve) => { + const receivedMessagePromise = new Promise((resolve) => { channel.subscribe(resolve); }); diff --git a/test/package/browser/template/src/index-modules.ts b/test/package/browser/template/src/index-modules.ts index 594835eaee..8f63f35f42 100644 --- a/test/package/browser/template/src/index-modules.ts +++ b/test/package/browser/template/src/index-modules.ts @@ -1,9 +1,9 @@ import { BaseRealtime, WebSocketTransport, FetchRequest, generateRandomKey } from 'ably/modules'; -import { Types } from 'ably'; +import { InboundMessage, RealtimeChannel } from 'ably'; import { createSandboxAblyAPIKey } from './sandbox'; -// This function exists to check that we can import the Types namespace and refer to its types. -async function attachChannel(channel: Types.RealtimeChannel) { +// This function exists to check that we can refer to the types exported by Ably. +async function attachChannel(channel: RealtimeChannel) { await channel.attach(); } @@ -23,7 +23,7 @@ globalThis.testAblyPackage = async function () { const channel = realtime.channels.get('channel'); await attachChannel(channel); - const receivedMessagePromise = new Promise((resolve) => { + const receivedMessagePromise = new Promise((resolve) => { channel.subscribe(resolve); }); From bc5908974c5b69c84b33d9e5245aa2058fd7a9e1 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 5 Jan 2024 20:38:20 +0000 Subject: [PATCH 239/468] Move 'isWebWorkerContext' to web platform config --- src/common/lib/util/utils.ts | 10 ---------- src/common/types/IPlatformConfig.d.ts | 1 + src/platform/web/config.ts | 11 +++++++++++ src/platform/web/lib/http/request/fetchrequest.ts | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 90af710dcb..9388ad2db2 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -571,13 +571,3 @@ export function arrEquals(a: any[], b: any[]) { export function throwMissingModuleError(moduleName: keyof ModulesMap): never { throw new ErrorInfo(`${moduleName} module not provided`, 40019, 400); } - -// from: https://stackoverflow.com/a/18002694 -export function isWebWorkerContext(): boolean { - // run this in global scope of window or worker. since window.self = window, we're ok - if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { - return true; - } else { - return false; - } -} diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 50263c7016..9d678f23bb 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -27,4 +27,5 @@ export interface IPlatformConfig { byteLength: number, callback: (err: Error | null, result: ArrayBuffer | null) => void ) => void; + isWebworker?: boolean; } diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index f3aac12c93..24ae69d451 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -20,6 +20,16 @@ function allowComet() { return !globalObject.WebSocket || !loc || !loc.origin || loc.origin.indexOf('http') > -1; } +// from: https://stackoverflow.com/a/18002694 +export function isWebWorkerContext(): boolean { + // run this in global scope of window or worker. since window.self = window, we're ok + if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { + return true; + } else { + return false; + } +} + const userAgent = globalObject.navigator && globalObject.navigator.userAgent.toString(); const currentUrl = globalObject.location && globalObject.location.href; @@ -68,6 +78,7 @@ const Config: IPlatformConfig = { } }; })(globalObject.crypto || msCrypto), + isWebworker: isWebWorkerContext(), }; export default Config; diff --git a/src/platform/web/lib/http/request/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts index abe39d7820..0291121297 100644 --- a/src/platform/web/lib/http/request/fetchrequest.ts +++ b/src/platform/web/lib/http/request/fetchrequest.ts @@ -54,7 +54,7 @@ export default function fetchRequest( body: body as any, }; - if (!Utils.isWebWorkerContext()) { + if (!Platform.Config.isWebworker) { requestInit.credentials = fetchHeaders.has('authorization') ? 'include' : 'same-origin'; } From d41d61f48e60133775d52aa8224a96bc8ca015aa Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 5 Jan 2024 20:59:45 +0000 Subject: [PATCH 240/468] Refactor IPlatformConfig Splits IPlatformConfig into two separate interfaces for common required and platform specific optional properties so it's more explicit where a new property should be added. --- src/common/types/IPlatformConfig.d.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 9d678f23bb..d6fb7447b4 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -1,4 +1,9 @@ -export interface IPlatformConfig { +/** + * Interface for common config properties shared between all platforms and that are relevant for all platforms. + * + * These properties must always be required and set for each platform. + */ +export interface ICommonPlatformConfig { agent: string; logTimestamps: boolean; binaryType: BinaryType; @@ -9,6 +14,14 @@ export interface IPlatformConfig { nextTick: process.nextTick; inspect: (value: unknown) => string; stringByteSize: Buffer.byteLength; +} + +/** + * Interface for platform specific config properties that do make sense on some platforms but not on others. + * + * These properties should always be optional, so that only relevant platforms would set them. + */ +export interface ISpecificPlatformConfig { addEventListener?: typeof window.addEventListener | typeof global.addEventListener | null; getRandomValues?: (arr: ArrayBufferView, callback?: (error: Error | null) => void) => void; userAgent?: string | null; @@ -29,3 +42,5 @@ export interface IPlatformConfig { ) => void; isWebworker?: boolean; } + +export type IPlatformConfig = ICommonPlatformConfig & ISpecificPlatformConfig; From e524d68946842270a853f50188b27eba00f3add7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 4 Jan 2024 09:50:08 +0000 Subject: [PATCH 241/468] Remove `any` from `stats()` param type --- ably.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 28a3f9808b..5028ccad67 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1588,10 +1588,10 @@ export declare abstract class AbstractRest { /** * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link PaginatedResult} object, containing an array of {@link Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). * - * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link StatsParams} interface. + * @param params - A set of parameters which are used to specify which statistics should be retrieved. If you do not provide this argument, then this method will use the default parameters described in the {@link StatsParams} interface. * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - stats(params?: StatsParams | any): Promise>; + stats(params?: StatsParams): Promise>; /** * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link ClientOptions.queryTime} property instead of this method. * @@ -1679,10 +1679,10 @@ export declare abstract class AbstractRealtime { /** * Queries the REST `/stats` API and retrieves your application's usage statistics. Returns a {@link PaginatedResult} object, containing an array of {@link Stats} objects. See the [Stats docs](https://ably.com/docs/general/statistics). * - * @param params - A set of parameters which are used to specify which statistics should be retrieved. This parameter should be a {@link StatsParams} object. For reasons of backwards compatibility this parameter will also accept `any`; this ability will be removed in the next major release of this SDK. If you do not provide this argument, then this method will use the default parameters described in the {@link StatsParams} interface. + * @param params - A set of parameters which are used to specify which statistics should be retrieved. If you do not provide this argument, then this method will use the default parameters described in the {@link StatsParams} interface. * @returns A promise which, upon success, will be fulfilled with a {@link PaginatedResult} object containing an array of {@link Stats} objects. Upon failure, the promise will be rejected with an {@link ErrorInfo} object which explains the error. */ - stats(params?: StatsParams | any): Promise>; + stats(params?: StatsParams): Promise>; /** * Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably {@link TokenRequest | `TokenRequest`s} with a more accurate timestamp should use the {@link ClientOptions.queryTime} property instead of this method. * From ff7cb418cc722c3bf65852bbe03dbc6157f7b5a1 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 1 Dec 2023 12:01:54 -0300 Subject: [PATCH 242/468] Remove false class exports in type declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type declarations claim that the SDK exports all manner of classes that it does not in fact export, such as Auth, Presence, PushAdmin, AbstractRest, AbstractRealtime etc. Trying to import these classes will result in a runtime (or bundler) error. So, we convert them all to interfaces. (I can’t see an obvious downside of doing this; let me know if there is.) I’ve also addressed the ClientOptions docstring’s links to the Abstract* constructors by rewording their text, since these links are now completely broken (they were previously linking to a useless entry for a nonexistent constructor). Resolves #1519, resolves #1520. --- ably.d.ts | 93 +++++++++++++++++++++++++++++++++++++++------------- modules.d.ts | 66 +++++++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 25 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index e2163e568b..08eb091803 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -375,7 +375,7 @@ export interface ChannelMetrics { } /** - * Passes additional client-specific properties to the REST {@link AbstractRest.constructor | `constructor()`} or the Realtime {@link AbstractRealtime.constructor | `constructor()`}. + * Passes additional client-specific properties to the REST constructor or the Realtime constructor. */ export interface ClientOptions extends AuthOptions { /** @@ -1472,7 +1472,7 @@ export type recoverConnectionCallback = ( callback: recoverConnectionCompletionCallback ) => void; -// Internal Classes +// Internal Interfaces // To allow a uniform (callback) interface between on and once even in the // promisified version of the lib, but still allow once to be used in a way @@ -1481,7 +1481,7 @@ export type recoverConnectionCallback = ( /** * A generic interface for event registration and delivery used in a number of the types in the Realtime client library. For example, the {@link Connection} object emits events for connection state using the `EventEmitter` pattern. */ -export declare class EventEmitter { +export declare interface EventEmitter { /** * Registers the provided listener for the specified event. If `on()` is called more than once with the same listener and event, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using `on()`, and an event is emitted once, the listener would be invoked twice. * @@ -1553,11 +1553,11 @@ export declare class EventEmitter { listeners(eventName?: EventType): CallbackType[] | null; } -// Classes +// Interfaces /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare abstract class AbstractRest { +export declare interface AbstractRest { /** * An {@link Auth} object. */ @@ -1631,7 +1631,7 @@ export declare abstract class AbstractRest { /** * A client that extends the functionality of {@link AbstractRest} and provides additional realtime-specific features. */ -export declare abstract class AbstractRealtime { +export declare interface AbstractRealtime { /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. */ @@ -1721,7 +1721,7 @@ export declare abstract class AbstractRealtime { /** * Creates Ably {@link TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients. */ -export declare class Auth { +export declare interface Auth { /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. Note that a `clientId` may also be implicit in a token used to instantiate the library. An error is raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. Find out more about [identified clients](https://ably.com/docs/core-features/authentication#identified-clients). */ @@ -1767,7 +1767,7 @@ export declare class Auth { /** * Enables the retrieval of the current and historic presence set for a channel. */ -export declare class Presence { +export declare interface Presence { /** * Retrieves the current members present on the channel and the metadata for each member, such as their {@link PresenceAction} and ID. Returns a {@link PaginatedResult} object, containing an array of {@link PresenceMessage} objects. * @@ -1787,7 +1787,7 @@ export declare class Presence { /** * Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel. */ -export declare class RealtimePresence { +export declare interface RealtimePresence { /** * Indicates whether the presence set synchronization between Ably and the clients on the channel has been completed. Set to `true` when the sync is complete. */ @@ -1908,7 +1908,7 @@ export declare class RealtimePresence { /** * Enables messages to be published and historic messages to be retrieved for a channel. */ -export declare class Channel { +export declare interface Channel { /** * The channel name. */ @@ -1961,7 +1961,7 @@ export declare class Channel { /** * Enables messages to be published and subscribed to. Also enables historic messages to be retrieved and provides access to the {@link RealtimePresence} object of a channel. */ -export declare class RealtimeChannel extends EventEmitter { +export declare interface RealtimeChannel extends EventEmitter { /** * The channel name. */ @@ -2158,7 +2158,7 @@ export type MessageFilter = { /** * Creates and destroys {@link Channel} and {@link RealtimeChannel} objects. */ -export declare class Channels { +export declare interface Channels { /** * Creates a new {@link Channel} or {@link RealtimeChannel} object, with the specified {@link ChannelOptions}, or returns the existing channel object. * @@ -2258,7 +2258,7 @@ export interface MessageStatic { /** * Contains an individual presence update sent to, or received from, Ably. */ -export declare class PresenceMessage { +export declare interface PresenceMessage { /** * The type of {@link PresenceAction} the `PresenceMessage` is for. */ @@ -2375,7 +2375,8 @@ export interface Crypto { /** * Enables the management of a connection to Ably. */ -export declare class Connection extends EventEmitter { +export declare interface Connection + extends EventEmitter { /** * An {@link ErrorInfo} object describing the last error received if a connection failure occurs. */ @@ -2426,7 +2427,7 @@ export declare class Connection extends EventEmitter { +export declare interface PaginatedResult { /** * Contains the current page of results; for example, an array of {@link InboundMessage} or {@link PresenceMessage} objects for a channel history request. */ @@ -2490,7 +2491,7 @@ export declare class PaginatedResult { /** * A superset of {@link PaginatedResult} which represents a page of results plus metadata indicating the relative queries available to it. `HttpPaginatedResponse` additionally carries information about the response to an HTTP request. */ -export declare class HttpPaginatedResponse extends PaginatedResult { +export declare interface HttpPaginatedResponse extends PaginatedResult { /** * The HTTP status code of the response. */ @@ -2516,7 +2517,7 @@ export declare class HttpPaginatedResponse extends PaginatedResult { /** * Enables a device to be registered and deregistered from receiving push notifications. */ -export declare class Push { +export declare interface Push { /** * A {@link PushAdmin} object. */ @@ -2526,7 +2527,7 @@ export declare class Push { /** * Enables the management of device registrations and push notification subscriptions. Also enables the publishing of push notifications to devices. */ -export declare class PushAdmin { +export declare interface PushAdmin { /** * A {@link PushDeviceRegistrations} object. */ @@ -2548,7 +2549,7 @@ export declare class PushAdmin { /** * Enables the management of push notification registrations with Ably. */ -export declare class PushDeviceRegistrations { +export declare interface PushDeviceRegistrations { /** * Registers or updates a {@link DeviceDetails} object with Ably. Returns the new, or updated {@link DeviceDetails} object. * @@ -2603,7 +2604,7 @@ export declare class PushDeviceRegistrations { /** * Enables device push channel subscriptions. */ -export declare class PushChannelSubscriptions { +export declare interface PushChannelSubscriptions { /** * Subscribes a device, or a group of devices sharing the same `clientId` to push notifications on a channel. Returns a {@link PushChannelSubscription} object. * @@ -2644,7 +2645,7 @@ export declare class PushChannelSubscriptions { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare class Rest extends AbstractRest { +export declare class Rest implements AbstractRest { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -2669,12 +2670,33 @@ export declare class Rest extends AbstractRest { * Static utilities related to presence messages. */ static PresenceMessage: PresenceMessageStatic; + + // Requirements of AbstractRest + + auth: Auth; + channels: Channels; + request( + method: string, + path: string, + version: number, + params?: any, + body?: any[] | any, + headers?: any + ): Promise>; + stats(params?: StatsParams | any): Promise>; + time(): Promise; + batchPublish(spec: BatchPublishSpec): Promise>; + batchPublish( + specs: BatchPublishSpec[] + ): Promise[]>; + batchPresence(channels: string[]): Promise[]>; + push: Push; } /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ -export declare class Realtime extends AbstractRealtime { +export declare class Realtime implements AbstractRealtime { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -2699,6 +2721,31 @@ export declare class Realtime extends AbstractRealtime { * Static utilities related to presence messages. */ static PresenceMessage: PresenceMessageStatic; + + // Requirements of AbstractRealtime + + clientId: string; + close(): void; + connect(): void; + auth: Auth; + channels: Channels; + connection: Connection; + request( + method: string, + path: string, + version: number, + params?: any, + body?: any[] | any, + headers?: any + ): Promise>; + stats(params?: StatsParams | any): Promise>; + time(): Promise; + batchPublish(spec: BatchPublishSpec): Promise>; + batchPublish( + specs: BatchPublishSpec[] + ): Promise[]>; + batchPresence(channels: string[]): Promise[]>; + push: Push; } /** diff --git a/modules.d.ts b/modules.d.ts index e238fcf677..bf2ea58cc8 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -30,6 +30,22 @@ import { MessageStatic, PresenceMessageStatic, AbstractRealtime, + Auth, + Channels, + Channel, + HttpPaginatedResponse, + StatsParams, + PaginatedResult, + Stats, + BatchPublishSpec, + BatchResult, + BatchPublishSuccessResult, + BatchPresenceFailureResult, + BatchPresenceSuccessResult, + BatchPublishFailureResult, + Push, + RealtimeChannel, + Connection, } from './ably'; export declare const generateRandomKey: CryptoClass['generateRandomKey']; @@ -274,7 +290,7 @@ export interface ModulesMap { * * `BaseRest` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Rest`](../../default/classes/Rest.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRest extends AbstractRest { +export declare class BaseRest implements AbstractRest { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -286,6 +302,27 @@ export declare class BaseRest extends AbstractRest { * The {@link Rest} module is always implicitly included. */ constructor(options: ClientOptions, modules: ModulesMap); + + // Requirements of AbstractRest + + auth: Auth; + channels: Channels; + request( + method: string, + path: string, + version: number, + params?: any, + body?: any[] | any, + headers?: any + ): Promise>; + stats(params?: StatsParams | any): Promise>; + time(): Promise; + batchPublish(spec: BatchPublishSpec): Promise>; + batchPublish( + specs: BatchPublishSpec[] + ): Promise[]>; + batchPresence(channels: string[]): Promise[]>; + push: Push; } /** @@ -293,7 +330,7 @@ export declare class BaseRest extends AbstractRest { * * `BaseRealtime` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Realtime`](../../default/classes/Realtime.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRealtime extends AbstractRealtime { +export declare class BaseRealtime implements AbstractRealtime { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -306,6 +343,31 @@ export declare class BaseRealtime extends AbstractRealtime { * - at least one realtime transport implementation; that is, one of {@link WebSocketTransport}, {@link XHRStreaming}, or {@link XHRPolling} — for minimum bundle size, favour `WebSocketTransport`. */ constructor(options: ClientOptions, modules: ModulesMap); + + // Requirements of AbstractRealtime + + clientId: string; + close(): void; + connect(): void; + auth: Auth; + channels: Channels; + connection: Connection; + request( + method: string, + path: string, + version: number, + params?: any, + body?: any[] | any, + headers?: any + ): Promise>; + stats(params?: StatsParams | any): Promise>; + time(): Promise; + batchPublish(spec: BatchPublishSpec): Promise>; + batchPublish( + specs: BatchPublishSpec[] + ): Promise[]>; + batchPresence(channels: string[]): Promise[]>; + push: Push; } export { ErrorInfo }; From 44eae5d4b6010b86c2043edaa4bd83523903d69e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 4 Dec 2023 15:29:41 -0300 Subject: [PATCH 243/468] Rename Abstract* classes to *Client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I _think_ that this reads better, now that as of ff7cb41 they’re no longer classes. --- ably.d.ts | 18 +++++++++--------- modules.d.ts | 12 ++++++------ src/platform/react-hooks/src/AblyProvider.tsx | 4 ++-- src/platform/react-hooks/src/hooks/useAbly.ts | 4 ++-- .../react-hooks/src/hooks/useChannel.test.tsx | 4 ++-- .../react-hooks/src/hooks/useChannel.ts | 2 +- .../src/hooks/useChannelStateListener.test.tsx | 2 +- .../hooks/useConnectionStateListener.test.tsx | 2 +- .../react-hooks/src/hooks/usePresence.test.tsx | 4 ++-- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 08eb091803..0f761197a5 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1235,8 +1235,8 @@ export interface PushChannelsParams { /** * The `StatsParams` interface describes the parameters accepted by the following methods: * - * - {@link AbstractRest.stats} - * - {@link AbstractRealtime.stats} + * - {@link RestClient.stats} + * - {@link RealtimeClient.stats} */ export interface StatsParams { /** @@ -1557,7 +1557,7 @@ export declare interface EventEmitter { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare interface AbstractRest { +export declare interface RestClient { /** * An {@link Auth} object. */ @@ -1629,9 +1629,9 @@ export declare interface AbstractRest { } /** - * A client that extends the functionality of {@link AbstractRest} and provides additional realtime-specific features. + * A client that extends the functionality of {@link RestClient} and provides additional realtime-specific features. */ -export declare interface AbstractRealtime { +export declare interface RealtimeClient { /** * A client ID, used for identifying this client when publishing messages or for presence purposes. The `clientId` can be any non-empty string, except it cannot contain a `*`. This option is primarily intended to be used in situations where the library is instantiated with a key. A `clientId` may also be implicit in a token used to instantiate the library; an error will be raised if a `clientId` specified here conflicts with the `clientId` implicit in the token. */ @@ -2645,7 +2645,7 @@ export declare interface PushChannelSubscriptions { /** * A client that offers a simple stateless API to interact directly with Ably's REST API. */ -export declare class Rest implements AbstractRest { +export declare class Rest implements RestClient { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -2671,7 +2671,7 @@ export declare class Rest implements AbstractRest { */ static PresenceMessage: PresenceMessageStatic; - // Requirements of AbstractRest + // Requirements of RestClient auth: Auth; channels: Channels; @@ -2696,7 +2696,7 @@ export declare class Rest implements AbstractRest { /** * A client that extends the functionality of {@link Rest} and provides additional realtime-specific features. */ -export declare class Realtime implements AbstractRealtime { +export declare class Realtime implements RealtimeClient { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -2722,7 +2722,7 @@ export declare class Realtime implements AbstractRealtime { */ static PresenceMessage: PresenceMessageStatic; - // Requirements of AbstractRealtime + // Requirements of RealtimeClient clientId: string; close(): void; diff --git a/modules.d.ts b/modules.d.ts index bf2ea58cc8..7736e63b7a 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -24,12 +24,12 @@ import { ErrorInfo, - AbstractRest, + RestClient, ClientOptions, Crypto as CryptoClass, MessageStatic, PresenceMessageStatic, - AbstractRealtime, + RealtimeClient, Auth, Channels, Channel, @@ -290,7 +290,7 @@ export interface ModulesMap { * * `BaseRest` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Rest`](../../default/classes/Rest.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRest implements AbstractRest { +export declare class BaseRest implements RestClient { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -303,7 +303,7 @@ export declare class BaseRest implements AbstractRest { */ constructor(options: ClientOptions, modules: ModulesMap); - // Requirements of AbstractRest + // Requirements of RestClient auth: Auth; channels: Channels; @@ -330,7 +330,7 @@ export declare class BaseRest implements AbstractRest { * * `BaseRealtime` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Realtime`](../../default/classes/Realtime.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. */ -export declare class BaseRealtime implements AbstractRealtime { +export declare class BaseRealtime implements RealtimeClient { /** * Construct a client object using an Ably {@link ClientOptions} object. * @@ -344,7 +344,7 @@ export declare class BaseRealtime implements AbstractRealtime { */ constructor(options: ClientOptions, modules: ModulesMap); - // Requirements of AbstractRealtime + // Requirements of RealtimeClient clientId: string; close(): void; diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index d91bea6b59..603f83e86f 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -5,11 +5,11 @@ const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'func interface AblyProviderProps { children?: React.ReactNode | React.ReactNode[] | null; - client?: Ably.AbstractRealtime; + client?: Ably.RealtimeClient; id?: string; } -type AblyContextType = React.Context; +type AblyContextType = React.Context; // An object is appended to `React.createContext` which stores all contexts // indexed by id, which is used by useAbly to find the correct context when an diff --git a/src/platform/react-hooks/src/hooks/useAbly.ts b/src/platform/react-hooks/src/hooks/useAbly.ts index df4d1c93c4..50d3afc69e 100644 --- a/src/platform/react-hooks/src/hooks/useAbly.ts +++ b/src/platform/react-hooks/src/hooks/useAbly.ts @@ -2,8 +2,8 @@ import React from 'react'; import { getContext } from '../AblyProvider.js'; import * as API from 'ably'; -export function useAbly(id = 'default'): API.AbstractRealtime { - const client = React.useContext(getContext(id)) as API.AbstractRealtime; +export function useAbly(id = 'default'): API.RealtimeClient { + const client = React.useContext(getContext(id)) as API.RealtimeClient; if (!client) { throw new Error( diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 0a2cf63262..0fe50da284 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useChannel', () => { @@ -57,7 +57,7 @@ describe('useChannel', () => { it('useChannel works with multiple clients', async () => { renderInCtxProvider( ablyClient, - + ); diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index 49d1bc714a..55abe27bb3 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -8,7 +8,7 @@ export type AblyMessageCallback = Ably.messageCallback; export interface ChannelResult { channel: Ably.RealtimeChannel; - ably: Ably.AbstractRealtime; + ably: Ably.RealtimeClient; connectionError: Ably.ErrorInfo | null; channelError: Ably.ErrorInfo | null; } diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx index 3187087a57..156604a5b0 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useChannelStateListener', () => { diff --git a/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx b/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx index b96c5e2b2b..547ec6b6a0 100644 --- a/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/useConnectionStateListener.test.tsx @@ -9,7 +9,7 @@ import { AblyProvider } from '../AblyProvider.js'; import { useConnectionStateListener } from './useConnectionStateListener.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } describe('useConnectionStateListener', () => { diff --git a/src/platform/react-hooks/src/hooks/usePresence.test.tsx b/src/platform/react-hooks/src/hooks/usePresence.test.tsx index f22dbe048b..5d2cd0ea1d 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.test.tsx +++ b/src/platform/react-hooks/src/hooks/usePresence.test.tsx @@ -7,7 +7,7 @@ import { AblyProvider } from '../AblyProvider.js'; import * as Ably from '../../../../../ably.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render({children}); } const testChannelName = 'testChannel'; @@ -97,7 +97,7 @@ describe('usePresence', () => { it('usePresence works with multiple clients', async () => { renderInCtxProvider( ablyClient, - + ); From 272ab4239c3cb090f3c5b97ec61cd7b17224574b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 16 Jan 2024 15:29:40 +0000 Subject: [PATCH 244/468] Remove Request from IHttp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s just an implementation detail of the web class. --- src/common/types/http.ts | 8 -------- src/platform/nodejs/lib/util/http.ts | 9 --------- src/platform/web/lib/http/http.ts | 2 +- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/common/types/http.ts b/src/common/types/http.ts index a13ba86e49..99abfedadf 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -26,14 +26,6 @@ export interface IHttp { supportsLinkHeaders: boolean; agent?: Agents | null; - Request?: ( - method: HttpMethods, - uri: string, - headers: Record | null, - params: RequestParams, - body: unknown, - callback: RequestCallback - ) => void; _getHosts: (client: BaseClient) => string[]; do( method: HttpMethods, diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 6ed73b76d5..9edafd64aa 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -264,15 +264,6 @@ const Http: IHttpStatic = class { } ); }; - - Request?: ( - method: HttpMethods, - uri: string, - headers: Record | null, - params: RequestParams, - body: unknown, - callback: RequestCallback - ) => void = undefined; }; export default Http; diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index 626a946758..39dcf52c65 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -243,7 +243,7 @@ const Http = class { this.Request(method, uri, headers, params, body, callback); } - Request?: ( + private Request?: ( method: HttpMethods, uri: string, headers: Record | null, From 2a840c0cb7bb42a0d55cd38c4e5f9afa8d13b3ea Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 16 Jan 2024 15:53:17 +0000 Subject: [PATCH 245/468] Remove `agent` from IHttp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s just an implementation detail of the Node class. --- src/common/types/http.ts | 2 -- src/platform/nodejs/lib/util/http.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 99abfedadf..4281f20f7a 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -1,7 +1,6 @@ import HttpMethods from '../constants/HttpMethods'; import BaseClient from '../lib/client/baseclient'; import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; -import { Agents } from 'got'; export type PathParameter = string | ((host: string) => string); export type RequestCallbackHeaders = Partial>; @@ -24,7 +23,6 @@ export interface IHttpStatic { export interface IHttp { supportsAuthHeaders: boolean; supportsLinkHeaders: boolean; - agent?: Agents | null; _getHosts: (client: BaseClient) => string[]; do( diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 9edafd64aa..ad76f847ba 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -101,7 +101,7 @@ const Http: IHttpStatic = class { static methods = [HttpMethods.Get, HttpMethods.Delete, HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; - agent: Agents | null = null; + private agent: Agents | null = null; _getHosts = getHosts; supportsAuthHeaders = true; supportsLinkHeaders = true; From 5dce7f7730dd4c853daa9b5345cfc71d68aa0f83 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 18 Jan 2024 15:51:12 +0000 Subject: [PATCH 246/468] Rename IHttp to IPlatformHttp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’m going to introduce a platform-agnostic class called Http. --- src/common/lib/client/baseclient.ts | 4 ++-- src/common/platform.ts | 4 ++-- src/common/types/http.ts | 6 +++--- src/platform/nodejs/lib/util/http.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 2ee0ed41d3..521c3a61bd 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -5,7 +5,7 @@ import { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; import ErrorInfo from '../types/errorinfo'; import Stats from '../types/stats'; import { StandardCallback } from '../../types/utils'; -import { IHttp, RequestParams } from '../../types/http'; +import { IPlatformHttp, RequestParams } from '../../types/http'; import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOptions'; import * as API from '../../../../ably'; @@ -37,7 +37,7 @@ class BaseClient { validUntil: number; }; serverTimeOffset: number | null; - http: IHttp; + http: IPlatformHttp; auth: Auth; private readonly _rest: Rest | null; diff --git a/src/common/platform.ts b/src/common/platform.ts index bfd1c5fd64..725d8beb66 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -1,5 +1,5 @@ import { IPlatformConfig } from './types/IPlatformConfig'; -import { IHttpStatic } from './types/http'; +import { IPlatformHttpStatic } from './types/http'; import { TransportInitialiser } from './lib/transport/connectionmanager'; import IDefaults from './types/IDefaults'; import IWebStorage from './types/IWebStorage'; @@ -32,7 +32,7 @@ export default class Platform { comment above. */ static Crypto: IUntypedCryptoStatic | null; - static Http: IHttpStatic; + static Http: IPlatformHttpStatic; static Transports: { order: TransportName[]; // Transport implementations that always come with this platform diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 4281f20f7a..4fbc793970 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -13,14 +13,14 @@ export type RequestCallback = ( ) => void; export type RequestParams = Record | null; -export interface IHttpStatic { - new (client?: BaseClient): IHttp; +export interface IPlatformHttpStatic { + new (client?: BaseClient): IPlatformHttp; methods: Array; methodsWithBody: Array; methodsWithoutBody: Array; } -export interface IHttp { +export interface IPlatformHttp { supportsAuthHeaders: boolean; supportsLinkHeaders: boolean; diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index ad76f847ba..486b6995ee 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -3,7 +3,7 @@ import Defaults from 'common/lib/util/defaults'; import ErrorInfo from 'common/lib/types/errorinfo'; import { ErrnoException, - IHttpStatic, + IPlatformHttpStatic, PathParameter, RequestCallback, RequestParams, @@ -97,7 +97,7 @@ function getHosts(client: BaseClient): string[] { return Defaults.getHosts(client.options); } -const Http: IHttpStatic = class { +const Http: IPlatformHttpStatic = class { static methods = [HttpMethods.Get, HttpMethods.Delete, HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; From 6db96f6c0d5d2fe2912f701df2e477d315148ae3 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 16 Jan 2024 15:47:16 +0000 Subject: [PATCH 247/468] Create a platform-agnostic HTTP client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will hold HTTP-related functionality that’s shared across platforms, such fallback host usage or logging. --- scripts/moduleReport.ts | 3 +- src/common/lib/client/baseclient.ts | 6 +-- src/common/lib/client/defaultrealtime.ts | 4 ++ src/common/lib/client/defaultrest.ts | 4 ++ src/common/types/http.ts | 49 ++++++++++++++++++++++++ test/realtime/auth.test.js | 2 +- test/realtime/connectivity.test.js | 2 +- test/realtime/init.test.js | 4 +- test/rest/message.test.js | 6 +-- 9 files changed, 69 insertions(+), 11 deletions(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 56ebce35c2..32968faf23 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { explore } from 'source-map-explorer'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) -const minimalUsefulRealtimeBundleSizeThresholdKiB = 108; +const minimalUsefulRealtimeBundleSizeThresholdKiB = 109; // List of all modules accepted in ModulesMap const moduleNames = [ @@ -212,6 +212,7 @@ async function checkBaseRealtimeFiles() { 'src/common/lib/util/logger.ts', 'src/common/lib/util/multicaster.ts', 'src/common/lib/util/utils.ts', + 'src/common/types/http.ts', 'src/platform/web/config.ts', 'src/platform/web/lib/http/http.ts', 'src/platform/web/lib/util/bufferutils.ts', diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 521c3a61bd..bee97b89e4 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -5,7 +5,7 @@ import { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; import ErrorInfo from '../types/errorinfo'; import Stats from '../types/stats'; import { StandardCallback } from '../../types/utils'; -import { IPlatformHttp, RequestParams } from '../../types/http'; +import { Http, RequestParams } from '../../types/http'; import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOptions'; import * as API from '../../../../ably'; @@ -37,7 +37,7 @@ class BaseClient { validUntil: number; }; serverTimeOffset: number | null; - http: IPlatformHttp; + http: Http; auth: Auth; private readonly _rest: Rest | null; @@ -95,7 +95,7 @@ class BaseClient { this._currentFallback = null; this.serverTimeOffset = null; - this.http = new Platform.Http(this); + this.http = new Http(this); this.auth = new Auth(this, normalOptions); this._rest = modules.Rest ? new modules.Rest(this) : null; diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 9e5f581dff..588c9511e4 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -15,6 +15,7 @@ import { fromValues as presenceMessageFromValues, fromValuesArray as presenceMessagesFromValuesArray, } from '../types/presencemessage'; +import { Http } from 'common/types/http'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -60,4 +61,7 @@ export class DefaultRealtime extends BaseRealtime { static PresenceMessage = DefaultPresenceMessage; static _MsgPack: MsgPack | null = null; + + // Used by tests + static _Http = Http; } diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index c03849af37..9fb9ac7102 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -5,6 +5,7 @@ import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; +import { Http } from 'common/types/http'; /** `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -39,4 +40,7 @@ export class DefaultRest extends BaseRest { static PresenceMessage = DefaultPresenceMessage; static _MsgPack: MsgPack | null = null; + + // Used by tests + static _Http = Http; } diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 4fbc793970..cc2e4e4459 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -1,3 +1,4 @@ +import Platform from 'common/platform'; import HttpMethods from '../constants/HttpMethods'; import BaseClient from '../lib/client/baseclient'; import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; @@ -44,6 +45,54 @@ export interface IPlatformHttp { checkConnectivity?: (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => void; } +export class Http { + private readonly platformHttp: IPlatformHttp; + checkConnectivity?: (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => void; + + constructor(private readonly client?: BaseClient) { + this.platformHttp = new Platform.Http(client); + + this.checkConnectivity = this.platformHttp.checkConnectivity + ? (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => + this.platformHttp.checkConnectivity!(callback) + : undefined; + } + + get supportsAuthHeaders() { + return this.platformHttp.supportsAuthHeaders; + } + + get supportsLinkHeaders() { + return this.platformHttp.supportsLinkHeaders; + } + + _getHosts(client: BaseClient) { + return this.platformHttp._getHosts(client); + } + + do( + method: HttpMethods, + path: PathParameter, + headers: Record | null, + body: unknown, + params: RequestParams, + callback?: RequestCallback | undefined + ): void { + this.platformHttp.do(method, path, headers, body, params, callback); + } + + doUri( + method: HttpMethods, + uri: string, + headers: Record | null, + body: unknown, + params: RequestParams, + callback?: RequestCallback | undefined + ): void { + this.platformHttp.doUri(method, uri, headers, body, params, callback); + } +} + export interface ErrnoException extends Error { errno?: number; code?: string; diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index affb68bae6..166ba4adc5 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -12,7 +12,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var monitorConnection = helper.monitorConnection; var testOnAllTransports = helper.testOnAllTransports; var mixin = helper.Utils.mixin; - var http = new Ably.Realtime.Platform.Http(); + var http = new Ably.Realtime._Http(); var jwtTestChannelName = 'JWT_test' + String(Math.floor(Math.random() * 10000) + 1); var echoServer = 'https://echo.ably.io'; var whenPromiseSettles = helper.whenPromiseSettles; diff --git a/test/realtime/connectivity.test.js b/test/realtime/connectivity.test.js index ea4edc9177..cea8ab6606 100644 --- a/test/realtime/connectivity.test.js +++ b/test/realtime/connectivity.test.js @@ -22,7 +22,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { * Connect with available http transports; internet connectivity check should work */ it('http_connectivity_check', function (done) { - new Ably.Realtime.Platform.Http().checkConnectivity(function (err, res) { + new Ably.Realtime._Http().checkConnectivity(function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; } catch (err) { diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 9b056eb6eb..8a8f549a8c 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -376,7 +376,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); realtime.connection.once('connected', function () { try { - var hosts = new Ably.Rest.Platform.Http()._getHosts(realtime); + var hosts = new Ably.Rest._Http()._getHosts(realtime); /* restHost rather than realtimeHost as that's what connectionManager * knows about; converted to realtimeHost by the websocketTransport */ expect(hosts[0]).to.equal(realtime.options.restHost, 'Check connected realtime host is the first option'); @@ -397,7 +397,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { fallbackHosts: [goodHost, 'b', 'c'], }); realtime.connection.once('connected', function () { - var hosts = new Ably.Realtime.Platform.Http()._getHosts(realtime); + var hosts = new Ably.Realtime._Http()._getHosts(realtime); /* restHost rather than realtimeHost as that's what connectionManager * knows about; converted to realtimeHost by the websocketTransport */ try { diff --git a/test/rest/message.test.js b/test/rest/message.test.js index 090e6eba70..1f5f384b05 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -141,7 +141,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async idOne, idTwo, originalPublish = channel._publish, - originalDoUri = Ably.Realtime.Platform.Http.doUri; + originalDoUri = Ably.Realtime._Http.doUri; channel._publish = function (requestBody) { var messageOne = JSON.parse(requestBody)[0]; @@ -157,7 +157,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async originalPublish.apply(channel, arguments); }; - Ably.Rest.Platform.Http.doUri = function (method, uri, headers, body, params, callback) { + Ably.Rest._Http.doUri = function (method, uri, headers, body, params, callback) { originalDoUri(method, uri, headers, body, params, function (err) { if (err) { callback(err); @@ -166,7 +166,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Fake a publish error from realtime */ callback({ message: 'moo', code: 50300, statusCode: 503 }); }); - Ably.Rest.Platform.Http.doUri = originalDoUri; + Ably.Rest._Http.doUri = originalDoUri; }; await channel.publish([{ name: 'one' }, { name: 'two' }]); From aef1771e7ba15ab6e400a7b71186567ba29484cf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 17 Jan 2024 09:52:02 +0000 Subject: [PATCH 248/468] =?UTF-8?q?Use=20doUri=20inside=20web=E2=80=99s=20?= =?UTF-8?q?HTTP.do?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes it consistent with the Node version. --- src/platform/web/lib/http/http.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index 39dcf52c65..afad319970 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -177,11 +177,7 @@ const Http = class { if (currentFallback) { if (currentFallback.validUntil > Utils.now()) { /* Use stored fallback */ - if (!this.Request) { - callback?.(new PartialErrorInfo('Request invoked before assigned to', null, 500)); - return; - } - this.Request(method, uriFromHost(currentFallback.host), headers, params, body, (err?, ...args) => { + this.doUri(method, uriFromHost(currentFallback.host), headers, body, params, (err?, ...args) => { // This typecast is safe because ErrnoExceptions are only thrown in NodeJS if (err && shouldFallback(err as ErrorInfo)) { /* unstore the fallback and start from the top with the default sequence */ From 8eda7716a9faa9f2285388a3695cf11debd41e87 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 17 Jan 2024 10:16:11 +0000 Subject: [PATCH 249/468] Remove duplication of HTTP.{do, _getHosts} implementations --- src/common/types/http.ts | 91 ++++++++++++++++++--- src/platform/nodejs/lib/util/http.ts | 117 ++++----------------------- src/platform/web/lib/http/http.ts | 115 +++----------------------- 3 files changed, 107 insertions(+), 216 deletions(-) diff --git a/src/common/types/http.ts b/src/common/types/http.ts index cc2e4e4459..9bcfc53dc2 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -1,12 +1,15 @@ +import Defaults from 'common/lib/util/defaults'; import Platform from 'common/platform'; +import BaseRealtime from 'common/lib/client/baserealtime'; import HttpMethods from '../constants/HttpMethods'; import BaseClient from '../lib/client/baseclient'; import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; export type PathParameter = string | ((host: string) => string); export type RequestCallbackHeaders = Partial>; +export type RequestCallbackError = ErrnoException | IPartialErrorInfo; export type RequestCallback = ( - error?: ErrnoException | IPartialErrorInfo | null, + error?: RequestCallbackError | null, body?: unknown, headers?: RequestCallbackHeaders, unpacked?: boolean, @@ -25,15 +28,6 @@ export interface IPlatformHttp { supportsAuthHeaders: boolean; supportsLinkHeaders: boolean; - _getHosts: (client: BaseClient) => string[]; - do( - method: HttpMethods, - path: PathParameter, - headers: Record | null, - body: unknown, - params: RequestParams, - callback?: RequestCallback - ): void; doUri( method: HttpMethods, uri: string, @@ -43,6 +37,11 @@ export interface IPlatformHttp { callback?: RequestCallback ): void; checkConnectivity?: (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => void; + + /** + * @param error An error returned by {@link doUri}’s callback. + */ + shouldFallback(error: RequestCallbackError): boolean; } export class Http { @@ -67,7 +66,17 @@ export class Http { } _getHosts(client: BaseClient) { - return this.platformHttp._getHosts(client); + /* If we're a connected realtime client, try the endpoint we're connected + * to first -- but still have fallbacks, being connected is not an absolute + * guarantee that a datacenter has free capacity to service REST requests. */ + const connection = (client as BaseRealtime).connection, + connectionHost = connection && connection.connectionManager.host; + + if (connectionHost) { + return [connectionHost].concat(Defaults.getFallbackHosts(client.options)); + } + + return Defaults.getHosts(client.options); } do( @@ -78,7 +87,65 @@ export class Http { params: RequestParams, callback?: RequestCallback | undefined ): void { - this.platformHttp.do(method, path, headers, body, params, callback); + /* Unlike for doUri, the presence of `this.client` here is mandatory, as it's used to generate the hosts */ + const client = this.client; + if (!client) { + throw new Error('http.do called without client'); + } + + const uriFromHost = + typeof path === 'function' + ? path + : function (host: string) { + return client.baseUri(host) + path; + }; + + const currentFallback = client._currentFallback; + if (currentFallback) { + if (currentFallback.validUntil > Date.now()) { + /* Use stored fallback */ + this.doUri(method, uriFromHost(currentFallback.host), headers, body, params, (err, ...args) => { + if (err && this.platformHttp.shouldFallback(err as ErrnoException)) { + /* unstore the fallback and start from the top with the default sequence */ + client._currentFallback = null; + this.do(method, path, headers, body, params, callback); + return; + } + callback?.(err, ...args); + }); + return; + } else { + /* Fallback expired; remove it and fallthrough to normal sequence */ + client._currentFallback = null; + } + } + + const hosts = this._getHosts(client); + + /* see if we have one or more than one host */ + if (hosts.length === 1) { + this.doUri(method, uriFromHost(hosts[0]), headers, body, params, callback); + return; + } + + const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { + const host = candidateHosts.shift(); + this.doUri(method, uriFromHost(host as string), headers, body, params, (err, ...args) => { + if (err && this.platformHttp.shouldFallback(err as ErrnoException) && candidateHosts.length) { + tryAHost(candidateHosts, true); + return; + } + if (persistOnSuccess) { + /* RSC15f */ + client._currentFallback = { + host: host as string, + validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, + }; + } + callback?.(err, ...args); + }); + }; + tryAHost(hosts); } doUri( diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 486b6995ee..09e68cdb0a 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -4,8 +4,8 @@ import ErrorInfo from 'common/lib/types/errorinfo'; import { ErrnoException, IPlatformHttpStatic, - PathParameter, RequestCallback, + RequestCallbackError, RequestParams, } from '../../../../common/types/http'; import HttpMethods from '../../../../common/constants/HttpMethods'; @@ -13,7 +13,6 @@ import got, { Response, Options, CancelableRequest, Agents } from 'got'; import http from 'http'; import https from 'https'; import BaseClient from 'common/lib/client/baseclient'; -import BaseRealtime from 'common/lib/client/baserealtime'; import { RestAgentOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; import { shallowEquals, throwMissingModuleError } from 'common/lib/util/utils'; @@ -68,41 +67,11 @@ const handler = function (uri: string, params: unknown, client: BaseClient | nul }; }; -function shouldFallback(err: ErrnoException) { - const { code, statusCode } = err; - return ( - code === 'ENETUNREACH' || - code === 'EHOSTUNREACH' || - code === 'EHOSTDOWN' || - code === 'ETIMEDOUT' || - code === 'ESOCKETTIMEDOUT' || - code === 'ENOTFOUND' || - code === 'ECONNRESET' || - code === 'ECONNREFUSED' || - (statusCode >= 500 && statusCode <= 504) - ); -} - -function getHosts(client: BaseClient): string[] { - /* If we're a connected realtime client, try the endpoint we're connected - * to first -- but still have fallbacks, being connected is not an absolute - * guarantee that a datacenter has free capacity to service REST requests. */ - const connection = (client as BaseRealtime).connection; - const connectionHost = connection && connection.connectionManager.host; - - if (connectionHost) { - return [connectionHost].concat(Defaults.getFallbackHosts(client.options)); - } - - return Defaults.getHosts(client.options); -} - const Http: IPlatformHttpStatic = class { static methods = [HttpMethods.Get, HttpMethods.Delete, HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; static methodsWithBody = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; private agent: Agents | null = null; - _getHosts = getHosts; supportsAuthHeaders = true; supportsLinkHeaders = true; private client: BaseClient | null; @@ -111,75 +80,6 @@ const Http: IPlatformHttpStatic = class { this.client = client ?? null; } - do( - method: HttpMethods, - path: PathParameter, - headers: Record | null, - body: unknown, - params: RequestParams, - callback: RequestCallback - ): void { - /* Unlike for doUri, the presence of `this.client` here is mandatory, as it's used to generate the hosts */ - const client = this.client; - if (!client) { - throw new Error('http.do called without client'); - } - - const uriFromHost = - typeof path === 'function' - ? path - : function (host: string) { - return client.baseUri(host) + path; - }; - - const currentFallback = client._currentFallback; - if (currentFallback) { - if (currentFallback.validUntil > Date.now()) { - /* Use stored fallback */ - this.doUri(method, uriFromHost(currentFallback.host), headers, body, params, (err, ...args) => { - if (err && shouldFallback(err as ErrnoException)) { - /* unstore the fallback and start from the top with the default sequence */ - client._currentFallback = null; - this.do(method, path, headers, body, params, callback); - return; - } - callback(err, ...args); - }); - return; - } else { - /* Fallback expired; remove it and fallthrough to normal sequence */ - client._currentFallback = null; - } - } - - const hosts = getHosts(client); - - /* see if we have one or more than one host */ - if (hosts.length === 1) { - this.doUri(method, uriFromHost(hosts[0]), headers, body, params, callback); - return; - } - - const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { - const host = candidateHosts.shift(); - this.doUri(method, uriFromHost(host as string), headers, body, params, function (err, ...args) { - if (err && shouldFallback(err as ErrnoException) && candidateHosts.length) { - tryAHost(candidateHosts, true); - return; - } - if (persistOnSuccess) { - /* RSC15f */ - client._currentFallback = { - host: host as string, - validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, - }; - } - callback(err, ...args); - }); - }; - tryAHost(hosts); - } - doUri( method: HttpMethods, uri: string, @@ -264,6 +164,21 @@ const Http: IPlatformHttpStatic = class { } ); }; + + shouldFallback(err: RequestCallbackError) { + const { code, statusCode } = err as ErrnoException; + return ( + code === 'ENETUNREACH' || + code === 'EHOSTUNREACH' || + code === 'EHOSTDOWN' || + code === 'ETIMEDOUT' || + code === 'ESOCKETTIMEDOUT' || + code === 'ENOTFOUND' || + code === 'ECONNRESET' || + code === 'ECONNREFUSED' || + (statusCode >= 500 && statusCode <= 504) + ); + } }; export default Http; diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index afad319970..17e801f275 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -1,11 +1,9 @@ import Platform from 'common/platform'; -import * as Utils from 'common/lib/util/utils'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestCallback, RequestParams } from 'common/types/http'; +import { RequestCallback, RequestCallbackError, RequestParams } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; -import BaseRealtime from 'common/lib/client/baserealtime'; import XHRStates from 'common/constants/XHRStates'; import Logger from 'common/lib/util/logger'; import { StandardCallback } from 'common/types/utils'; @@ -14,32 +12,6 @@ import { ModulesMap } from 'common/lib/client/modulesmap'; export type HTTPRequestImplementations = Pick; -function shouldFallback(errorInfo: ErrorInfo) { - const statusCode = errorInfo.statusCode as number; - /* 400 + no code = a generic xhr onerror. Browser doesn't give us enough - * detail to know whether it's fallback-fixable, but it may be (eg if a - * network issue), so try just in case */ - return ( - (statusCode === 408 && !errorInfo.code) || - (statusCode === 400 && !errorInfo.code) || - (statusCode >= 500 && statusCode <= 504) - ); -} - -function getHosts(client: BaseClient): string[] { - /* If we're a connected realtime client, try the endpoint we're connected - * to first -- but still have fallbacks, being connected is not an absolute - * guarantee that a datacenter has free capacity to service REST requests. */ - const connection = (client as BaseRealtime).connection, - connectionHost = connection && connection.connectionManager.host; - - if (connectionHost) { - return [connectionHost].concat(Defaults.getFallbackHosts(client.options)); - } - - return Defaults.getHosts(client.options); -} - function createMissingImplementationError() { return new ErrorInfo( 'No HTTP request module provided. Provide at least one of the FetchRequest or XHRRequest modules.', @@ -151,79 +123,6 @@ const Http = class { } } - /* Unlike for doUri, the 'client' param here is mandatory, as it's used to generate the hosts */ - do( - method: HttpMethods, - path: string, - headers: Record | null, - body: unknown, - params: RequestParams, - callback?: RequestCallback - ): void { - /* Unlike for doUri, the presence of `this.client` here is mandatory, as it's used to generate the hosts */ - const client = this.client; - if (!client) { - throw new Error('http.do called without client'); - } - - const uriFromHost = - typeof path == 'function' - ? path - : function (host: string) { - return client.baseUri(host) + path; - }; - - const currentFallback = client._currentFallback; - if (currentFallback) { - if (currentFallback.validUntil > Utils.now()) { - /* Use stored fallback */ - this.doUri(method, uriFromHost(currentFallback.host), headers, body, params, (err?, ...args) => { - // This typecast is safe because ErrnoExceptions are only thrown in NodeJS - if (err && shouldFallback(err as ErrorInfo)) { - /* unstore the fallback and start from the top with the default sequence */ - client._currentFallback = null; - this.do(method, path, headers, body, params, callback); - return; - } - callback?.(err, ...args); - }); - return; - } else { - /* Fallback expired; remove it and fallthrough to normal sequence */ - client._currentFallback = null; - } - } - - const hosts = getHosts(client); - - /* if there is only one host do it */ - if (hosts.length === 1) { - this.doUri(method, uriFromHost(hosts[0]), headers, body, params, callback as RequestCallback); - return; - } - - /* hosts is an array with preferred host plus at least one fallback */ - const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { - const host = candidateHosts.shift(); - this.doUri(method, uriFromHost(host as string), headers, body, params, function (err, ...args) { - // This typecast is safe because ErrnoExceptions are only thrown in NodeJS - if (err && shouldFallback(err as ErrorInfo) && candidateHosts.length) { - tryAHost(candidateHosts, true); - return; - } - if (persistOnSuccess) { - /* RSC15f */ - client._currentFallback = { - host: host as string, - validUntil: Utils.now() + client.options.timeouts.fallbackRetryTimeout, - }; - } - callback?.(err, ...args); - }); - }; - tryAHost(hosts); - } - doUri( method: HttpMethods, uri: string, @@ -253,7 +152,17 @@ const Http = class { supportsAuthHeaders = false; supportsLinkHeaders = false; - _getHosts = getHosts; + shouldFallback(errorInfo: RequestCallbackError) { + const statusCode = errorInfo.statusCode as number; + /* 400 + no code = a generic xhr onerror. Browser doesn't give us enough + * detail to know whether it's fallback-fixable, but it may be (eg if a + * network issue), so try just in case */ + return ( + (statusCode === 408 && !errorInfo.code) || + (statusCode === 400 && !errorInfo.code) || + (statusCode >= 500 && statusCode <= 504) + ); + } }; export default Http; From e86ebf998e7bd5ab8b16b72f72802dcb1a88eaaf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 17 Jan 2024 16:46:57 +0000 Subject: [PATCH 250/468] Tighten the type of HTTP request body a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looked into what types we pass, because I wanted to understand it better for some logging that I’m going to add. Thought I might as well update the types to reflect what I’d found out. --- src/common/lib/client/paginatedresource.ts | 8 ++++---- src/common/lib/client/resource.ts | 10 +++++----- src/common/lib/client/rest.ts | 10 +++++++--- src/common/lib/client/restchannel.ts | 8 +++++++- src/common/types/http.ts | 10 +++++++--- src/platform/nodejs/lib/util/http.ts | 3 ++- src/platform/web/lib/http/http.ts | 8 ++++---- src/platform/web/lib/http/request/fetchrequest.ts | 4 ++-- src/platform/web/lib/http/request/xhrrequest.ts | 8 ++++---- src/platform/web/lib/transport/xhrpollingtransport.ts | 4 ++-- .../web/lib/transport/xhrstreamingtransport.ts | 4 ++-- 11 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index af9ce5e395..0513949be8 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -4,7 +4,7 @@ import Resource from './resource'; import { IPartialErrorInfo } from '../types/errorinfo'; import { PaginatedResultCallback } from '../../types/utils'; import BaseClient from './baseclient'; -import { RequestCallbackHeaders } from 'common/types/http'; +import { RequestBody, RequestCallbackHeaders } from 'common/types/http'; export type BodyHandler = (body: unknown, headers: RequestCallbackHeaders, unpacked?: boolean) => Promise; @@ -85,7 +85,7 @@ class PaginatedResource { ); } - post(params: Record, body: unknown, callback: PaginatedResultCallback): void { + post(params: Record, body: RequestBody | null, callback: PaginatedResultCallback): void { Resource.post( this.client, this.path, @@ -101,7 +101,7 @@ class PaginatedResource { ); } - put(params: Record, body: unknown, callback: PaginatedResultCallback): void { + put(params: Record, body: RequestBody | null, callback: PaginatedResultCallback): void { Resource.put( this.client, this.path, @@ -117,7 +117,7 @@ class PaginatedResource { ); } - patch(params: Record, body: unknown, callback: PaginatedResultCallback): void { + patch(params: Record, body: RequestBody | null, callback: PaginatedResultCallback): void { Resource.patch( this.client, this.path, diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 1364e01d06..bfe105a838 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -6,7 +6,7 @@ import HttpMethods from '../../constants/HttpMethods'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import BaseClient from './baseclient'; import { MsgPack } from 'common/types/msgpack'; -import { RequestCallbackHeaders } from 'common/types/http'; +import { RequestBody, RequestCallbackHeaders } from 'common/types/http'; function withAuthDetails( client: BaseClient, @@ -161,7 +161,7 @@ class Resource { static post( client: BaseClient, path: string, - body: unknown, + body: RequestBody | null, headers: Record, params: Record, envelope: Utils.Format | null, @@ -173,7 +173,7 @@ class Resource { static patch( client: BaseClient, path: string, - body: unknown, + body: RequestBody | null, headers: Record, params: Record, envelope: Utils.Format | null, @@ -185,7 +185,7 @@ class Resource { static put( client: BaseClient, path: string, - body: unknown, + body: RequestBody | null, headers: Record, params: Record, envelope: Utils.Format | null, @@ -198,7 +198,7 @@ class Resource { method: HttpMethods, client: BaseClient, path: string, - body: unknown, + body: RequestBody | null, headers: Record, params: Record, envelope: Utils.Format | null, diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index c61c22215d..a6a2d3ab88 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -9,7 +9,7 @@ import Stats from '../types/stats'; import HttpMethods from '../../constants/HttpMethods'; import { ChannelOptions } from '../../types/channel'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import { RequestParams } from '../../types/http'; +import { RequestBody, RequestParams } from '../../types/http'; import * as API from '../../../../ably'; import Resource from './resource'; @@ -152,7 +152,7 @@ export class Rest { } if (typeof body !== 'string') { - body = encoder(body); + body = encoder(body) ?? null; } Utils.mixin(headers, this.client.options.headers); if (customHeaders) { @@ -174,7 +174,11 @@ export class Rest { } if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { - paginatedResource[_method as HttpMethods.Post](params, body, callback as PaginatedResultCallback); + paginatedResource[_method as HttpMethods.Post]( + params, + body as RequestBody, + callback as PaginatedResultCallback + ); } else { paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete]( params, diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 08fda3be76..31b2b39fd7 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -18,6 +18,7 @@ import BaseRest from './baseclient'; import * as API from '../../../../ably'; import Defaults, { normaliseChannelOptions } from '../util/defaults'; import { RestHistoryParams } from './restchannelmixin'; +import { RequestBody } from 'common/types/http'; const MSG_ID_ENTROPY_BYTES = 9; @@ -141,7 +142,12 @@ class RestChannel { }); } - _publish(requestBody: unknown, headers: Record, params: any, callback: ResourceCallback): void { + _publish( + requestBody: RequestBody | null, + headers: Record, + params: any, + callback: ResourceCallback + ): void { Resource.post( this.client, this.client.rest.channelMixin.basePath(this) + '/messages', diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 9bcfc53dc2..923a7d0ef8 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -16,6 +16,10 @@ export type RequestCallback = ( statusCode?: number ) => void; export type RequestParams = Record | null; +export type RequestBody = + | Buffer // only on Node + | ArrayBuffer // only on web + | string; export interface IPlatformHttpStatic { new (client?: BaseClient): IPlatformHttp; @@ -32,7 +36,7 @@ export interface IPlatformHttp { method: HttpMethods, uri: string, headers: Record | null, - body: unknown, + body: RequestBody | null, params: RequestParams, callback?: RequestCallback ): void; @@ -83,7 +87,7 @@ export class Http { method: HttpMethods, path: PathParameter, headers: Record | null, - body: unknown, + body: RequestBody | null, params: RequestParams, callback?: RequestCallback | undefined ): void { @@ -152,7 +156,7 @@ export class Http { method: HttpMethods, uri: string, headers: Record | null, - body: unknown, + body: RequestBody | null, params: RequestParams, callback?: RequestCallback | undefined ): void { diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 09e68cdb0a..1dfb0752c3 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -3,6 +3,7 @@ import Defaults from 'common/lib/util/defaults'; import ErrorInfo from 'common/lib/types/errorinfo'; import { ErrnoException, + RequestBody, IPlatformHttpStatic, RequestCallback, RequestCallbackError, @@ -84,7 +85,7 @@ const Http: IPlatformHttpStatic = class { method: HttpMethods, uri: string, headers: Record | null, - body: unknown, + body: RequestBody | null, params: RequestParams, callback: RequestCallback ): void { diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index 17e801f275..5071f66668 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -1,7 +1,7 @@ import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestCallback, RequestCallbackError, RequestParams } from 'common/types/http'; +import { RequestBody, RequestCallback, RequestCallbackError, RequestParams } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import XHRStates from 'common/constants/XHRStates'; @@ -54,7 +54,7 @@ const Http = class { uri: string, headers: Record | null, params: RequestParams, - body: unknown, + body: RequestBody | null, callback: RequestCallback ) { const req = xhrRequestImplementation.createRequest( @@ -127,7 +127,7 @@ const Http = class { method: HttpMethods, uri: string, headers: Record | null, - body: unknown, + body: RequestBody | null, params: RequestParams, callback: RequestCallback ): void { @@ -143,7 +143,7 @@ const Http = class { uri: string, headers: Record | null, params: RequestParams, - body: unknown, + body: RequestBody | null, callback: RequestCallback ) => void; diff --git a/src/platform/web/lib/http/request/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts index 0291121297..9bd0ce42b6 100644 --- a/src/platform/web/lib/http/request/fetchrequest.ts +++ b/src/platform/web/lib/http/request/fetchrequest.ts @@ -1,7 +1,7 @@ import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestCallback, RequestCallbackHeaders, RequestParams } from 'common/types/http'; +import { RequestBody, RequestCallback, RequestCallbackHeaders, RequestParams } from 'common/types/http'; import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import * as Utils from 'common/lib/util/utils'; @@ -32,7 +32,7 @@ export default function fetchRequest( uri: string, headers: Record | null, params: RequestParams, - body: unknown, + body: RequestBody | null, callback: RequestCallback ) { const fetchHeaders = new Headers(headers || {}); diff --git a/src/platform/web/lib/http/request/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts index b2ff5141d9..99801578fd 100644 --- a/src/platform/web/lib/http/request/xhrrequest.ts +++ b/src/platform/web/lib/http/request/xhrrequest.ts @@ -5,7 +5,7 @@ import Logger from 'common/lib/util/logger'; import Defaults from 'common/lib/util/defaults'; import HttpMethods from 'common/constants/HttpMethods'; import IXHRRequest from 'common/types/IXHRRequest'; -import { RequestParams } from 'common/types/http'; +import { RequestBody, RequestParams } from 'common/types/http'; import XHRStates from 'common/constants/XHRStates'; import Platform from 'common/platform'; @@ -68,7 +68,7 @@ function getHeadersAsObject(xhr: XMLHttpRequest) { class XHRRequest extends EventEmitter implements IXHRRequest { uri: string; headers: Record; - body: unknown; + body: RequestBody | null; method: string; requestMode: number; timeouts: Record; @@ -83,7 +83,7 @@ class XHRRequest extends EventEmitter implements IXHRRequest { uri: string, headers: Record | null, params: Record, - body: unknown, + body: RequestBody | null, requestMode: number, timeouts: Record, method?: HttpMethods @@ -108,7 +108,7 @@ class XHRRequest extends EventEmitter implements IXHRRequest { uri: string, headers: Record | null, params: RequestParams, - body: unknown, + body: RequestBody | null, requestMode: number, timeouts: Record | null, method?: HttpMethods diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index bf0f13befd..aa029b334c 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -3,7 +3,7 @@ import CometTransport from '../../../../common/lib/transport/comettransport'; import XHRRequest from '../http/request/xhrrequest'; import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; -import { RequestParams } from 'common/types/http'; +import { RequestBody, RequestParams } from 'common/types/http'; import { TransportNames } from 'common/constants/TransportName'; var shortName = TransportNames.XhrPolling; @@ -27,7 +27,7 @@ class XHRPollingTransport extends CometTransport { uri: string, headers: Record, params: RequestParams, - body: unknown, + body: RequestBody | null, requestMode: number ) { return XHRRequest.createRequest(uri, headers, params, body, requestMode, this.timeouts); diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index 5dd77e6f6d..6aff8ec0fd 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -3,7 +3,7 @@ import Platform from '../../../../common/platform'; import XHRRequest from '../http/request/xhrrequest'; import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; -import { RequestParams } from 'common/types/http'; +import { RequestBody, RequestParams } from 'common/types/http'; import { TransportNames } from 'common/constants/TransportName'; const shortName = TransportNames.XhrStreaming; @@ -25,7 +25,7 @@ class XHRStreamingTransport extends CometTransport { uri: string, headers: Record, params: RequestParams, - body: unknown, + body: RequestBody | null, requestMode: number ) { return XHRRequest.createRequest(uri, headers, params, body, requestMode, this.timeouts); From 7cd8580ee1329b5a1c8682e541448aace06afbfc Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 18 Jan 2024 10:28:33 +0000 Subject: [PATCH 251/468] Base64 encode binary data when logging response To avoid loss of information when unprintable characters (e.g. in MessagePack responses) get replaced (either by console.log or by the terminal, not sure which) with U+FFFD. --- src/common/lib/client/resource.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index bfe105a838..e06bc4a733 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -117,8 +117,8 @@ function logResponseHandler( paramString(headers as Record) + '; StatusCode: ' + statusCode + - '; Body: ' + - (Platform.BufferUtils.isBuffer(body) ? body.toString() : body) + '; Body' + + (Platform.BufferUtils.isBuffer(body) ? ' (Base64): ' + Platform.BufferUtils.base64Encode(body) : ': ' + body) ); } if (callback) { From 258af9ae95cffe1ed500cec8b276198bcbd78649 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 18 Jan 2024 11:57:26 +0000 Subject: [PATCH 252/468] Use `inspect` when printing Resource response body Else we often just see "[object Object]" in the logs. --- src/common/lib/client/resource.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index e06bc4a733..7996ec35c6 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -118,7 +118,9 @@ function logResponseHandler( '; StatusCode: ' + statusCode + '; Body' + - (Platform.BufferUtils.isBuffer(body) ? ' (Base64): ' + Platform.BufferUtils.base64Encode(body) : ': ' + body) + (Platform.BufferUtils.isBuffer(body) + ? ' (Base64): ' + Platform.BufferUtils.base64Encode(body) + : ': ' + Platform.Config.inspect(body)) ); } if (callback) { From f36f5374ec37118231a0587500e70f3ca84cefc1 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 18 Jan 2024 12:10:20 +0000 Subject: [PATCH 253/468] Remove duplicate log statement --- src/common/lib/client/resource.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 7996ec35c6..fb2c688922 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -216,14 +216,6 @@ class Resource { } function doRequest(this: any, headers: Record, params: Record) { - if (Logger.shouldLog(Logger.LOG_MICRO)) { - Logger.logAction( - Logger.LOG_MICRO, - 'Resource.' + method + '()', - 'Sending; ' + urlFromPathAndParams(path, params) - ); - } - if (Logger.shouldLog(Logger.LOG_MICRO)) { let decodedBody = body; if (headers['content-type']?.indexOf('msgpack') > 0) { From d43f96509a33b15298385463906069f40617386b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 16 Jan 2024 16:10:22 +0000 Subject: [PATCH 254/468] Log all HTTP requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owen requested this during a review of #1526, which strips most of our logging from the modular variant of the SDK, but aims to still log all network activity. A bonus benefit is that we now also have logging for requests which get tried against different hosts, which we previously didn’t. The log statements are adaptations of the existing ones in resource.ts. --- src/common/lib/client/resource.ts | 21 +++------- src/common/types/http.ts | 68 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index fb2c688922..bfb860fd65 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -6,7 +6,12 @@ import HttpMethods from '../../constants/HttpMethods'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import BaseClient from './baseclient'; import { MsgPack } from 'common/types/msgpack'; -import { RequestBody, RequestCallbackHeaders } from 'common/types/http'; +import { + RequestBody, + RequestCallbackHeaders, + appendingParams as urlFromPathAndParams, + paramString, +} from 'common/types/http'; function withAuthDetails( client: BaseClient, @@ -80,20 +85,6 @@ function unenvelope( }; } -function paramString(params: Record) { - const paramPairs = []; - if (params) { - for (const needle in params) { - paramPairs.push(needle + '=' + params[needle]); - } - } - return paramPairs.join('&'); -} - -function urlFromPathAndParams(path: string, params: Record) { - return path + (params ? '?' : '') + paramString(params); -} - function logResponseHandler( callback: ResourceCallback, method: HttpMethods, diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 923a7d0ef8..93d4e0f229 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -4,6 +4,8 @@ import BaseRealtime from 'common/lib/client/baserealtime'; import HttpMethods from '../constants/HttpMethods'; import BaseClient from '../lib/client/baseclient'; import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; +import Logger from 'common/lib/util/logger'; +import * as Utils from 'common/lib/util/utils'; export type PathParameter = string | ((host: string) => string); export type RequestCallbackHeaders = Partial>; @@ -48,6 +50,66 @@ export interface IPlatformHttp { shouldFallback(error: RequestCallbackError): boolean; } +export function paramString(params: Record | null) { + const paramPairs = []; + if (params) { + for (const needle in params) { + paramPairs.push(needle + '=' + params[needle]); + } + } + return paramPairs.join('&'); +} + +export function appendingParams(uri: string, params: Record | null) { + return uri + (params ? '?' : '') + paramString(params); +} + +function logResponseHandler( + callback: RequestCallback | undefined, + method: HttpMethods, + uri: string, + params: Record | null +): RequestCallback { + return (err, body, headers, unpacked, statusCode) => { + if (err) { + Logger.logAction( + Logger.LOG_MICRO, + 'Http.' + method + '()', + 'Received Error; ' + appendingParams(uri, params) + '; Error: ' + Utils.inspectError(err) + ); + } else { + Logger.logAction( + Logger.LOG_MICRO, + 'Http.' + method + '()', + 'Received; ' + + appendingParams(uri, params) + + '; Headers: ' + + paramString(headers as Record) + + '; StatusCode: ' + + statusCode + + '; Body' + + (Platform.BufferUtils.isBuffer(body) ? ' (Base64): ' + Platform.BufferUtils.base64Encode(body) : ': ' + body) + ); + } + if (callback) { + callback(err, body, headers, unpacked, statusCode); + } + }; +} + +function logRequest(method: HttpMethods, uri: string, body: RequestBody | null, params: RequestParams) { + if (Logger.shouldLog(Logger.LOG_MICRO)) { + Logger.logAction( + Logger.LOG_MICRO, + 'Http.' + method + '()', + 'Sending; ' + + appendingParams(uri, params) + + '; Body' + + (Platform.BufferUtils.isBuffer(body) ? ' (Base64): ' + Platform.BufferUtils.base64Encode(body) : ': ' + body) + ); + } +} + export class Http { private readonly platformHttp: IPlatformHttp; checkConnectivity?: (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => void; @@ -160,6 +222,12 @@ export class Http { params: RequestParams, callback?: RequestCallback | undefined ): void { + logRequest(method, uri, body, params); + + if (Logger.shouldLog(Logger.LOG_MICRO)) { + callback = logResponseHandler(callback, method, uri, params); + } + this.platformHttp.doUri(method, uri, headers, body, params, callback); } } From 324838596522cff79338a596b9e3483dbc39a5ba Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 6 Dec 2023 09:35:00 -0300 Subject: [PATCH 255/468] Run modules build asynchronously Need this in order to use esbuild plugins. --- Gruntfile.js | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1c67b7c050..8b65e4a43c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -110,35 +110,41 @@ module.exports = function (grunt) { grunt.registerTask('build:browser', function () { var done = this.async(); - var baseConfig = { - entryPoints: ['src/platform/web/index.ts'], - outfile: 'build/ably.js', - bundle: true, - sourcemap: true, - format: 'umd', - banner: { js: '/*' + banner + '*/' }, - plugins: [umdWrapper.default()], - target: 'es6', - }; - - var modulesConfig = { - ...baseConfig, - entryPoints: ['src/platform/web/modules.ts'], - outfile: 'build/modules/index.js', - format: 'esm', - plugins: [], - }; + function createBaseConfig() { + return { + entryPoints: ['src/platform/web/index.ts'], + outfile: 'build/ably.js', + bundle: true, + sourcemap: true, + format: 'umd', + banner: { js: '/*' + banner + '*/' }, + plugins: [umdWrapper.default()], + target: 'es6', + }; + } - // For reasons I don't understand this build fails when run asynchronously - esbuild.buildSync(modulesConfig); + function createModulesConfig() { + return { + // We need to create a new copy of the base config, because calling + // esbuild.build() with the base config causes it to mutate the passed + // config’s `banner.js` property to add some weird modules shim code, + // which we don’t want here. + ...createBaseConfig(), + entryPoints: ['src/platform/web/modules.ts'], + outfile: 'build/modules/index.js', + format: 'esm', + plugins: [], + }; + } Promise.all([ - esbuild.build(baseConfig), + esbuild.build(createBaseConfig()), esbuild.build({ - ...baseConfig, + ...createBaseConfig(), outfile: 'build/ably.min.js', minify: true, }), + esbuild.build(createModulesConfig()), ]).then(() => { console.log('esbuild succeeded'); done(true); From c5935d88bf4d65f3e03e8ba545c57236f005068e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 5 Dec 2023 16:52:16 -0300 Subject: [PATCH 256/468] Strip less-important logging from modular variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to reduce bundle size further, we’ve decided to strip all logging from the modular variant of the SDK, except for errors and certain network events. (We also considered providing a separate tree-shakable module with all of this logging code so that a user of the modular variant of the library can opt in to it, but decided against it for now; we might add it in later. This does mean that there is currently no version of the SDK that allows you to use both deltas and verbose logging on web.) I couldn’t find any out-of-the-box esbuild functionality that let us do this. The only stuff I could find related to stripping code was: - the `pure` option, but that code only gets stripped if you minify the code (and even in that case I couldn’t actually get it to be stripped, perhaps would have been able to with further trying though), but minifying our generated modules bundle causes the bundle size of those who use it (as tested by our modulereport script) to increase considerably (for reasons I’m not sure of) - the `drop` option, but that only lets you remove calls to `console` or `debugger` So instead I’ve implemented it as an esbuild plugin. Resolves #1526. --- .eslintrc.js | 1 + Gruntfile.js | 3 +- README.md | 9 +- grunt/esbuild/strip-logs.js | 111 ++++++++++++++++++ modules.d.ts | 18 +++ package-lock.json | 15 ++- package.json | 7 +- scripts/moduleReport.ts | 2 +- src/common/lib/client/realtimechannel.ts | 13 +- src/common/lib/transport/connectionmanager.ts | 13 +- src/common/lib/transport/protocol.ts | 2 +- src/common/lib/transport/transport.ts | 4 +- src/common/lib/util/logger.ts | 14 ++- src/common/types/http.ts | 6 +- 14 files changed, 188 insertions(+), 30 deletions(-) create mode 100644 grunt/esbuild/strip-logs.js diff --git a/.eslintrc.js b/.eslintrc.js index 2094269b8f..f77c816271 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -58,6 +58,7 @@ module.exports = { "typedoc/generated", "react", "Gruntfile.js", + "grunt", ], settings: { jsdoc: { diff --git a/Gruntfile.js b/Gruntfile.js index 8b65e4a43c..90f9b2d7c1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,6 +7,7 @@ var esbuild = require('esbuild'); var umdWrapper = require('esbuild-plugin-umd-wrapper'); var banner = require('./src/fragments/license'); var process = require('process'); +var stripLogsPlugin = require('./grunt/esbuild/strip-logs').default; module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-concat'); @@ -133,7 +134,7 @@ module.exports = function (grunt) { entryPoints: ['src/platform/web/modules.ts'], outfile: 'build/modules/index.js', format: 'esm', - plugins: [], + plugins: [stripLogsPlugin], }; } diff --git a/README.md b/README.md index 65611eb5bf..65ecbc379f 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,14 @@ You must provide: `BaseRealtime` offers the same API as the `Realtime` class described in the rest of this `README`. This means that you can develop an application using the default variant of the SDK and switch to the modular version when you wish to optimize your bundle size. -For more information, see the [generated documentation](https://sdk.ably.com/builds/ably/ably-js/main/typedoc/modules/modules.html) (this link points to the documentation for the `main` branch). +In order to further reduce bundle size, the modular variant of the SDK performs less logging than the default variant. It only logs: + +- messages that have a `logLevel` of 1 (that is, errors) +- a small number of other network events + +If you need more verbose logging, use the default variant of the SDK. + +For more information about the modular variant of the SDK, see the [generated documentation](https://sdk.ably.com/builds/ably/ably-js/main/typedoc/modules/modules.html) (this link points to the documentation for the `main` branch). ### TypeScript diff --git a/grunt/esbuild/strip-logs.js b/grunt/esbuild/strip-logs.js new file mode 100644 index 0000000000..6fc755513d --- /dev/null +++ b/grunt/esbuild/strip-logs.js @@ -0,0 +1,111 @@ +var path = require('path'); +var fs = require('fs'); +var babel = { + types: require('@babel/types'), + parser: require('@babel/parser'), + traverse: require('@babel/traverse'), + generator: require('@babel/generator'), +}; + +// This function is copied from +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +// This esbuild plugin strips all log messages from the modular variant of +// the library, except for error-level logs and other logging statements +// explicitly marked as not to be stripped. +const stripLogsPlugin = { + name: 'stripLogs', + setup(build) { + let foundLogToStrip = false; + let foundErrorLog = false; + let foundNoStripLog = false; + + const filter = new RegExp(`^${escapeRegExp(path.join(__dirname, '..', '..', 'src') + path.sep)}.*\\.[tj]s$`); + build.onLoad({ filter }, async (args) => { + const contents = (await fs.promises.readFile(args.path)).toString(); + const lines = contents.split('\n'); + const ast = babel.parser.parse(contents, { sourceType: 'module', plugins: ['typescript'] }); + const errors = []; + + babel.traverse.default(ast, { + enter(path) { + if ( + path.isCallExpression() && + babel.types.isMemberExpression(path.node.callee) && + babel.types.isIdentifier(path.node.callee.object, { name: 'Logger' }) + ) { + if (babel.types.isIdentifier(path.node.callee.property, { name: 'logAction' })) { + const firstArgument = path.node.arguments[0]; + + if ( + babel.types.isMemberExpression(firstArgument) && + babel.types.isIdentifier(firstArgument.object, { name: 'Logger' }) && + firstArgument.property.name.startsWith('LOG_') + ) { + if (firstArgument.property.name === 'LOG_ERROR') { + // `path` is a call to `Logger.logAction(Logger.LOG_ERROR, ...)`; preserve it. + foundErrorLog = true; + } else { + // `path` is a call to `Logger.logAction(Logger.LOG_*, ...) for some other log level; strip it. + foundLogToStrip = true; + path.remove(); + } + } else { + // `path` is a call to `Logger.logAction(...)` with some argument other than a `Logger.LOG_*` expression; raise an error because we can’t determine whether to strip it. + errors.push({ + location: { + file: args.path, + column: firstArgument.loc.start.column, + line: firstArgument.loc.start.line, + lineText: lines[firstArgument.loc.start.line - 1], + }, + text: `First argument passed to Logger.logAction() must be Logger.LOG_*, got \`${ + babel.generator.default(firstArgument).code + }\``, + }); + } + } else if (babel.types.isIdentifier(path.node.callee.property, { name: 'logActionNoStrip' })) { + // `path` is a call to `Logger.logActionNoStrip(...)`; preserve it. + foundNoStripLog = true; + } + } + }, + }); + + return { contents: babel.generator.default(ast).code, loader: 'ts', errors }; + }); + + build.onEnd(() => { + const errorMessages = []; + + // Perform a sense check to make sure that we found some logging + // calls to strip (to protect us against accidentally changing the + // internal logging API in such a way that would cause us to no + // longer strip any calls). + + if (!foundLogToStrip) { + errorMessages.push('Did not find any Logger.logAction(...) calls to strip'); + } + + // Perform a sense check to make sure that we found some logging + // calls to preserve (to protect us against accidentally changing the + // internal logging API in such a way that would cause us to + // accidentally strip all logging calls). + + if (!foundErrorLog) { + errorMessages.push('Did not find any Logger.logAction(Logger.LOG_ERROR, ...) calls to preserve'); + } + + if (!foundNoStripLog) { + errorMessages.push('Did not find any Logger.logActionNoStrip(...) calls to preserve'); + } + + return { errors: errorMessages.map((text) => ({ text })) }; + }); + }, +}; + +exports.default = stripLogsPlugin; diff --git a/modules.d.ts b/modules.d.ts index e238fcf677..4875132db3 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -273,6 +273,15 @@ export interface ModulesMap { * A client that offers a simple stateless API to interact directly with Ably's REST API. * * `BaseRest` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Rest`](../../default/classes/Rest.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. + * + * > **Note** + * > + * > In order to further reduce bundle size, `BaseRest` performs less logging than the `Rest` class exported by the default variant of the SDK. It only logs: + * > + * > - messages that have a {@link ClientOptions.logLevel | `logLevel`} of 1 (that is, errors) + * > - a small number of other network events + * > + * > If you need more verbose logging, use the default variant of the SDK. */ export declare class BaseRest extends AbstractRest { /** @@ -292,6 +301,15 @@ export declare class BaseRest extends AbstractRest { * A client that extends the functionality of {@link BaseRest} and provides additional realtime-specific features. * * `BaseRealtime` is the equivalent, in the modular variant of the Ably Client Library SDK, of the [`Realtime`](../../default/classes/Realtime.html) class in the default variant of the SDK. The difference is that its constructor allows you to decide exactly which functionality the client should include. This allows unused functionality to be tree-shaken, reducing bundle size. + * + * > **Note** + * > + * > In order to further reduce bundle size, `BaseRealtime` performs less logging than the `Realtime` class exported by the default variant of the SDK. It only logs: + * > + * > - messages that have a {@link ClientOptions.logLevel | `logLevel`} of 1 (that is, errors) + * > - a small number of other network events + * > + * > If you need more verbose logging, use the default variant of the SDK. */ export declare class BaseRealtime extends AbstractRealtime { /** diff --git a/package-lock.json b/package-lock.json index 2a6904f9bd..277a60789d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,9 @@ "devDependencies": { "@ably/vcdiff-decoder": "1.0.6", "@arethetypeswrong/cli": "^0.13.1", + "@babel/generator": "^7.23.6", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", "@testing-library/react": "^13.3.0", "@types/jmespath": "^0.15.2", "@types/node": "^18.0.0", @@ -702,9 +705,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", @@ -11269,9 +11272,9 @@ } }, "@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dev": true, "requires": { "@babel/code-frame": "^7.23.5", diff --git a/package.json b/package.json index eafed5c020..760795238e 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,9 @@ "devDependencies": { "@ably/vcdiff-decoder": "1.0.6", "@arethetypeswrong/cli": "^0.13.1", + "@babel/generator": "^7.23.6", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", "@testing-library/react": "^13.3.0", "@types/jmespath": "^0.15.2", "@types/node": "^18.0.0", @@ -132,8 +135,8 @@ "lint": "eslint .", "lint:fix": "eslint --fix .", "prepare": "npm run build", - "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md", - "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s", + "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md grunt", + "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s grunt", "sourcemap": "source-map-explorer build/ably.min.js", "modulereport": "tsc --noEmit scripts/moduleReport.ts && esr scripts/moduleReport.ts", "docs": "typedoc" diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 32968faf23..22cfe14e91 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { explore } from 'source-map-explorer'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) -const minimalUsefulRealtimeBundleSizeThresholdKiB = 109; +const minimalUsefulRealtimeBundleSizeThresholdKiB = 94; // List of all modules accepted in ModulesMap const moduleNames = [ diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 1b2ba59f45..eb2b94068a 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -750,12 +750,13 @@ class RealtimeChannel extends EventEmitter { this.errorReason = reason; } const change = new ChannelStateChange(this.state, state, resumed, hasBacklog, reason); - const logLevel = state === 'failed' ? Logger.LOG_ERROR : Logger.LOG_MAJOR; - Logger.logAction( - logLevel, - 'Channel state for channel "' + this.name + '"', - state + (reason ? '; reason: ' + reason : '') - ); + const action = 'Channel state for channel "' + this.name + '"'; + const message = state + (reason ? '; reason: ' + reason : ''); + if (state === 'failed') { + Logger.logAction(Logger.LOG_ERROR, action, message); + } else { + Logger.logAction(Logger.LOG_MAJOR, action, message); + } if (state !== 'attaching' && state !== 'suspended') { this.retryCount = 0; diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 3f30237475..99b6581016 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1187,12 +1187,13 @@ class ConnectionManager extends EventEmitter { } enactStateChange(stateChange: ConnectionStateChange): void { - const logLevel = stateChange.current === 'failed' ? Logger.LOG_ERROR : Logger.LOG_MAJOR; - Logger.logAction( - logLevel, - 'Connection state', - stateChange.current + (stateChange.reason ? '; reason: ' + stateChange.reason : '') - ); + const action = 'Connection state'; + const message = stateChange.current + (stateChange.reason ? '; reason: ' + stateChange.reason : ''); + if (stateChange.current === 'failed') { + Logger.logAction(Logger.LOG_ERROR, action, message); + } else { + Logger.logAction(Logger.LOG_MAJOR, action, message); + } Logger.logAction( Logger.LOG_MINOR, 'ConnectionManager.enactStateChange', diff --git a/src/common/lib/transport/protocol.ts b/src/common/lib/transport/protocol.ts index 436fb7d202..683b2c36ec 100644 --- a/src/common/lib/transport/protocol.ts +++ b/src/common/lib/transport/protocol.ts @@ -71,7 +71,7 @@ class Protocol extends EventEmitter { this.messageQueue.push(pendingMessage); } if (Logger.shouldLog(Logger.LOG_MICRO)) { - Logger.logAction( + Logger.logActionNoStrip( Logger.LOG_MICRO, 'Protocol.send()', 'sending msg; ' + diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 1042e303ef..0cdfc2b2a8 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -117,7 +117,7 @@ abstract class Transport extends EventEmitter { onProtocolMessage(message: ProtocolMessage): void { if (Logger.shouldLog(Logger.LOG_MICRO)) { - Logger.logAction( + Logger.logActionNoStrip( Logger.LOG_MICRO, 'Transport.onProtocolMessage()', 'received on ' + @@ -132,7 +132,7 @@ abstract class Transport extends EventEmitter { switch (message.action) { case actions.HEARTBEAT: - Logger.logAction( + Logger.logActionNoStrip( Logger.LOG_MICRO, 'Transport.onProtocolMessage()', this.shortName + ' heartbeat; connectionId = ' + this.connectionManager.connectionId diff --git a/src/common/lib/util/logger.ts b/src/common/lib/util/logger.ts index bfafe2c500..c2a9f406c2 100644 --- a/src/common/lib/util/logger.ts +++ b/src/common/lib/util/logger.ts @@ -98,11 +98,23 @@ class Logger { } /* public static functions */ + /** + * In the modular variant of the SDK, the `stripLogs` esbuild plugin strips out all calls to this method (when invoked as `Logger.logAction(...)`) except when called with level `Logger.LOG_ERROR`. If you wish for a log statement to never be stripped, use the {@link logActionNoStrip} method instead. + * + * The aforementioned plugin expects `level` to be an expression of the form `Logger.LOG_*`; that is, you can’t dynamically specify the log level. + */ static logAction = (level: LogLevels, action: string, message?: string) => { + this.logActionNoStrip(level, action, message); + }; + + /** + * Calls to this method are never stripped by the `stripLogs` esbuild plugin. Use it for log statements that you wish to always be included in the modular variant of the SDK. + */ + static logActionNoStrip(level: LogLevels, action: string, message?: string) { if (Logger.shouldLog(level)) { (level === LogLevels.Error ? Logger.logErrorHandler : Logger.logHandler)('Ably: ' + action + ': ' + message); } - }; + } /* Where a logging operation is expensive, such as serialisation of data, use shouldLog will prevent the object being serialised if the log level will not output the message */ diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 93d4e0f229..4f92b7b684 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -72,13 +72,13 @@ function logResponseHandler( ): RequestCallback { return (err, body, headers, unpacked, statusCode) => { if (err) { - Logger.logAction( + Logger.logActionNoStrip( Logger.LOG_MICRO, 'Http.' + method + '()', 'Received Error; ' + appendingParams(uri, params) + '; Error: ' + Utils.inspectError(err) ); } else { - Logger.logAction( + Logger.logActionNoStrip( Logger.LOG_MICRO, 'Http.' + method + '()', 'Received; ' + @@ -99,7 +99,7 @@ function logResponseHandler( function logRequest(method: HttpMethods, uri: string, body: RequestBody | null, params: RequestParams) { if (Logger.shouldLog(Logger.LOG_MICRO)) { - Logger.logAction( + Logger.logActionNoStrip( Logger.LOG_MICRO, 'Http.' + method + '()', 'Sending; ' + From fccd4ac950858634ccb7e0120ad5520261be63b8 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 19 Jan 2024 12:42:46 +0000 Subject: [PATCH 257/468] Fixes same array in memory was used for `generateRandom` in web crypto I'm not entirely sure why `generateRandom` was reusing the same Uint32Array array stored in `blockRandomArray` variable between calls. It may be the relic of the past, or a bug that was only discovered recently, since we stopped copying this array for some operations. This was causing race condition errors in crypto browser tests, specifically with `multiple_send_*` tests. They generated cipher key using `Crypto.generateRandomKey()` and thus shared the same array in memory which was used for iv generation on each message encryption. This way, cipher key was actually changed in memory after each `CBCCipher.encrypt` call, and if two messages were encrypted back to back, then next call to `CBCCipher.decrypt` would fail due to incorrect cipher key. This regression was introduced in e14f1bf, when we switched from using WordArray and `BufferUtils.toWordArray`, which was copying the array, to ArrayBuffer and `BufferUtils.toArrayBuffer`, which is returning the same array in memory. Resolves #1557 --- src/platform/web/lib/util/crypto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index ecc70ebf90..3a48b3393a 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -33,8 +33,8 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B if (config.getRandomArrayBuffer) { generateRandom = config.getRandomArrayBuffer; } else if (typeof Uint32Array !== 'undefined' && config.getRandomValues) { - var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); generateRandom = function (bytes, callback) { + var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); var words = bytes / 4, nativeArray = words == DEFAULT_BLOCKLENGTH_WORDS ? blockRandomArray : new Uint32Array(words); config.getRandomValues!(nativeArray, function (err) { From 0be021aa78ab24b2ca68696bf5bebb3e1f1cfe3a Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 19 Jan 2024 12:59:10 +0000 Subject: [PATCH 258/468] Removes wrong comment in web crypto This is probably was copied from node platform. In web there is no XOR operation applied when generating new iv. --- src/platform/web/lib/util/crypto.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 3a48b3393a..ad8bb8c3e3 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -300,9 +300,6 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B return; } - /* Since the iv for a new block is the ciphertext of the last, this - * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as - * returning it */ generateRandom(DEFAULT_BLOCKLENGTH, function (err, randomBlock) { if (err) { callback(err, null); From c94cbf18f600c25c532d54463ab5570df507ff1d Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 19 Jan 2024 13:01:58 +0000 Subject: [PATCH 259/468] Adds 2 more multiple_send_* tests with low delay These tests would allow to catch possible race condition errors due to shared state on encryption and decryption stages, like in fccd4ac. --- test/realtime/crypto.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 407dbc6e25..d3f114d001 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -501,6 +501,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async _multiple_send(done, true, 20, 100); }); + it('multiple_send_binary_10_10', function (done) { + _multiple_send(done, false, 10, 10); + }); + + it('multiple_send_text_10_10', function (done) { + _multiple_send(done, true, 10, 10); + }); + function _single_send_separate_realtimes(done, txOpts, rxOpts) { if (!Crypto) { done(new Error('Encryption not supported')); From b91692cec882a1a839070ceb12b107629a7d46ba Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 19 Jan 2024 13:15:10 +0000 Subject: [PATCH 260/468] Add try/catch to `_multiple_send` to allow fast fail in case of errors This allows multiple_send_* tests to fast fail and show an actual assertion arrow, instead of waiting for a timeout and showing a timeout exceeded error. --- test/realtime/crypto.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 407dbc6e25..192d1a3d88 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -468,7 +468,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function recvAll(recvCb) { var received = 0; channel.subscribe('event0', function (msg) { - expect(msg.data == messageText).to.be.ok; + try { + expect(msg.data == messageText).to.be.ok; + } catch (error) { + recvCb(error); + } if (++received == iterations) recvCb(null); }); } From f2b9fcb62e491d7c134cbc66f742f067a5431109 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 19 Jan 2024 13:22:40 +0000 Subject: [PATCH 261/468] Fix mocha web server process keeps running after failed playwright tests --- test/support/runPlaywrightTests.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/test/support/runPlaywrightTests.js b/test/support/runPlaywrightTests.js index 7b24eaa10c..46f39547f1 100644 --- a/test/support/runPlaywrightTests.js +++ b/test/support/runPlaywrightTests.js @@ -1,6 +1,6 @@ const playwright = require('playwright'); const path = require('path'); -const serverProcess = require('child_process').fork(path.resolve(__dirname, '..', 'web_server'), { +const mochaWebServerProcess = require('child_process').fork(path.resolve(__dirname, '..', 'web_server'), { env: { PLAYWRIGHT_TEST: 1 }, }); @@ -22,7 +22,7 @@ const runTests = async (browserType) => { console.log(`\nrunning tests in ${browserType.name()}`); - await new Promise((resolve) => { + await new Promise((resolve, reject) => { // Expose a function inside the playwright browser to log to the NodeJS process stdout page.exposeFunction('onTestLog', ({ detail }) => { console.log(detail); @@ -39,8 +39,7 @@ const runTests = async (browserType) => { browser.close(); resolve(); } else { - console.log(`${browserType.name()} tests failed, exiting with code 1`); - process.exit(1); + reject(new Error(`${browserType.name()} tests failed, exiting with code 1`)); } }); @@ -58,6 +57,8 @@ const runTests = async (browserType) => { }; (async () => { + let caughtError; + try { if (browserEnv) { // If the PLAYWRIGHT_BROWSER env var is set, only run tests in the specified browser... @@ -68,7 +69,18 @@ const runTests = async (browserType) => { await runTests(playwright.webkit); await runTests(playwright.firefox); } - } finally { - serverProcess.kill(); + } catch (error) { + // save error for now, we must ensure we end mocha web server first. + // if we end current process too early, mocha web server will be left running, + // causing problems when launching tests second time. + caughtError = error; + } + + mochaWebServerProcess.kill(); + + // now whem mocha web server is terminated, if there was an error, we can log it and exit with a failure code + if (caughtError) { + console.log(caughtError.message); + process.exit(1); } })(); From 6c6ac42a68d9dbbbd43c4bffea1fd85abb842ec0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 22 Jan 2024 15:46:17 +0000 Subject: [PATCH 262/468] Document that queryTime auth option requires Rest module Because this option causes time() to be called internally. Resolves #1592. --- modules.d.ts | 4 ++++ test/browser/modules.test.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/modules.d.ts b/modules.d.ts index 7736e63b7a..06ac84b755 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -46,6 +46,9 @@ import { Push, RealtimeChannel, Connection, + // The ESLint warning is triggered because we only use this type in a documentation comment. + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars + AuthOptions, } from './ably'; export declare const generateRandomKey: CryptoClass['generateRandomKey']; @@ -72,6 +75,7 @@ export declare const constructPresenceMessage: PresenceMessageStatic['fromValues * * - { @link ably!Push | push admin } * - { @link BaseRealtime.time | retrieving Ably service time } + * - { @link ably!Auth.createTokenRequest | creating a token request } using the { @link ably!AuthOptions.queryTime } option * - { @link BaseRealtime.stats | retrieving your application’s usage statistics } * - { @link BaseRealtime.request | making arbitrary REST requests } * - { @link BaseRealtime.batchPublish | batch publishing of messages } diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 22b16f8672..d4ab644f28 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -62,6 +62,14 @@ function registerAblyModulesTests(helper, registerDeltaTests) { action: (client) => client.push.admin.publish({ clientId: 'foo' }, { data: { bar: 'baz' } }), }, { description: 'call `time()`', action: (client) => client.time() }, + { + description: 'call `auth.createTokenRequest()` with `queryTime` option enabled', + action: (client) => + client.auth.createTokenRequest(undefined, { + key: getTestApp().keys[0].keyStr /* if passing authOptions you have to explicitly pass the key */, + queryTime: true, + }), + }, { description: 'call `stats()`', action: (client) => client.stats() }, { description: 'call `request(...)`', action: (client) => client.request('get', '/channels/channel', 2) }, { @@ -153,6 +161,13 @@ function registerAblyModulesTests(helper, registerDeltaTests) { expect(receivedMessage.data).to.eql({ foo: 'bar' }); }); + it('allows `auth.createTokenRequest()` without `queryTime` option enabled', async () => { + const client = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest }); + + const tokenRequest = await client.auth.createTokenRequest(); + expect(tokenRequest).to.be.an('object'); + }); + for (const scenario of restScenarios) { it(`throws an error when attempting to ${scenario.description}`, async () => { const client = new BaseRealtime(ablyClientOptions(scenario.getAdditionalClientOptions?.()), { From a95b38a3cbf4c14b53231bc7d2e254f4e3c007ba Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 24 Jan 2024 13:59:39 +0000 Subject: [PATCH 263/468] Remove unnecessary Object.values call Not sure why I added this in 63f5524. --- scripts/moduleReport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 22cfe14e91..4186f2de12 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -101,7 +101,7 @@ function printAndCheckModuleSizes() { console.log(`${baseClient}: ${formatBytes(baseClientSize)}`); // Then display the size of each export together with the base client - [...moduleNames, ...Object.values(functions).map((functionData) => functionData.name)].forEach((exportName) => { + [...moduleNames, ...functions.map((functionData) => functionData.name)].forEach((exportName) => { const size = getImportSize([baseClient, exportName]); console.log(`${baseClient} + ${exportName}: ${formatBytes(size)}`); From 698fde2793941b0170af158da5e709616972131c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 19 Jan 2024 15:02:41 +0000 Subject: [PATCH 264/468] Make modulereport print gzipped size too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s quite likely that some of our users will be serving their bundles with gzip encoding, so this is useful information to have. Resolves #1580. --- scripts/moduleReport.ts | 84 +++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 4186f2de12..3a525ab97d 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -1,9 +1,11 @@ import * as esbuild from 'esbuild'; import * as path from 'path'; import { explore } from 'source-map-explorer'; +import { promisify } from 'util'; +import { gzip } from 'zlib'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) -const minimalUsefulRealtimeBundleSizeThresholdKiB = 94; +const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 94, gzip: 29 }; // List of all modules accepted in ModulesMap const moduleNames = [ @@ -46,6 +48,15 @@ interface BundleInfo { sourceMap: Uint8Array; } +interface ByteSizes { + rawByteSize: number; + gzipEncodedByteSize: number; +} + +function formatByteSizes(sizes: ByteSizes) { + return `raw: ${formatBytes(sizes.rawByteSize)}; gzip: ${formatBytes(sizes.gzipEncodedByteSize)}`; +} + // Uses esbuild to create a bundle containing the named exports from 'ably/modules' function getBundleInfo(modules: string[]): BundleInfo { const outfile = modules.join(''); @@ -79,9 +90,14 @@ function getBundleInfo(modules: string[]): BundleInfo { } // Gets the bundled size in bytes of an array of named exports from 'ably/modules' -function getImportSize(modules: string[]) { +async function getImportSizes(modules: string[]): Promise { const bundleInfo = getBundleInfo(modules); - return bundleInfo.byteSize; + + return { + rawByteSize: bundleInfo.byteSize, + // I’m trusting that the default settings of the `gzip` function (e.g. compression level) are somewhat representative of how gzip compression is normally used when serving files in the real world + gzipEncodedByteSize: (await promisify(gzip)(bundleInfo.code)).byteLength, + }; } async function runSourceMapExplorer(bundleInfo: BundleInfo) { @@ -91,48 +107,48 @@ async function runSourceMapExplorer(bundleInfo: BundleInfo) { }); } -function printAndCheckModuleSizes() { +async function printAndCheckModuleSizes() { const errors: Error[] = []; - ['BaseRest', 'BaseRealtime'].forEach((baseClient) => { - const baseClientSize = getImportSize([baseClient]); + for (const baseClient of ['BaseRest', 'BaseRealtime']) { + const baseClientSizes = await getImportSizes([baseClient]); // First display the size of the base client - console.log(`${baseClient}: ${formatBytes(baseClientSize)}`); + console.log(`${baseClient}: ${formatByteSizes(baseClientSizes)}`); // Then display the size of each export together with the base client - [...moduleNames, ...functions.map((functionData) => functionData.name)].forEach((exportName) => { - const size = getImportSize([baseClient, exportName]); - console.log(`${baseClient} + ${exportName}: ${formatBytes(size)}`); + for (const exportName of [...moduleNames, ...functions.map((functionData) => functionData.name)]) { + const sizes = await getImportSizes([baseClient, exportName]); + console.log(`${baseClient} + ${exportName}: ${formatByteSizes(sizes)}`); - if (!(baseClientSize < size) && !(baseClient === 'BaseRest' && exportName === 'Rest')) { + if (!(baseClientSizes.rawByteSize < sizes.rawByteSize) && !(baseClient === 'BaseRest' && exportName === 'Rest')) { // Emit an error if adding the module does not increase the bundle size // (this means that the module is not being tree-shaken correctly). errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); } - }); - }); + } + } return errors; } -function printAndCheckFunctionSizes() { +async function printAndCheckFunctionSizes() { const errors: Error[] = []; for (const functionData of functions) { const { name: functionName, transitiveImports } = functionData; // First display the size of the function - const standaloneSize = getImportSize([functionName]); - console.log(`${functionName}: ${formatBytes(standaloneSize)}`); + const standaloneSizes = await getImportSizes([functionName]); + console.log(`${functionName}: ${formatByteSizes(standaloneSizes)}`); // Then display the size of the function together with the modules we expect // it to transitively import if (transitiveImports.length > 0) { - const withTransitiveImportsSize = getImportSize([functionName, ...transitiveImports]); - console.log(`${functionName} + ${transitiveImports.join(' + ')}: ${formatBytes(withTransitiveImportsSize)}`); + const withTransitiveImportsSizes = await getImportSizes([functionName, ...transitiveImports]); + console.log(`${functionName} + ${transitiveImports.join(' + ')}: ${formatByteSizes(withTransitiveImportsSizes)}`); - if (withTransitiveImportsSize > standaloneSize) { + if (withTransitiveImportsSizes.rawByteSize > standaloneSizes.rawByteSize) { // Emit an error if the bundle size is increased by adding the modules // that we expect this function to have transitively imported anyway. // This seemed like a useful sense check, but it might need tweaking in @@ -150,20 +166,30 @@ function printAndCheckFunctionSizes() { return errors; } -function printAndCheckMinimalUsefulRealtimeBundleSize() { +async function printAndCheckMinimalUsefulRealtimeBundleSize() { const errors: Error[] = []; const exports = ['BaseRealtime', 'FetchRequest', 'WebSocketTransport']; - const size = getImportSize(exports); + const sizes = await getImportSizes(exports); - console.log(`Minimal useful Realtime (${exports.join(' + ')}): ${formatBytes(size)}`); + console.log(`Minimal useful Realtime (${exports.join(' + ')}): ${formatByteSizes(sizes)}`); + + if (sizes.rawByteSize > minimalUsefulRealtimeBundleSizeThresholdsKiB.raw * 1024) { + errors.push( + new Error( + `Minimal raw useful Realtime bundle is ${formatBytes( + sizes.rawByteSize + )}, which is greater than allowed maximum of ${minimalUsefulRealtimeBundleSizeThresholdsKiB.raw} KiB.` + ) + ); + } - if (size > minimalUsefulRealtimeBundleSizeThresholdKiB * 1024) { + if (sizes.gzipEncodedByteSize > minimalUsefulRealtimeBundleSizeThresholdsKiB.gzip * 1024) { errors.push( new Error( - `Minimal useful Realtime bundle is ${formatBytes( - size - )}, which is greater than allowed maximum of ${minimalUsefulRealtimeBundleSizeThresholdKiB} KiB.` + `Minimal gzipped useful Realtime bundle is ${formatBytes( + sizes.gzipEncodedByteSize + )}, which is greater than allowed maximum of ${minimalUsefulRealtimeBundleSizeThresholdsKiB.gzip} KiB.` ) ); } @@ -258,9 +284,9 @@ async function checkBaseRealtimeFiles() { (async function run() { const errors: Error[] = []; - errors.push(...printAndCheckMinimalUsefulRealtimeBundleSize()); - errors.push(...printAndCheckModuleSizes()); - errors.push(...printAndCheckFunctionSizes()); + errors.push(...(await printAndCheckMinimalUsefulRealtimeBundleSize())); + errors.push(...(await printAndCheckModuleSizes())); + errors.push(...(await printAndCheckFunctionSizes())); errors.push(...(await checkBaseRealtimeFiles())); if (errors.length > 0) { From 883e262967553d185b1f46a9b359751d7ed3d888 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 24 Jan 2024 09:38:56 +0000 Subject: [PATCH 265/468] Format modulereport output as a table A bit easier to read. --- package-lock.json | 52 +++++++++++++++++++++++ package.json | 4 +- scripts/moduleReport.ts | 91 ++++++++++++++++++++++++++--------------- 3 files changed, 114 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 277a60789d..6c90d8cd65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@testing-library/react": "^13.3.0", + "@types/cli-table": "^0.3.4", "@types/jmespath": "^0.15.2", "@types/node": "^18.0.0", "@types/request": "^2.48.7", @@ -30,6 +31,7 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", "chai": "^4.2.0", + "cli-table": "^0.3.11", "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", @@ -1436,6 +1438,12 @@ "@types/chai": "*" } }, + "node_modules/@types/cli-table": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz", + "integrity": "sha512-GsALrTL69mlwbAw/MHF1IPTadSLZQnsxe7a80G8l4inN/iEXCOcVeT/S7aRc6hbhqzL9qZ314kHPDQnQ3ev+HA==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.44.8", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", @@ -2798,6 +2806,27 @@ "node": ">=6.0" } }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-table/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -11719,6 +11748,12 @@ "@types/chai": "*" } }, + "@types/cli-table": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz", + "integrity": "sha512-GsALrTL69mlwbAw/MHF1IPTadSLZQnsxe7a80G8l4inN/iEXCOcVeT/S7aRc6hbhqzL9qZ314kHPDQnQ3ev+HA==", + "dev": true + }, "@types/eslint": { "version": "8.44.8", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", @@ -12757,6 +12792,23 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "requires": { + "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + } + } + }, "cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", diff --git a/package.json b/package.json index 760795238e..ae880a90d5 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@testing-library/react": "^13.3.0", + "@types/cli-table": "^0.3.4", "@types/jmespath": "^0.15.2", "@types/node": "^18.0.0", "@types/request": "^2.48.7", @@ -62,6 +63,7 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", "chai": "^4.2.0", + "cli-table": "^0.3.11", "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", @@ -138,7 +140,7 @@ "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md grunt", "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s grunt", "sourcemap": "source-map-explorer build/ably.min.js", - "modulereport": "tsc --noEmit scripts/moduleReport.ts && esr scripts/moduleReport.ts", + "modulereport": "tsc --noEmit --esModuleInterop scripts/moduleReport.ts && esr scripts/moduleReport.ts", "docs": "typedoc" } } diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 3a525ab97d..8df69416e5 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import { explore } from 'source-map-explorer'; import { promisify } from 'util'; import { gzip } from 'zlib'; +import Table from 'cli-table'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 94, gzip: 29 }; @@ -53,8 +54,14 @@ interface ByteSizes { gzipEncodedByteSize: number; } -function formatByteSizes(sizes: ByteSizes) { - return `raw: ${formatBytes(sizes.rawByteSize)}; gzip: ${formatBytes(sizes.gzipEncodedByteSize)}`; +interface TableRow { + description: string; + sizes: ByteSizes; +} + +interface Output { + tableRows: TableRow[]; + errors: Error[]; } // Uses esbuild to create a bundle containing the named exports from 'ably/modules' @@ -107,46 +114,49 @@ async function runSourceMapExplorer(bundleInfo: BundleInfo) { }); } -async function printAndCheckModuleSizes() { - const errors: Error[] = []; +async function calculateAndCheckModuleSizes(): Promise { + const output: Output = { tableRows: [], errors: [] }; for (const baseClient of ['BaseRest', 'BaseRealtime']) { const baseClientSizes = await getImportSizes([baseClient]); - // First display the size of the base client - console.log(`${baseClient}: ${formatByteSizes(baseClientSizes)}`); + // First output the size of the base client + output.tableRows.push({ description: baseClient, sizes: baseClientSizes }); - // Then display the size of each export together with the base client + // Then output the size of each export together with the base client for (const exportName of [...moduleNames, ...functions.map((functionData) => functionData.name)]) { const sizes = await getImportSizes([baseClient, exportName]); - console.log(`${baseClient} + ${exportName}: ${formatByteSizes(sizes)}`); + output.tableRows.push({ description: `${baseClient} + ${exportName}`, sizes }); if (!(baseClientSizes.rawByteSize < sizes.rawByteSize) && !(baseClient === 'BaseRest' && exportName === 'Rest')) { // Emit an error if adding the module does not increase the bundle size // (this means that the module is not being tree-shaken correctly). - errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); + output.errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); } } } - return errors; + return output; } -async function printAndCheckFunctionSizes() { - const errors: Error[] = []; +async function calculateAndCheckFunctionSizes(): Promise { + const output: Output = { tableRows: [], errors: [] }; for (const functionData of functions) { const { name: functionName, transitiveImports } = functionData; - // First display the size of the function + // First output the size of the function const standaloneSizes = await getImportSizes([functionName]); - console.log(`${functionName}: ${formatByteSizes(standaloneSizes)}`); + output.tableRows.push({ description: functionName, sizes: standaloneSizes }); - // Then display the size of the function together with the modules we expect + // Then output the size of the function together with the modules we expect // it to transitively import if (transitiveImports.length > 0) { const withTransitiveImportsSizes = await getImportSizes([functionName, ...transitiveImports]); - console.log(`${functionName} + ${transitiveImports.join(' + ')}: ${formatByteSizes(withTransitiveImportsSizes)}`); + output.tableRows.push({ + description: `${functionName} + ${transitiveImports.join(' + ')}`, + sizes: withTransitiveImportsSizes, + }); if (withTransitiveImportsSizes.rawByteSize > standaloneSizes.rawByteSize) { // Emit an error if the bundle size is increased by adding the modules @@ -154,7 +164,7 @@ async function printAndCheckFunctionSizes() { // This seemed like a useful sense check, but it might need tweaking in // the future if we make future optimisations that mean that the // standalone functions don’t necessarily import the whole module. - errors.push( + output.errors.push( new Error( `Adding ${transitiveImports.join(' + ')} to ${functionName} unexpectedly increases the bundle size.` ) @@ -163,19 +173,19 @@ async function printAndCheckFunctionSizes() { } } - return errors; + return output; } -async function printAndCheckMinimalUsefulRealtimeBundleSize() { - const errors: Error[] = []; +async function calculateAndCheckMinimalUsefulRealtimeBundleSize(): Promise { + const output: Output = { tableRows: [], errors: [] }; const exports = ['BaseRealtime', 'FetchRequest', 'WebSocketTransport']; const sizes = await getImportSizes(exports); - console.log(`Minimal useful Realtime (${exports.join(' + ')}): ${formatByteSizes(sizes)}`); + output.tableRows.push({ description: `Minimal useful Realtime (${exports.join(' + ')})`, sizes }); if (sizes.rawByteSize > minimalUsefulRealtimeBundleSizeThresholdsKiB.raw * 1024) { - errors.push( + output.errors.push( new Error( `Minimal raw useful Realtime bundle is ${formatBytes( sizes.rawByteSize @@ -185,7 +195,7 @@ async function printAndCheckMinimalUsefulRealtimeBundleSize() { } if (sizes.gzipEncodedByteSize > minimalUsefulRealtimeBundleSizeThresholdsKiB.gzip * 1024) { - errors.push( + output.errors.push( new Error( `Minimal gzipped useful Realtime bundle is ${formatBytes( sizes.gzipEncodedByteSize @@ -194,7 +204,7 @@ async function printAndCheckMinimalUsefulRealtimeBundleSize() { ); } - return errors; + return output; } // Performs a sense check that there are no unexpected files making a large contribution to the BaseRealtime bundle size. @@ -282,15 +292,32 @@ async function checkBaseRealtimeFiles() { } (async function run() { - const errors: Error[] = []; - - errors.push(...(await printAndCheckMinimalUsefulRealtimeBundleSize())); - errors.push(...(await printAndCheckModuleSizes())); - errors.push(...(await printAndCheckFunctionSizes())); - errors.push(...(await checkBaseRealtimeFiles())); + const output = ( + await Promise.all([ + calculateAndCheckMinimalUsefulRealtimeBundleSize(), + calculateAndCheckModuleSizes(), + calculateAndCheckFunctionSizes(), + ]) + ).reduce((accum, current) => ({ + tableRows: [...accum.tableRows, ...current.tableRows], + errors: [...accum.errors, ...current.errors], + })); + + output.errors.push(...(await checkBaseRealtimeFiles())); + + const table = new Table({ + style: { head: ['green'] }, + head: ['Modules', 'Size (raw, KiB)', 'Size (gzipped, KiB)'], + rows: output.tableRows.map((row) => [ + row.description, + formatBytes(row.sizes.rawByteSize), + formatBytes(row.sizes.gzipEncodedByteSize), + ]), + }); + console.log(table.toString()); - if (errors.length > 0) { - for (const error of errors) { + if (output.errors.length > 0) { + for (const error of output.errors) { console.log(error.message); } process.exit(1); From 792e10b04dcc7d942f7c2c1b6585865a78f24531 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 24 Jan 2024 10:19:16 +0000 Subject: [PATCH 266/468] Make modulereport print size of bundle with all modules Seems like useful information to have. --- scripts/moduleReport.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 8df69416e5..9b22c609d0 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -8,6 +8,8 @@ import Table from 'cli-table'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 94, gzip: 29 }; +const baseClientNames = ['BaseRest', 'BaseRealtime']; + // List of all modules accepted in ModulesMap const moduleNames = [ 'Rest', @@ -117,7 +119,7 @@ async function runSourceMapExplorer(bundleInfo: BundleInfo) { async function calculateAndCheckModuleSizes(): Promise { const output: Output = { tableRows: [], errors: [] }; - for (const baseClient of ['BaseRest', 'BaseRealtime']) { + for (const baseClient of baseClientNames) { const baseClientSizes = await getImportSizes([baseClient]); // First output the size of the base client @@ -207,6 +209,13 @@ async function calculateAndCheckMinimalUsefulRealtimeBundleSize(): Promise { + const exports = [...baseClientNames, ...moduleNames, ...functions.map((val) => val.name)]; + const sizes = await getImportSizes(exports); + + return { tableRows: [{ description: 'All modules', sizes }], errors: [] }; +} + // Performs a sense check that there are no unexpected files making a large contribution to the BaseRealtime bundle size. async function checkBaseRealtimeFiles() { const baseRealtimeBundleInfo = getBundleInfo(['BaseRealtime']); @@ -295,6 +304,7 @@ async function checkBaseRealtimeFiles() { const output = ( await Promise.all([ calculateAndCheckMinimalUsefulRealtimeBundleSize(), + calculateAllModulesBundleSize(), calculateAndCheckModuleSizes(), calculateAndCheckFunctionSizes(), ]) From 79c145a5a796c916ea2fbc7151498b726abe0693 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Thu, 25 Jan 2024 15:03:58 +0000 Subject: [PATCH 267/468] Improves error handling in runPlaywrightTests script Related to changes made to in f2b9fcb62e491d7c134cbc66f742f067a5431109. Moves error handling for incorrect PLAYWRIGHT_BROWSER variable to try/catch block so mocha server will be closed. Also removes unreachable else statement when launching tests. --- test/support/runPlaywrightTests.js | 31 +++++++++++++----------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/test/support/runPlaywrightTests.js b/test/support/runPlaywrightTests.js index 46f39547f1..526e1c4ab0 100644 --- a/test/support/runPlaywrightTests.js +++ b/test/support/runPlaywrightTests.js @@ -6,14 +6,7 @@ const mochaWebServerProcess = require('child_process').fork(path.resolve(__dirna const port = process.env.PORT || 3000; const host = 'localhost'; - -const browserEnv = process.env.PLAYWRIGHT_BROWSER; - -if (!['chromium', 'firefox', 'webkit'].includes(browserEnv)) { - throw new Error( - `PLAYWRIGHT_BROWSER environment variable must be either 'chromium', 'webkit' or 'firefox' (currently ${browserEnv})` - ); -} +const playwrightBrowsers = ['chromium', 'firefox', 'webkit']; const runTests = async (browserType) => { const browser = await browserType.launch(); @@ -60,25 +53,27 @@ const runTests = async (browserType) => { let caughtError; try { - if (browserEnv) { - // If the PLAYWRIGHT_BROWSER env var is set, only run tests in the specified browser... - await runTests(playwright[browserEnv]); - } else { - // ...otherwise run all the browsers - await runTests(playwright.chromium); - await runTests(playwright.webkit); - await runTests(playwright.firefox); + const browserEnv = process.env.PLAYWRIGHT_BROWSER; + + if (!playwrightBrowsers.includes(browserEnv)) { + throw new Error( + `PLAYWRIGHT_BROWSER environment variable must be one of: ${playwrightBrowsers.join( + ', ' + )}. Currently: ${browserEnv}` + ); } + + await runTests(playwright[browserEnv]); } catch (error) { // save error for now, we must ensure we end mocha web server first. // if we end current process too early, mocha web server will be left running, - // causing problems when launching tests second time. + // causing problems when launching tests the second time. caughtError = error; } mochaWebServerProcess.kill(); - // now whem mocha web server is terminated, if there was an error, we can log it and exit with a failure code + // now when mocha web server is terminated, if there was an error, we can log it and exit with a failure code if (caughtError) { console.log(caughtError.message); process.exit(1); From 04f0f16427c0caab9799b02ee73877f6b71e5ded Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 29 Jan 2024 09:41:00 +0000 Subject: [PATCH 268/468] Remove public ChannelModes type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It doesn’t add any value, is inconsistent (we don’t create any other types for arrays), and I want to introduce a different type with the same name. --- ably.d.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 0f761197a5..d13b50f74b 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -833,11 +833,6 @@ export type ChannelMode = | ChannelMode.PRESENCE | ChannelMode.PRESENCE_SUBSCRIBE | ChannelMode.ATTACH_RESUME; -/** - * Describes the possible flags used to configure client capabilities, using {@link ChannelOptions}. - */ -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type ChannelModes = Array; /** * Passes additional properties to a {@link Channel} or {@link RealtimeChannel} object, such as encryption, {@link ChannelMode} and channel parameters. @@ -854,7 +849,7 @@ export interface ChannelOptions { /** * An array of {@link ChannelMode} objects. */ - modes?: ChannelModes; + modes?: ChannelMode[]; } /** @@ -1981,7 +1976,7 @@ export declare interface RealtimeChannel extends EventEmitter Date: Mon, 29 Jan 2024 09:40:47 +0000 Subject: [PATCH 269/468] Remove duplicate type names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We were using TypeDoc’s declaration reference functionality [1] to disambiguate these, but this broke when we upgraded TypeDoc in 5f69795 — it started generating link text of ":type" instead of the name of the type. This is because TypeDoc 0.24 added the --useTsLinkResolution option, which is on by default. This option parses @link tags using the TypeScript compiler. It does this in order to more closely match the way in which editors such as VS Code will handle @link tags. However, the TypeScript compiler’s parsing does not support TypeDoc declaration syntax (and does not offer its own alternative), only falling back to TypeDoc @link parsing when TypeScript fails to parse a @link. (See the TypeDoc issue I raised [2] for further context.) So, stop using declaration references to disambiguate types. We still make some use of declaration references in modules.d.ts, to link to the types in ably.d.ts and to link to a specific overload of a method. These are still being parsed correctly (because TypeScript is unable to parse them) and there is no good alternative, so I’ll leave them as is, accepting that editors won’t handle them well (as was always the case). Resolves #1560. [1] https://typedoc.org/guides/declaration-references/ [2] https://github.com/TypeStrong/typedoc/issues/2485 --- ably.d.ts | 186 ++++++++++++++++++++++++++---------------------------- 1 file changed, 88 insertions(+), 98 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index d13b50f74b..8fc3a93cea 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -12,9 +12,9 @@ */ /** - * The `ChannelState` namespace describes the possible values of the {@link ChannelState:type} type. + * The `ChannelStates` namespace describes the possible values of the {@link ChannelState} type. */ -declare namespace ChannelState { +declare namespace ChannelStates { /** * The channel has been initialized but no attach has yet been attempted. */ @@ -47,20 +47,19 @@ declare namespace ChannelState { /** * Describes the possible states of a {@link Channel} or {@link RealtimeChannel} object. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type ChannelState = - | ChannelState.FAILED - | ChannelState.INITIALIZED - | ChannelState.SUSPENDED - | ChannelState.ATTACHED - | ChannelState.ATTACHING - | ChannelState.DETACHED - | ChannelState.DETACHING; + | ChannelStates.FAILED + | ChannelStates.INITIALIZED + | ChannelStates.SUSPENDED + | ChannelStates.ATTACHED + | ChannelStates.ATTACHING + | ChannelStates.DETACHED + | ChannelStates.DETACHING; /** - * The `ChannelEvent` namespace describes the possible values of the {@link ChannelEvent:type} type. + * The `ChannelEvents` namespace describes the possible values of the {@link ChannelEvent} type. */ -declare namespace ChannelEvent { +declare namespace ChannelEvents { /** * The channel has been initialized but no attach has yet been attempted. */ @@ -97,21 +96,20 @@ declare namespace ChannelEvent { /** * Describes the events emitted by a {@link Channel} or {@link RealtimeChannel} object. An event is either an `UPDATE` or a {@link ChannelState}. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type ChannelEvent = - | ChannelEvent.FAILED - | ChannelEvent.INITIALIZED - | ChannelEvent.SUSPENDED - | ChannelEvent.ATTACHED - | ChannelEvent.ATTACHING - | ChannelEvent.DETACHED - | ChannelEvent.DETACHING - | ChannelEvent.UPDATE; + | ChannelEvents.FAILED + | ChannelEvents.INITIALIZED + | ChannelEvents.SUSPENDED + | ChannelEvents.ATTACHED + | ChannelEvents.ATTACHING + | ChannelEvents.DETACHED + | ChannelEvents.DETACHING + | ChannelEvents.UPDATE; /** - * The `ConnectionState` namespace describes the possible values of the {@link ConnectionState:type} type. + * The `ConnectionStates` namespace describes the possible values of the {@link ConnectionState} type. */ -declare namespace ConnectionState { +declare namespace ConnectionStates { /** * A connection with this state has been initialized but no connection has yet been attempted. */ @@ -148,21 +146,20 @@ declare namespace ConnectionState { /** * Describes the realtime {@link Connection} object states. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type ConnectionState = - | ConnectionState.INITIALIZED - | ConnectionState.CONNECTED - | ConnectionState.CONNECTING - | ConnectionState.DISCONNECTED - | ConnectionState.SUSPENDED - | ConnectionState.CLOSED - | ConnectionState.CLOSING - | ConnectionState.FAILED; + | ConnectionStates.INITIALIZED + | ConnectionStates.CONNECTED + | ConnectionStates.CONNECTING + | ConnectionStates.DISCONNECTED + | ConnectionStates.SUSPENDED + | ConnectionStates.CLOSED + | ConnectionStates.CLOSING + | ConnectionStates.FAILED; /** - * The `ConnectionEvent` namespace describes the possible values of the {@link ConnectionEvent:type} type. + * The `ConnectionEvents` namespace describes the possible values of the {@link ConnectionEvent} type. */ -declare namespace ConnectionEvent { +declare namespace ConnectionEvents { /** * A connection with this state has been initialized but no connection has yet been attempted. */ @@ -203,22 +200,21 @@ declare namespace ConnectionEvent { /** * Describes the events emitted by a {@link Connection} object. An event is either an `UPDATE` or a {@link ConnectionState}. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type ConnectionEvent = - | ConnectionEvent.INITIALIZED - | ConnectionEvent.CONNECTED - | ConnectionEvent.CONNECTING - | ConnectionEvent.DISCONNECTED - | ConnectionEvent.SUSPENDED - | ConnectionEvent.CLOSED - | ConnectionEvent.CLOSING - | ConnectionEvent.FAILED - | ConnectionEvent.UPDATE; + | ConnectionEvents.INITIALIZED + | ConnectionEvents.CONNECTED + | ConnectionEvents.CONNECTING + | ConnectionEvents.DISCONNECTED + | ConnectionEvents.SUSPENDED + | ConnectionEvents.CLOSED + | ConnectionEvents.CLOSING + | ConnectionEvents.FAILED + | ConnectionEvents.UPDATE; /** - * The `PresenceAction` namespace describes the possible values of the {@link PresenceAction:type} type. + * The `PresenceActions` namespace describes the possible values of the {@link PresenceAction} type. */ -declare namespace PresenceAction { +declare namespace PresenceActions { /** * A member is not present in the channel. */ @@ -243,18 +239,17 @@ declare namespace PresenceAction { /** * Describes the possible actions members in the presence set can emit. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type PresenceAction = - | PresenceAction.ABSENT - | PresenceAction.PRESENT - | PresenceAction.ENTER - | PresenceAction.LEAVE - | PresenceAction.UPDATE; + | PresenceActions.ABSENT + | PresenceActions.PRESENT + | PresenceActions.ENTER + | PresenceActions.LEAVE + | PresenceActions.UPDATE; /** - * The `StatsIntervalGranularity` namespace describes the possible values of the {@link StatsIntervalGranularity:type} type. + * The `StatsIntervalGranularities` namespace describes the possible values of the {@link StatsIntervalGranularity} type. */ -declare namespace StatsIntervalGranularity { +declare namespace StatsIntervalGranularities { /** * Interval unit over which statistics are gathered as minutes. */ @@ -275,12 +270,11 @@ declare namespace StatsIntervalGranularity { /** * Describes the interval unit over which statistics are gathered. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type StatsIntervalGranularity = - | StatsIntervalGranularity.MINUTE - | StatsIntervalGranularity.HOUR - | StatsIntervalGranularity.DAY - | StatsIntervalGranularity.MONTH; + | StatsIntervalGranularities.MINUTE + | StatsIntervalGranularities.HOUR + | StatsIntervalGranularities.DAY + | StatsIntervalGranularities.MONTH; /** * HTTP Methods, used internally. @@ -298,8 +292,7 @@ declare namespace HTTPMethods { /** * HTTP Methods, used internally. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type HTTPMethods = HTTPMethods.GET | HTTPMethods.POST; +export type HTTPMethod = HTTPMethods.GET | HTTPMethods.POST; /** * A type which specifies the valid transport names. [See here](https://faqs.ably.com/which-transports-are-supported) for more information. @@ -353,7 +346,7 @@ export interface ChannelMetrics { */ connections: number; /** - * The number of realtime connections attached to the channel with permission to enter the presence set, regardless of whether or not they have entered it. This requires the `presence` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PRESENCE}. + * The number of realtime connections attached to the channel with permission to enter the presence set, regardless of whether or not they have entered it. This requires the `presence` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelModes.PRESENCE}. */ presenceConnections: number; /** @@ -361,15 +354,15 @@ export interface ChannelMetrics { */ presenceMembers: number; /** - * The number of realtime attachments receiving presence messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PRESENCE_SUBSCRIBE}. + * The number of realtime attachments receiving presence messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelModes.PRESENCE_SUBSCRIBE}. */ presenceSubscribers: number; /** - * The number of realtime attachments permitted to publish messages to the channel. This requires the `publish` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.PUBLISH}. + * The number of realtime attachments permitted to publish messages to the channel. This requires the `publish` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelModes.PUBLISH}. */ publishers: number; /** - * The number of realtime attachments receiving messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelMode.SUBSCRIBE}. + * The number of realtime attachments receiving messages on the channel. This requires the `subscribe` capability and for a client to not have specified a {@link ChannelMode} flag that excludes {@link ChannelModes.SUBSCRIBE}. */ subscribers: number; } @@ -507,14 +500,14 @@ export interface ClientOptions extends AuthOptions { disableConnectivityCheck?: boolean; /** - * If the connection is still in the {@link ConnectionState.DISCONNECTED} state after this delay in milliseconds, the client library will attempt to reconnect automatically. The default is 15 seconds. + * If the connection is still in the {@link ConnectionStates.DISCONNECTED} state after this delay in milliseconds, the client library will attempt to reconnect automatically. The default is 15 seconds. * * @defaultValue 15s */ disconnectedRetryTimeout?: number; /** - * When the connection enters the {@link ConnectionState.SUSPENDED} state, after this delay in milliseconds, if the state is still {@link ConnectionState.SUSPENDED | `SUSPENDED`}, the client library attempts to reconnect automatically. The default is 30 seconds. + * When the connection enters the {@link ConnectionStates.SUSPENDED} state, after this delay in milliseconds, if the state is still {@link ConnectionStates.SUSPENDED | `SUSPENDED`}, the client library attempts to reconnect automatically. The default is 30 seconds. * * @defaultValue 30s */ @@ -610,9 +603,9 @@ export interface AuthOptions { /** * The HTTP verb to use for any request made to the `authUrl`, either `GET` or `POST`. The default value is `GET`. * - * @defaultValue `HTTPMethods.GET` + * @defaultValue `HTTPMethod.GET` */ - authMethod?: HTTPMethods; + authMethod?: HTTPMethod; /** * A set of key-value pair params to be added to any request made to the `authUrl`. When the `authMethod` is `GET`, query params are added to the URL, whereas when `authMethod` is `POST`, the params are sent as URL encoded form data. Useful when an application requires these to be added to validate the request or implement the response. @@ -798,9 +791,9 @@ export interface TokenRequest { export type ChannelParams = { [key: string]: string }; /** - * The `ChannelMode` namespace describes the possible values of the {@link ChannelMode:type} type. + * The `ChannelModes` namespace describes the possible values of the {@link ChannelMode} type. */ -declare namespace ChannelMode { +declare namespace ChannelModes { /** * The client can publish messages. */ @@ -826,13 +819,12 @@ declare namespace ChannelMode { /** * Describes the possible flags used to configure client capabilities, using {@link ChannelOptions}. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type ChannelMode = - | ChannelMode.PUBLISH - | ChannelMode.SUBSCRIBE - | ChannelMode.PRESENCE - | ChannelMode.PRESENCE_SUBSCRIBE - | ChannelMode.ATTACH_RESUME; + | ChannelModes.PUBLISH + | ChannelModes.SUBSCRIBE + | ChannelModes.PRESENCE + | ChannelModes.PRESENCE_SUBSCRIBE + | ChannelModes.ATTACH_RESUME; /** * Passes additional properties to a {@link Channel} or {@link RealtimeChannel} object, such as encryption, {@link ChannelMode} and channel parameters. @@ -918,7 +910,7 @@ export interface RestPresenceParams { */ export interface RealtimePresenceParams { /** - * Sets whether to wait for a full presence set synchronization between Ably and the clients on the channel to complete before returning the results. Synchronization begins as soon as the channel is {@link ChannelState.ATTACHED}. When set to `true` the results will be returned as soon as the sync is complete. When set to `false` the current list of members will be returned without the sync completing. The default is `true`. + * Sets whether to wait for a full presence set synchronization between Ably and the clients on the channel to complete before returning the results. Synchronization begins as soon as the channel is {@link ChannelStates.ATTACHED}. When set to `true` the results will be returned as soon as the sync is complete. When set to `false` the current list of members will be returned without the sync completing. The default is `true`. * * @defaultValue `true` */ @@ -977,7 +969,7 @@ export interface ChannelStateChange { */ current: ChannelState; /** - * The previous state. For the {@link ChannelEvent.UPDATE} event, this is equal to the `current` {@link ChannelState}. + * The previous state. For the {@link ChannelEvents.UPDATE} event, this is equal to the `current` {@link ChannelState}. */ previous: ChannelState; /** @@ -1003,7 +995,7 @@ export interface ConnectionStateChange { */ current: ConnectionState; /** - * The previous {@link ConnectionState}. For the {@link ConnectionEvent.UPDATE} event, this is equal to the current {@link ConnectionState}. + * The previous {@link ConnectionState}. For the {@link ConnectionEvents.UPDATE} event, this is equal to the current {@link ConnectionState}. */ previous: ConnectionState; /** @@ -1017,9 +1009,9 @@ export interface ConnectionStateChange { } /** - * The `DevicePlatform` namespace describes the possible values of the {@link DevicePlatform:type} type. + * The `DevicePlatforms` namespace describes the possible values of the {@link DevicePlatform} type. */ -declare namespace DevicePlatform { +declare namespace DevicePlatforms { /** * The device platform is Android. */ @@ -1037,13 +1029,12 @@ declare namespace DevicePlatform { /** * Describes the device receiving push notifications. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare -export type DevicePlatform = DevicePlatform.ANDROID | DevicePlatform.IOS | DevicePlatform.BROWSER; +export type DevicePlatform = DevicePlatforms.ANDROID | DevicePlatforms.IOS | DevicePlatforms.BROWSER; /** - * The `DeviceFormFactor` namespace describes the possible values of the {@link DeviceFormFactor:type} type. + * The `DeviceFormFactors` namespace describes the possible values of the {@link DeviceFormFactor} type. */ -declare namespace DeviceFormFactor { +declare namespace DeviceFormFactors { /** * The device is a phone. */ @@ -1081,16 +1072,15 @@ declare namespace DeviceFormFactor { /** * Describes the type of device receiving a push notification. */ -// eslint-disable-next-line @typescript-eslint/no-redeclare export type DeviceFormFactor = - | DeviceFormFactor.PHONE - | DeviceFormFactor.TABLET - | DeviceFormFactor.DESKTOP - | DeviceFormFactor.TV - | DeviceFormFactor.WATCH - | DeviceFormFactor.CAR - | DeviceFormFactor.EMBEDDED - | DeviceFormFactor.OTHER; + | DeviceFormFactors.PHONE + | DeviceFormFactors.TABLET + | DeviceFormFactors.DESKTOP + | DeviceFormFactors.TV + | DeviceFormFactors.WATCH + | DeviceFormFactors.CAR + | DeviceFormFactors.EMBEDDED + | DeviceFormFactors.OTHER; /** * Contains the properties of a device registered for push notifications. @@ -1861,7 +1851,7 @@ export declare interface RealtimePresence { */ enter(data?: any): Promise; /** - * Updates the `data` payload for a presence member. If called before entering the presence set, this is treated as an {@link PresenceAction.ENTER} event. + * Updates the `data` payload for a presence member. If called before entering the presence set, this is treated as an {@link PresenceActions.ENTER} event. * * @param data - The payload to update for the presence member. * @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure. @@ -2397,11 +2387,11 @@ export declare interface Connection */ readonly state: ConnectionState; /** - * Causes the connection to close, entering the {@link ConnectionState.CLOSING} state. Once closed, the library does not attempt to re-establish the connection without an explicit call to {@link Connection.connect | `connect()`}. + * Causes the connection to close, entering the {@link ConnectionStates.CLOSING} state. Once closed, the library does not attempt to re-establish the connection without an explicit call to {@link Connection.connect | `connect()`}. */ close(): void; /** - * Explicitly calling `connect()` is unnecessary unless the `autoConnect` attribute of the {@link ClientOptions} object is `false`. Unless already connected or connecting, this method causes the connection to open, entering the {@link ConnectionState.CONNECTING} state. + * Explicitly calling `connect()` is unnecessary unless the `autoConnect` attribute of the {@link ClientOptions} object is `false`. Unless already connected or connecting, this method causes the connection to open, entering the {@link ConnectionStates.CONNECTING} state. */ connect(): void; From 709f66a862788320d96db87521e3def6b5314bf9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 16:28:43 -0300 Subject: [PATCH 270/468] Update recently-changed tests to use Promise-based API This applies the approach of 43a2d1d to the test changes that were added in `main` subsequent to that commit. --- test/realtime/channel.test.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index 1ed1aab1dc..ac745fb036 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -1535,7 +1535,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var realtime = helper.AblyRealtime(); var channelName = 'attach_returns_state_chnage'; var channel = realtime.channels.get(channelName); - channel.attach(function (err, stateChange) { + whenPromiseSettles(channel.attach(), function (err, stateChange) { if (err) { closeAndFinish(done, realtime, err); return; @@ -1550,7 +1550,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } // for an already-attached channel, null is returned - channel.attach(function (err, stateChange) { + whenPromiseSettles(channel.attach(), function (err, stateChange) { if (err) { closeAndFinish(done, realtime, err); return; @@ -1571,9 +1571,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var realtime = helper.AblyRealtime(); var channelName = 'subscribe_returns_state_chnage'; var channel = realtime.channels.get(channelName); - channel.subscribe( - function () {}, // message listener - // attach callback + whenPromiseSettles( + channel.subscribe( + function () {} // message listener + ), function (err, stateChange) { if (err) { closeAndFinish(done, realtime, err); @@ -1599,7 +1600,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channel = realtime.channels.get(channelName, channelOpts); // attach with rewind but no channel history - hasBacklog should be false - channel.attach(function (err, stateChange) { + whenPromiseSettles(channel.attach(), function (err, stateChange) { if (err) { closeAndFinish(done, realtime, err); return; @@ -1624,12 +1625,12 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var restChannel = rest.channels.get(channelName); // attach with rewind after publishing - hasBacklog should be true - restChannel.publish('foo', 'bar', function (err) { + whenPromiseSettles(restChannel.publish('foo', 'bar'), function (err) { if (err) { closeAndFinish(done, realtime, err); return; } - rtChannel.attach(function (err, stateChange) { + whenPromiseSettles(rtChannel.attach(), function (err, stateChange) { if (err) { closeAndFinish(done, realtime, err); return; From b6475815346b784e84876cbbdddbb95160376f84 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 16:30:28 -0300 Subject: [PATCH 271/468] Update some missed tests to use Promise-based API This applies the approach of 43a2d1d to some API calls that I missed in that commit. --- test/realtime/channel.test.js | 8 ++++---- test/realtime/crypto.test.js | 9 ++++----- test/realtime/failure.test.js | 6 +++--- test/realtime/message.test.js | 10 ++++------ test/realtime/presence.test.js | 6 +++--- test/realtime/shared/delta_tests.js | 10 +++++----- test/realtime/upgrade.test.js | 16 +++++++--------- 7 files changed, 30 insertions(+), 35 deletions(-) diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index ac745fb036..3ce6edfd0f 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -686,7 +686,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.series( [ function (cb) { - channel.attach(cb); + whenPromiseSettles(channel.attach(), cb); }, function (cb) { var channelUpdated = false; @@ -1100,13 +1100,13 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async realtime.connection.on('connected', function () { channelByEvent = realtime.channels.get('channelsubscribe1-event'); - channelByEvent.subscribe('event', listenerByEvent, function () { + whenPromiseSettles(channelByEvent.subscribe('event', listenerByEvent), function () { channelByEvent.publish('event', 'data'); channelByListener = realtime.channels.get('channelsubscribe1-listener'); - channelByListener.subscribe(null, listenerNoEvent, function () { + whenPromiseSettles(channelByListener.subscribe(null, listenerNoEvent), function () { channelByListener.publish(null, 'data'); channelAll = realtime.channels.get('channelsubscribe1-all'); - channelAll.subscribe(listenerAllEvents, function () { + whenPromiseSettles(channelAll.subscribe(listenerAllEvents), function () { channelAll.publish(null, 'data'); }); }); diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index 3d66366a2f..ded9b31c0a 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -477,7 +477,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); } - channel.attach(function (err) { + whenPromiseSettles(channel.attach(), function (err) { if (err) { closeAndFinish(done, realtime, err); return; @@ -606,9 +606,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } var rxChannel = rxRealtime.channels.get(channelName, { cipher: { key: key } }); - rxChannel.subscribe( - 'event0', - function (msg) { + whenPromiseSettles( + rxChannel.subscribe('event0', function (msg) { try { expect(msg.data == messageText).to.be.ok; } catch (err) { @@ -616,7 +615,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } closeAndFinish(done, [txRealtime, rxRealtime]); - }, + }), function () { var txChannel = txRealtime.channels.get(channelName, { cipher: { key: key } }); txChannel.publish('event0', messageText); diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index 23c05a7e3a..ef488fc2f7 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -251,7 +251,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (callback) { - failChan.subscribe('event', noop, function (err) { + whenPromiseSettles(failChan.subscribe('event', noop), function (err) { try { expect(err, 'subscribe failed').to.be.ok; expect(err.code).to.equal(channelFailedCode, 'subscribe failure code'); @@ -284,7 +284,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (callback) { - failChan.presence.subscribe('event', noop, function (err) { + whenPromiseSettles(failChan.presence.subscribe('event', noop), function (err) { try { expect(err, 'presence subscribe failed').to.be.ok; expect(err.code).to.equal(channelFailedCode, 'subscribe failure code'); @@ -295,7 +295,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }, function (callback) { - failChan.presence.subscribe('event', noop, function (err) { + whenPromiseSettles(failChan.presence.subscribe('event', noop), function (err) { try { expect(err, 'presence unsubscribe failed').to.be.ok; expect(err.code).to.equal(channelFailedCode, 'subscribe failure code'); diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index 11f5ff37e4..f80d4e1e8e 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -399,8 +399,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async async.eachSeries( testArguments, function iterator(args, callback) { - args.push(callback); - restChannel.publish.apply(restChannel, args); + whenPromiseSettles(restChannel.publish.apply(restChannel, args), callback); }, function (err) { if (err) { @@ -607,12 +606,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var realtime = helper.AblyRealtime(realtimeOpts); var channel = realtime.channels.get('publish ' + JSON.stringify(realtimeOpts)); /* subscribe to event */ - channel.subscribe( - 'event0', - function () { + whenPromiseSettles( + channel.subscribe('event0', function () { --count; checkFinish(); - }, + }), function () { var dataFn = function () { return 'Hello world at: ' + new Date(); diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index d915eba312..1046e2d43e 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -351,8 +351,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channelName = 'presenceMessageAction'; var clientChannel = clientRealtime.channels.get(channelName); var presence = clientChannel.presence; - presence.subscribe( - function (presenceMessage) { + whenPromiseSettles( + presence.subscribe(function (presenceMessage) { try { expect(presenceMessage.action).to.equal('enter', 'Action should contain string "enter"'); } catch (err) { @@ -360,7 +360,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } closeAndFinish(done, clientRealtime); - }, + }), function onPresenceSubscribe(err) { if (err) { closeAndFinish(done, clientRealtime, err); diff --git a/test/realtime/shared/delta_tests.js b/test/realtime/shared/delta_tests.js index 0de7ebde1f..a70bb9b685 100644 --- a/test/realtime/shared/delta_tests.js +++ b/test/realtime/shared/delta_tests.js @@ -91,7 +91,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + whenPromiseSettles(channel.publish(i.toString(), testData[i]), cb); }); }); @@ -129,7 +129,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + whenPromiseSettles(channel.publish(i.toString(), testData[i]), cb); }); }); @@ -193,7 +193,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + whenPromiseSettles(channel.publish(i.toString(), testData[i]), cb); }); }); @@ -241,7 +241,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + whenPromiseSettles(channel.publish(i.toString(), testData[i]), cb); }); }); @@ -273,7 +273,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { closeAndFinish(done, realtime); }); async.timesSeries(testData.length, function (i, cb) { - channel.publish(i.toString(), testData[i], cb); + whenPromiseSettles(channel.publish(i.toString(), testData[i]), cb); }); }); diff --git a/test/realtime/upgrade.test.js b/test/realtime/upgrade.test.js index 3ae14f839c..fc3779b4b5 100644 --- a/test/realtime/upgrade.test.js +++ b/test/realtime/upgrade.test.js @@ -5,7 +5,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai var rest; var publishIntervalHelper = function (currentMessageNum, channel, dataFn, onPublish) { return function (currentMessageNum) { - channel.publish('event0', dataFn(), function () { + whenPromiseSettles(channel.publish('event0', dataFn()), function () { onPublish(); }); }; @@ -223,12 +223,11 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai var realtime = helper.AblyRealtime(transportOpts); var channel = realtime.channels.get('upgradepublish0'); /* subscribe to event */ - channel.subscribe( - 'event0', - function () { + whenPromiseSettles( + channel.subscribe('event0', function () { --count; checkFinish(); - }, + }), function () { var dataFn = function () { return 'Hello world at: ' + new Date(); @@ -257,12 +256,11 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai var realtime = helper.AblyRealtime(transportOpts); var channel = realtime.channels.get('upgradepublish1'); /* subscribe to event */ - channel.subscribe( - 'event0', - function () { + whenPromiseSettles( + channel.subscribe('event0', function () { --count; checkFinish(); - }, + }), function () { var dataFn = function () { return 'Hello world at: ' + new Date(); From 16f3d38aa9a9c00c089c8781027e8651d632c4be Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 8 Jan 2024 15:31:34 +0000 Subject: [PATCH 272/468] Mark Rest.revokeTokens as async Should have done this in ce9a3cf. --- src/common/lib/client/rest.ts | 2 +- test/rest/batch.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index a6a2d3ab88..5340ce09ae 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -270,7 +270,7 @@ export class Rest { ); } - revokeTokens( + async revokeTokens( specifiers: TokenRevocationTargetSpecifier[], options?: TokenRevocationOptions ): Promise { diff --git a/test/rest/batch.test.js b/test/rest/batch.test.js index 9c250c54f4..028ccf1ef6 100644 --- a/test/rest/batch.test.js +++ b/test/rest/batch.test.js @@ -320,14 +320,14 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { expect(result.results[0].appliesAt).to.be.greaterThan(serverTimeThirtySecondsAfterStartOfTest); }); - it('throws an error when using token auth', function () { + it('throws an error when using token auth', async function () { const rest = helper.AblyRest({ useTokenAuth: true, }); let verifiedError = false; try { - rest.auth.revokeTokens([{ type: 'clientId', value: 'clientId1' }], function () {}); + await rest.auth.revokeTokens([{ type: 'clientId', value: 'clientId1' }], function () {}); } catch (err) { expect(err.statusCode).to.equal(401); expect(err.code).to.equal(40162); From 14ae7c9b8626b15d35e4978bed3022927eea15aa Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 15 Jan 2024 16:35:35 +0000 Subject: [PATCH 273/468] Make Multicaster.call private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not used outside the class. --- src/common/lib/util/multicaster.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/util/multicaster.ts b/src/common/lib/util/multicaster.ts index 068e3a798b..d09a7c1504 100644 --- a/src/common/lib/util/multicaster.ts +++ b/src/common/lib/util/multicaster.ts @@ -15,7 +15,7 @@ class Multicaster { this.members = (members as Array) || []; } - call(...args: unknown[]): void { + private call(...args: unknown[]): void { for (const member of this.members) { if (member) { try { From 1be691f3f3b70210f996b2bb52a21997c1f75e34 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 15 Jan 2024 16:43:16 +0000 Subject: [PATCH 274/468] Be more explicit about a type A bit more readable, I guess. --- src/common/lib/client/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index b7b9f56002..1342597886 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -1,6 +1,6 @@ import Logger from '../util/logger'; import * as Utils from '../util/utils'; -import Multicaster from '../util/multicaster'; +import Multicaster, { MulticasterInstance } from '../util/multicaster'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; import { ErrnoException, RequestCallback, RequestParams } from '../../types/http'; import * as API from '../../../../ably'; @@ -116,7 +116,7 @@ class Auth { client: BaseClient; tokenParams: API.TokenParams; currentTokenRequestId: number | null; - waitingForTokenRequest: ReturnType | null; + waitingForTokenRequest: MulticasterInstance | null; // This initialization is always overwritten and only used to prevent a TypeScript compiler error authOptions: API.AuthOptions = {} as API.AuthOptions; tokenDetails?: API.TokenDetails | null; From d0121257c88bfa2861a82df1179ba33f076ea172 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 11:23:54 -0300 Subject: [PATCH 275/468] Restrict Multicaster callback type Only allow standard (err, result) callbacks, so that we can bridge them to promises. --- src/common/lib/client/auth.ts | 4 +-- src/common/lib/transport/connectionmanager.ts | 4 +-- src/common/lib/util/multicaster.ts | 30 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 1342597886..74b49c3a04 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -116,7 +116,7 @@ class Auth { client: BaseClient; tokenParams: API.TokenParams; currentTokenRequestId: number | null; - waitingForTokenRequest: MulticasterInstance | null; + waitingForTokenRequest: MulticasterInstance | null; // This initialization is always overwritten and only used to prevent a TypeScript compiler error authOptions: API.AuthOptions = {} as API.AuthOptions; tokenDetails?: API.TokenDetails | null; @@ -959,7 +959,7 @@ class Auth { /* Request a new token */ const tokenRequestId = (this.currentTokenRequestId = getTokenRequestId()); - this.requestToken(this.tokenParams, this.authOptions, (err: Function, tokenResponse?: API.TokenDetails) => { + this.requestToken(this.tokenParams, this.authOptions, (err: ErrorInfo | null, tokenResponse?: API.TokenDetails) => { if ((this.currentTokenRequestId as number) > tokenRequestId) { Logger.logAction( Logger.LOG_MINOR, diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 99b6581016..04c8bc798b 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1951,10 +1951,10 @@ class ConnectionManager extends EventEmitter { * the dup, they'll be lost */ if (lastQueued && !lastQueued.sendAttempted && bundleWith(lastQueued.message, msg, maxSize)) { if (!lastQueued.merged) { - lastQueued.callback = Multicaster.create([lastQueued.callback as any]); + lastQueued.callback = Multicaster.create([lastQueued.callback]); lastQueued.merged = true; } - (lastQueued.callback as MulticasterInstance).push(callback as any); + (lastQueued.callback as MulticasterInstance).push(callback); } else { this.queuedMessages.push(new PendingMessage(msg, callback)); } diff --git a/src/common/lib/util/multicaster.ts b/src/common/lib/util/multicaster.ts index d09a7c1504..81f1fb6344 100644 --- a/src/common/lib/util/multicaster.ts +++ b/src/common/lib/util/multicaster.ts @@ -1,25 +1,25 @@ +import { StandardCallback } from 'common/types/utils'; +import ErrorInfo from 'common/lib/types/errorinfo'; import Logger from './logger'; -type AnyFunction = (...args: any[]) => unknown; - -export interface MulticasterInstance extends Function { - (...args: unknown[]): void; - push: (fn: AnyFunction) => void; +export interface MulticasterInstance extends Function { + (err?: ErrorInfo | null, result?: T): void; + push: (fn: StandardCallback) => void; } -class Multicaster { - members: Array; +class Multicaster { + members: Array>; // Private constructor; use static Multicaster.create instead - private constructor(members?: Array) { - this.members = (members as Array) || []; + private constructor(members?: Array | undefined>) { + this.members = (members as Array>) || []; } - private call(...args: unknown[]): void { + private call(err?: ErrorInfo | null, result?: T): void { for (const member of this.members) { if (member) { try { - member(...args); + member(err, result); } catch (e) { Logger.logAction( Logger.LOG_ERROR, @@ -31,14 +31,14 @@ class Multicaster { } } - push(...args: Array): void { + push(...args: Array>): void { this.members.push(...args); } - static create(members?: Array): MulticasterInstance { + static create(members?: Array | undefined>): MulticasterInstance { const instance = new Multicaster(members); - return Object.assign((...args: unknown[]) => instance.call(...args), { - push: (fn: AnyFunction) => instance.push(fn), + return Object.assign((err?: ErrorInfo | null, result?: T) => instance.call(err, result), { + push: (fn: StandardCallback) => instance.push(fn), }); } } From dfaa6ba865f8921c200a484ce6e8e7c0fad69a76 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 10:21:10 -0300 Subject: [PATCH 276/468] Add a method for bridging Multicaster with promises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparation for using promises internally in the codebase. Part of #1535; will remove the functional interface to Multicaster if/when it’s no longer needed. --- src/common/lib/util/multicaster.ts | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/common/lib/util/multicaster.ts b/src/common/lib/util/multicaster.ts index 81f1fb6344..eb7ba19b28 100644 --- a/src/common/lib/util/multicaster.ts +++ b/src/common/lib/util/multicaster.ts @@ -5,6 +5,18 @@ import Logger from './logger'; export interface MulticasterInstance extends Function { (err?: ErrorInfo | null, result?: T): void; push: (fn: StandardCallback) => void; + /** + * Creates a promise that will be resolved or rejected when this instance is called. + */ + createPromise: () => Promise; + /** + * Syntatic sugar for when working in a context that uses promises; equivalent to calling as a function with arguments (null, result). + */ + resolveAll(result: T): void; + /** + * Syntatic sugar for when working in a context that uses promises; equivalent to calling as a function with arguments (err). + */ + rejectAll(err: ErrorInfo): void; } class Multicaster { @@ -35,10 +47,29 @@ class Multicaster { this.members.push(...args); } + createPromise(): Promise { + return new Promise((resolve, reject) => { + this.push((err, result) => { + err ? reject(err) : resolve(result!); + }); + }); + } + + resolveAll(result: T) { + this.call(null, result); + } + + rejectAll(err: ErrorInfo) { + this.call(err); + } + static create(members?: Array | undefined>): MulticasterInstance { const instance = new Multicaster(members); return Object.assign((err?: ErrorInfo | null, result?: T) => instance.call(err, result), { push: (fn: StandardCallback) => instance.push(fn), + createPromise: () => instance.createPromise(), + resolveAll: (result: T) => instance.resolveAll(result), + rejectAll: (err: ErrorInfo) => instance.rejectAll(err), }); } } From cd6c2d8ea601134b8afdcf10e2d8e9629245153d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 11:37:21 -0300 Subject: [PATCH 277/468] Add whenPromiseSettles function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copied from the tests. We’ll use it for bridging between promise-using methods and callback-using methods as we start changing the codebase to use promises internally. --- src/common/lib/util/utils.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 9388ad2db2..4fea24cc8e 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -452,6 +452,22 @@ export function promisify(ob: Record, fnName: string, args: IArg }); } +/** + * Uses a callback to communicate the result of a `Promise`. The first argument passed to the callback will be either an error (when the promise is rejected) or `null` (when the promise is fulfilled). In the case where the promise is fulfilled, the resulting value will be passed to the callback as a second argument. + */ +export function whenPromiseSettles( + promise: Promise, + callback?: (err: E | null, result?: T) => void +) { + promise + .then((result) => { + callback?.(null, result); + }) + .catch((err) => { + callback?.(err); + }); +} + export function decodeBody(body: unknown, MsgPack: MsgPack | null, format?: Format | null): T { if (format == 'msgpack') { if (!MsgPack) { From 4aa7494f494458e574db8570c5f7bdeb7be1a6e8 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 14:40:25 -0300 Subject: [PATCH 278/468] Fix type of ChannelSubscriptions.removeWhere callback --- src/common/lib/client/push.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 3d0b7b9f44..6ee772ec44 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -285,7 +285,7 @@ class ChannelSubscriptions { }).get(params, callback); } - removeWhere(params: any, callback: PaginatedResultCallback) { + removeWhere(params: any, callback: ErrCallback) { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultGetHeaders(client.options, { format }); From 3e06ea9baf85d77581f00872b95914d595d913ed Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 14:47:02 -0300 Subject: [PATCH 279/468] Fix type of ChannelSubscriptions.save callback --- src/common/lib/client/push.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 6ee772ec44..5344718414 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -223,7 +223,7 @@ class ChannelSubscriptions { this.client = client; } - save(subscription: Record, callback: PaginatedResultCallback) { + save(subscription: Record, callback: StandardCallback) { const client = this.client; const body = PushChannelSubscription.fromValues(subscription); const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, @@ -249,12 +249,13 @@ class ChannelSubscriptions { function (err, body, headers, unpacked) { callback( err, - !err && - PushChannelSubscription.fromResponseBody( - body as Record, - client._MsgPack, - unpacked ? undefined : format - ) + !err + ? (PushChannelSubscription.fromResponseBody( + body as Record, + client._MsgPack, + unpacked ? undefined : format + ) as PushChannelSubscription) + : undefined ); } ); From 0eb0018d3bafac5a2095a6fd57dbd1c2b76c6427 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 15:13:00 -0300 Subject: [PATCH 280/468] Fix return type of Connection.ping --- src/common/lib/client/connection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/client/connection.ts b/src/common/lib/client/connection.ts index 47170b9430..1447edde04 100644 --- a/src/common/lib/client/connection.ts +++ b/src/common/lib/client/connection.ts @@ -53,7 +53,7 @@ class Connection extends EventEmitter { this.connectionManager.requestState({ state: 'connecting' }); } - ping(callback: Function): Promise | void { + ping(callback: Function): Promise | void { Logger.logAction(Logger.LOG_MINOR, 'Connection.ping()', ''); if (!callback) { return Utils.promisify(this, 'ping', arguments); From 35970d6536eed92202ce407db23dc5a5dace21ea Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 15:42:53 -0300 Subject: [PATCH 281/468] Fix return type of RealtimePresence.get --- src/common/lib/client/realtimepresence.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 51f460a618..1bf1d0a9d6 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -251,7 +251,7 @@ class RealtimePresence extends EventEmitter { this: RealtimePresence, params: RealtimePresenceParams, callback: StandardCallback - ): void | Promise { + ): void | Promise { const args = Array.prototype.slice.call(arguments); if (args.length == 1 && typeof args[0] == 'function') args.unshift(null); From 2c560300fa12ebc66912962ac32516a7bebab04c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 16:41:33 -0300 Subject: [PATCH 282/468] Fix return type of Auth.authorize --- src/common/lib/client/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 74b49c3a04..a458111d74 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -251,7 +251,7 @@ class Auth { tokenParams: Record | Function | null, authOptions?: API.AuthOptions | null | Function, callback?: Function - ): void | Promise { + ): void | Promise { let _authOptions: API.AuthOptions | null; /* shuffle and normalise arguments as necessary */ if (typeof tokenParams == 'function' && !callback) { From 501dd7489f7d3550ae0850a3d1d65528545fa610 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 16:44:22 -0300 Subject: [PATCH 283/468] Fix return type of Auth.requestToken --- src/common/lib/client/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index a458111d74..155a767281 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -408,7 +408,7 @@ class Auth { tokenParams: API.TokenParams | StandardCallback | null, authOptions?: any | StandardCallback, callback?: StandardCallback - ): void | Promise { + ): void | Promise { /* shuffle and normalise arguments as necessary */ if (typeof tokenParams == 'function' && !callback) { callback = tokenParams; From 66e025b43475afdfdf53c13345dfc9d3bca3e0f9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 17:04:40 -0300 Subject: [PATCH 284/468] Fix return type of RealtimeChannel.subscribe --- src/common/lib/client/realtimechannel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index eb2b94068a..806ffdcb0a 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -436,7 +436,7 @@ class RealtimeChannel extends EventEmitter { this.sendMessage(msg, callback || noop); } - subscribe(...args: unknown[] /* [event], listener, [callback] */): void | Promise { + subscribe(...args: unknown[] /* [event], listener, [callback] */): void | Promise { const [event, listener, callback] = RealtimeChannel.processListenerArgs(args); if (!callback) { From 602ceaf6ade8e639737a6b3bd231501234061dc5 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 16:32:51 -0300 Subject: [PATCH 285/468] Fix return type of RealtimeChannel.history --- src/common/lib/client/realtimechannel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 806ffdcb0a..34ba69e7ad 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -27,6 +27,7 @@ import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../ty import BaseRealtime from './baserealtime'; import { ChannelOptions } from '../../types/channel'; import { normaliseChannelOptions } from '../util/defaults'; +import { PaginatedResult } from './paginatedresource'; interface RealtimeHistoryParams { start?: number; @@ -887,7 +888,7 @@ class RealtimeChannel extends EventEmitter { this: RealtimeChannel, params: RealtimeHistoryParams | null, callback: PaginatedResultCallback - ): void | Promise> { + ): void | Promise> { Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.history()', 'channel = ' + this.name); /* params and callback are optional; see if params contains the callback */ if (callback === undefined) { From 336695a4b918d7cdcc763b4f49f4e3e1171cd551 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 17:10:33 -0300 Subject: [PATCH 286/468] Remove useless return The return type of `restMixin.history(...)` is `void`. --- src/common/lib/client/realtimechannel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 34ba69e7ad..fba5065ed6 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -922,7 +922,7 @@ class RealtimeChannel extends EventEmitter { params.from_serial = this.properties.attachSerial; } - return restMixin.history(this, params, callback); + restMixin.history(this, params, callback); } as any; whenState = ((state: string, listener: ErrCallback) => { From 5e95872ad2e9c12806d7565b355b0ab3ada855da Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 11:52:23 -0300 Subject: [PATCH 287/468] Add Promise overload signatures to some methods This adds Promise overload signatures to all of the methods which: 1. offer dual callback / promise operation, and 2. are called internally in the codebase This will allow us to switch these internal calls to use the promise-based versions of these methods. --- src/common/lib/client/auth.ts | 18 +++++++++++++----- src/common/lib/client/baseclient.ts | 3 +++ src/common/lib/client/realtimechannel.ts | 4 +++- src/common/lib/client/realtimepresence.ts | 19 ++++++++++++++++--- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 155a767281..1088649d64 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -164,6 +164,7 @@ class Auth { * @param callback (err, tokenDetails) */ authorize(callback: Function): void; + authorize(): Promise; /** * Instructs the library to get a token immediately and ensures Token Auth @@ -190,6 +191,7 @@ class Auth { * @param callback (err, tokenDetails) */ authorize(tokenParams: API.TokenParams | null, callback: Function): void; + authorize(tokenParams: API.TokenParams | null): Promise; /** * Instructs the library to get a token immediately and ensures Token Auth @@ -246,9 +248,10 @@ class Auth { * @param callback (err, tokenDetails) */ authorize(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null, callback: Function): void; + authorize(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null): Promise; authorize( - tokenParams: Record | Function | null, + tokenParams?: Record | Function | null, authOptions?: API.AuthOptions | null | Function, callback?: Function ): void | Promise { @@ -329,6 +332,7 @@ class Auth { * @param callback (err, tokenDetails) */ requestToken(callback: StandardCallback): void; + requestToken(): Promise; /** * Request an access token @@ -351,6 +355,7 @@ class Auth { * @param callback (err, tokenDetails) */ requestToken(tokenParams: API.TokenParams | null, callback: StandardCallback): void; + requestToken(tokenParams: API.TokenParams | null): Promise; /** * Request an access token @@ -403,9 +408,10 @@ class Auth { authOptions: API.AuthOptions, callback: StandardCallback ): void; + requestToken(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions): Promise; requestToken( - tokenParams: API.TokenParams | StandardCallback | null, + tokenParams?: API.TokenParams | StandardCallback | null, authOptions?: any | StandardCallback, callback?: StandardCallback ): void | Promise { @@ -698,6 +704,8 @@ class Auth { }); } + createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any, callback: Function): void; + createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any): Promise; /** * Create and sign a token request based on the given options. * NOTE this can only be used when the key value is available locally. @@ -733,7 +741,7 @@ class Auth { * * @param callback */ - createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any, callback: Function) { + createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any, callback?: Function) { /* shuffle and normalise arguments as necessary */ if (typeof tokenParams == 'function' && !callback) { callback = tokenParams; @@ -785,7 +793,7 @@ class Auth { } this.getTimestamp(authOptions && authOptions.queryTime, function (err?: ErrorInfo | null, time?: number) { if (err) { - callback(err); + callback!(err); return; } request.timestamp = time; @@ -811,7 +819,7 @@ class Auth { request.mac = request.mac || hmac(signText, keySecret); Logger.logAction(Logger.LOG_MINOR, 'Auth.getTokenRequest()', 'generated signed request'); - callback(null, request); + callback!(null, request); }); } diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index bee97b89e4..b5c982ac75 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -136,6 +136,9 @@ class BaseClient { return this.rest.stats(params, callback); } + time(params: RequestParams, callback: StandardCallback): void; + time(callback: StandardCallback): void; + time(params?: RequestParams): Promise; time(params?: RequestParams | StandardCallback, callback?: StandardCallback): Promise | void { return this.rest.time(params, callback); } diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index fba5065ed6..3bd0ccf0fd 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -305,7 +305,9 @@ class RealtimeChannel extends EventEmitter { } } - attach(callback?: StandardCallback): void | Promise { + attach(): Promise; + attach(callback: StandardCallback): void; + attach(callback?: StandardCallback): void | Promise { if (!callback) { return Utils.promisify(this, 'attach', arguments); } diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 1bf1d0a9d6..196009365f 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -123,12 +123,25 @@ class RealtimePresence extends EventEmitter { return this._enterOrUpdateClient(undefined, clientId, data, 'update', callback); } + _enterOrUpdateClient( + id: string | undefined, + clientId: string | undefined, + data: unknown, + action: string + ): Promise; _enterOrUpdateClient( id: string | undefined, clientId: string | undefined, data: unknown, action: string, callback: ErrCallback + ): void; + _enterOrUpdateClient( + id: string | undefined, + clientId: string | undefined, + data: unknown, + action: string, + callback?: ErrCallback ): void | Promise { if (!callback) { if (typeof data === 'function') { @@ -162,7 +175,7 @@ class RealtimePresence extends EventEmitter { encodePresenceMessage(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { if (err) { - callback(err); + callback!(err); return; } switch (channel.state) { @@ -176,7 +189,7 @@ class RealtimePresence extends EventEmitter { case 'attaching': this.pendingPresence.push({ presence: presence, - callback: callback, + callback: callback!, }); break; default: @@ -185,7 +198,7 @@ class RealtimePresence extends EventEmitter { 90001 ); err.code = 90001; - callback(err); + callback!(err); } }); } From df28f8dafc9e5d8ab7d20e132bc50f891c2aa38e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 11:09:24 -0300 Subject: [PATCH 288/468] Use the promise-based version of callback/promise methods Whenever we call a method that offers dual callback/promise operation, we now use the promise-based version. This in preparation for removing the callback functionality from these methods. --- src/common/lib/client/auth.ts | 43 ++++++++++++----------- src/common/lib/client/realtimepresence.ts | 4 +-- src/common/lib/client/resource.ts | 2 +- src/common/lib/transport/transport.ts | 2 +- test/realtime/auth.test.js | 4 +-- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 1088649d64..b0aa44e3a1 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -554,8 +554,8 @@ class Auth { }; } else if (authOptions.key) { Logger.logAction(Logger.LOG_MINOR, 'Auth.requestToken()', 'using token auth with client-side signing'); - tokenRequestCallback = (params: any, cb: Function) => { - this.createTokenRequest(params, authOptions, cb); + tokenRequestCallback = (params: any, cb: StandardCallback) => { + Utils.whenPromiseSettles(this.createTokenRequest(params, authOptions), cb); }; } else { const msg = @@ -871,7 +871,7 @@ class Auth { */ getTimestamp(queryTime: boolean, callback: StandardCallback): void { if (!this.isTimeOffsetSet() && (queryTime || this.authOptions.queryTime)) { - this.client.time(callback); + Utils.whenPromiseSettles(this.client.time(), callback); } else { callback(null, this.getTimestampUsingOffset()); } @@ -967,24 +967,27 @@ class Auth { /* Request a new token */ const tokenRequestId = (this.currentTokenRequestId = getTokenRequestId()); - this.requestToken(this.tokenParams, this.authOptions, (err: ErrorInfo | null, tokenResponse?: API.TokenDetails) => { - if ((this.currentTokenRequestId as number) > tokenRequestId) { - Logger.logAction( - Logger.LOG_MINOR, - 'Auth._ensureValidAuthCredentials()', - 'Discarding token request response; overtaken by newer one' - ); - return; - } - this.currentTokenRequestId = null; - const callbacks = this.waitingForTokenRequest || noop; - this.waitingForTokenRequest = null; - if (err) { - callbacks(err); - return; + Utils.whenPromiseSettles( + this.requestToken(this.tokenParams, this.authOptions), + (err: ErrorInfo | null, tokenResponse?: API.TokenDetails) => { + if ((this.currentTokenRequestId as number) > tokenRequestId) { + Logger.logAction( + Logger.LOG_MINOR, + 'Auth._ensureValidAuthCredentials()', + 'Discarding token request response; overtaken by newer one' + ); + return; + } + this.currentTokenRequestId = null; + const callbacks = this.waitingForTokenRequest || noop; + this.waitingForTokenRequest = null; + if (err) { + callbacks(err); + return; + } + callbacks(null, (this.tokenDetails = tokenResponse)); } - callbacks(null, (this.tokenDetails = tokenResponse)); - }); + ); } /* User-set: check types, '*' is disallowed, throw any errors */ diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 196009365f..b0010d74b5 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -54,7 +54,7 @@ function waitAttached(channel: RealtimeChannel, callback: ErrCallback, action: ( case 'detached': case 'detaching': case 'attaching': - channel.attach(function (err: Error) { + Utils.whenPromiseSettles(channel.attach(), function (err: Error | null) { if (err) callback(err); else action(); }); @@ -495,7 +495,7 @@ class RealtimePresence extends EventEmitter { ); // RTP17g: Send ENTER containing the member id, clientId and data // attributes. - this._enterOrUpdateClient(entry.id, entry.clientId, entry.data, 'enter', reenterCb); + Utils.whenPromiseSettles(this._enterOrUpdateClient(entry.id, entry.clientId, entry.data, 'enter'), reenterCb); } } diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index bfb860fd65..69de333a3b 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -233,7 +233,7 @@ class Resource { client.http.do(method, path, headers, body, params, function (err, res, resHeaders, unpacked, statusCode) { if (err && Auth.isTokenErr(err as ErrorInfo)) { /* token has expired, so get a new one */ - client.auth.authorize(null, null, function (err: ErrorInfo) { + Utils.whenPromiseSettles(client.auth.authorize(null, null), function (err: ErrorInfo | null) { if (err) { callback(err); return; diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 0cdfc2b2a8..5153cd63bf 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -162,7 +162,7 @@ abstract class Transport extends EventEmitter { // Ignored. break; case actions.AUTH: - this.auth.authorize(function (err: ErrorInfo) { + Utils.whenPromiseSettles(this.auth.authorize(), function (err: ErrorInfo | null) { if (err) { Logger.logAction( Logger.LOG_ERROR, diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 166ba4adc5..115173d70a 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -688,9 +688,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async originalTime = rest.time; /* stub time */ - rest.time = function (callback) { + rest.time = async function () { timeRequestCount += 1; - originalTime.call(rest, callback); + return originalTime.call(rest); }; try { From 7c465d671bea2d8c2cff3d508b49860f471ee8e0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Dec 2023 14:39:36 -0300 Subject: [PATCH 289/468] Remove dual callback/promise operation Methods that previously offered the ability to use callbacks or promises now only offer promises. --- src/common/lib/client/auth.ts | 422 +++++++++------------ src/common/lib/client/baseclient.ts | 24 +- src/common/lib/client/connection.ts | 10 +- src/common/lib/client/paginatedresource.ts | 35 +- src/common/lib/client/push.ts | 312 +++++++-------- src/common/lib/client/realtimechannel.ts | 236 +++++------- src/common/lib/client/realtimepresence.ts | 276 ++++++-------- src/common/lib/client/rest.ts | 210 ++++------ src/common/lib/client/restchannel.ts | 98 ++--- src/common/lib/client/restchannelmixin.ts | 19 +- src/common/lib/client/restpresence.ts | 49 +-- src/common/lib/client/restpresencemixin.ts | 44 +-- src/common/lib/util/utils.ts | 8 - 13 files changed, 726 insertions(+), 1017 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index b0aa44e3a1..0ca538bed7 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -160,11 +160,8 @@ class Auth { * Instructs the library to get a token immediately and ensures Token Auth * is used for all future requests, storing the tokenParams and authOptions * given as the new defaults for subsequent use. - * - * @param callback (err, tokenDetails) */ - authorize(callback: Function): void; - authorize(): Promise; + async authorize(): Promise; /** * Instructs the library to get a token immediately and ensures Token Auth @@ -187,11 +184,8 @@ class Auth { * * - timestamp: (optional) the time in ms since the epoch. If none is specified, * the system will be queried for a time value to use. - * - * @param callback (err, tokenDetails) */ - authorize(tokenParams: API.TokenParams | null, callback: Function): void; - authorize(tokenParams: API.TokenParams | null): Promise; + async authorize(tokenParams: API.TokenParams | null): Promise; /** * Instructs the library to get a token immediately and ensures Token Auth @@ -244,65 +238,51 @@ class Auth { * * - requestHeaders (optional, unsupported, for testing only) extra headers to add to the * requestToken request - * - * @param callback (err, tokenDetails) */ - authorize(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null, callback: Function): void; - authorize(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null): Promise; - - authorize( - tokenParams?: Record | Function | null, - authOptions?: API.AuthOptions | null | Function, - callback?: Function - ): void | Promise { - let _authOptions: API.AuthOptions | null; - /* shuffle and normalise arguments as necessary */ - if (typeof tokenParams == 'function' && !callback) { - callback = tokenParams; - _authOptions = tokenParams = null; - } else if (typeof authOptions == 'function' && !callback) { - callback = authOptions; - _authOptions = null; - } else { - _authOptions = authOptions as API.AuthOptions; - } - if (!callback) { - return Utils.promisify(this, 'authorize', arguments); - } + async authorize(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null): Promise; + async authorize( + tokenParams?: Record | null, + authOptions?: API.AuthOptions | null + ): Promise { /* RSA10a: authorize() call implies token auth. If a key is passed it, we * just check if it doesn't clash and assume we're generating a token from it */ - if (_authOptions && _authOptions.key && this.authOptions.key !== _authOptions.key) { + if (authOptions && authOptions.key && this.authOptions.key !== authOptions.key) { throw new ErrorInfo('Unable to update auth options with incompatible key', 40102, 401); } - this._forceNewToken( - tokenParams as API.TokenParams, - _authOptions, - (err: ErrorInfo, tokenDetails: API.TokenDetails) => { - if (err) { - if ((this.client as BaseRealtime).connection && err.statusCode === HttpStatusCodes.Forbidden) { - /* Per RSA4d & RSA4d1, if the auth server explicitly repudiates our right to - * stay connecticed by returning a 403, we actively disconnect the connection - * even though we may well still have time left in the old token. */ - (this.client as BaseRealtime).connection.connectionManager.actOnErrorFromAuthorize(err); + return new Promise((resolve, reject) => { + this._forceNewToken( + tokenParams ?? null, + authOptions ?? null, + (err: ErrorInfo, tokenDetails: API.TokenDetails) => { + if (err) { + if ((this.client as BaseRealtime).connection && err.statusCode === HttpStatusCodes.Forbidden) { + /* Per RSA4d & RSA4d1, if the auth server explicitly repudiates our right to + * stay connecticed by returning a 403, we actively disconnect the connection + * even though we may well still have time left in the old token. */ + (this.client as BaseRealtime).connection.connectionManager.actOnErrorFromAuthorize(err); + } + reject(err); + return; } - callback?.(err); - return; - } - /* RTC8 - * - When authorize called by an end user and have a realtime connection, - * don't call back till new token has taken effect. - * - Use this.client.connection as a proxy for (this.client instanceof BaseRealtime), - * which doesn't work in node as BaseRealtime isn't part of the vm context for Rest clients */ - if (isRealtime(this.client)) { - this.client.connection.connectionManager.onAuthUpdated(tokenDetails, callback || noop); - } else { - callback?.(null, tokenDetails); + /* RTC8 + * - When authorize called by an end user and have a realtime connection, + * don't call back till new token has taken effect. + * - Use this.client.connection as a proxy for (this.client instanceof BaseRealtime), + * which doesn't work in node as BaseRealtime isn't part of the vm context for Rest clients */ + if (isRealtime(this.client)) { + this.client.connection.connectionManager.onAuthUpdated( + tokenDetails, + (err: unknown, tokenDetails?: API.TokenDetails) => (err ? reject(err) : resolve(tokenDetails!)) + ); + } else { + resolve(tokenDetails); + } } - } - ); + ); + }); } /* For internal use, eg by connectionManager - useful when want to call back @@ -329,10 +309,8 @@ class Auth { /** * Request an access token - * @param callback (err, tokenDetails) */ - requestToken(callback: StandardCallback): void; - requestToken(): Promise; + async requestToken(): Promise; /** * Request an access token @@ -351,11 +329,8 @@ class Auth { * * - timestamp: (optional) the time in ms since the epoch. If none is specified, * the system will be queried for a time value to use. - * - * @param callback (err, tokenDetails) */ - requestToken(tokenParams: API.TokenParams | null, callback: StandardCallback): void; - requestToken(tokenParams: API.TokenParams | null): Promise; + async requestToken(tokenParams: API.TokenParams | null): Promise; /** * Request an access token @@ -400,41 +375,17 @@ class Auth { * * - requestHeaders (optional, unsupported, for testing only) extra headers to add to the * requestToken request - * - * @param callback (err, tokenDetails) */ - requestToken( - tokenParams: API.TokenParams | null, - authOptions: API.AuthOptions, - callback: StandardCallback - ): void; - requestToken(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions): Promise; - - requestToken( - tokenParams?: API.TokenParams | StandardCallback | null, - authOptions?: any | StandardCallback, - callback?: StandardCallback - ): void | Promise { - /* shuffle and normalise arguments as necessary */ - if (typeof tokenParams == 'function' && !callback) { - callback = tokenParams; - authOptions = tokenParams = null; - } else if (typeof authOptions == 'function' && !callback) { - callback = authOptions; - authOptions = null; - } - if (!callback) { - return Utils.promisify(this, 'requestToken', arguments); - } + async requestToken(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions): Promise; + async requestToken(tokenParams?: API.TokenParams | null, authOptions?: any): Promise { /* RSA8e: if authOptions passed in, they're used instead of stored, don't merge them */ authOptions = authOptions || this.authOptions; tokenParams = tokenParams || Utils.copy(this.tokenParams); - const _callback = callback || noop; /* first set up whatever callback will be used to get signed * token requests */ - let tokenRequestCallback, + let tokenRequestCallback: any, client = this.client; if (authOptions.authCallback) { @@ -565,8 +516,7 @@ class Auth { 'Auth()', 'library initialized with a token literal without any way to renew the token when it expires (no authUrl, authCallback, or key). See https://help.ably.io/error/40171 for help' ); - _callback(new ErrorInfo(msg, 40171, 403)); - return; + throw new ErrorInfo(msg, 40171, 403); } /* normalise token params */ @@ -597,115 +547,118 @@ class Auth { ); }; - let tokenRequestCallbackTimeoutExpired = false, - timeoutLength = this.client.options.timeouts.realtimeRequestTimeout, - tokenRequestCallbackTimeout = setTimeout(function () { - tokenRequestCallbackTimeoutExpired = true; - const msg = 'Token request callback timed out after ' + timeoutLength / 1000 + ' seconds'; - Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); - _callback(new ErrorInfo(msg, 40170, 401)); - }, timeoutLength); + return new Promise((resolve, reject) => { + let tokenRequestCallbackTimeoutExpired = false, + timeoutLength = this.client.options.timeouts.realtimeRequestTimeout, + tokenRequestCallbackTimeout = setTimeout(function () { + tokenRequestCallbackTimeoutExpired = true; + const msg = 'Token request callback timed out after ' + timeoutLength / 1000 + ' seconds'; + Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); + reject(new ErrorInfo(msg, 40170, 401)); + }, timeoutLength); - tokenRequestCallback(tokenParams, function (err: ErrorInfo, tokenRequestOrDetails: any, contentType: string) { - if (tokenRequestCallbackTimeoutExpired) return; - clearTimeout(tokenRequestCallbackTimeout); + tokenRequestCallback(tokenParams, function (err: ErrorInfo, tokenRequestOrDetails: any, contentType: string) { + if (tokenRequestCallbackTimeoutExpired) return; + clearTimeout(tokenRequestCallbackTimeout); - if (err) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.requestToken()', - 'token request signing call returned error; err = ' + Utils.inspectError(err) - ); - _callback(normaliseAuthcallbackError(err)); - return; - } - /* the response from the callback might be a token string, a signed request or a token details */ - if (typeof tokenRequestOrDetails === 'string') { - if (tokenRequestOrDetails.length === 0) { - _callback(new ErrorInfo('Token string is empty', 40170, 401)); - } else if (tokenRequestOrDetails.length > MAX_TOKEN_LENGTH) { - _callback( - new ErrorInfo( - 'Token string exceeded max permitted length (was ' + tokenRequestOrDetails.length + ' bytes)', - 40170, - 401 - ) + if (err) { + Logger.logAction( + Logger.LOG_ERROR, + 'Auth.requestToken()', + 'token request signing call returned error; err = ' + Utils.inspectError(err) ); - } else if (tokenRequestOrDetails === 'undefined' || tokenRequestOrDetails === 'null') { - /* common failure mode with poorly-implemented authCallbacks */ - _callback(new ErrorInfo('Token string was literal null/undefined', 40170, 401)); - } else if (tokenRequestOrDetails[0] === '{' && !(contentType && contentType.indexOf('application/jwt') > -1)) { - _callback( + reject(normaliseAuthcallbackError(err)); + return; + } + /* the response from the callback might be a token string, a signed request or a token details */ + if (typeof tokenRequestOrDetails === 'string') { + if (tokenRequestOrDetails.length === 0) { + reject(new ErrorInfo('Token string is empty', 40170, 401)); + } else if (tokenRequestOrDetails.length > MAX_TOKEN_LENGTH) { + reject( + new ErrorInfo( + 'Token string exceeded max permitted length (was ' + tokenRequestOrDetails.length + ' bytes)', + 40170, + 401 + ) + ); + } else if (tokenRequestOrDetails === 'undefined' || tokenRequestOrDetails === 'null') { + /* common failure mode with poorly-implemented authCallbacks */ + reject(new ErrorInfo('Token string was literal null/undefined', 40170, 401)); + } else if ( + tokenRequestOrDetails[0] === '{' && + !(contentType && contentType.indexOf('application/jwt') > -1) + ) { + reject( + new ErrorInfo( + "Token was double-encoded; make sure you're not JSON-encoding an already encoded token request or details", + 40170, + 401 + ) + ); + } else { + resolve({ token: tokenRequestOrDetails } as API.TokenDetails); + } + return; + } + if (typeof tokenRequestOrDetails !== 'object') { + const msg = + 'Expected token request callback to call back with a token string or token request/details object, but got a ' + + typeof tokenRequestOrDetails; + Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); + reject(new ErrorInfo(msg, 40170, 401)); + return; + } + const objectSize = JSON.stringify(tokenRequestOrDetails).length; + if (objectSize > MAX_TOKEN_LENGTH && !authOptions.suppressMaxLengthCheck) { + reject( new ErrorInfo( - "Token was double-encoded; make sure you're not JSON-encoding an already encoded token request or details", + 'Token request/details object exceeded max permitted stringified size (was ' + objectSize + ' bytes)', 40170, 401 ) ); - } else { - _callback(null, { token: tokenRequestOrDetails } as API.TokenDetails); + return; } - return; - } - if (typeof tokenRequestOrDetails !== 'object') { - const msg = - 'Expected token request callback to call back with a token string or token request/details object, but got a ' + - typeof tokenRequestOrDetails; - Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); - _callback(new ErrorInfo(msg, 40170, 401)); - return; - } - const objectSize = JSON.stringify(tokenRequestOrDetails).length; - if (objectSize > MAX_TOKEN_LENGTH && !authOptions.suppressMaxLengthCheck) { - _callback( - new ErrorInfo( - 'Token request/details object exceeded max permitted stringified size (was ' + objectSize + ' bytes)', - 40170, - 401 - ) - ); - return; - } - if ('issued' in tokenRequestOrDetails) { - /* a tokenDetails object */ - _callback(null, tokenRequestOrDetails); - return; - } - if (!('keyName' in tokenRequestOrDetails)) { - const msg = - 'Expected token request callback to call back with a token string, token request object, or token details object'; - Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); - _callback(new ErrorInfo(msg, 40170, 401)); - return; - } - /* it's a token request, so make the request */ - tokenRequest( - tokenRequestOrDetails, - function ( - err?: ErrorInfo | ErrnoException | null, - tokenResponse?: API.TokenDetails | string, - headers?: Record, - unpacked?: boolean - ) { - if (err) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.requestToken()', - 'token request API call returned error; err = ' + Utils.inspectError(err) - ); - _callback(normaliseAuthcallbackError(err)); - return; - } - if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); - Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); - _callback(null, tokenResponse as API.TokenDetails); + if ('issued' in tokenRequestOrDetails) { + /* a tokenDetails object */ + resolve(tokenRequestOrDetails); + return; } - ); + if (!('keyName' in tokenRequestOrDetails)) { + const msg = + 'Expected token request callback to call back with a token string, token request object, or token details object'; + Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); + reject(new ErrorInfo(msg, 40170, 401)); + return; + } + /* it's a token request, so make the request */ + tokenRequest( + tokenRequestOrDetails, + function ( + err?: ErrorInfo | ErrnoException | null, + tokenResponse?: API.TokenDetails | string, + headers?: Record, + unpacked?: boolean + ) { + if (err) { + Logger.logAction( + Logger.LOG_ERROR, + 'Auth.requestToken()', + 'token request API call returned error; err = ' + Utils.inspectError(err) + ); + reject(normaliseAuthcallbackError(err)); + return; + } + if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); + Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); + resolve(tokenResponse as API.TokenDetails); + } + ); + }); }); } - createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any, callback: Function): void; - createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any): Promise; /** * Create and sign a token request based on the given options. * NOTE this can only be used when the key value is available locally. @@ -738,88 +691,73 @@ class Auth { * * - timestamp: (optional) the time in ms since the epoch. If none is specified, * the system will be queried for a time value to use. - * - * @param callback */ - createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any, callback?: Function) { - /* shuffle and normalise arguments as necessary */ - if (typeof tokenParams == 'function' && !callback) { - callback = tokenParams; - authOptions = tokenParams = null; - } else if (typeof authOptions == 'function' && !callback) { - callback = authOptions; - authOptions = null; - } - if (!callback) { - return Utils.promisify(this, 'createTokenRequest', arguments); - } - + async createTokenRequest(tokenParams: API.TokenParams | null, authOptions: any): Promise { /* RSA9h: if authOptions passed in, they're used instead of stored, don't merge them */ authOptions = authOptions || this.authOptions; tokenParams = tokenParams || Utils.copy(this.tokenParams); const key = authOptions.key; if (!key) { - callback(new ErrorInfo('No key specified', 40101, 403)); - return; + throw new ErrorInfo('No key specified', 40101, 403); } const keyParts = key.split(':'), keyName = keyParts[0], keySecret = keyParts[1]; if (!keySecret) { - callback(new ErrorInfo('Invalid key specified', 40101, 403)); - return; + throw new ErrorInfo('Invalid key specified', 40101, 403); } if (tokenParams.clientId === '') { - callback(new ErrorInfo('clientId can’t be an empty string', 40012, 400)); - return; + throw new ErrorInfo('clientId can’t be an empty string', 40012, 400); } if ('capability' in tokenParams) { tokenParams.capability = c14n(tokenParams.capability); } - const request = Utils.mixin({ keyName: keyName }, tokenParams), + const request: Partial = Utils.mixin({ keyName: keyName }, tokenParams), clientId = tokenParams.clientId || '', ttl = tokenParams.ttl || '', capability = tokenParams.capability || ''; - ((authoriseCb) => { - if (request.timestamp) { - authoriseCb(); - return; - } - this.getTimestamp(authOptions && authOptions.queryTime, function (err?: ErrorInfo | null, time?: number) { - if (err) { - callback!(err); + return new Promise((resolve, reject) => { + ((authoriseCb) => { + if (request.timestamp) { + authoriseCb(); return; } - request.timestamp = time; - authoriseCb(); + this.getTimestamp(authOptions && authOptions.queryTime, function (err?: ErrorInfo | null, time?: number) { + if (err) { + reject(err); + return; + } + request.timestamp = time; + authoriseCb(); + }); + })(function () { + /* nonce */ + /* NOTE: there is no expectation that the client + * specifies the nonce; this is done by the library + * However, this can be overridden by the client + * simply for testing purposes. */ + const nonce = request.nonce || (request.nonce = random()), + timestamp = request.timestamp; + + const signText = + request.keyName + '\n' + ttl + '\n' + capability + '\n' + clientId + '\n' + timestamp + '\n' + nonce + '\n'; + + /* mac */ + /* NOTE: there is no expectation that the client + * specifies the mac; this is done by the library + * However, this can be overridden by the client + * simply for testing purposes. */ + request.mac = request.mac || hmac(signText, keySecret); + + Logger.logAction(Logger.LOG_MINOR, 'Auth.getTokenRequest()', 'generated signed request'); + resolve(request as API.TokenRequest); }); - })(function () { - /* nonce */ - /* NOTE: there is no expectation that the client - * specifies the nonce; this is done by the library - * However, this can be overridden by the client - * simply for testing purposes. */ - const nonce = request.nonce || (request.nonce = random()), - timestamp = request.timestamp; - - const signText = - request.keyName + '\n' + ttl + '\n' + capability + '\n' + clientId + '\n' + timestamp + '\n' + nonce + '\n'; - - /* mac */ - /* NOTE: there is no expectation that the client - * specifies the mac; this is done by the library - * However, this can be overridden by the client - * simply for testing purposes. */ - request.mac = request.mac || hmac(signText, keySecret); - - Logger.logAction(Logger.LOG_MINOR, 'Auth.getTokenRequest()', 'generated signed request'); - callback!(null, request); }); } diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index b5c982ac75..e445ee6361 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -4,7 +4,6 @@ import Auth from './auth'; import { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; import ErrorInfo from '../types/errorinfo'; import Stats from '../types/stats'; -import { StandardCallback } from '../../types/utils'; import { Http, RequestParams } from '../../types/http'; import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOptions'; import * as API from '../../../../ably'; @@ -129,30 +128,23 @@ class BaseClient { return Defaults.getHttpScheme(this.options) + host + ':' + Defaults.getPort(this.options, false); } - stats( - params: RequestParams, - callback: StandardCallback> - ): Promise> | void { - return this.rest.stats(params, callback); + async stats(params: RequestParams): Promise> { + return this.rest.stats(params); } - time(params: RequestParams, callback: StandardCallback): void; - time(callback: StandardCallback): void; - time(params?: RequestParams): Promise; - time(params?: RequestParams | StandardCallback, callback?: StandardCallback): Promise | void { - return this.rest.time(params, callback); + async time(params?: RequestParams): Promise { + return this.rest.time(params); } - request( + async request( method: string, path: string, version: number, params: RequestParams, body: unknown, - customHeaders: Record, - callback: StandardCallback> - ): Promise> | void { - return this.rest.request(method, path, version, params, body, customHeaders, callback); + customHeaders: Record + ): Promise> { + return this.rest.request(method, path, version, params, body, customHeaders); } batchPublish( diff --git a/src/common/lib/client/connection.ts b/src/common/lib/client/connection.ts index 1447edde04..0f822ee1ec 100644 --- a/src/common/lib/client/connection.ts +++ b/src/common/lib/client/connection.ts @@ -1,4 +1,3 @@ -import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import ConnectionManager from '../transport/connectionmanager'; import Logger from '../util/logger'; @@ -53,12 +52,11 @@ class Connection extends EventEmitter { this.connectionManager.requestState({ state: 'connecting' }); } - ping(callback: Function): Promise | void { + async ping(): Promise { Logger.logAction(Logger.LOG_MINOR, 'Connection.ping()', ''); - if (!callback) { - return Utils.promisify(this, 'ping', arguments); - } - this.connectionManager.ping(null, callback); + return new Promise((resolve, reject) => { + this.connectionManager.ping(null, (err: unknown, result: number) => (err ? reject(err) : resolve(result))); + }); } close(): void { diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 0513949be8..4ff93711cf 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -182,9 +182,9 @@ class PaginatedResource { export class PaginatedResult { resource: PaginatedResource; items: T[]; - first?: (results: PaginatedResultCallback) => void; - next?: (results: PaginatedResultCallback) => void; - current?: (results: PaginatedResultCallback) => void; + first?: () => Promise>; + next?: () => Promise | null>; + current?: () => Promise>; hasNext?: () => boolean; isLast?: () => boolean; @@ -195,29 +195,26 @@ export class PaginatedResult { const self = this; if (relParams) { if ('first' in relParams) { - this.first = function (callback) { - if (!callback) { - return Utils.promisify(self, 'first', []); - } - self.get(relParams.first, callback); + this.first = async function () { + return new Promise((resolve, reject) => { + self.get(relParams.first, (err, result) => (err ? reject(err) : resolve(result))); + }); }; } if ('current' in relParams) { - this.current = function (callback) { - if (!callback) { - return Utils.promisify(self, 'current', []); - } - self.get(relParams.current, callback); + this.current = async function () { + return new Promise((resolve, reject) => { + self.get(relParams.current, (err, result) => (err ? reject(err) : resolve(result))); + }); }; } - this.next = function (callback) { - if (!callback) { - return Utils.promisify(self, 'next', []); - } + this.next = async function () { if ('next' in relParams) { - self.get(relParams.next, callback); + return new Promise((resolve, reject) => { + self.get(relParams.next, (err, result) => (err ? reject(err) : resolve(result))); + }); } else { - callback(null, null); + return null; } }; diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 5344718414..3c1b10edfc 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -1,10 +1,9 @@ import * as Utils from '../util/utils'; import DeviceDetails from '../types/devicedetails'; import Resource from './resource'; -import PaginatedResource from './paginatedresource'; +import PaginatedResource, { PaginatedResult } from './paginatedresource'; import ErrorInfo from '../types/errorinfo'; import PushChannelSubscription from '../types/pushchannelsubscription'; -import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseClient from './baseclient'; import Defaults from '../util/defaults'; @@ -29,23 +28,23 @@ class Admin { this.channelSubscriptions = new ChannelSubscriptions(client); } - publish(recipient: any, payload: any, callback: ErrCallback) { + async publish(recipient: any, payload: any): Promise { const client = this.client; const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultPostHeaders(client.options, { format }), params = {}; const body = Utils.mixin({ recipient: recipient }, payload); - if (typeof callback !== 'function') { - return Utils.promisify(this, 'publish', arguments); - } - Utils.mixin(headers, client.options.headers); if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, client._MsgPack, format); - Resource.post(client, '/push/publish', requestBody, headers, params, null, (err) => callback(err)); + return new Promise((resolve, reject) => { + Resource.post(client, '/push/publish', requestBody, headers, params, null, (err) => + err ? reject(err) : resolve() + ); + }); } } @@ -56,163 +55,147 @@ class DeviceRegistrations { this.client = client; } - save(device: any, callback: StandardCallback) { + async save(device: any): Promise { const client = this.client; const body = DeviceDetails.fromValues(device); const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultPostHeaders(client.options, { format }), params = {}; - if (typeof callback !== 'function') { - return Utils.promisify(this, 'save', arguments); - } - Utils.mixin(headers, client.options.headers); if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, client._MsgPack, format); - Resource.put( - client, - '/push/deviceRegistrations/' + encodeURIComponent(device.id), - requestBody, - headers, - params, - null, - (err, body, headers, unpacked) => { - callback( - err, - !err - ? (DeviceDetails.fromResponseBody( - body as Record, - client._MsgPack, - unpacked ? undefined : format - ) as DeviceDetails) - : undefined - ); - } - ); + return new Promise((resolve, reject) => { + Resource.put( + client, + '/push/deviceRegistrations/' + encodeURIComponent(device.id), + requestBody, + headers, + params, + null, + (err, body, headers, unpacked) => { + err + ? reject(err) + : resolve( + DeviceDetails.fromResponseBody( + body as Record, + client._MsgPack, + unpacked ? undefined : format + ) as DeviceDetails + ); + } + ); + }); } - get(deviceIdOrDetails: any, callback: StandardCallback) { + async get(deviceIdOrDetails: any): Promise { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultGetHeaders(client.options, { format }), deviceId = deviceIdOrDetails.id || deviceIdOrDetails; - if (typeof callback !== 'function') { - return Utils.promisify(this, 'get', arguments); - } - if (typeof deviceId !== 'string' || !deviceId.length) { - callback( - new ErrorInfo( - 'First argument to DeviceRegistrations#get must be a deviceId string or DeviceDetails', - 40000, - 400 - ) + throw new ErrorInfo( + 'First argument to DeviceRegistrations#get must be a deviceId string or DeviceDetails', + 40000, + 400 ); - return; } Utils.mixin(headers, client.options.headers); - Resource.get( - client, - '/push/deviceRegistrations/' + encodeURIComponent(deviceId), - headers, - {}, - null, - function (err, body, headers, unpacked) { - callback( - err, - !err - ? (DeviceDetails.fromResponseBody( - body as Record, - client._MsgPack, - unpacked ? undefined : format - ) as DeviceDetails) - : undefined - ); - } - ); + return new Promise((resolve, reject) => { + Resource.get( + client, + '/push/deviceRegistrations/' + encodeURIComponent(deviceId), + headers, + {}, + null, + function (err, body, headers, unpacked) { + err + ? reject(err) + : resolve( + DeviceDetails.fromResponseBody( + body as Record, + client._MsgPack, + unpacked ? undefined : format + ) as DeviceDetails + ); + } + ); + }); } - list(params: any, callback: PaginatedResultCallback) { + async list(params: any): Promise> { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format, headers = Defaults.defaultGetHeaders(client.options, { format }); - if (typeof callback !== 'function') { - return Utils.promisify(this, 'list', arguments); - } - Utils.mixin(headers, client.options.headers); - new PaginatedResource(client, '/push/deviceRegistrations', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return DeviceDetails.fromResponseBody( - body as Record[], - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, callback); + return new Promise((resolve, reject) => { + new PaginatedResource(client, '/push/deviceRegistrations', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return DeviceDetails.fromResponseBody( + body as Record[], + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params, (err, result) => (err ? reject(err) : resolve(result))); + }); } - remove(deviceIdOrDetails: any, callback: ErrCallback) { + async remove(deviceIdOrDetails: any): Promise { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultGetHeaders(client.options, { format }), params = {}, deviceId = deviceIdOrDetails.id || deviceIdOrDetails; - if (typeof callback !== 'function') { - return Utils.promisify(this, 'remove', arguments); - } - if (typeof deviceId !== 'string' || !deviceId.length) { - callback( - new ErrorInfo( - 'First argument to DeviceRegistrations#remove must be a deviceId string or DeviceDetails', - 40000, - 400 - ) + throw new ErrorInfo( + 'First argument to DeviceRegistrations#remove must be a deviceId string or DeviceDetails', + 40000, + 400 ); - return; } Utils.mixin(headers, client.options.headers); if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - Resource['delete']( - client, - '/push/deviceRegistrations/' + encodeURIComponent(deviceId), - headers, - params, - null, - (err) => callback(err) - ); + return new Promise((resolve, reject) => { + Resource['delete']( + client, + '/push/deviceRegistrations/' + encodeURIComponent(deviceId), + headers, + params, + null, + (err) => (err ? reject(err) : resolve()) + ); + }); } - removeWhere(params: any, callback: ErrCallback) { + async removeWhere(params: any): Promise { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultGetHeaders(client.options, { format }); - if (typeof callback !== 'function') { - return Utils.promisify(this, 'removeWhere', arguments); - } - Utils.mixin(headers, client.options.headers); if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - Resource['delete'](client, '/push/deviceRegistrations', headers, params, null, (err) => callback(err)); + return new Promise((resolve, reject) => { + Resource['delete'](client, '/push/deviceRegistrations', headers, params, null, (err) => + err ? reject(err) : resolve() + ); + }); } } @@ -223,112 +206,105 @@ class ChannelSubscriptions { this.client = client; } - save(subscription: Record, callback: StandardCallback) { + async save(subscription: Record): Promise { const client = this.client; const body = PushChannelSubscription.fromValues(subscription); const format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultPostHeaders(client.options, { format }), params = {}; - if (typeof callback !== 'function') { - return Utils.promisify(this, 'save', arguments); - } - Utils.mixin(headers, client.options.headers); if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, client._MsgPack, format); - Resource.post( - client, - '/push/channelSubscriptions', - requestBody, - headers, - params, - null, - function (err, body, headers, unpacked) { - callback( - err, - !err - ? (PushChannelSubscription.fromResponseBody( - body as Record, - client._MsgPack, - unpacked ? undefined : format - ) as PushChannelSubscription) - : undefined - ); - } - ); + return new Promise((resolve, reject) => { + Resource.post( + client, + '/push/channelSubscriptions', + requestBody, + headers, + params, + null, + function (err, body, headers, unpacked) { + err + ? reject(err) + : resolve( + PushChannelSubscription.fromResponseBody( + body as Record, + client._MsgPack, + unpacked ? undefined : format + ) as PushChannelSubscription + ); + } + ); + }); } - list(params: any, callback: PaginatedResultCallback) { + async list(params: any): Promise> { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format, headers = Defaults.defaultGetHeaders(client.options, { format }); - if (typeof callback !== 'function') { - return Utils.promisify(this, 'list', arguments); - } - Utils.mixin(headers, client.options.headers); - new PaginatedResource(client, '/push/channelSubscriptions', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return PushChannelSubscription.fromResponseBody( - body as Record[], - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, callback); + return new Promise((resolve, reject) => { + new PaginatedResource(client, '/push/channelSubscriptions', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return PushChannelSubscription.fromResponseBody( + body as Record[], + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params, (err, result) => (err ? reject(err) : resolve(result))); + }); } - removeWhere(params: any, callback: ErrCallback) { + async removeWhere(params: any): Promise { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultGetHeaders(client.options, { format }); - if (typeof callback !== 'function') { - return Utils.promisify(this, 'removeWhere', arguments); - } - Utils.mixin(headers, client.options.headers); if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - Resource['delete'](client, '/push/channelSubscriptions', headers, params, null, (err) => callback(err)); + return new Promise((resolve, reject) => { + Resource['delete'](client, '/push/channelSubscriptions', headers, params, null, (err) => + err ? reject(err) : resolve() + ); + }); } /* ChannelSubscriptions have no unique id; removing one is equivalent to removeWhere by its properties */ remove = ChannelSubscriptions.prototype.removeWhere; - listChannels(params: any, callback: PaginatedResultCallback) { + async listChannels(params: any): Promise> { const client = this.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format, headers = Defaults.defaultGetHeaders(client.options, { format }); - if (typeof callback !== 'function') { - return Utils.promisify(this, 'listChannels', arguments); - } - Utils.mixin(headers, client.options.headers); if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - new PaginatedResource(client, '/push/channels', headers, envelope, async function (body, headers, unpacked) { - const parsedBody = ( - !unpacked && format ? Utils.decodeBody(body, client._MsgPack, format) : body - ) as Array; - - for (let i = 0; i < parsedBody.length; i++) { - parsedBody[i] = String(parsedBody[i]); - } - return parsedBody; - }).get(params, callback); + return new Promise((resolve, reject) => { + new PaginatedResource(client, '/push/channels', headers, envelope, async function (body, headers, unpacked) { + const parsedBody = ( + !unpacked && format ? Utils.decodeBody(body, client._MsgPack, format) : body + ) as Array; + + for (let i = 0; i < parsedBody.length; i++) { + parsedBody[i] = String(parsedBody[i]); + } + return parsedBody; + }).get(params, (err, result) => (err ? reject(err) : resolve(result))); + }); } } diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 3bd0ccf0fd..0f5838d5f6 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -17,13 +17,13 @@ import Message, { EncodingDecodingContext, } from '../types/message'; import ChannelStateChange from './channelstatechange'; -import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; +import ErrorInfo, { PartialErrorInfo } from '../types/errorinfo'; import PresenceMessage, { decode as decodePresenceMessage } from '../types/presencemessage'; import ConnectionErrors from '../transport/connectionerrors'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; import ConnectionStateChange from './connectionstatechange'; -import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; +import { ErrCallback, StandardCallback } from '../../types/utils'; import BaseRealtime from './baserealtime'; import { ChannelOptions } from '../../types/channel'; import { normaliseChannelOptions } from '../util/defaults'; @@ -143,32 +143,18 @@ class RealtimeChannel extends EventEmitter { } static processListenerArgs(args: unknown[]): any[] { - /* [event], listener, [callback] */ + /* [event], listener */ args = Array.prototype.slice.call(args); if (typeof args[0] === 'function') { args.unshift(null); } - if (args[args.length - 1] == undefined) { - args.pop(); - } return args; } - setOptions(options?: API.ChannelOptions, callback?: ErrCallback): void | Promise { - if (!callback) { - return Utils.promisify(this, 'setOptions', arguments); - } - const _callback = - callback || - function (err?: IPartialErrorInfo | null) { - if (err) { - Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.setOptions()', 'Set options failed: ' + err.toString()); - } - }; + async setOptions(options?: API.ChannelOptions): Promise { const err = validateChannelOptions(options); if (err) { - _callback(err); - return; + throw err; } this.channelOptions = normaliseChannelOptions(this.client._Crypto ?? null, options); if (this._decodingContext) this._decodingContext.channelOptions = this.channelOptions; @@ -180,25 +166,24 @@ class RealtimeChannel extends EventEmitter { * rejecting messages until we have confirmation that the options have changed, * which would unnecessarily lose message continuity. */ this.attachImpl(); - // Ignore 'attaching' -- could be just due to to a resume & reattach, should not - // call back setOptions until we're definitely attached with the new options (or - // else in a terminal state) - this._allChannelChanges.once( - ['attached', 'update', 'detached', 'failed'], - function (this: { event: string }, stateChange: ConnectionStateChange) { - switch (this.event) { - case 'update': - case 'attached': - _callback?.(null); - return; - default: - _callback?.(stateChange.reason); - return; + return new Promise((resolve, reject) => { + // Ignore 'attaching' -- could be just due to to a resume & reattach, should not + // call back setOptions until we're definitely attached with the new options (or + // else in a terminal state) + this._allChannelChanges.once( + ['attached', 'update', 'detached', 'failed'], + function (this: { event: string }, stateChange: ConnectionStateChange) { + switch (this.event) { + case 'update': + case 'attached': + resolve(); + break; + default: + reject(stateChange.reason); + } } - } - ); - } else { - _callback(); + ); + }); } } @@ -226,19 +211,14 @@ class RealtimeChannel extends EventEmitter { return false; } - publish(...args: any[]): void | Promise { + async publish(...args: any[]): Promise { let messages = args[0]; let argCount = args.length; - let callback = args[argCount - 1]; - if (typeof callback !== 'function') { - return Utils.promisify(this, 'publish', arguments); - } if (!this.connectionManager.activeState()) { - callback(this.connectionManager.getError()); - return; + throw this.connectionManager.getError(); } - if (argCount == 2) { + if (argCount == 1) { if (Utils.isObject(messages)) messages = [messageFromValues(messages)]; else if (Utils.isArray(messages)) messages = messagesFromValuesArray(messages); else @@ -251,28 +231,30 @@ class RealtimeChannel extends EventEmitter { messages = [messageFromValues({ name: args[0], data: args[1] })]; } const maxMessageSize = this.client.options.maxMessageSize; - encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { - if (err) { - callback(err); - return; - } - /* RSL1i */ - const size = getMessagesSize(messages); - if (size > maxMessageSize) { - callback( - new ErrorInfo( - 'Maximum size of messages that can be published at once exceeded ( was ' + - size + - ' bytes; limit is ' + - maxMessageSize + - ' bytes)', - 40009, - 400 - ) - ); - return; - } - this._publish(messages, callback); + return new Promise((resolve, reject) => { + encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { + if (err) { + reject(err); + return; + } + /* RSL1i */ + const size = getMessagesSize(messages); + if (size > maxMessageSize) { + reject( + new ErrorInfo( + 'Maximum size of messages that can be published at once exceeded ( was ' + + size + + ' bytes; limit is ' + + maxMessageSize + + ' bytes)', + 40009, + 400 + ) + ); + return; + } + this._publish(messages, (err) => (err ? reject(err) : resolve())); + }); }); } @@ -305,18 +287,14 @@ class RealtimeChannel extends EventEmitter { } } - attach(): Promise; - attach(callback: StandardCallback): void; - attach(callback?: StandardCallback): void | Promise { - if (!callback) { - return Utils.promisify(this, 'attach', arguments); - } + async attach(): Promise { if (this.state === 'attached') { - callback(null, null); - return; + return null; } - this._attach(false, null, callback); + return new Promise((resolve, reject) => { + this._attach(false, null, (err, result) => (err ? reject(err) : resolve(result!))); + }); } _attach( @@ -387,48 +365,43 @@ class RealtimeChannel extends EventEmitter { this.sendMessage(attachMsg, noop); } - detach(callback: ErrCallback): void | Promise { - if (!callback) { - return Utils.promisify(this, 'detach', arguments); - } + async detach(): Promise { const connectionManager = this.connectionManager; if (!connectionManager.activeState()) { - callback(connectionManager.getError()); - return; + throw connectionManager.getError(); } switch (this.state) { case 'suspended': this.notifyState('detached'); - callback(); - break; + return; case 'detached': - callback(); - break; + return; case 'failed': - callback(new ErrorInfo('Unable to detach; channel state = failed', 90001, 400)); - break; + throw new ErrorInfo('Unable to detach; channel state = failed', 90001, 400); default: this.requestState('detaching'); // eslint-disable-next-line no-fallthrough case 'detaching': - this.once(function (this: { event: string }, stateChange: ChannelStateChange) { - switch (this.event) { - case 'detached': - callback(); - break; - case 'attached': - case 'suspended': - case 'failed': - callback( - stateChange.reason || - connectionManager.getError() || - new ErrorInfo('Unable to detach; reason unknown; state = ' + this.event, 90000, 500) - ); - break; - case 'attaching': - callback(new ErrorInfo('Detach request superseded by a subsequent attach request', 90000, 409)); - break; - } + return new Promise((resolve, reject) => { + this.once(function (this: { event: string }, stateChange: ChannelStateChange) { + switch (this.event) { + case 'detached': + resolve(); + break; + case 'attached': + case 'suspended': + case 'failed': + reject( + stateChange.reason || + connectionManager.getError() || + new ErrorInfo('Unable to detach; reason unknown; state = ' + this.event, 90000, 500) + ); + break; + case 'attaching': + reject(new ErrorInfo('Detach request superseded by a subsequent attach request', 90000, 409)); + break; + } + }); }); } } @@ -439,16 +412,11 @@ class RealtimeChannel extends EventEmitter { this.sendMessage(msg, callback || noop); } - subscribe(...args: unknown[] /* [event], listener, [callback] */): void | Promise { - const [event, listener, callback] = RealtimeChannel.processListenerArgs(args); - - if (!callback) { - return Utils.promisify(this, 'subscribe', [event, listener]); - } + async subscribe(...args: unknown[] /* [event], listener */): Promise { + const [event, listener] = RealtimeChannel.processListenerArgs(args); if (this.state === 'failed') { - callback?.(ErrorInfo.fromValues(this.invalidStateError())); - return; + throw ErrorInfo.fromValues(this.invalidStateError()); } // Filtered @@ -458,7 +426,7 @@ class RealtimeChannel extends EventEmitter { this.subscriptions.on(event, listener); } - return this.attach(callback || noop); + return this.attach(); } unsubscribe(...args: unknown[] /* [event], listener */): void { @@ -886,45 +854,33 @@ class RealtimeChannel extends EventEmitter { } } - history = function ( + history = async function ( this: RealtimeChannel, - params: RealtimeHistoryParams | null, - callback: PaginatedResultCallback - ): void | Promise> { + params: RealtimeHistoryParams | null + ): Promise> { Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.history()', 'channel = ' + this.name); - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'history', arguments); - } - } // We fetch this first so that any module-not-provided error takes priority over other errors const restMixin = this.client.rest.channelMixin; if (params && params.untilAttach) { if (this.state !== 'attached') { - callback(new ErrorInfo('option untilAttach requires the channel to be attached', 40000, 400)); - return; + throw new ErrorInfo('option untilAttach requires the channel to be attached', 40000, 400); } if (!this.properties.attachSerial) { - callback( - new ErrorInfo( - 'untilAttach was specified and channel is attached, but attachSerial is not defined', - 40000, - 400 - ) + throw new ErrorInfo( + 'untilAttach was specified and channel is attached, but attachSerial is not defined', + 40000, + 400 ); - return; } delete params.untilAttach; params.from_serial = this.properties.attachSerial; } - restMixin.history(this, params, callback); + return new Promise((resolve, reject) => { + restMixin.history(this, params, (err, result) => (err ? reject(err) : resolve(result))); + }); } as any; whenState = ((state: string, listener: ErrCallback) => { @@ -959,8 +915,8 @@ class RealtimeChannel extends EventEmitter { } } - status(callback?: StandardCallback): void | Promise { - return this.client.rest.channelMixin.status(this, callback); + async status(): Promise { + return this.client.rest.channelMixin.status(this); } } diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index b0010d74b5..abaf126e6b 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -11,7 +11,7 @@ import RealtimeChannel from './realtimechannel'; import Multicaster from '../util/multicaster'; import ChannelStateChange from './channelstatechange'; import { CipherOptions } from '../types/message'; -import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils'; +import { ErrCallback } from '../../types/utils'; import { PaginatedResult } from './paginatedresource'; interface RealtimePresenceParams { @@ -101,61 +101,37 @@ class RealtimePresence extends EventEmitter { this.pendingPresence = []; } - enter(data: unknown, callback: ErrCallback): void | Promise { + async enter(data: unknown): Promise { if (isAnonymousOrWildcard(this)) { throw new ErrorInfo('clientId must be specified to enter a presence channel', 40012, 400); } - return this._enterOrUpdateClient(undefined, undefined, data, 'enter', callback); + return this._enterOrUpdateClient(undefined, undefined, data, 'enter'); } - update(data: unknown, callback: ErrCallback): void | Promise { + async update(data: unknown): Promise { if (isAnonymousOrWildcard(this)) { throw new ErrorInfo('clientId must be specified to update presence data', 40012, 400); } - return this._enterOrUpdateClient(undefined, undefined, data, 'update', callback); + return this._enterOrUpdateClient(undefined, undefined, data, 'update'); } - enterClient(clientId: string, data: unknown, callback: ErrCallback): void | Promise { - return this._enterOrUpdateClient(undefined, clientId, data, 'enter', callback); + async enterClient(clientId: string, data: unknown): Promise { + return this._enterOrUpdateClient(undefined, clientId, data, 'enter'); } - updateClient(clientId: string, data: unknown, callback: ErrCallback): void | Promise { - return this._enterOrUpdateClient(undefined, clientId, data, 'update', callback); + async updateClient(clientId: string, data: unknown): Promise { + return this._enterOrUpdateClient(undefined, clientId, data, 'update'); } - _enterOrUpdateClient( + async _enterOrUpdateClient( id: string | undefined, clientId: string | undefined, data: unknown, action: string - ): Promise; - _enterOrUpdateClient( - id: string | undefined, - clientId: string | undefined, - data: unknown, - action: string, - callback: ErrCallback - ): void; - _enterOrUpdateClient( - id: string | undefined, - clientId: string | undefined, - data: unknown, - action: string, - callback?: ErrCallback - ): void | Promise { - if (!callback) { - if (typeof data === 'function') { - callback = data as ErrCallback; - data = null; - } else { - return Utils.promisify(this, '_enterOrUpdateClient', [id, clientId, data, action]); - } - } - + ): Promise { const channel = this.channel; if (!channel.connectionManager.activeState()) { - callback(channel.connectionManager.getError()); - return; + throw channel.connectionManager.getError(); } Logger.logAction( @@ -173,57 +149,49 @@ class RealtimePresence extends EventEmitter { presence.clientId = clientId; } - encodePresenceMessage(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { - if (err) { - callback!(err); - return; - } - switch (channel.state) { - case 'attached': - channel.sendPresence(presence, callback); - break; - case 'initialized': - case 'detached': - channel.attach(); - // eslint-disable-next-line no-fallthrough - case 'attaching': - this.pendingPresence.push({ - presence: presence, - callback: callback!, - }); - break; - default: - err = new PartialErrorInfo( - 'Unable to ' + action + ' presence channel while in ' + channel.state + ' state', - 90001 - ); - err.code = 90001; - callback!(err); - } + return new Promise((resolve, reject) => { + encodePresenceMessage(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { + if (err) { + reject(err); + return; + } + switch (channel.state) { + case 'attached': + channel.sendPresence(presence, (err) => (err ? reject(err) : resolve())); + break; + case 'initialized': + case 'detached': + channel.attach(); + // eslint-disable-next-line no-fallthrough + case 'attaching': + this.pendingPresence.push({ + presence: presence, + callback: (err) => (err ? reject(err) : resolve()), + }); + break; + default: + err = new PartialErrorInfo( + 'Unable to ' + action + ' presence channel while in ' + channel.state + ' state', + 90001 + ); + err.code = 90001; + reject(err); + } + }); }); } - leave(data: unknown, callback: ErrCallback): void | Promise { + async leave(data: unknown): Promise { if (isAnonymousOrWildcard(this)) { throw new ErrorInfo('clientId must have been specified to enter or leave a presence channel', 40012, 400); } - return this.leaveClient(undefined, data, callback); + return this.leaveClient(undefined, data); } - leaveClient(clientId?: string, data?: unknown, callback?: ErrCallback): void | Promise { - if (!callback) { - if (typeof data === 'function') { - callback = data as ErrCallback; - data = null; - } else { - return Utils.promisify(this, 'leaveClient', [clientId, data]); - } - } - + async leaveClient(clientId?: string, data?: unknown): Promise { const channel = this.channel; if (!channel.connectionManager.activeState()) { - callback?.(channel.connectionManager.getError()); - return; + throw channel.connectionManager.getError(); } Logger.logAction( @@ -237,92 +205,74 @@ class RealtimePresence extends EventEmitter { presence.clientId = clientId; } - switch (channel.state) { - case 'attached': - channel.sendPresence(presence, callback); - break; - case 'attaching': - this.pendingPresence.push({ - presence: presence, - callback: callback, - }); - break; - case 'initialized': - case 'failed': { - /* we're not attached; therefore we let any entered status - * timeout by itself instead of attaching just in order to leave */ - const err = new PartialErrorInfo('Unable to leave presence channel (incompatible state)', 90001); - callback?.(err); - break; + return new Promise((resolve, reject) => { + switch (channel.state) { + case 'attached': + channel.sendPresence(presence, (err) => (err ? reject(err) : resolve())); + break; + case 'attaching': + this.pendingPresence.push({ + presence: presence, + callback: (err) => (err ? reject(err) : resolve()), + }); + break; + case 'initialized': + case 'failed': { + /* we're not attached; therefore we let any entered status + * timeout by itself instead of attaching just in order to leave */ + const err = new PartialErrorInfo('Unable to leave presence channel (incompatible state)', 90001); + reject(err); + break; + } + default: + reject(channel.invalidStateError()); } - default: - callback?.(channel.invalidStateError()); - } + }); } - get( - this: RealtimePresence, - params: RealtimePresenceParams, - callback: StandardCallback - ): void | Promise { - const args = Array.prototype.slice.call(arguments); - if (args.length == 1 && typeof args[0] == 'function') args.unshift(null); - - params = args[0] as RealtimePresenceParams; - callback = args[1] as StandardCallback; + async get(params?: RealtimePresenceParams): Promise { const waitForSync = !params || ('waitForSync' in params ? params.waitForSync : true); - if (!callback) { - return Utils.promisify(this, 'get', args); - } - - function returnMembers(members: PresenceMap) { - callback(null, params ? members.list(params) : members.values()); - } - - /* Special-case the suspended state: can still get (stale) presence set if waitForSync is false */ - if (this.channel.state === 'suspended') { - if (waitForSync) { - callback( - ErrorInfo.fromValues({ - statusCode: 400, - code: 91005, - message: 'Presence state is out of sync due to channel being in the SUSPENDED state', - }) - ); - } else { - returnMembers(this.members); + return new Promise((resolve, reject) => { + function returnMembers(members: PresenceMap) { + resolve(params ? members.list(params) : members.values()); } - return; - } - waitAttached(this.channel, callback, () => { - const members = this.members; - if (waitForSync) { - members.waitSync(function () { - returnMembers(members); - }); - } else { - returnMembers(members); + /* Special-case the suspended state: can still get (stale) presence set if waitForSync is false */ + if (this.channel.state === 'suspended') { + if (waitForSync) { + reject( + ErrorInfo.fromValues({ + statusCode: 400, + code: 91005, + message: 'Presence state is out of sync due to channel being in the SUSPENDED state', + }) + ); + } else { + returnMembers(this.members); + } + return; } + + waitAttached( + this.channel, + (err) => reject(err), + () => { + const members = this.members; + if (waitForSync) { + members.waitSync(function () { + returnMembers(members); + }); + } else { + returnMembers(members); + } + } + ); }); } - history( - params: RealtimeHistoryParams | null, - callback: PaginatedResultCallback - ): void | Promise> { + async history(params: RealtimeHistoryParams | null): Promise> { Logger.logAction(Logger.LOG_MICRO, 'RealtimePresence.history()', 'channel = ' + this.name); - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'history', arguments); - } - } - // We fetch this first so that any module-not-provided error takes priority over other errors const restMixin = this.channel.client.rest.presenceMixin; @@ -331,17 +281,15 @@ class RealtimePresence extends EventEmitter { delete params.untilAttach; params.from_serial = this.channel.properties.attachSerial; } else { - callback( - new ErrorInfo( - 'option untilAttach requires the channel to be attached, was: ' + this.channel.state, - 40000, - 400 - ) + throw new ErrorInfo( + 'option untilAttach requires the channel to be attached, was: ' + this.channel.state, + 40000, + 400 ); } } - return restMixin.history(this, params, callback); + return restMixin.history(this, params); } setPresence(presenceSet: PresenceMessage[], isSync: boolean, syncChannelSerial?: string): void { @@ -514,24 +462,18 @@ class RealtimePresence extends EventEmitter { }); } - subscribe(..._args: unknown[] /* [event], listener, [callback] */): void | Promise { + async subscribe(..._args: unknown[] /* [event], listener */): Promise { const args = RealtimeChannel.processListenerArgs(_args); const event = args[0]; const listener = args[1]; - let callback = args[2]; const channel = this.channel; - if (!callback) { - return Utils.promisify(this, 'subscribe', [event, listener]); - } - if (channel.state === 'failed') { - callback(ErrorInfo.fromValues(channel.invalidStateError())); - return; + throw ErrorInfo.fromValues(channel.invalidStateError()); } this.subscriptions.on(event, listener); - channel.attach(callback); + await channel.attach(); } unsubscribe(..._args: unknown[] /* [event], listener */): void { diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 5340ce09ae..ba445b95d8 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -8,7 +8,6 @@ import ErrorInfo from '../types/errorinfo'; import Stats from '../types/stats'; import HttpMethods from '../../constants/HttpMethods'; import { ChannelOptions } from '../../types/channel'; -import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; import { RequestBody, RequestParams } from '../../types/http'; import * as API from '../../../../ably'; import Resource from './resource'; @@ -35,7 +34,6 @@ type TokenRevocationSuccessResult = API.TokenRevocationSuccessResult; type TokenRevocationFailureResult = API.TokenRevocationFailureResult; type TokenRevocationResult = BatchResult; -const noop = function () {}; export class Rest { private readonly client: BaseClient; readonly channels: Channels; @@ -50,83 +48,62 @@ export class Rest { this.push = new Push(this.client); } - stats( - params: RequestParams, - callback: StandardCallback> - ): Promise> | void { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'stats', [params]) as Promise>; - } - } + async stats(params: RequestParams): Promise> { const headers = Defaults.defaultGetHeaders(this.client.options), format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.client.http.supportsLinkHeaders ? undefined : format; Utils.mixin(headers, this.client.options.headers); - new PaginatedResource(this.client, '/stats', headers, envelope, function (body, headers, unpacked) { - const statsValues = unpacked ? body : JSON.parse(body as string); - for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); - return statsValues; - }).get(params as Record, callback); + return new Promise((resolve, reject) => { + new PaginatedResource(this.client, '/stats', headers, envelope, function (body, headers, unpacked) { + const statsValues = unpacked ? body : JSON.parse(body as string); + for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); + return statsValues; + }).get(params as Record, (err, result) => (err ? reject(err) : resolve(result))); + }); } - time(params?: RequestParams | StandardCallback, callback?: StandardCallback): Promise | void { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'time', [params]) as Promise; - } - } - - const _callback = callback || noop; - + async time(params?: RequestParams): Promise { const headers = Defaults.defaultGetHeaders(this.client.options); if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); const timeUri = (host: string) => { return this.client.baseUri(host) + '/time'; }; - this.client.http.do( - HttpMethods.Get, - timeUri, - headers, - null, - params as RequestParams, - (err, res, headers, unpacked) => { - if (err) { - _callback(err); - return; - } - if (!unpacked) res = JSON.parse(res as string); - const time = (res as number[])[0]; - if (!time) { - _callback(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); - return; + return new Promise((resolve, reject) => { + this.client.http.do( + HttpMethods.Get, + timeUri, + headers, + null, + params as RequestParams, + (err, res, headers, unpacked) => { + if (err) { + reject(err); + return; + } + if (!unpacked) res = JSON.parse(res as string); + const time = (res as number[])[0]; + if (!time) { + reject(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); + return; + } + /* calculate time offset only once for this device by adding to the prototype */ + this.client.serverTimeOffset = time - Utils.now(); + resolve(time); } - /* calculate time offset only once for this device by adding to the prototype */ - this.client.serverTimeOffset = time - Utils.now(); - _callback(null, time); - } - ); + ); + }); } - request( + async request( method: string, path: string, version: number, params: RequestParams, body: unknown, - customHeaders: Record, - callback: StandardCallback> - ): Promise> | void { + customHeaders: Record + ): Promise> { const [encoder, decoder, format] = (() => { if (this.client.options.useBinaryProtocol) { if (!this.client._MsgPack) { @@ -145,12 +122,6 @@ export class Rest { ? Defaults.defaultGetHeaders(this.client.options, { format, protocolVersion: version }) : Defaults.defaultPostHeaders(this.client.options, { format, protocolVersion: version }); - if (callback === undefined) { - return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< - HttpPaginatedResponse - >; - } - if (typeof body !== 'string') { body = encoder(body) ?? null; } @@ -173,31 +144,22 @@ export class Rest { throw new ErrorInfo('Unsupported method ' + _method, 40500, 405); } - if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { - paginatedResource[_method as HttpMethods.Post]( - params, - body as RequestBody, - callback as PaginatedResultCallback - ); - } else { - paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete]( - params, - callback as PaginatedResultCallback - ); - } + return new Promise((resolve, reject) => { + if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { + paginatedResource[_method as HttpMethods.Post](params!, body as RequestBody, (err, result) => + err ? reject(err) : resolve(result) + ); + } else { + paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete](params!, (err, result) => + err ? reject(err) : resolve(result) + ); + } + }); } - batchPublish( + async batchPublish( specOrSpecs: T - ): Promise; - batchPublish( - specOrSpecs: T, - callback?: StandardCallback - ): void | Promise { - if (callback === undefined) { - return Utils.promisify(this, 'batchPublish', [specOrSpecs]); - } - + ): Promise { let requestBodyDTO: BatchPublishSpec[]; let singleSpecMode: boolean; if (Utils.isArray(specOrSpecs)) { @@ -214,34 +176,28 @@ export class Rest { if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); - Resource.post(this.client, '/messages', requestBody, headers, {}, null, (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } + return new Promise((resolve, reject) => { + Resource.post(this.client, '/messages', requestBody, headers, {}, null, (err, body, headers, unpacked) => { + if (err) { + reject(err); + return; + } - const batchResults = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as BatchPublishResult[]; + const batchResults = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as BatchPublishResult[]; - // I don't love the below type assertions for `callback` but not sure how to avoid them - if (singleSpecMode) { - (callback as StandardCallback)(null, batchResults[0]); - } else { - (callback as StandardCallback)(null, batchResults); - } + // I don't love the below type assertions for `resolve` but not sure how to avoid them + if (singleSpecMode) { + (resolve as (result: BatchPublishResult) => void)(batchResults[0]); + } else { + (resolve as (result: BatchPublishResult[]) => void)(batchResults); + } + }); }); } - batchPresence(channels: string[]): Promise; - batchPresence( - channels: string[], - callback?: StandardCallback - ): void | Promise { - if (callback === undefined) { - return Utils.promisify(this, 'batchPresence', [channels]); - } - + async batchPresence(channels: string[]): Promise { const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, headers = Defaults.defaultPostHeaders(this.client.options, { format }); @@ -249,25 +205,27 @@ export class Rest { const channelsParam = channels.join(','); - Resource.get( - this.client, - '/presence', - headers, - { channels: channelsParam }, - null, - (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } + return new Promise((resolve, reject) => { + Resource.get( + this.client, + '/presence', + headers, + { channels: channelsParam }, + null, + (err, body, headers, unpacked) => { + if (err) { + reject(err); + return; + } - const batchResult = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as BatchPresenceResult; + const batchResult = ( + unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) + ) as BatchPresenceResult; - callback(null, batchResult); - } - ); + resolve(batchResult); + } + ); + }); } async revokeTokens( diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 31b2b39fd7..cb201c3925 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -13,7 +13,6 @@ import ErrorInfo from '../types/errorinfo'; import { PaginatedResult } from './paginatedresource'; import Resource, { ResourceCallback } from './resource'; import { ChannelOptions } from '../../types/channel'; -import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; import BaseRest from './baseclient'; import * as API from '../../../../ably'; import Defaults, { normaliseChannelOptions } from '../util/defaults'; @@ -46,46 +45,29 @@ class RestChannel { this.channelOptions = normaliseChannelOptions(this.client._Crypto ?? null, options); } - history( - params: RestHistoryParams | null, - callback: PaginatedResultCallback - ): Promise> | void { + async history(params: RestHistoryParams | null): Promise> { Logger.logAction(Logger.LOG_MICRO, 'RestChannel.history()', 'channel = ' + this.name); - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'history', arguments); - } - } - - this.client.rest.channelMixin.history(this, params, callback); + return new Promise((resolve, reject) => { + this.client.rest.channelMixin.history(this, params, (err, result) => (err ? reject(err) : resolve(result))); + }); } - publish(): void | Promise { - const argCount = arguments.length, - first = arguments[0], - second = arguments[1]; - let callback = arguments[argCount - 1]; + async publish(...args: any[]): Promise { + const first = args[0], + second = args[1]; let messages: Array; let params: any; - if (typeof callback !== 'function') { - return Utils.promisify(this, 'publish', arguments); - } - if (typeof first === 'string' || first === null) { /* (name, data, ...) */ messages = [messageFromValues({ name: first, data: second })]; - params = arguments[2]; + params = args[2]; } else if (Utils.isObject(first)) { messages = [messageFromValues(first)]; - params = arguments[1]; + params = args[1]; } else if (Utils.isArray(first)) { messages = messagesFromValuesArray(first); - params = arguments[1]; + params = args[1]; } else { throw new ErrorInfo( 'The single-argument form of publish() expects a message object or an array of message objects', @@ -94,8 +76,8 @@ class RestChannel { ); } - if (typeof params !== 'object' || !params) { - /* No params supplied (so after-message argument is just the callback or undefined) */ + if (!params) { + /* No params supplied */ params = {}; } @@ -114,31 +96,35 @@ class RestChannel { }); } - encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error) => { - if (err) { - callback(err); - return; - } - - /* RSL1i */ - const size = getMessagesSize(messages), - maxMessageSize = options.maxMessageSize; - if (size > maxMessageSize) { - callback( - new ErrorInfo( - 'Maximum size of messages that can be published at once exceeded ( was ' + - size + - ' bytes; limit is ' + - maxMessageSize + - ' bytes)', - 40009, - 400 - ) + return new Promise((resolve, reject) => { + encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error) => { + if (err) { + reject(err); + return; + } + + /* RSL1i */ + const size = getMessagesSize(messages), + maxMessageSize = options.maxMessageSize; + if (size > maxMessageSize) { + reject( + new ErrorInfo( + 'Maximum size of messages that can be published at once exceeded ( was ' + + size + + ' bytes; limit is ' + + maxMessageSize + + ' bytes)', + 40009, + 400 + ) + ); + return; + } + + this._publish(serializeMessage(messages, client._MsgPack, format), headers, params, (err) => + err ? reject(err) : resolve() ); - return; - } - - this._publish(serializeMessage(messages, client._MsgPack, format), headers, params, callback); + }); }); } @@ -159,8 +145,8 @@ class RestChannel { ); } - status(callback?: StandardCallback): void | Promise { - return this.client.rest.channelMixin.status(this, callback); + async status(): Promise { + return this.client.rest.channelMixin.status(this); } } diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts index 967ef19b0e..609ddf6721 100644 --- a/src/common/lib/client/restchannelmixin.ts +++ b/src/common/lib/client/restchannelmixin.ts @@ -2,7 +2,7 @@ import * as API from '../../../../ably'; import RestChannel from './restchannel'; import RealtimeChannel from './realtimechannel'; import * as Utils from '../util/utils'; -import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; +import { PaginatedResultCallback } from '../../types/utils'; import Message, { fromResponseBody as messageFromResponseBody } from '../types/message'; import Defaults from '../util/defaults'; import PaginatedResource from './paginatedresource'; @@ -15,8 +15,6 @@ export interface RestHistoryParams { limit?: number; } -const noop = function () {}; - export class RestChannelMixin { static basePath(channel: RestChannel | RealtimeChannel) { return '/channels/' + encodeURIComponent(channel.name); @@ -44,17 +42,14 @@ export class RestChannelMixin { }).get(params as Record, callback); } - static status( - channel: RestChannel | RealtimeChannel, - callback?: StandardCallback - ): void | Promise { - if (typeof callback !== 'function') { - return Utils.promisify(this, 'status', [channel]); - } - + static async status(channel: RestChannel | RealtimeChannel): Promise { const format = channel.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; const headers = Defaults.defaultPostHeaders(channel.client.options, { format }); - Resource.get(channel.client, this.basePath(channel), headers, {}, format, callback || noop); + return new Promise((resolve, reject) => { + Resource.get(channel.client, this.basePath(channel), headers, {}, format, (err, result) => + err ? reject(err) : resolve(result!) + ); + }); } } diff --git a/src/common/lib/client/restpresence.ts b/src/common/lib/client/restpresence.ts index 8997f53526..ec745dd5bb 100644 --- a/src/common/lib/client/restpresence.ts +++ b/src/common/lib/client/restpresence.ts @@ -3,7 +3,6 @@ import Logger from '../util/logger'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; import PresenceMessage, { fromResponseBody as presenceMessageFromResponseBody } from '../types/presencemessage'; import { CipherOptions } from '../types/message'; -import { PaginatedResultCallback } from '../../types/utils'; import RestChannel from './restchannel'; import Defaults from '../util/defaults'; @@ -14,17 +13,8 @@ class RestPresence { this.channel = channel; } - get(params: any, callback: PaginatedResultCallback): void | Promise { + async get(params: any): Promise> { Logger.logAction(Logger.LOG_MICRO, 'RestPresence.get()', 'channel = ' + this.channel.name); - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'get', arguments); - } - } const client = this.channel.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = this.channel.client.http.supportsLinkHeaders ? undefined : format, @@ -33,28 +23,27 @@ class RestPresence { Utils.mixin(headers, client.options.headers); const options = this.channel.channelOptions; - new PaginatedResource( - client, - this.channel.client.rest.presenceMixin.basePath(this), - headers, - envelope, - async function (body, headers, unpacked) { - return await presenceMessageFromResponseBody( - body as Record[], - options as CipherOptions, - client._MsgPack, - unpacked ? undefined : format - ); - } - ).get(params, callback); + return new Promise((resolve, reject) => { + new PaginatedResource( + client, + this.channel.client.rest.presenceMixin.basePath(this), + headers, + envelope, + async function (body, headers, unpacked) { + return await presenceMessageFromResponseBody( + body as Record[], + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); + } + ).get(params, (err, result) => (err ? reject(err) : resolve(result))); + }); } - history( - params: any, - callback: PaginatedResultCallback - ): void | Promise> { + async history(params: any): Promise> { Logger.logAction(Logger.LOG_MICRO, 'RestPresence.history()', 'channel = ' + this.channel.name); - return this.channel.client.rest.presenceMixin.history(this, params, callback); + return this.channel.client.rest.presenceMixin.history(this, params); } } diff --git a/src/common/lib/client/restpresencemixin.ts b/src/common/lib/client/restpresencemixin.ts index 296a1ec6ff..e3920a7f34 100644 --- a/src/common/lib/client/restpresencemixin.ts +++ b/src/common/lib/client/restpresencemixin.ts @@ -1,7 +1,6 @@ import RestPresence from './restpresence'; import RealtimePresence from './realtimepresence'; import * as Utils from '../util/utils'; -import { PaginatedResultCallback } from '../../types/utils'; import Defaults from '../util/defaults'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; import PresenceMessage, { fromResponseBody as presenceMessageFromResponseBody } from '../types/presencemessage'; @@ -13,21 +12,10 @@ export class RestPresenceMixin { return RestChannelMixin.basePath(presence.channel) + '/presence'; } - static history( + static async history( presence: RestPresence | RealtimePresence, - params: any, - callback: PaginatedResultCallback - ): void | Promise> { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'history', [presence, params]); - } - } - + params: any + ): Promise> { const client = presence.channel.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = presence.channel.client.http.supportsLinkHeaders ? undefined : format, @@ -36,17 +24,19 @@ export class RestPresenceMixin { Utils.mixin(headers, client.options.headers); const options = presence.channel.channelOptions; - new PaginatedResource(client, this.basePath(presence) + '/history', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return await presenceMessageFromResponseBody( - body as Record[], - options as CipherOptions, - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, callback); + return new Promise((resolve, reject) => { + new PaginatedResource(client, this.basePath(presence) + '/history', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return await presenceMessageFromResponseBody( + body as Record[], + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params, (err, result) => (err ? reject(err) : resolve(result))); + }); } } diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 4fea24cc8e..0d68a8b041 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -444,14 +444,6 @@ export const trim = (String.prototype.trim as unknown) return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); }; -export function promisify(ob: Record, fnName: string, args: IArguments | unknown[]): Promise { - return new Promise(function (resolve, reject) { - ob[fnName](...(args as unknown[]), function (err: Error, res: unknown) { - err ? reject(err) : resolve(res as T); - }); - }); -} - /** * Uses a callback to communicate the result of a `Promise`. The first argument passed to the callback will be either an error (when the promise is rejected) or `null` (when the promise is fulfilled). In the case where the promise is fulfilled, the resulting value will be passed to the callback as a second argument. */ From 1ef2b2bc8d6cd67e7440e31bd013e39bd0f2cca9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 14:45:07 -0300 Subject: [PATCH 290/468] Convert Auth._forceNewToken to use promises --- src/common/lib/client/auth.ts | 76 +++++++++---------- src/common/lib/transport/connectionmanager.ts | 4 +- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 0ca538bed7..4a7ff7e47b 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -251,44 +251,42 @@ class Auth { throw new ErrorInfo('Unable to update auth options with incompatible key', 40102, 401); } - return new Promise((resolve, reject) => { - this._forceNewToken( - tokenParams ?? null, - authOptions ?? null, - (err: ErrorInfo, tokenDetails: API.TokenDetails) => { - if (err) { - if ((this.client as BaseRealtime).connection && err.statusCode === HttpStatusCodes.Forbidden) { - /* Per RSA4d & RSA4d1, if the auth server explicitly repudiates our right to - * stay connecticed by returning a 403, we actively disconnect the connection - * even though we may well still have time left in the old token. */ - (this.client as BaseRealtime).connection.connectionManager.actOnErrorFromAuthorize(err); - } - reject(err); - return; - } - - /* RTC8 - * - When authorize called by an end user and have a realtime connection, - * don't call back till new token has taken effect. - * - Use this.client.connection as a proxy for (this.client instanceof BaseRealtime), - * which doesn't work in node as BaseRealtime isn't part of the vm context for Rest clients */ - if (isRealtime(this.client)) { - this.client.connection.connectionManager.onAuthUpdated( - tokenDetails, - (err: unknown, tokenDetails?: API.TokenDetails) => (err ? reject(err) : resolve(tokenDetails!)) - ); - } else { - resolve(tokenDetails); - } - } - ); - }); + try { + let tokenDetails = await this._forceNewToken(tokenParams ?? null, authOptions ?? null); + + /* RTC8 + * - When authorize called by an end user and have a realtime connection, + * don't call back till new token has taken effect. + * - Use this.client.connection as a proxy for (this.client instanceof BaseRealtime), + * which doesn't work in node as BaseRealtime isn't part of the vm context for Rest clients */ + if (isRealtime(this.client)) { + return new Promise((resolve, reject) => { + (this.client as BaseRealtime).connection.connectionManager.onAuthUpdated( + tokenDetails, + (err: unknown, tokenDetails?: API.TokenDetails) => (err ? reject(err) : resolve(tokenDetails!)) + ); + }); + } else { + return tokenDetails; + } + } catch (err) { + if ((this.client as BaseRealtime).connection && (err as ErrorInfo).statusCode === HttpStatusCodes.Forbidden) { + /* Per RSA4d & RSA4d1, if the auth server explicitly repudiates our right to + * stay connecticed by returning a 403, we actively disconnect the connection + * even though we may well still have time left in the old token. */ + (this.client as BaseRealtime).connection.connectionManager.actOnErrorFromAuthorize(err as ErrorInfo); + } + throw err; + } } /* For internal use, eg by connectionManager - useful when want to call back * as soon as we have the new token, rather than waiting for it to take * effect on the connection as #authorize does */ - _forceNewToken(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null, callback: Function) { + async _forceNewToken( + tokenParams: API.TokenParams | null, + authOptions: API.AuthOptions | null + ): Promise { /* get rid of current token even if still valid */ this.tokenDetails = null; @@ -299,11 +297,13 @@ class Auth { logAndValidateTokenAuthMethod(this.authOptions); - this._ensureValidAuthCredentials(true, (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) => { - /* RSA10g */ - delete this.tokenParams.timestamp; - delete this.authOptions.queryTime; - callback(err, tokenDetails); + return new Promise((resolve, reject) => { + this._ensureValidAuthCredentials(true, (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) => { + /* RSA10g */ + delete this.tokenParams.timestamp; + delete this.authOptions.queryTime; + err ? reject(err) : resolve(tokenDetails!); + }); }); } diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 04c8bc798b..4eafaa58a3 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -533,7 +533,7 @@ class ConnectionManager extends EventEmitter { ) { this.errorReason = wrappedErr.error; /* re-get a token and try again */ - this.realtime.auth._forceNewToken(null, null, (err: ErrorInfo) => { + Utils.whenPromiseSettles(this.realtime.auth._forceNewToken(null, null), (err: ErrorInfo | null) => { if (err) { this.actOnErrorFromAuthorize(err); return; @@ -1504,7 +1504,7 @@ class ConnectionManager extends EventEmitter { }; if (this.errorReason && Auth.isTokenErr(this.errorReason as ErrorInfo)) { /* Force a refetch of a new token */ - auth._forceNewToken(null, null, authCb); + Utils.whenPromiseSettles(auth._forceNewToken(null, null), authCb); } else { auth._ensureValidAuthCredentials(false, authCb); } From ffea1831828306a4e15e1ec0bb89a0cb8360b9ae Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 14:59:30 -0300 Subject: [PATCH 291/468] Convert Auth.getAuthParams to use promises --- src/common/lib/client/auth.ts | 24 +++--- src/common/lib/client/resource.ts | 11 ++- src/common/lib/transport/comettransport.ts | 4 +- .../lib/transport/websockettransport.ts | 85 ++++++++++--------- 4 files changed, 66 insertions(+), 58 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 4a7ff7e47b..aaa29e3c5c 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -765,18 +765,20 @@ class Auth { * Get the auth query params to use for a websocket connection, * based on the current auth parameters */ - getAuthParams(callback: Function) { - if (this.method == 'basic') callback(null, { key: this.key }); + async getAuthParams(): Promise> { + if (this.method == 'basic') return { key: this.key! }; else - this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { - if (err) { - callback(err); - return; - } - if (!tokenDetails) { - throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); - } - callback(null, { access_token: tokenDetails.token }); + return new Promise((resolve, reject) => { + this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { + if (err) { + reject(err); + return; + } + if (!tokenDetails) { + throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); + } + resolve({ access_token: tokenDetails.token }); + }); }); } diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 69de333a3b..9457cf5ec6 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -26,10 +26,13 @@ function withAuthDetails( else opCallback(Utils.mixin(authHeaders, headers), params); }); } else { - client.auth.getAuthParams(function (err: Error, authParams: Record) { - if (err) errCallback(err); - else opCallback(headers, Utils.mixin(authParams, params)); - }); + Utils.whenPromiseSettles( + client.auth.getAuthParams(), + function (err: Error | null, authParams?: Record) { + if (err) errCallback(err); + else opCallback(headers, Utils.mixin(authParams!, params)); + } + ); } } diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index 02420ea393..88e094cd8e 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -84,7 +84,7 @@ abstract class CometTransport extends Transport { this.baseUri = cometScheme + host + ':' + port + '/comet/'; const connectUri = this.baseUri + 'connect'; Logger.logAction(Logger.LOG_MINOR, 'CometTransport.connect()', 'uri: ' + connectUri); - this.auth.getAuthParams((err: Error, authParams: Record) => { + Utils.whenPromiseSettles(this.auth.getAuthParams(), (err: Error | null, authParams?: Record) => { if (err) { this.disconnect(err); return; @@ -93,7 +93,7 @@ abstract class CometTransport extends Transport { return; } this.authParams = authParams; - const connectParams = this.params.getConnectParams(authParams); + const connectParams = this.params.getConnectParams(authParams!); if ('stream' in connectParams) this.stream = connectParams.stream; Logger.logAction( Logger.LOG_MINOR, diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index d56848ec6b..991804491f 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -54,49 +54,52 @@ class WebSocketTransport extends Transport { const wsScheme = options.tls ? 'wss://' : 'ws://'; const wsUri = wsScheme + this.wsHost + ':' + Defaults.getPort(options) + '/'; Logger.logAction(Logger.LOG_MINOR, 'WebSocketTransport.connect()', 'uri: ' + wsUri); - this.auth.getAuthParams(function (err: ErrorInfo, authParams: Record) { - if (self.isDisposed) { - return; - } - let paramStr = ''; - for (const param in authParams) paramStr += ' ' + param + ': ' + authParams[param] + ';'; - Logger.logAction(Logger.LOG_MINOR, 'WebSocketTransport.connect()', 'authParams:' + paramStr + ' err: ' + err); - if (err) { - self.disconnect(err); - return; - } - const connectParams = params.getConnectParams(authParams); - try { - const wsConnection = (self.wsConnection = self.createWebSocket(wsUri, connectParams)); - wsConnection.binaryType = Platform.Config.binaryType; - wsConnection.onopen = function () { - self.onWsOpen(); - }; - wsConnection.onclose = function (ev: CloseEvent) { - self.onWsClose(ev); - }; - wsConnection.onmessage = function (ev: MessageEvent) { - self.onWsData(ev.data); - }; - wsConnection.onerror = function (ev: Event) { - self.onWsError(ev as ErrorEvent); - }; - if (isNodeWebSocket(wsConnection)) { - /* node; browsers currently don't have a general eventemitter and can't detect - * pings. Also, no need to reply with a pong explicitly, ws lib handles that */ - wsConnection.on('ping', function () { - self.onActivity(); - }); + Utils.whenPromiseSettles( + this.auth.getAuthParams(), + function (err: ErrorInfo | null, authParams?: Record) { + if (self.isDisposed) { + return; + } + let paramStr = ''; + for (const param in authParams) paramStr += ' ' + param + ': ' + authParams[param] + ';'; + Logger.logAction(Logger.LOG_MINOR, 'WebSocketTransport.connect()', 'authParams:' + paramStr + ' err: ' + err); + if (err) { + self.disconnect(err); + return; + } + const connectParams = params.getConnectParams(authParams!); + try { + const wsConnection = (self.wsConnection = self.createWebSocket(wsUri, connectParams)); + wsConnection.binaryType = Platform.Config.binaryType; + wsConnection.onopen = function () { + self.onWsOpen(); + }; + wsConnection.onclose = function (ev: CloseEvent) { + self.onWsClose(ev); + }; + wsConnection.onmessage = function (ev: MessageEvent) { + self.onWsData(ev.data); + }; + wsConnection.onerror = function (ev: Event) { + self.onWsError(ev as ErrorEvent); + }; + if (isNodeWebSocket(wsConnection)) { + /* node; browsers currently don't have a general eventemitter and can't detect + * pings. Also, no need to reply with a pong explicitly, ws lib handles that */ + wsConnection.on('ping', function () { + self.onActivity(); + }); + } + } catch (e) { + Logger.logAction( + Logger.LOG_ERROR, + 'WebSocketTransport.connect()', + 'Unexpected exception creating websocket: err = ' + ((e as Error).stack || (e as Error).message) + ); + self.disconnect(e as Error); } - } catch (e) { - Logger.logAction( - Logger.LOG_ERROR, - 'WebSocketTransport.connect()', - 'Unexpected exception creating websocket: err = ' + ((e as Error).stack || (e as Error).message) - ); - self.disconnect(e as Error); } - }); + ); } send(message: ProtocolMessage) { From c2f02a0ebb6b9d01bb1facfaf29d7c287a5637bf Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 15:00:53 -0300 Subject: [PATCH 292/468] Convert Auth.getAuthHeaders to use promises --- src/common/lib/client/auth.ts | 24 +++++++++++++----------- src/common/lib/client/resource.ts | 11 +++++++---- test/rest/auth.test.js | 20 ++------------------ 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index aaa29e3c5c..1519ade7d3 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -786,19 +786,21 @@ class Auth { * Get the authorization header to use for a REST or comet request, * based on the current auth parameters */ - getAuthHeaders(callback: Function) { + async getAuthHeaders(): Promise> { if (this.method == 'basic') { - callback(null, { authorization: 'Basic ' + this.basicKey }); + return { authorization: 'Basic ' + this.basicKey }; } else { - this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { - if (err) { - callback(err); - return; - } - if (!tokenDetails) { - throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); - } - callback(null, { authorization: 'Bearer ' + Utils.toBase64(tokenDetails.token) }); + return new Promise((resolve, reject) => { + this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { + if (err) { + reject(err); + return; + } + if (!tokenDetails) { + throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); + } + resolve({ authorization: 'Bearer ' + Utils.toBase64(tokenDetails.token) }); + }); }); } } diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 9457cf5ec6..f3c808ebc8 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -21,10 +21,13 @@ function withAuthDetails( opCallback: Function ) { if (client.http.supportsAuthHeaders) { - client.auth.getAuthHeaders(function (err: Error, authHeaders: Record) { - if (err) errCallback(err); - else opCallback(Utils.mixin(authHeaders, headers), params); - }); + Utils.whenPromiseSettles( + client.auth.getAuthHeaders(), + function (err: Error | null, authHeaders?: Record) { + if (err) errCallback(err); + else opCallback(Utils.mixin(authHeaders!, headers), params); + } + ); } else { Utils.whenPromiseSettles( client.auth.getAuthParams(), diff --git a/test/rest/auth.test.js b/test/rest/auth.test.js index c1aed009bd..0824bcca8d 100644 --- a/test/rest/auth.test.js +++ b/test/rest/auth.test.js @@ -130,15 +130,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); it('Token generation with explicit auth', async function () { - var authHeaders = await new Promise((resolve, reject) => { - rest.auth.getAuthHeaders(function (err, authHeaders) { - if (err) { - reject(err); - return; - } - resolve(authHeaders); - }); - }); + const authHeaders = await rest.auth.getAuthHeaders(); rest.auth.authOptions.requestHeaders = authHeaders; var tokenDetails = await rest.auth.requestToken(); delete rest.auth.authOptions.requestHeaders; @@ -149,15 +141,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); it('Token generation with explicit auth, different key', async function () { - var authHeaders = await new Promise((resolve, reject) => { - rest.auth.getAuthHeaders(function (err, authHeaders) { - if (err) { - reject(err); - return; - } - resolve(authHeaders); - }); - }); + const authHeaders = await rest.auth.getAuthHeaders(); var testKeyOpts = { key: helper.getTestApp().keys[1].keyStr }; var testCapability = JSON.parse(helper.getTestApp().keys[1].capability); var tokenDetails = await rest.auth.requestToken(null, testKeyOpts); From 9dd15155961f62d778f35e6111792c3f95cdf7c2 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 15:07:59 -0300 Subject: [PATCH 293/468] Convert Auth.getTimestamp to use promises --- src/common/lib/client/auth.ts | 68 +++++++++++++++-------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 1519ade7d3..726eee8690 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -722,43 +722,31 @@ class Auth { ttl = tokenParams.ttl || '', capability = tokenParams.capability || ''; - return new Promise((resolve, reject) => { - ((authoriseCb) => { - if (request.timestamp) { - authoriseCb(); - return; - } - this.getTimestamp(authOptions && authOptions.queryTime, function (err?: ErrorInfo | null, time?: number) { - if (err) { - reject(err); - return; - } - request.timestamp = time; - authoriseCb(); - }); - })(function () { - /* nonce */ - /* NOTE: there is no expectation that the client - * specifies the nonce; this is done by the library - * However, this can be overridden by the client - * simply for testing purposes. */ - const nonce = request.nonce || (request.nonce = random()), - timestamp = request.timestamp; - - const signText = - request.keyName + '\n' + ttl + '\n' + capability + '\n' + clientId + '\n' + timestamp + '\n' + nonce + '\n'; - - /* mac */ - /* NOTE: there is no expectation that the client - * specifies the mac; this is done by the library - * However, this can be overridden by the client - * simply for testing purposes. */ - request.mac = request.mac || hmac(signText, keySecret); - - Logger.logAction(Logger.LOG_MINOR, 'Auth.getTokenRequest()', 'generated signed request'); - resolve(request as API.TokenRequest); - }); - }); + if (!request.timestamp) { + request.timestamp = await this.getTimestamp(authOptions && authOptions.queryTime); + } + + /* nonce */ + /* NOTE: there is no expectation that the client + * specifies the nonce; this is done by the library + * However, this can be overridden by the client + * simply for testing purposes. */ + const nonce = request.nonce || (request.nonce = random()), + timestamp = request.timestamp; + + const signText = + request.keyName + '\n' + ttl + '\n' + capability + '\n' + clientId + '\n' + timestamp + '\n' + nonce + '\n'; + + /* mac */ + /* NOTE: there is no expectation that the client + * specifies the mac; this is done by the library + * However, this can be overridden by the client + * simply for testing purposes. */ + request.mac = request.mac || hmac(signText, keySecret); + + Logger.logAction(Logger.LOG_MINOR, 'Auth.getTokenRequest()', 'generated signed request'); + + return request as API.TokenRequest; } /** @@ -811,11 +799,11 @@ class Auth { * The server time offset from the local time is stored so that * only one request to the server to get the time is ever needed */ - getTimestamp(queryTime: boolean, callback: StandardCallback): void { + async getTimestamp(queryTime: boolean): Promise { if (!this.isTimeOffsetSet() && (queryTime || this.authOptions.queryTime)) { - Utils.whenPromiseSettles(this.client.time(), callback); + return this.client.time(); } else { - callback(null, this.getTimestampUsingOffset()); + return this.getTimestampUsingOffset(); } } From 73b30af292b05fb40409bcc070a72f7b1a093055 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 15:28:53 -0300 Subject: [PATCH 294/468] Convert Auth._ensureValidAuthCredentials to use promises Resolves #1527. --- src/common/lib/client/auth.ts | 127 ++++++++---------- src/common/lib/transport/connectionmanager.ts | 2 +- 2 files changed, 58 insertions(+), 71 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 726eee8690..7cd89f0acb 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -21,7 +21,6 @@ type TokenRevocationFailureResult = API.TokenRevocationFailureResult; type TokenRevocationResult = BatchResult; const MAX_TOKEN_LENGTH = Math.pow(2, 17); -function noop() {} function random() { return ('000000' + Math.floor(Math.random() * 1e16)).slice(-16); } @@ -297,14 +296,13 @@ class Auth { logAndValidateTokenAuthMethod(this.authOptions); - return new Promise((resolve, reject) => { - this._ensureValidAuthCredentials(true, (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) => { - /* RSA10g */ - delete this.tokenParams.timestamp; - delete this.authOptions.queryTime; - err ? reject(err) : resolve(tokenDetails!); - }); - }); + try { + return this._ensureValidAuthCredentials(true); + } finally { + /* RSA10g */ + delete this.tokenParams.timestamp; + delete this.authOptions.queryTime; + } } /** @@ -755,19 +753,13 @@ class Auth { */ async getAuthParams(): Promise> { if (this.method == 'basic') return { key: this.key! }; - else - return new Promise((resolve, reject) => { - this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { - if (err) { - reject(err); - return; - } - if (!tokenDetails) { - throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); - } - resolve({ access_token: tokenDetails.token }); - }); - }); + else { + let tokenDetails = await this._ensureValidAuthCredentials(false); + if (!tokenDetails) { + throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); + } + return { access_token: tokenDetails.token }; + } } /** @@ -778,18 +770,11 @@ class Auth { if (this.method == 'basic') { return { authorization: 'Basic ' + this.basicKey }; } else { - return new Promise((resolve, reject) => { - this._ensureValidAuthCredentials(false, function (err: ErrorInfo | null, tokenDetails?: API.TokenDetails) { - if (err) { - reject(err); - return; - } - if (!tokenDetails) { - throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); - } - resolve({ authorization: 'Bearer ' + Utils.toBase64(tokenDetails.token) }); - }); - }); + const tokenDetails = await this._ensureValidAuthCredentials(false); + if (!tokenDetails) { + throw new Error('Auth.getAuthParams(): _ensureValidAuthCredentials returned no error or tokenDetails'); + } + return { authorization: 'Bearer ' + Utils.toBase64(tokenDetails.token) }; } } @@ -859,65 +844,67 @@ class Auth { /* @param forceSupersede: force a new token request even if there's one in * progress, making all pending callbacks wait for the new one */ - _ensureValidAuthCredentials( - forceSupersede: boolean, - callback: (err: ErrorInfo | null, token?: API.TokenDetails) => void - ) { + async _ensureValidAuthCredentials(forceSupersede: boolean): Promise { const token = this.tokenDetails; if (token) { if (this._tokenClientIdMismatch(token.clientId)) { /* 403 to trigger a permanently failed client - RSA15c */ - callback( - new ErrorInfo( - 'Mismatch between clientId in token (' + token.clientId + ') and current clientId (' + this.clientId + ')', - 40102, - 403 - ) + throw new ErrorInfo( + 'Mismatch between clientId in token (' + token.clientId + ') and current clientId (' + this.clientId + ')', + 40102, + 403 ); - return; } /* RSA4b1 -- if we have a server time offset set already, we can * automatically remove expired tokens. Else just use the cached token. If it is * expired Ably will tell us and we'll discard it then. */ if (!this.isTimeOffsetSet() || !token.expires || token.expires >= this.getTimestampUsingOffset()) { Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'using cached token; expires = ' + token.expires); - callback(null, token); - return; + return token; } /* expired, so remove and fallthrough to getting a new one */ Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'deleting expired token'); this.tokenDetails = null; } - (this.waitingForTokenRequest || (this.waitingForTokenRequest = Multicaster.create())).push(callback); + const promise = ( + this.waitingForTokenRequest || (this.waitingForTokenRequest = Multicaster.create()) + ).createPromise(); if (this.currentTokenRequestId !== null && !forceSupersede) { - return; + return promise; } /* Request a new token */ const tokenRequestId = (this.currentTokenRequestId = getTokenRequestId()); - Utils.whenPromiseSettles( - this.requestToken(this.tokenParams, this.authOptions), - (err: ErrorInfo | null, tokenResponse?: API.TokenDetails) => { - if ((this.currentTokenRequestId as number) > tokenRequestId) { - Logger.logAction( - Logger.LOG_MINOR, - 'Auth._ensureValidAuthCredentials()', - 'Discarding token request response; overtaken by newer one' - ); - return; - } - this.currentTokenRequestId = null; - const callbacks = this.waitingForTokenRequest || noop; - this.waitingForTokenRequest = null; - if (err) { - callbacks(err); - return; - } - callbacks(null, (this.tokenDetails = tokenResponse)); - } - ); + + let tokenResponse: API.TokenDetails, + caughtError: ErrorInfo | null = null; + try { + tokenResponse = await this.requestToken(this.tokenParams, this.authOptions); + } catch (err) { + caughtError = err as ErrorInfo; + } + + if ((this.currentTokenRequestId as number) > tokenRequestId) { + Logger.logAction( + Logger.LOG_MINOR, + 'Auth._ensureValidAuthCredentials()', + 'Discarding token request response; overtaken by newer one' + ); + return promise; + } + + this.currentTokenRequestId = null; + const multicaster = this.waitingForTokenRequest; + this.waitingForTokenRequest = null; + if (caughtError) { + multicaster?.rejectAll(caughtError); + return promise; + } + multicaster?.resolveAll((this.tokenDetails = tokenResponse!)); + + return promise; } /* User-set: check types, '*' is disallowed, throw any errors */ diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 4eafaa58a3..1e34a0852c 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1506,7 +1506,7 @@ class ConnectionManager extends EventEmitter { /* Force a refetch of a new token */ Utils.whenPromiseSettles(auth._forceNewToken(null, null), authCb); } else { - auth._ensureValidAuthCredentials(false, authCb); + Utils.whenPromiseSettles(auth._ensureValidAuthCredentials(false), authCb); } } } From 5b3338186a6a27ff0107fd40f459a35dbd7e1a0b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 15:53:09 -0300 Subject: [PATCH 295/468] Convert PaginatedResource.handlePage to use promises --- src/common/lib/client/paginatedresource.ts | 62 ++++++++++------------ 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 4ff93711cf..f009cd2dab 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -29,7 +29,7 @@ function parseRelLinks(linkHeader: string | Array) { function returnErrOnly(err: IPartialErrorInfo, body: unknown, useHPR?: boolean) { /* If using httpPaginatedResponse, errors from Ably are returned as part of - * the HPR, only do callback(err) for network errors etc. which don't + * the HPR, only throw `err` for network errors etc. which don't * return a body and/or have no ably-originated error code (non-numeric * error codes originate from node) */ return !(useHPR && (body || typeof err.code === 'number')); @@ -67,7 +67,7 @@ class PaginatedResource { params, this.envelope, (err, body, headers, unpacked, statusCode) => { - this.handlePage(err, body, headers, unpacked, statusCode, callback); + Utils.whenPromiseSettles(this.handlePage(err, body, headers, unpacked, statusCode), callback); } ); } @@ -80,7 +80,7 @@ class PaginatedResource { params, this.envelope, (err, body, headers, unpacked, statusCode) => { - this.handlePage(err, body, headers, unpacked, statusCode, callback); + Utils.whenPromiseSettles(this.handlePage(err, body, headers, unpacked, statusCode), callback); } ); } @@ -95,7 +95,7 @@ class PaginatedResource { this.envelope, (err, responseBody, headers, unpacked, statusCode) => { if (callback) { - this.handlePage(err, responseBody, headers, unpacked, statusCode, callback); + Utils.whenPromiseSettles(this.handlePage(err, responseBody, headers, unpacked, statusCode), callback); } } ); @@ -111,7 +111,7 @@ class PaginatedResource { this.envelope, (err, responseBody, headers, unpacked, statusCode) => { if (callback) { - this.handlePage(err, responseBody, headers, unpacked, statusCode, callback); + Utils.whenPromiseSettles(this.handlePage(err, responseBody, headers, unpacked, statusCode), callback); } } ); @@ -127,55 +127,47 @@ class PaginatedResource { this.envelope, (err, responseBody, headers, unpacked, statusCode) => { if (callback) { - this.handlePage(err, responseBody, headers, unpacked, statusCode, callback); + Utils.whenPromiseSettles(this.handlePage(err, responseBody, headers, unpacked, statusCode), callback); } } ); } - handlePage( + async handlePage( err: IPartialErrorInfo | null, body: unknown, headers: RequestCallbackHeaders | undefined, unpacked: boolean | undefined, - statusCode: number | undefined, - callback: PaginatedResultCallback - ): void { + statusCode: number | undefined + ): Promise> { if (err && returnErrOnly(err, body, this.useHttpPaginatedResponse)) { Logger.logAction( Logger.LOG_ERROR, 'PaginatedResource.handlePage()', 'Unexpected error getting resource: err = ' + Utils.inspectError(err) ); - callback?.(err); - return; + throw err; } - const handleBody = async () => { - let items, linkHeader, relParams; + let items, linkHeader, relParams; - try { - items = await this.bodyHandler(body, headers || {}, unpacked); - } catch (e) { - /* If we got an error, the failure to parse the body is almost certainly - * due to that, so throw that in preference over the parse error */ - throw err || e; - } - - if (headers && (linkHeader = headers['Link'] || headers['link'])) { - relParams = parseRelLinks(linkHeader); - } + try { + items = await this.bodyHandler(body, headers || {}, unpacked); + } catch (e) { + /* If we got an error, the failure to parse the body is almost certainly + * due to that, so throw that in preference over the parse error */ + throw err || e; + } - if (this.useHttpPaginatedResponse) { - return new HttpPaginatedResponse(this, items, headers || {}, statusCode as number, relParams, err); - } else { - return new PaginatedResult(this, items, relParams); - } - }; + if (headers && (linkHeader = headers['Link'] || headers['link'])) { + relParams = parseRelLinks(linkHeader); + } - handleBody() - .then((result) => callback(null, result)) - .catch((err) => callback(err, null)); + if (this.useHttpPaginatedResponse) { + return new HttpPaginatedResponse(this, items, headers || {}, statusCode as number, relParams, err); + } else { + return new PaginatedResult(this, items, relParams); + } } } @@ -238,7 +230,7 @@ export class PaginatedResult { params, res.envelope, function (err, body, headers, unpacked, statusCode) { - res.handlePage(err, body, headers, unpacked, statusCode, callback); + Utils.whenPromiseSettles(res.handlePage(err, body, headers, unpacked, statusCode), callback); } ); } From 0d2d90c92e85bdb2ea2e193db97fa80c8ee55e3d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 15:57:51 -0300 Subject: [PATCH 296/468] Convert PaginatedResult.get to use promises --- src/common/lib/client/paginatedresource.ts | 36 ++++++++++------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index f009cd2dab..34d4686d6e 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -188,23 +188,17 @@ export class PaginatedResult { if (relParams) { if ('first' in relParams) { this.first = async function () { - return new Promise((resolve, reject) => { - self.get(relParams.first, (err, result) => (err ? reject(err) : resolve(result))); - }); + return self.get(relParams.first); }; } if ('current' in relParams) { this.current = async function () { - return new Promise((resolve, reject) => { - self.get(relParams.current, (err, result) => (err ? reject(err) : resolve(result))); - }); + return self.get(relParams.current); }; } this.next = async function () { if ('next' in relParams) { - return new Promise((resolve, reject) => { - self.get(relParams.next, (err, result) => (err ? reject(err) : resolve(result))); - }); + return self.get(relParams.next); } else { return null; } @@ -221,18 +215,20 @@ export class PaginatedResult { /* We assume that only the initial request can be a POST, and that accessing * the rest of a multipage set of results can always be done with GET */ - get(params: any, callback: PaginatedResultCallback): void { + async get(params: any): Promise> { const res = this.resource; - Resource.get( - res.client, - res.path, - res.headers, - params, - res.envelope, - function (err, body, headers, unpacked, statusCode) { - Utils.whenPromiseSettles(res.handlePage(err, body, headers, unpacked, statusCode), callback); - } - ); + return new Promise((resolve) => { + Resource.get( + res.client, + res.path, + res.headers, + params, + res.envelope, + function (err, body, headers, unpacked, statusCode) { + resolve(res.handlePage(err, body, headers, unpacked, statusCode)); + } + ); + }); } } From 96655afe58690328886623aca7f369838b9cc247 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 16:14:43 -0300 Subject: [PATCH 297/468] Convert PaginatedResource request methods to use promises --- src/common/lib/client/paginatedresource.ts | 127 +++++++++++---------- src/common/lib/client/push.ts | 68 +++++------ src/common/lib/client/rest.ts | 32 +++--- src/common/lib/client/restchannelmixin.ts | 22 ++-- src/common/lib/client/restpresence.ts | 30 +++-- src/common/lib/client/restpresencemixin.ts | 26 ++--- 6 files changed, 151 insertions(+), 154 deletions(-) diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 34d4686d6e..2be9eb1325 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -2,7 +2,6 @@ import * as Utils from '../util/utils'; import Logger from '../util/logger'; import Resource from './resource'; import { IPartialErrorInfo } from '../types/errorinfo'; -import { PaginatedResultCallback } from '../../types/utils'; import BaseClient from './baseclient'; import { RequestBody, RequestCallbackHeaders } from 'common/types/http'; @@ -59,78 +58,82 @@ class PaginatedResource { this.useHttpPaginatedResponse = useHttpPaginatedResponse || false; } - get(params: Record, callback: PaginatedResultCallback): void { - Resource.get( - this.client, - this.path, - this.headers, - params, - this.envelope, - (err, body, headers, unpacked, statusCode) => { - Utils.whenPromiseSettles(this.handlePage(err, body, headers, unpacked, statusCode), callback); - } - ); + async get(params: Record): Promise> { + return new Promise((resolve) => { + Resource.get( + this.client, + this.path, + this.headers, + params, + this.envelope, + (err, body, headers, unpacked, statusCode) => { + resolve(this.handlePage(err, body, headers, unpacked, statusCode)); + } + ); + }); } - delete(params: Record, callback: PaginatedResultCallback): void { - Resource.delete( - this.client, - this.path, - this.headers, - params, - this.envelope, - (err, body, headers, unpacked, statusCode) => { - Utils.whenPromiseSettles(this.handlePage(err, body, headers, unpacked, statusCode), callback); - } - ); + async delete(params: Record): Promise> { + return new Promise((resolve) => { + Resource.delete( + this.client, + this.path, + this.headers, + params, + this.envelope, + (err, body, headers, unpacked, statusCode) => { + resolve(this.handlePage(err, body, headers, unpacked, statusCode)); + } + ); + }); } - post(params: Record, body: RequestBody | null, callback: PaginatedResultCallback): void { - Resource.post( - this.client, - this.path, - body, - this.headers, - params, - this.envelope, - (err, responseBody, headers, unpacked, statusCode) => { - if (callback) { - Utils.whenPromiseSettles(this.handlePage(err, responseBody, headers, unpacked, statusCode), callback); + async post(params: Record, body: RequestBody | null): Promise> { + return new Promise((resolve) => { + Resource.post( + this.client, + this.path, + body, + this.headers, + params, + this.envelope, + (err, responseBody, headers, unpacked, statusCode) => { + resolve(this.handlePage(err, responseBody, headers, unpacked, statusCode)); } - } - ); + ); + }); } - put(params: Record, body: RequestBody | null, callback: PaginatedResultCallback): void { - Resource.put( - this.client, - this.path, - body, - this.headers, - params, - this.envelope, - (err, responseBody, headers, unpacked, statusCode) => { - if (callback) { - Utils.whenPromiseSettles(this.handlePage(err, responseBody, headers, unpacked, statusCode), callback); + async put(params: Record, body: RequestBody | null): Promise> { + return new Promise((resolve) => { + Resource.put( + this.client, + this.path, + body, + this.headers, + params, + this.envelope, + (err, responseBody, headers, unpacked, statusCode) => { + resolve(this.handlePage(err, responseBody, headers, unpacked, statusCode)); } - } - ); + ); + }); } - patch(params: Record, body: RequestBody | null, callback: PaginatedResultCallback): void { - Resource.patch( - this.client, - this.path, - body, - this.headers, - params, - this.envelope, - (err, responseBody, headers, unpacked, statusCode) => { - if (callback) { - Utils.whenPromiseSettles(this.handlePage(err, responseBody, headers, unpacked, statusCode), callback); + async patch(params: Record, body: RequestBody | null): Promise> { + return new Promise((resolve) => { + Resource.patch( + this.client, + this.path, + body, + this.headers, + params, + this.envelope, + (err, responseBody, headers, unpacked, statusCode) => { + resolve(this.handlePage(err, responseBody, headers, unpacked, statusCode)); } - } - ); + ); + }); } async handlePage( diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index 3c1b10edfc..e6ab5b3083 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -136,19 +136,17 @@ class DeviceRegistrations { Utils.mixin(headers, client.options.headers); - return new Promise((resolve, reject) => { - new PaginatedResource(client, '/push/deviceRegistrations', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return DeviceDetails.fromResponseBody( - body as Record[], - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, (err, result) => (err ? reject(err) : resolve(result))); - }); + return new PaginatedResource(client, '/push/deviceRegistrations', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return DeviceDetails.fromResponseBody( + body as Record[], + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params); } async remove(deviceIdOrDetails: any): Promise { @@ -249,19 +247,17 @@ class ChannelSubscriptions { Utils.mixin(headers, client.options.headers); - return new Promise((resolve, reject) => { - new PaginatedResource(client, '/push/channelSubscriptions', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return PushChannelSubscription.fromResponseBody( - body as Record[], - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, (err, result) => (err ? reject(err) : resolve(result))); - }); + return new PaginatedResource(client, '/push/channelSubscriptions', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return PushChannelSubscription.fromResponseBody( + body as Record[], + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params); } async removeWhere(params: any): Promise { @@ -293,18 +289,16 @@ class ChannelSubscriptions { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - return new Promise((resolve, reject) => { - new PaginatedResource(client, '/push/channels', headers, envelope, async function (body, headers, unpacked) { - const parsedBody = ( - !unpacked && format ? Utils.decodeBody(body, client._MsgPack, format) : body - ) as Array; + return new PaginatedResource(client, '/push/channels', headers, envelope, async function (body, headers, unpacked) { + const parsedBody = ( + !unpacked && format ? Utils.decodeBody(body, client._MsgPack, format) : body + ) as Array; - for (let i = 0; i < parsedBody.length; i++) { - parsedBody[i] = String(parsedBody[i]); - } - return parsedBody; - }).get(params, (err, result) => (err ? reject(err) : resolve(result))); - }); + for (let i = 0; i < parsedBody.length; i++) { + parsedBody[i] = String(parsedBody[i]); + } + return parsedBody; + }).get(params); } } diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index ba445b95d8..b9d7a9d6ce 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -55,13 +55,11 @@ export class Rest { Utils.mixin(headers, this.client.options.headers); - return new Promise((resolve, reject) => { - new PaginatedResource(this.client, '/stats', headers, envelope, function (body, headers, unpacked) { - const statsValues = unpacked ? body : JSON.parse(body as string); - for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); - return statsValues; - }).get(params as Record, (err, result) => (err ? reject(err) : resolve(result))); - }); + return new PaginatedResource(this.client, '/stats', headers, envelope, function (body, headers, unpacked) { + const statsValues = unpacked ? body : JSON.parse(body as string); + for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); + return statsValues; + }).get(params as Record); } async time(params?: RequestParams): Promise { @@ -144,17 +142,15 @@ export class Rest { throw new ErrorInfo('Unsupported method ' + _method, 40500, 405); } - return new Promise((resolve, reject) => { - if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { - paginatedResource[_method as HttpMethods.Post](params!, body as RequestBody, (err, result) => - err ? reject(err) : resolve(result) - ); - } else { - paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete](params!, (err, result) => - err ? reject(err) : resolve(result) - ); - } - }); + if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { + return paginatedResource[_method as HttpMethods.Post](params, body as RequestBody) as Promise< + HttpPaginatedResponse + >; + } else { + return paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete](params) as Promise< + HttpPaginatedResponse + >; + } } async batchPublish( diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts index 609ddf6721..24469980e3 100644 --- a/src/common/lib/client/restchannelmixin.ts +++ b/src/common/lib/client/restchannelmixin.ts @@ -33,13 +33,21 @@ export class RestChannelMixin { Utils.mixin(headers, client.options.headers); const options = channel.channelOptions; - new PaginatedResource(client, this.basePath(channel) + '/messages', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return await messageFromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); - }).get(params as Record, callback); + Utils.whenPromiseSettles( + new PaginatedResource(client, this.basePath(channel) + '/messages', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return await messageFromResponseBody( + body as Message[], + options, + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params as Record), + callback + ); } static async status(channel: RestChannel | RealtimeChannel): Promise { diff --git a/src/common/lib/client/restpresence.ts b/src/common/lib/client/restpresence.ts index ec745dd5bb..0bbdc79f82 100644 --- a/src/common/lib/client/restpresence.ts +++ b/src/common/lib/client/restpresence.ts @@ -23,22 +23,20 @@ class RestPresence { Utils.mixin(headers, client.options.headers); const options = this.channel.channelOptions; - return new Promise((resolve, reject) => { - new PaginatedResource( - client, - this.channel.client.rest.presenceMixin.basePath(this), - headers, - envelope, - async function (body, headers, unpacked) { - return await presenceMessageFromResponseBody( - body as Record[], - options as CipherOptions, - client._MsgPack, - unpacked ? undefined : format - ); - } - ).get(params, (err, result) => (err ? reject(err) : resolve(result))); - }); + return new PaginatedResource( + client, + this.channel.client.rest.presenceMixin.basePath(this), + headers, + envelope, + async function (body, headers, unpacked) { + return await presenceMessageFromResponseBody( + body as Record[], + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); + } + ).get(params); } async history(params: any): Promise> { diff --git a/src/common/lib/client/restpresencemixin.ts b/src/common/lib/client/restpresencemixin.ts index e3920a7f34..0a1aacea0f 100644 --- a/src/common/lib/client/restpresencemixin.ts +++ b/src/common/lib/client/restpresencemixin.ts @@ -24,19 +24,17 @@ export class RestPresenceMixin { Utils.mixin(headers, client.options.headers); const options = presence.channel.channelOptions; - return new Promise((resolve, reject) => { - new PaginatedResource(client, this.basePath(presence) + '/history', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return await presenceMessageFromResponseBody( - body as Record[], - options as CipherOptions, - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params, (err, result) => (err ? reject(err) : resolve(result))); - }); + return new PaginatedResource(client, this.basePath(presence) + '/history', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return await presenceMessageFromResponseBody( + body as Record[], + options as CipherOptions, + client._MsgPack, + unpacked ? undefined : format + ); + }).get(params); } } From cd0a6c47c3c47760b1f496d02811bfa9a106ff65 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 8 Dec 2023 16:30:35 -0300 Subject: [PATCH 298/468] Convert RestChannelMixin.history to use promises --- src/common/lib/client/realtimechannel.ts | 4 +-- src/common/lib/client/restchannel.ts | 4 +-- src/common/lib/client/restchannelmixin.ts | 30 ++++++++--------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 0f5838d5f6..ea8e615a3d 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -878,9 +878,7 @@ class RealtimeChannel extends EventEmitter { params.from_serial = this.properties.attachSerial; } - return new Promise((resolve, reject) => { - restMixin.history(this, params, (err, result) => (err ? reject(err) : resolve(result))); - }); + return restMixin.history(this, params); } as any; whenState = ((state: string, listener: ErrCallback) => { diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index cb201c3925..9dc0e12f49 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -47,9 +47,7 @@ class RestChannel { async history(params: RestHistoryParams | null): Promise> { Logger.logAction(Logger.LOG_MICRO, 'RestChannel.history()', 'channel = ' + this.name); - return new Promise((resolve, reject) => { - this.client.rest.channelMixin.history(this, params, (err, result) => (err ? reject(err) : resolve(result))); - }); + return this.client.rest.channelMixin.history(this, params); } async publish(...args: any[]): Promise { diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts index 24469980e3..fa10386a8d 100644 --- a/src/common/lib/client/restchannelmixin.ts +++ b/src/common/lib/client/restchannelmixin.ts @@ -2,10 +2,9 @@ import * as API from '../../../../ably'; import RestChannel from './restchannel'; import RealtimeChannel from './realtimechannel'; import * as Utils from '../util/utils'; -import { PaginatedResultCallback } from '../../types/utils'; import Message, { fromResponseBody as messageFromResponseBody } from '../types/message'; import Defaults from '../util/defaults'; -import PaginatedResource from './paginatedresource'; +import PaginatedResource, { PaginatedResult } from './paginatedresource'; import Resource from './resource'; export interface RestHistoryParams { @@ -22,9 +21,8 @@ export class RestChannelMixin { static history( channel: RestChannel | RealtimeChannel, - params: RestHistoryParams | null, - callback: PaginatedResultCallback - ): void { + params: RestHistoryParams | null + ): Promise> { const client = channel.client, format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, envelope = channel.client.http.supportsLinkHeaders ? undefined : format, @@ -33,21 +31,13 @@ export class RestChannelMixin { Utils.mixin(headers, client.options.headers); const options = channel.channelOptions; - Utils.whenPromiseSettles( - new PaginatedResource(client, this.basePath(channel) + '/messages', headers, envelope, async function ( - body, - headers, - unpacked - ) { - return await messageFromResponseBody( - body as Message[], - options, - client._MsgPack, - unpacked ? undefined : format - ); - }).get(params as Record), - callback - ); + return new PaginatedResource(client, this.basePath(channel) + '/messages', headers, envelope, async function ( + body, + headers, + unpacked + ) { + return await messageFromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); + }).get(params as Record); } static async status(channel: RestChannel | RealtimeChannel): Promise { From 9093f0d33f595645288d0af598e30dc3d77d11c9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 14:43:57 -0300 Subject: [PATCH 299/468] Change RestChannel._publish to only pass error to callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That’s all that the `publish` method looks at anyway, and will aid us in converting `_publish` to use promises. --- src/common/lib/client/restchannel.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 9dc0e12f49..c926cb9568 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -11,8 +11,9 @@ import Message, { } from '../types/message'; import ErrorInfo from '../types/errorinfo'; import { PaginatedResult } from './paginatedresource'; -import Resource, { ResourceCallback } from './resource'; +import Resource from './resource'; import { ChannelOptions } from '../../types/channel'; +import { ErrCallback } from '../../types/utils'; import BaseRest from './baseclient'; import * as API from '../../../../ably'; import Defaults, { normaliseChannelOptions } from '../util/defaults'; @@ -126,12 +127,7 @@ class RestChannel { }); } - _publish( - requestBody: RequestBody | null, - headers: Record, - params: any, - callback: ResourceCallback - ): void { + _publish(requestBody: RequestBody | null, headers: Record, params: any, callback: ErrCallback): void { Resource.post( this.client, this.client.rest.channelMixin.basePath(this) + '/messages', @@ -139,7 +135,7 @@ class RestChannel { headers, params, null, - callback + (err) => callback(err) ); } From 564e4aa6e43018f60b30ad27b495d266faf4f349 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 11 Dec 2023 14:51:02 -0300 Subject: [PATCH 300/468] Convert RestChannel._publish to use promises --- src/common/lib/client/restchannel.ts | 31 ++++++++++++++-------------- test/rest/message.test.js | 20 +++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index c926cb9568..6b8186df81 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -13,7 +13,6 @@ import ErrorInfo from '../types/errorinfo'; import { PaginatedResult } from './paginatedresource'; import Resource from './resource'; import { ChannelOptions } from '../../types/channel'; -import { ErrCallback } from '../../types/utils'; import BaseRest from './baseclient'; import * as API from '../../../../ably'; import Defaults, { normaliseChannelOptions } from '../util/defaults'; @@ -95,7 +94,7 @@ class RestChannel { }); } - return new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error) => { if (err) { reject(err); @@ -120,23 +119,25 @@ class RestChannel { return; } - this._publish(serializeMessage(messages, client._MsgPack, format), headers, params, (err) => - err ? reject(err) : resolve() - ); + resolve(); }); }); + + await this._publish(serializeMessage(messages, client._MsgPack, format), headers, params); } - _publish(requestBody: RequestBody | null, headers: Record, params: any, callback: ErrCallback): void { - Resource.post( - this.client, - this.client.rest.channelMixin.basePath(this) + '/messages', - requestBody, - headers, - params, - null, - (err) => callback(err) - ); + async _publish(requestBody: RequestBody | null, headers: Record, params: any): Promise { + return new Promise((resolve, reject) => { + Resource.post( + this.client, + this.client.rest.channelMixin.basePath(this) + '/messages', + requestBody, + headers, + params, + null, + (err) => (err ? reject(err) : resolve()) + ); + }); } async status(): Promise { diff --git a/test/rest/message.test.js b/test/rest/message.test.js index 1f5f384b05..b6cd469bef 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -24,11 +24,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel = rest.channels.get('rest_implicit_client_id_0'); var originalPublish = channel._publish; - channel._publish = function (requestBody) { + channel._publish = async function (requestBody) { var message = JSON.parse(requestBody)[0]; expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; expect(!message.clientId, 'client ID is not added by the client library as it is implicit').to.be.ok; - originalPublish.apply(channel, arguments); + return originalPublish.apply(channel, arguments); }; await channel.publish('event0', null); @@ -46,14 +46,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel = rest.channels.get('rest_explicit_client_id_0'); var originalPublish = channel._publish; - channel._publish = function (requestBody) { + channel._publish = async function (requestBody) { var message = JSON.parse(requestBody)[0]; expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; expect( message.clientId == clientId, 'client ID is added by the client library as it is explicit in the publish' ).to.be.ok; - originalPublish.apply(channel, arguments); + return originalPublish.apply(channel, arguments); }; await channel.publish({ name: 'event0', clientId: clientId }); @@ -76,14 +76,14 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel = rest.channels.get('rest_explicit_client_id_1'); var originalPublish = channel._publish; - channel._publish = function (requestBody) { + channel._publish = async function (requestBody) { var message = JSON.parse(requestBody)[0]; expect(message.name === 'event0', 'Outgoing message interecepted').to.be.ok; expect( message.clientId == invalidClientId, 'invalid client ID is added by the client library as it is explicit in the publish' ).to.be.ok; - originalPublish.apply(channel, arguments); + return originalPublish.apply(channel, arguments); }; try { @@ -143,7 +143,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async originalPublish = channel._publish, originalDoUri = Ably.Realtime._Http.doUri; - channel._publish = function (requestBody) { + channel._publish = async function (requestBody) { var messageOne = JSON.parse(requestBody)[0]; var messageTwo = JSON.parse(requestBody)[1]; expect(messageOne.name).to.equal('one', 'Outgoing message 1 interecepted'); @@ -154,7 +154,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(idTwo, 'id set on message 2').to.be.ok; expect(idOne && idOne.split(':')[1]).to.equal('0', 'check zero-based index'); expect(idTwo && idTwo.split(':')[1]).to.equal('1', 'check zero-based index'); - originalPublish.apply(channel, arguments); + return originalPublish.apply(channel, arguments); }; Ably.Rest._Http.doUri = function (method, uri, headers, body, params, callback) { @@ -186,9 +186,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var originalPublish = channel._publish; /* Stub out _publish to check params */ - channel._publish = function (requestBody, headers, params) { + channel._publish = async function (requestBody, headers, params) { expect(params && params.testParam).to.equal('testParamValue'); - originalPublish.apply(channel, arguments); + return originalPublish.apply(channel, arguments); }; await channel.publish('foo', 'bar', { testParam: 'testParamValue' }); From 2001675d4bc86fa6e6e190d8d65813cb5a0a8775 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 15:22:53 +0000 Subject: [PATCH 301/468] Convert Resource request methods to use promises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `throwError` parameter here is to handle the fact that, when using an HttpPaginatedResponse, PaginatedResource wishes to have access to both the error that comes from the HTTP client _and_ the response body, before deciding how to handle an error (see the `returnErrOnly` function). I’ve converted Resource.do to return a promise here, but I’ll do the full conversion of that method in a separate commit for readability’s sake. --- src/common/lib/client/paginatedresource.ts | 118 +++---------- src/common/lib/client/push.ts | 142 ++++++--------- src/common/lib/client/resource.ts | 195 ++++++++++++++++++--- src/common/lib/client/rest.ts | 85 +++------ src/common/lib/client/restchannel.ts | 20 +-- src/common/lib/client/restchannelmixin.ts | 15 +- 6 files changed, 298 insertions(+), 277 deletions(-) diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index 2be9eb1325..fb56ea0732 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -1,6 +1,6 @@ import * as Utils from '../util/utils'; import Logger from '../util/logger'; -import Resource from './resource'; +import Resource, { ResourceResult } from './resource'; import { IPartialErrorInfo } from '../types/errorinfo'; import BaseClient from './baseclient'; import { RequestBody, RequestCallbackHeaders } from 'common/types/http'; @@ -59,115 +59,63 @@ class PaginatedResource { } async get(params: Record): Promise> { - return new Promise((resolve) => { - Resource.get( - this.client, - this.path, - this.headers, - params, - this.envelope, - (err, body, headers, unpacked, statusCode) => { - resolve(this.handlePage(err, body, headers, unpacked, statusCode)); - } - ); - }); + const result = await Resource.get(this.client, this.path, this.headers, params, this.envelope, false); + return this.handlePage(result); } async delete(params: Record): Promise> { - return new Promise((resolve) => { - Resource.delete( - this.client, - this.path, - this.headers, - params, - this.envelope, - (err, body, headers, unpacked, statusCode) => { - resolve(this.handlePage(err, body, headers, unpacked, statusCode)); - } - ); - }); + const result = await Resource.delete(this.client, this.path, this.headers, params, this.envelope, false); + return this.handlePage(result); } async post(params: Record, body: RequestBody | null): Promise> { - return new Promise((resolve) => { - Resource.post( - this.client, - this.path, - body, - this.headers, - params, - this.envelope, - (err, responseBody, headers, unpacked, statusCode) => { - resolve(this.handlePage(err, responseBody, headers, unpacked, statusCode)); - } - ); - }); + const result = await Resource.post(this.client, this.path, body, this.headers, params, this.envelope, false); + return this.handlePage(result); } async put(params: Record, body: RequestBody | null): Promise> { - return new Promise((resolve) => { - Resource.put( - this.client, - this.path, - body, - this.headers, - params, - this.envelope, - (err, responseBody, headers, unpacked, statusCode) => { - resolve(this.handlePage(err, responseBody, headers, unpacked, statusCode)); - } - ); - }); + const result = await Resource.put(this.client, this.path, body, this.headers, params, this.envelope, false); + return this.handlePage(result); } async patch(params: Record, body: RequestBody | null): Promise> { - return new Promise((resolve) => { - Resource.patch( - this.client, - this.path, - body, - this.headers, - params, - this.envelope, - (err, responseBody, headers, unpacked, statusCode) => { - resolve(this.handlePage(err, responseBody, headers, unpacked, statusCode)); - } - ); - }); + const result = await Resource.patch(this.client, this.path, body, this.headers, params, this.envelope, false); + return this.handlePage(result); } - async handlePage( - err: IPartialErrorInfo | null, - body: unknown, - headers: RequestCallbackHeaders | undefined, - unpacked: boolean | undefined, - statusCode: number | undefined - ): Promise> { - if (err && returnErrOnly(err, body, this.useHttpPaginatedResponse)) { + async handlePage(result: ResourceResult): Promise> { + if (result.err && returnErrOnly(result.err, result.body, this.useHttpPaginatedResponse)) { Logger.logAction( Logger.LOG_ERROR, 'PaginatedResource.handlePage()', - 'Unexpected error getting resource: err = ' + Utils.inspectError(err) + 'Unexpected error getting resource: err = ' + Utils.inspectError(result.err) ); - throw err; + throw result.err; } let items, linkHeader, relParams; try { - items = await this.bodyHandler(body, headers || {}, unpacked); + items = await this.bodyHandler(result.body, result.headers || {}, result.unpacked); } catch (e) { /* If we got an error, the failure to parse the body is almost certainly * due to that, so throw that in preference over the parse error */ - throw err || e; + throw result.err || e; } - if (headers && (linkHeader = headers['Link'] || headers['link'])) { + if (result.headers && (linkHeader = result.headers['Link'] || result.headers['link'])) { relParams = parseRelLinks(linkHeader); } if (this.useHttpPaginatedResponse) { - return new HttpPaginatedResponse(this, items, headers || {}, statusCode as number, relParams, err); + return new HttpPaginatedResponse( + this, + items, + result.headers || {}, + result.statusCode as number, + relParams, + result.err + ); } else { return new PaginatedResult(this, items, relParams); } @@ -220,18 +168,8 @@ export class PaginatedResult { * the rest of a multipage set of results can always be done with GET */ async get(params: any): Promise> { const res = this.resource; - return new Promise((resolve) => { - Resource.get( - res.client, - res.path, - res.headers, - params, - res.envelope, - function (err, body, headers, unpacked, statusCode) { - resolve(res.handlePage(err, body, headers, unpacked, statusCode)); - } - ); - }); + const result = await Resource.get(res.client, res.path, res.headers, params, res.envelope, false); + return res.handlePage(result); } } diff --git a/src/common/lib/client/push.ts b/src/common/lib/client/push.ts index e6ab5b3083..4ec11dcba8 100644 --- a/src/common/lib/client/push.ts +++ b/src/common/lib/client/push.ts @@ -40,11 +40,7 @@ class Admin { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, client._MsgPack, format); - return new Promise((resolve, reject) => { - Resource.post(client, '/push/publish', requestBody, headers, params, null, (err) => - err ? reject(err) : resolve() - ); - }); + await Resource.post(client, '/push/publish', requestBody, headers, params, null, true); } } @@ -67,27 +63,21 @@ class DeviceRegistrations { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, client._MsgPack, format); - return new Promise((resolve, reject) => { - Resource.put( - client, - '/push/deviceRegistrations/' + encodeURIComponent(device.id), - requestBody, - headers, - params, - null, - (err, body, headers, unpacked) => { - err - ? reject(err) - : resolve( - DeviceDetails.fromResponseBody( - body as Record, - client._MsgPack, - unpacked ? undefined : format - ) as DeviceDetails - ); - } - ); - }); + const response = await Resource.put( + client, + '/push/deviceRegistrations/' + encodeURIComponent(device.id), + requestBody, + headers, + params, + null, + true + ); + + return DeviceDetails.fromResponseBody( + response.body as Record, + client._MsgPack, + response.unpacked ? undefined : format + ) as DeviceDetails; } async get(deviceIdOrDetails: any): Promise { @@ -106,26 +96,20 @@ class DeviceRegistrations { Utils.mixin(headers, client.options.headers); - return new Promise((resolve, reject) => { - Resource.get( - client, - '/push/deviceRegistrations/' + encodeURIComponent(deviceId), - headers, - {}, - null, - function (err, body, headers, unpacked) { - err - ? reject(err) - : resolve( - DeviceDetails.fromResponseBody( - body as Record, - client._MsgPack, - unpacked ? undefined : format - ) as DeviceDetails - ); - } - ); - }); + const response = await Resource.get( + client, + '/push/deviceRegistrations/' + encodeURIComponent(deviceId), + headers, + {}, + null, + true + ); + + return DeviceDetails.fromResponseBody( + response.body as Record, + client._MsgPack, + response.unpacked ? undefined : format + ) as DeviceDetails; } async list(params: any): Promise> { @@ -168,16 +152,14 @@ class DeviceRegistrations { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - return new Promise((resolve, reject) => { - Resource['delete']( - client, - '/push/deviceRegistrations/' + encodeURIComponent(deviceId), - headers, - params, - null, - (err) => (err ? reject(err) : resolve()) - ); - }); + await Resource['delete']( + client, + '/push/deviceRegistrations/' + encodeURIComponent(deviceId), + headers, + params, + null, + true + ); } async removeWhere(params: any): Promise { @@ -189,11 +171,7 @@ class DeviceRegistrations { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - return new Promise((resolve, reject) => { - Resource['delete'](client, '/push/deviceRegistrations', headers, params, null, (err) => - err ? reject(err) : resolve() - ); - }); + await Resource['delete'](client, '/push/deviceRegistrations', headers, params, null, true); } } @@ -216,27 +194,21 @@ class ChannelSubscriptions { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); const requestBody = Utils.encodeBody(body, client._MsgPack, format); - return new Promise((resolve, reject) => { - Resource.post( - client, - '/push/channelSubscriptions', - requestBody, - headers, - params, - null, - function (err, body, headers, unpacked) { - err - ? reject(err) - : resolve( - PushChannelSubscription.fromResponseBody( - body as Record, - client._MsgPack, - unpacked ? undefined : format - ) as PushChannelSubscription - ); - } - ); - }); + const response = await Resource.post( + client, + '/push/channelSubscriptions', + requestBody, + headers, + params, + null, + true + ); + + return PushChannelSubscription.fromResponseBody( + response.body as Record, + client._MsgPack, + response.unpacked ? undefined : format + ) as PushChannelSubscription; } async list(params: any): Promise> { @@ -269,11 +241,7 @@ class ChannelSubscriptions { if (client.options.pushFullWait) Utils.mixin(params, { fullWait: 'true' }); - return new Promise((resolve, reject) => { - Resource['delete'](client, '/push/channelSubscriptions', headers, params, null, (err) => - err ? reject(err) : resolve() - ); - }); + await Resource['delete'](client, '/push/channelSubscriptions', headers, params, null, true); } /* ChannelSubscriptions have no unique id; removing one is equivalent to removeWhere by its properties */ diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index f3c808ebc8..002fb5d44c 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -134,66 +134,191 @@ export type ResourceCallback = ( statusCode?: number ) => void; +export interface ResourceResponse { + body?: T; + headers?: RequestCallbackHeaders; + unpacked?: boolean; + statusCode?: number; +} + +export interface ResourceResult extends ResourceResponse { + /** + * Any error returned by the underlying HTTP client. + */ + err: IPartialErrorInfo | null; +} + class Resource { - static get( + /** + * @param throwError Whether to throw any error returned by the underlying HTTP client. + * + * If you specify `true`, then this method will return a `ResourceResponse`, and if the underlying HTTP client returns an error, this method call will throw that error. If you specify `false`, then it will return a `ResourceResult`, whose `err` property contains any error that was returned by the underlying HTTP client. + */ + static async get( + client: BaseClient, + path: string, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: true + ): Promise>; + static async get( client: BaseClient, path: string, headers: Record, params: Record, envelope: Utils.Format | null, - callback: ResourceCallback - ): void { - Resource.do(HttpMethods.Get, client, path, null, headers, params, envelope, callback); + throwError: false + ): Promise>; + static async get( + client: BaseClient, + path: string, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: boolean + ): Promise | ResourceResult> { + return Resource.do(HttpMethods.Get, client, path, null, headers, params, envelope, throwError ?? false); } - static delete( + /** + * @param throwError Whether to throw any error returned by the underlying HTTP client. + * + * If you specify `true`, then this method will return a `ResourceResponse`, and if the underlying HTTP client returns an error, this method call will throw that error. If you specify `false`, then it will return a `ResourceResult`, whose `err` property contains any error that was returned by the underlying HTTP client. + */ + static async delete( client: BaseClient, path: string, headers: Record, params: Record, envelope: Utils.Format | null, - callback: ResourceCallback - ): void { - Resource.do(HttpMethods.Delete, client, path, null, headers, params, envelope, callback); + throwError: true + ): Promise>; + static async delete( + client: BaseClient, + path: string, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: false + ): Promise>; + static async delete( + client: BaseClient, + path: string, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: boolean + ): Promise | ResourceResult> { + return Resource.do(HttpMethods.Delete, client, path, null, headers, params, envelope, throwError); } - static post( + /** + * @param throwError Whether to throw any error returned by the underlying HTTP client. + * + * If you specify `true`, then this method will return a `ResourceResponse`, and if the underlying HTTP client returns an error, this method call will throw that error. If you specify `false`, then it will return a `ResourceResult`, whose `err` property contains any error that was returned by the underlying HTTP client. + */ + static async post( + client: BaseClient, + path: string, + body: RequestBody | null, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: true + ): Promise>; + static async post( + client: BaseClient, + path: string, + body: RequestBody | null, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: false + ): Promise>; + static async post( client: BaseClient, path: string, body: RequestBody | null, headers: Record, params: Record, envelope: Utils.Format | null, - callback: ResourceCallback - ): void { - Resource.do(HttpMethods.Post, client, path, body, headers, params, envelope, callback); + throwError: boolean + ): Promise | ResourceResult> { + return Resource.do(HttpMethods.Post, client, path, body, headers, params, envelope, throwError); } - static patch( + /** + * @param throwError Whether to throw any error returned by the underlying HTTP client. + * + * If you specify `true`, then this method will return a `ResourceResponse`, and if the underlying HTTP client returns an error, this method call will throw that error. If you specify `false`, then it will return a `ResourceResult`, whose `err` property contains any error that was returned by the underlying HTTP client. + */ + static async patch( + client: BaseClient, + path: string, + body: RequestBody | null, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: true + ): Promise>; + static async patch( client: BaseClient, path: string, body: RequestBody | null, headers: Record, params: Record, envelope: Utils.Format | null, - callback: ResourceCallback - ): void { - Resource.do(HttpMethods.Patch, client, path, body, headers, params, envelope, callback); + throwError: false + ): Promise>; + static async patch( + client: BaseClient, + path: string, + body: RequestBody | null, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: boolean + ): Promise | ResourceResult> { + return Resource.do(HttpMethods.Patch, client, path, body, headers, params, envelope, throwError); } - static put( + /** + * @param throwError Whether to throw any error returned by the underlying HTTP client. + * + * If you specify `true`, then this method will return a `ResourceResponse`, and if the underlying HTTP client returns an error, this method call will throw that error. If you specify `false`, then it will return a `ResourceResult`, whose `err` property contains any error that was returned by the underlying HTTP client. + */ + static async put( client: BaseClient, path: string, body: RequestBody | null, headers: Record, params: Record, envelope: Utils.Format | null, - callback: ResourceCallback - ): void { - Resource.do(HttpMethods.Put, client, path, body, headers, params, envelope, callback); + throwError: true + ): Promise>; + static async put( + client: BaseClient, + path: string, + body: RequestBody | null, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: false + ): Promise>; + static async put( + client: BaseClient, + path: string, + body: RequestBody | null, + headers: Record, + params: Record, + envelope: Utils.Format | null, + throwError: boolean + ): Promise | ResourceResult> { + return Resource.do(HttpMethods.Put, client, path, body, headers, params, envelope, throwError); } - static do( + static async do( method: HttpMethods, client: BaseClient, path: string, @@ -201,14 +326,30 @@ class Resource { headers: Record, params: Record, envelope: Utils.Format | null, - callback: ResourceCallback - ): void { + throwError: boolean + ): Promise | ResourceResult> { + let callback: ResourceCallback; + + const promise = new Promise | ResourceResult>((resolve, reject) => { + callback = (err, body, headers, unpacked, statusCode) => { + if (throwError) { + if (err) { + reject(err); + } else { + resolve({ body, headers, unpacked, statusCode }); + } + } else { + resolve({ err, body, headers, unpacked, statusCode }); + } + }; + }); + if (Logger.shouldLog(Logger.LOG_MICRO)) { - callback = logResponseHandler(callback, method, path, params); + callback = logResponseHandler(callback!, method, path, params); } if (envelope) { - callback = callback && unenvelope(callback, client._MsgPack, envelope); + callback = unenvelope(callback!, client._MsgPack, envelope); (params = params || {})['envelope'] = envelope; } @@ -253,7 +394,9 @@ class Resource { }); } - withAuthDetails(client, headers, params, callback, doRequest); + withAuthDetails(client, headers, params, callback!, doRequest); + + return promise; } } diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index b9d7a9d6ce..20a8e9b737 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -172,25 +172,19 @@ export class Rest { if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); - return new Promise((resolve, reject) => { - Resource.post(this.client, '/messages', requestBody, headers, {}, null, (err, body, headers, unpacked) => { - if (err) { - reject(err); - return; - } - const batchResults = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as BatchPublishResult[]; + const response = await Resource.post(this.client, '/messages', requestBody, headers, {}, null, true); - // I don't love the below type assertions for `resolve` but not sure how to avoid them - if (singleSpecMode) { - (resolve as (result: BatchPublishResult) => void)(batchResults[0]); - } else { - (resolve as (result: BatchPublishResult[]) => void)(batchResults); - } - }); - }); + const batchResults = ( + response.unpacked ? response.body : Utils.decodeBody(response.body, this.client._MsgPack, format) + ) as BatchPublishResult[]; + + // I don't love the below type assertions but not sure how to avoid them + if (singleSpecMode) { + return batchResults[0] as T extends BatchPublishSpec ? BatchPublishResult : BatchPublishResult[]; + } else { + return batchResults as T extends BatchPublishSpec ? BatchPublishResult : BatchPublishResult[]; + } } async batchPresence(channels: string[]): Promise { @@ -201,27 +195,11 @@ export class Rest { const channelsParam = channels.join(','); - return new Promise((resolve, reject) => { - Resource.get( - this.client, - '/presence', - headers, - { channels: channelsParam }, - null, - (err, body, headers, unpacked) => { - if (err) { - reject(err); - return; - } - - const batchResult = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as BatchPresenceResult; + const response = await Resource.get(this.client, '/presence', headers, { channels: channelsParam }, null, true); - resolve(batchResult); - } - ); - }); + return ( + response.unpacked ? response.body : Utils.decodeBody(response.body, this.client._MsgPack, format) + ) as BatchPresenceResult; } async revokeTokens( @@ -248,28 +226,19 @@ export class Rest { const requestBody = Utils.encodeBody(requestBodyDTO, this.client._MsgPack, format); - return new Promise((resolve, reject) => { - Resource.post( - this.client, - `/keys/${keyName}/revokeTokens`, - requestBody, - headers, - {}, - null, - (err, body, headers, unpacked) => { - if (err) { - reject(err); - return; - } - - const batchResult = ( - unpacked ? body : Utils.decodeBody(body, this.client._MsgPack, format) - ) as TokenRevocationResult; + const response = await Resource.post( + this.client, + `/keys/${keyName}/revokeTokens`, + requestBody, + headers, + {}, + null, + true + ); - resolve(batchResult); - } - ); - }); + return ( + response.unpacked ? response.body : Utils.decodeBody(response.body, this.client._MsgPack, format) + ) as TokenRevocationResult; } setLog(logOptions: LoggerOptions): void { diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 6b8186df81..20825833ae 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -127,17 +127,15 @@ class RestChannel { } async _publish(requestBody: RequestBody | null, headers: Record, params: any): Promise { - return new Promise((resolve, reject) => { - Resource.post( - this.client, - this.client.rest.channelMixin.basePath(this) + '/messages', - requestBody, - headers, - params, - null, - (err) => (err ? reject(err) : resolve()) - ); - }); + await Resource.post( + this.client, + this.client.rest.channelMixin.basePath(this) + '/messages', + requestBody, + headers, + params, + null, + true + ); } async status(): Promise { diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts index fa10386a8d..481067355c 100644 --- a/src/common/lib/client/restchannelmixin.ts +++ b/src/common/lib/client/restchannelmixin.ts @@ -44,10 +44,15 @@ export class RestChannelMixin { const format = channel.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json; const headers = Defaults.defaultPostHeaders(channel.client.options, { format }); - return new Promise((resolve, reject) => { - Resource.get(channel.client, this.basePath(channel), headers, {}, format, (err, result) => - err ? reject(err) : resolve(result!) - ); - }); + const response = await Resource.get( + channel.client, + this.basePath(channel), + headers, + {}, + format, + true + ); + + return response.body!; } } From 4951ec185ee3047a1f98ff160aeffaf89c34e420 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 16:53:53 +0000 Subject: [PATCH 302/468] Convert Resource.do to use promises Resolves #1528. --- src/common/lib/client/resource.ts | 249 ++++++++++++++---------------- 1 file changed, 115 insertions(+), 134 deletions(-) diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 002fb5d44c..90a114810a 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -12,128 +12,95 @@ import { appendingParams as urlFromPathAndParams, paramString, } from 'common/types/http'; +import { ErrnoException } from '../../types/http'; -function withAuthDetails( +async function withAuthDetails( client: BaseClient, headers: RequestCallbackHeaders | undefined, params: Record, - errCallback: Function, opCallback: Function -) { +): Promise> { if (client.http.supportsAuthHeaders) { - Utils.whenPromiseSettles( - client.auth.getAuthHeaders(), - function (err: Error | null, authHeaders?: Record) { - if (err) errCallback(err); - else opCallback(Utils.mixin(authHeaders!, headers), params); - } - ); + const authHeaders = await client.auth.getAuthHeaders(); + return opCallback(Utils.mixin(authHeaders!, headers), params); } else { - Utils.whenPromiseSettles( - client.auth.getAuthParams(), - function (err: Error | null, authParams?: Record) { - if (err) errCallback(err); - else opCallback(headers, Utils.mixin(authParams!, params)); - } - ); + const authParams = await client.auth.getAuthParams(); + return opCallback(headers, Utils.mixin(authParams!, params)); } } function unenvelope( - callback: ResourceCallback, + result: ResourceResult, MsgPack: MsgPack | null, format: Utils.Format | null -): ResourceCallback { - return (err, body, outerHeaders, unpacked, outerStatusCode) => { - if (err && !body) { - callback(err); - return; - } +): ResourceResult { + if (result.err && !result.body) { + return { err: result.err }; + } - if (!unpacked) { - try { - body = Utils.decodeBody(body, MsgPack, format); - } catch (e) { - if (Utils.isErrorInfoOrPartialErrorInfo(e)) { - callback(e); - } else { - callback(new PartialErrorInfo(Utils.inspectError(e), null)); - } - return; + let body = result.body; + + if (!result.unpacked) { + try { + body = Utils.decodeBody(body, MsgPack, format); + } catch (e) { + if (Utils.isErrorInfoOrPartialErrorInfo(e)) { + return { err: e }; + } else { + return { err: new PartialErrorInfo(Utils.inspectError(e), null) }; } } + } - if (!body) { - callback(new PartialErrorInfo('unenvelope(): Response body is missing', null)); - return; - } + if (!body) { + return { err: new PartialErrorInfo('unenvelope(): Response body is missing', null) }; + } - const { statusCode: wrappedStatusCode, response, headers: wrappedHeaders } = body as Record; + const { statusCode: wrappedStatusCode, response, headers: wrappedHeaders } = body as Record; - if (wrappedStatusCode === undefined) { - /* Envelope already unwrapped by the transport */ - callback(err, body, outerHeaders, true, outerStatusCode); - return; - } + if (wrappedStatusCode === undefined) { + /* Envelope already unwrapped by the transport */ + return { ...result, body, unpacked: true }; + } - if (wrappedStatusCode < 200 || wrappedStatusCode >= 300) { - /* handle wrapped errors */ - let wrappedErr = (response && response.error) || err; - if (!wrappedErr) { - wrappedErr = new Error('Error in unenveloping ' + body); - wrappedErr.statusCode = wrappedStatusCode; - } - callback(wrappedErr, response, wrappedHeaders, true, wrappedStatusCode); - return; + if (wrappedStatusCode < 200 || wrappedStatusCode >= 300) { + /* handle wrapped errors */ + let wrappedErr = (response && response.error) || result.err; + if (!wrappedErr) { + wrappedErr = new Error('Error in unenveloping ' + body); + wrappedErr.statusCode = wrappedStatusCode; } + return { err: wrappedErr, body: response, headers: wrappedHeaders, unpacked: true, statusCode: wrappedStatusCode }; + } - callback(err, response, wrappedHeaders, true, wrappedStatusCode); - }; + return { err: result.err, body: response, headers: wrappedHeaders, unpacked: true, statusCode: wrappedStatusCode }; } -function logResponseHandler( - callback: ResourceCallback, - method: HttpMethods, - path: string, - params: Record -): ResourceCallback { - return (err, body, headers, unpacked, statusCode) => { - if (err) { - Logger.logAction( - Logger.LOG_MICRO, - 'Resource.' + method + '()', - 'Received Error; ' + urlFromPathAndParams(path, params) + '; Error: ' + Utils.inspectError(err) - ); - } else { - Logger.logAction( - Logger.LOG_MICRO, - 'Resource.' + method + '()', - 'Received; ' + - urlFromPathAndParams(path, params) + - '; Headers: ' + - paramString(headers as Record) + - '; StatusCode: ' + - statusCode + - '; Body' + - (Platform.BufferUtils.isBuffer(body) - ? ' (Base64): ' + Platform.BufferUtils.base64Encode(body) - : ': ' + Platform.Config.inspect(body)) - ); - } - if (callback) { - callback(err, body as T, headers, unpacked, statusCode); - } - }; +function logResult(result: ResourceResult, method: HttpMethods, path: string, params: Record) { + if (result.err) { + Logger.logAction( + Logger.LOG_MICRO, + 'Resource.' + method + '()', + 'Received Error; ' + urlFromPathAndParams(path, params) + '; Error: ' + Utils.inspectError(result.err) + ); + } else { + Logger.logAction( + Logger.LOG_MICRO, + 'Resource.' + method + '()', + 'Received; ' + + urlFromPathAndParams(path, params) + + '; Headers: ' + + paramString(result.headers as Record) + + '; StatusCode: ' + + result.statusCode + + '; Body: ' + + (Platform.BufferUtils.isBuffer(result.body) + ? ' (Base64): ' + Platform.BufferUtils.base64Encode(result.body) + : ': ' + Platform.Config.inspect(result.body)) + ); + } } -export type ResourceCallback = ( - err: IPartialErrorInfo | null, - body?: T, - headers?: RequestCallbackHeaders, - unpacked?: boolean, - statusCode?: number -) => void; - export interface ResourceResponse { body?: T; headers?: RequestCallbackHeaders; @@ -328,32 +295,15 @@ class Resource { envelope: Utils.Format | null, throwError: boolean ): Promise | ResourceResult> { - let callback: ResourceCallback; - - const promise = new Promise | ResourceResult>((resolve, reject) => { - callback = (err, body, headers, unpacked, statusCode) => { - if (throwError) { - if (err) { - reject(err); - } else { - resolve({ body, headers, unpacked, statusCode }); - } - } else { - resolve({ err, body, headers, unpacked, statusCode }); - } - }; - }); - - if (Logger.shouldLog(Logger.LOG_MICRO)) { - callback = logResponseHandler(callback!, method, path, params); - } - if (envelope) { - callback = unenvelope(callback!, client._MsgPack, envelope); (params = params || {})['envelope'] = envelope; } - function doRequest(this: any, headers: Record, params: Record) { + async function doRequest( + this: any, + headers: Record, + params: Record + ): Promise> { if (Logger.shouldLog(Logger.LOG_MICRO)) { let decodedBody = body; if (headers['content-type']?.indexOf('msgpack') > 0) { @@ -377,26 +327,57 @@ class Resource { ); } - client.http.do(method, path, headers, body, params, function (err, res, resHeaders, unpacked, statusCode) { - if (err && Auth.isTokenErr(err as ErrorInfo)) { - /* token has expired, so get a new one */ - Utils.whenPromiseSettles(client.auth.authorize(null, null), function (err: ErrorInfo | null) { - if (err) { - callback(err); - return; - } - /* retry ... */ - withAuthDetails(client, headers, params, callback, doRequest); - }); - return; - } - callback(err as ErrorInfo, res as T | undefined, resHeaders, unpacked, statusCode); + type HttpResult = { + error?: ErrnoException | IPartialErrorInfo | null; + body?: unknown; + headers?: RequestCallbackHeaders; + unpacked?: boolean; + statusCode?: number; + }; + + const httpResult = await new Promise((resolve) => { + client.http.do(method, path, headers, body, params, function (error, body, headers, unpacked, statusCode) { + resolve({ error, body, headers, unpacked, statusCode }); + }); }); + + if (httpResult.error && Auth.isTokenErr(httpResult.error as ErrorInfo)) { + /* token has expired, so get a new one */ + await client.auth.authorize(null, null); + /* retry ... */ + return withAuthDetails(client, headers, params, doRequest); + } + + return { + err: httpResult.error as ErrorInfo, + body: httpResult.body as T | undefined, + headers: httpResult.headers, + unpacked: httpResult.unpacked, + statusCode: httpResult.statusCode, + }; + } + + let result = await withAuthDetails(client, headers, params, doRequest); + + if (envelope) { + result = unenvelope(result, client._MsgPack, envelope); } - withAuthDetails(client, headers, params, callback!, doRequest); + if (Logger.shouldLog(Logger.LOG_MICRO)) { + logResult(result, method, path, params); + } + + if (throwError) { + if (result.err) { + throw result.err; + } else { + const response: Omit, 'err'> & Pick>, 'err'> = { ...result }; + delete response.err; + return response; + } + } - return promise; + return result; } } From da6d2064a47250f7bd889dd5da426e4a4549683c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 09:39:29 +0000 Subject: [PATCH 303/468] Convert message `encrypt` to use promises --- src/common/lib/types/defaultmessage.ts | 7 ++++++- src/common/lib/types/message.ts | 29 ++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/common/lib/types/defaultmessage.ts b/src/common/lib/types/defaultmessage.ts index 9350098c84..58198b09fa 100644 --- a/src/common/lib/types/defaultmessage.ts +++ b/src/common/lib/types/defaultmessage.ts @@ -10,6 +10,7 @@ import * as API from '../../../../ably'; import Platform from 'common/platform'; import PresenceMessage from './presencemessage'; import { ChannelOptions } from 'common/types/channel'; +import { StandardCallback } from 'common/types/utils'; /** `DefaultMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `Message` static property. It introduces the static methods described in the `MessageStatic` interface of the public API of the non tree-shakable version of the library. @@ -29,7 +30,11 @@ export class DefaultMessage extends Message { } // Used by tests - static encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { + static encode( + msg: T, + options: CipherOptions, + callback: StandardCallback + ): void { encode(msg, options, callback); } diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 828cba66d9..ac36419c31 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -8,6 +8,7 @@ import { Bufferlike as BrowserBufferlike } from '../../../platform/web/lib/util/ import * as API from '../../../../ably'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; import { MsgPack } from 'common/types/msgpack'; +import { StandardCallback } from 'common/types/utils'; export type CipherOptions = { channelCipher: { @@ -101,7 +102,7 @@ export async function fromEncodedArray( ); } -function encrypt(msg: Message | PresenceMessage, options: CipherOptions, callback: Function) { +async function encrypt(msg: T, options: CipherOptions): Promise { let data = msg.data, encoding = msg.encoding, cipher = options.channelCipher; @@ -111,18 +112,24 @@ function encrypt(msg: Message | PresenceMessage, options: CipherOptions, callbac data = Platform.BufferUtils.utf8Encode(String(data)); encoding = encoding + 'utf-8/'; } - cipher.encrypt(data, function (err: Error, data: unknown) { - if (err) { - callback(err); - return; - } - msg.data = data; - msg.encoding = encoding + 'cipher+' + cipher.algorithm; - callback(null, msg); + return new Promise((resolve, reject) => { + cipher.encrypt(data, function (err: Error, data: unknown) { + if (err) { + reject(err); + return; + } + msg.data = data; + msg.encoding = encoding + 'cipher+' + cipher.algorithm; + resolve(msg); + }); }); } -export function encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { +export function encode( + msg: T, + options: CipherOptions, + callback: StandardCallback +): void { const data = msg.data; const nativeDataType = typeof data == 'string' || Platform.BufferUtils.isBuffer(data) || data === null || data === undefined; @@ -137,7 +144,7 @@ export function encode(msg: Message | PresenceMessage, options: CipherOptions, c } if (options != null && options.cipher) { - encrypt(msg, options, callback); + Utils.whenPromiseSettles(encrypt(msg, options), callback); } else { callback(null, msg); } From 019c3156b674a750b4ace1d0d5ad2d565282a58d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 09:54:31 +0000 Subject: [PATCH 304/468] Convert message `encode` to use promises The changes to the `connectionQueuing` test are because the test assumed that the message would get marked as `sendAttempted` synchronously as soon as `publish` was called. This relied on the fact that when no encryption is required, the Message `encode` function called its callback synchronously. However, this was an implementation detail (this function has an asynchronous signature), and one which is no longer true now that promises are used. --- src/common/lib/client/realtimepresence.ts | 58 ++++++------- src/common/lib/types/defaultmessage.ts | 9 +- src/common/lib/types/message.ts | 31 +++---- test/realtime/connection.test.js | 101 ++++++++++++++-------- test/realtime/crypto.test.js | 8 +- 5 files changed, 110 insertions(+), 97 deletions(-) diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index abaf126e6b..435ecf5c87 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -6,7 +6,7 @@ import PresenceMessage, { fromData as presenceMessageFromData, encode as encodePresenceMessage, } from '../types/presencemessage'; -import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; +import ErrorInfo, { PartialErrorInfo } from '../types/errorinfo'; import RealtimeChannel from './realtimechannel'; import Multicaster from '../util/multicaster'; import ChannelStateChange from './channelstatechange'; @@ -149,36 +149,32 @@ class RealtimePresence extends EventEmitter { presence.clientId = clientId; } - return new Promise((resolve, reject) => { - encodePresenceMessage(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { - if (err) { - reject(err); - return; - } - switch (channel.state) { - case 'attached': - channel.sendPresence(presence, (err) => (err ? reject(err) : resolve())); - break; - case 'initialized': - case 'detached': - channel.attach(); - // eslint-disable-next-line no-fallthrough - case 'attaching': - this.pendingPresence.push({ - presence: presence, - callback: (err) => (err ? reject(err) : resolve()), - }); - break; - default: - err = new PartialErrorInfo( - 'Unable to ' + action + ' presence channel while in ' + channel.state + ' state', - 90001 - ); - err.code = 90001; - reject(err); - } - }); - }); + await encodePresenceMessage(presence, channel.channelOptions as CipherOptions); + switch (channel.state) { + case 'attached': + return new Promise((resolve, reject) => { + channel.sendPresence(presence, (err) => (err ? reject(err) : resolve())); + }); + case 'initialized': + case 'detached': + channel.attach(); + // eslint-disable-next-line no-fallthrough + case 'attaching': + return new Promise((resolve, reject) => { + this.pendingPresence.push({ + presence: presence, + callback: (err) => (err ? reject(err) : resolve()), + }); + }); + default: { + const err = new PartialErrorInfo( + 'Unable to ' + action + ' presence channel while in ' + channel.state + ' state', + 90001 + ); + err.code = 90001; + throw err; + } + } } async leave(data: unknown): Promise { diff --git a/src/common/lib/types/defaultmessage.ts b/src/common/lib/types/defaultmessage.ts index 58198b09fa..8ec587fdf7 100644 --- a/src/common/lib/types/defaultmessage.ts +++ b/src/common/lib/types/defaultmessage.ts @@ -10,7 +10,6 @@ import * as API from '../../../../ably'; import Platform from 'common/platform'; import PresenceMessage from './presencemessage'; import { ChannelOptions } from 'common/types/channel'; -import { StandardCallback } from 'common/types/utils'; /** `DefaultMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `Message` static property. It introduces the static methods described in the `MessageStatic` interface of the public API of the non tree-shakable version of the library. @@ -30,12 +29,8 @@ export class DefaultMessage extends Message { } // Used by tests - static encode( - msg: T, - options: CipherOptions, - callback: StandardCallback - ): void { - encode(msg, options, callback); + static async encode(msg: T, options: CipherOptions): Promise { + return encode(msg, options); } // Used by tests diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index ac36419c31..d973ec7e5c 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -125,11 +125,7 @@ async function encrypt(msg: T, options: Cip }); } -export function encode( - msg: T, - options: CipherOptions, - callback: StandardCallback -): void { +export async function encode(msg: T, options: CipherOptions): Promise { const data = msg.data; const nativeDataType = typeof data == 'string' || Platform.BufferUtils.isBuffer(data) || data === null || data === undefined; @@ -144,26 +140,19 @@ export function encode( } if (options != null && options.cipher) { - Utils.whenPromiseSettles(encrypt(msg, options), callback); + return encrypt(msg, options); } else { - callback(null, msg); + return msg; } } -export function encodeArray(messages: Array, options: CipherOptions, callback: Function): void { - let processed = 0; - for (let i = 0; i < messages.length; i++) { - encode(messages[i], options, function (err: Error) { - if (err) { - callback(err); - return; - } - processed++; - if (processed == messages.length) { - callback(null, messages); - } - }); - } +export function encodeArray( + messages: Array, + options: CipherOptions, + callback: StandardCallback> +): void { + const promises = messages.map((message) => encode(message, options)); + return Utils.whenPromiseSettles(Promise.all(promises), callback); } export const serialize = Utils.encodeBody; diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index 04d193c4fe..8d5b9fb163 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -178,62 +178,95 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async closeAndFinish(done, realtime, err); return; } + + let transportSendCallback; + /* Sabotage sending the message */ transport.send = function (msg) { if (msg.action == 15) { expect(msg.msgSerial).to.equal(0, 'Expect msgSerial to be 0'); + + if (!transportSendCallback) { + done(new Error('transport.send override called before transportSendCallback populated')); + } + + transportSendCallback(null); } }; - async.parallel( + let publishCallback; + + async.series( [ function (cb) { + transportSendCallback = cb; + /* Sabotaged publish */ whenPromiseSettles(channel.publish('first', null), function (err) { - try { - expect(!err, 'Check publish happened (eventually) without err').to.be.ok; - } catch (err) { - cb(err); - return; + if (!publishCallback) { + done(new Error('publish completed before publishCallback populated')); } - cb(); + publishCallback(err); }); }, - function (cb) { - /* After the disconnect, on reconnect, spy on transport.send again */ - connectionManager.once('transport.pending', function (transport) { - var oldSend = transport.send; - transport.send = function (msg, msgCb) { - if (msg.action === 15) { - if (msg.messages[0].name === 'first') { - try { - expect(msg.msgSerial).to.equal(0, 'Expect msgSerial of original message to still be 0'); - expect(msg.messages.length).to.equal( - 1, - 'Expect second message to not have been merged with the attempted message' - ); - } catch (err) { - cb(err); - return; - } - } else if (msg.messages[0].name === 'second') { + // We wait for transport.send to recieve the message that we just + // published before we proceed to disconnecting the transport, to + // make sure that the message got marked as `sendAttempted`. + + function (cb) { + async.parallel( + [ + function (cb) { + publishCallback = function (err) { try { - expect(msg.msgSerial).to.equal(1, 'Expect msgSerial of new message to be 1'); + expect(!err, 'Check publish happened (eventually) without err').to.be.ok; } catch (err) { cb(err); return; } cb(); - } - } - oldSend.call(transport, msg, msgCb); - }; - channel.publish('second', null); - }); + }; + }, + function (cb) { + /* After the disconnect, on reconnect, spy on transport.send again */ + connectionManager.once('transport.pending', function (transport) { + var oldSend = transport.send; + + transport.send = function (msg, msgCb) { + if (msg.action === 15) { + if (msg.messages[0].name === 'first') { + try { + expect(msg.msgSerial).to.equal(0, 'Expect msgSerial of original message to still be 0'); + expect(msg.messages.length).to.equal( + 1, + 'Expect second message to not have been merged with the attempted message' + ); + } catch (err) { + cb(err); + return; + } + } else if (msg.messages[0].name === 'second') { + try { + expect(msg.msgSerial).to.equal(1, 'Expect msgSerial of new message to be 1'); + } catch (err) { + cb(err); + return; + } + cb(); + } + } + oldSend.call(transport, msg, msgCb); + }; + channel.publish('second', null); + }); - /* Disconnect the transport (will automatically reconnect and resume) () */ - connectionManager.disconnectAllTransports(); + /* Disconnect the transport (will automatically reconnect and resume) () */ + connectionManager.disconnectAllTransports(); + }, + ], + cb + ); }, ], function (err) { diff --git a/test/realtime/crypto.test.js b/test/realtime/crypto.test.js index ded9b31c0a..af44e1ee39 100644 --- a/test/realtime/crypto.test.js +++ b/test/realtime/crypto.test.js @@ -223,7 +223,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async true, function (channelOpts, testMessage, encryptedMessage) { /* encrypt plaintext message; encode() also to handle data that is not already string or buffer */ - Message.encode(testMessage, channelOpts, function () { + whenPromiseSettles(Message.encode(testMessage, channelOpts), function () { /* compare */ testMessageEquality(done, testMessage, encryptedMessage); }); @@ -240,7 +240,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async true, function (channelOpts, testMessage, encryptedMessage) { /* encrypt plaintext message; encode() also to handle data that is not already string or buffer */ - Message.encode(testMessage, channelOpts, function () { + whenPromiseSettles(Message.encode(testMessage, channelOpts), function () { /* compare */ testMessageEquality(done, testMessage, encryptedMessage); }); @@ -314,7 +314,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async 2, false, function (channelOpts, testMessage, encryptedMessage, msgpackEncodedMessage) { - Message.encode(testMessage, channelOpts, function () { + whenPromiseSettles(Message.encode(testMessage, channelOpts), function () { var msgpackFromEncoded = msgpack.encode(testMessage); var msgpackFromEncrypted = msgpack.encode(encryptedMessage); var messageFromMsgpack = Message.fromValues( @@ -348,7 +348,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async 2, false, function (channelOpts, testMessage, encryptedMessage, msgpackEncodedMessage) { - Message.encode(testMessage, channelOpts, function () { + whenPromiseSettles(Message.encode(testMessage, channelOpts), function () { var msgpackFromEncoded = msgpack.encode(testMessage); var msgpackFromEncrypted = msgpack.encode(encryptedMessage); var messageFromMsgpack = Message.fromValues( From 69b64dca2e90393cf4727d44d1398490c049be9f Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 10:01:52 +0000 Subject: [PATCH 305/468] Convert message `encodeArray` to use promises Resolves #1531. --- src/common/lib/client/realtimechannel.ts | 38 ++++++++------------ src/common/lib/client/restchannel.ts | 44 +++++++++--------------- src/common/lib/types/message.ts | 10 ++---- 3 files changed, 33 insertions(+), 59 deletions(-) diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index ea8e615a3d..42090d0e66 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -231,30 +231,22 @@ class RealtimeChannel extends EventEmitter { messages = [messageFromValues({ name: args[0], data: args[1] })]; } const maxMessageSize = this.client.options.maxMessageSize; + await encodeMessagesArray(messages, this.channelOptions as CipherOptions); + /* RSL1i */ + const size = getMessagesSize(messages); + if (size > maxMessageSize) { + throw new ErrorInfo( + 'Maximum size of messages that can be published at once exceeded ( was ' + + size + + ' bytes; limit is ' + + maxMessageSize + + ' bytes)', + 40009, + 400 + ); + } return new Promise((resolve, reject) => { - encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { - if (err) { - reject(err); - return; - } - /* RSL1i */ - const size = getMessagesSize(messages); - if (size > maxMessageSize) { - reject( - new ErrorInfo( - 'Maximum size of messages that can be published at once exceeded ( was ' + - size + - ' bytes; limit is ' + - maxMessageSize + - ' bytes)', - 40009, - 400 - ) - ); - return; - } - this._publish(messages, (err) => (err ? reject(err) : resolve())); - }); + this._publish(messages, (err) => (err ? reject(err) : resolve())); }); } diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 20825833ae..23c4e9b9d6 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -94,34 +94,22 @@ class RestChannel { }); } - await new Promise((resolve, reject) => { - encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error) => { - if (err) { - reject(err); - return; - } - - /* RSL1i */ - const size = getMessagesSize(messages), - maxMessageSize = options.maxMessageSize; - if (size > maxMessageSize) { - reject( - new ErrorInfo( - 'Maximum size of messages that can be published at once exceeded ( was ' + - size + - ' bytes; limit is ' + - maxMessageSize + - ' bytes)', - 40009, - 400 - ) - ); - return; - } - - resolve(); - }); - }); + await encodeMessagesArray(messages, this.channelOptions as CipherOptions); + + /* RSL1i */ + const size = getMessagesSize(messages), + maxMessageSize = options.maxMessageSize; + if (size > maxMessageSize) { + throw new ErrorInfo( + 'Maximum size of messages that can be published at once exceeded ( was ' + + size + + ' bytes; limit is ' + + maxMessageSize + + ' bytes)', + 40009, + 400 + ); + } await this._publish(serializeMessage(messages, client._MsgPack, format), headers, params); } diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index d973ec7e5c..98e0fa2265 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -8,7 +8,6 @@ import { Bufferlike as BrowserBufferlike } from '../../../platform/web/lib/util/ import * as API from '../../../../ably'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; import { MsgPack } from 'common/types/msgpack'; -import { StandardCallback } from 'common/types/utils'; export type CipherOptions = { channelCipher: { @@ -146,13 +145,8 @@ export async function encode(msg: T, option } } -export function encodeArray( - messages: Array, - options: CipherOptions, - callback: StandardCallback> -): void { - const promises = messages.map((message) => encode(message, options)); - return Utils.whenPromiseSettles(Promise.all(promises), callback); +export async function encodeArray(messages: Array, options: CipherOptions): Promise> { + return Promise.all(messages.map((message) => encode(message, options))); } export const serialize = Utils.encodeBody; From 499ca191586b2ebf3b9f186d5d25c0ed617bbdb1 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 11:34:28 +0000 Subject: [PATCH 306/468] Remove leftover documentation comment Missed this in a0105eb. --- src/platform/nodejs/lib/util/crypto.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index d971b842ca..d9b64660a3 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -186,7 +186,6 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { * Generate a random encryption key from the supplied keylength (or the * default keyLength if none supplied) as a Buffer * @param keyLength (optional) the required keyLength in bits - * @param callback (optional) (err, key) */ static async generateRandomKey(keyLength?: number): Promise { return new Promise((resolve, reject) => { From 1e15e53cee690d5e8000a078be253e89d88705eb Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 10:29:23 +0000 Subject: [PATCH 307/468] Convert web crypto `generateRandom` to use promises --- src/platform/web/lib/util/crypto.ts | 43 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index ad8bb8c3e3..5316c0bacd 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -6,6 +6,7 @@ import ICipher from '../../../../common/types/ICipher'; import { CryptoDataTypes } from '../../../../common/types/cryptoDataTypes'; import BufferUtils, { Bufferlike, Output as BufferUtilsOutput } from './bufferutils'; import { IPlatformConfig } from 'common/types/IPlatformConfig'; +import * as Utils from '../../../../common/lib/util/utils'; // The type to which ./msgpack.ts deserializes elements of the `bin` or `ext` type type MessagePackBinaryType = ArrayBuffer; @@ -27,24 +28,27 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B /** * Internal: generate an array of secure random data corresponding to the given length of bytes * @param bytes - * @param callback */ - var generateRandom: (byteLength: number, callback: (error: Error | null, result: ArrayBuffer | null) => void) => void; + var generateRandom: (byteLength: number) => Promise; if (config.getRandomArrayBuffer) { - generateRandom = config.getRandomArrayBuffer; + generateRandom = async (byteLength) => { + return new Promise((resolve, reject) => { + config.getRandomArrayBuffer!(byteLength, (err, result) => (err ? reject(err) : resolve(result!))); + }); + }; } else if (typeof Uint32Array !== 'undefined' && config.getRandomValues) { - generateRandom = function (bytes, callback) { + generateRandom = async function (bytes) { var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); var words = bytes / 4, nativeArray = words == DEFAULT_BLOCKLENGTH_WORDS ? blockRandomArray : new Uint32Array(words); - config.getRandomValues!(nativeArray, function (err) { - if (typeof callback !== 'undefined') { - callback(err, bufferUtils.toArrayBuffer(nativeArray)); - } + return new Promise((resolve, reject) => { + config.getRandomValues!(nativeArray, (err) => + err ? reject(err) : resolve(bufferUtils.toArrayBuffer(nativeArray)) + ); }); }; } else { - generateRandom = function (bytes, callback) { + generateRandom = async function (bytes) { Logger.logAction( Logger.LOG_MAJOR, 'Ably.Crypto.generateRandom()', @@ -56,7 +60,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B array[i] = Math.floor(Math.random() * UINT32_SUP); } - callback(null, bufferUtils.toArrayBuffer(array)); + return bufferUtils.toArrayBuffer(array); }; } @@ -181,16 +185,11 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B * @param keyLength (optional) the required keyLength in bits */ static async generateRandomKey(keyLength?: number): Promise { - return new Promise((resolve, reject) => { - generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { - if (err) { - const errorInfo = new ErrorInfo('Failed to generate random key: ' + err.message, 400, 50000, err); - reject(errorInfo); - } else { - resolve(buf!); - } - }); - }); + try { + return await generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8); + } catch (err) { + throw new ErrorInfo('Failed to generate random key: ' + (err as Error).message, 400, 50000, err as Error); + } } /** @@ -300,9 +299,9 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B return; } - generateRandom(DEFAULT_BLOCKLENGTH, function (err, randomBlock) { + Utils.whenPromiseSettles(generateRandom(DEFAULT_BLOCKLENGTH), function (err, randomBlock) { if (err) { - callback(err, null); + callback(err as Error, null); return; } callback(null, bufferUtils.toArrayBuffer(randomBlock!)); From ec45a10516ee6aa8976cc95afce7037949f62a96 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 11:15:26 +0000 Subject: [PATCH 308/468] Make ICipher.encrypt signature consistent with our other callbacks --- src/common/types/ICipher.ts | 2 +- src/platform/nodejs/lib/util/crypto.ts | 2 +- src/platform/web/lib/util/crypto.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/types/ICipher.ts b/src/common/types/ICipher.ts index a7d07c0598..83a7653a78 100644 --- a/src/common/types/ICipher.ts +++ b/src/common/types/ICipher.ts @@ -1,5 +1,5 @@ export default interface ICipher { algorithm: string; - encrypt: (plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) => void; + encrypt: (plaintext: InputPlaintext, callback: (error: Error | null, data?: OutputCiphertext) => void) => void; decrypt: (ciphertext: InputCiphertext) => Promise; } diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index d9b64660a3..442a75f557 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -233,7 +233,7 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { this.blockLength = this.iv.length; } - encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) { + encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data?: OutputCiphertext) => void) { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); var plaintextBuffer = bufferUtils.toBuffer(plaintext); var plaintextLength = plaintextBuffer.length, diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 5316c0bacd..0fa0ffb90a 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -251,7 +251,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B return output; } - encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data: OutputCiphertext | null) => void) { + encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data?: OutputCiphertext) => void) { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); const encryptAsync = async () => { @@ -276,7 +276,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B callback(null, ciphertext); }) .catch((error) => { - callback(error, null); + callback(error); }); } From d87123dd7ac10a0cbaf7f961c7ba00d869bff6da Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 11:11:32 +0000 Subject: [PATCH 309/468] Convert Node crypto `generateRandom` to use promises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’ve removed the redundant upfront generation of the IV and just kept the generation in `getIv`; this makes it consistent with the web crypto code. --- src/platform/nodejs/lib/util/crypto.ts | 85 +++++++++++++------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index 442a75f557..9a3010f47a 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -8,6 +8,8 @@ import ICipher from '../../../../common/types/ICipher'; import { CryptoDataTypes } from '../../../../common/types/cryptoDataTypes'; import { Cipher as NodeCipher, CipherKey as NodeCipherKey } from 'crypto'; import BufferUtils, { Bufferlike, Output as BufferUtilsOutput } from './bufferutils'; +import util from 'util'; +import * as Utils from '../../../../common/lib/util/utils'; // The type to which ably-forks/msgpack-js deserializes elements of the `bin` or `ext` type type MessagePackBinaryType = Buffer; @@ -27,12 +29,9 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { /** * Internal: generate a buffer of secure random bytes of the given length * @param bytes - * @param callback (optional) */ - function generateRandom(bytes: number): Buffer; - function generateRandom(bytes: number, callback: (err: Error | null, buf: Buffer) => void): void; - function generateRandom(bytes: number, callback?: (err: Error | null, buf: Buffer) => void) { - return callback === undefined ? crypto.randomBytes(bytes) : crypto.randomBytes(bytes, callback); + async function generateRandom(bytes: number): Promise { + return util.promisify(crypto.randomBytes)(bytes); } /** @@ -188,16 +187,11 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { * @param keyLength (optional) the required keyLength in bits */ static async generateRandomKey(keyLength?: number): Promise { - return new Promise((resolve, reject) => { - generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8, function (err, buf) { - if (err) { - const errorInfo = new ErrorInfo('Failed to generate random key: ' + err.message, 500, 50000, err); - reject(errorInfo); - } else { - resolve(buf!); - } - }); - }); + try { + return generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8); + } catch (err) { + throw new ErrorInfo('Failed to generate random key: ' + (err as Error).message, 500, 50000, err as Error); + } } /** @@ -208,10 +202,9 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { static getCipher(params: IGetCipherParams) { var cipherParams = isInstCipherParams(params) ? (params as CipherParams) : this.getDefaultParams(params); - var iv = params.iv || generateRandom(DEFAULT_BLOCKLENGTH); return { cipherParams: cipherParams, - cipher: new CBCCipher(cipherParams, iv), + cipher: new CBCCipher(cipherParams, params.iv ?? null), }; } } @@ -222,51 +215,61 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { algorithm: string; key: NodeCipherKey; iv: Buffer | null; - encryptCipher: NodeCipher; - blockLength: number; + encryptCipher: NodeCipher | null = null; - constructor(params: CipherParams, iv: Buffer) { + constructor(params: CipherParams, iv: Buffer | null) { this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode; this.key = params.key; this.iv = iv; - this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, this.iv); - this.blockLength = this.iv.length; } encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data?: OutputCiphertext) => void) { - Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - var plaintextBuffer = bufferUtils.toBuffer(plaintext); - var plaintextLength = plaintextBuffer.length, - paddedLength = getPaddedLength(plaintextLength), - iv = this.getIv(); - var cipherOut = this.encryptCipher.update( - Buffer.concat([plaintextBuffer, pkcs5Padding[paddedLength - plaintextLength]]) - ); - var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]); - return callback(null, ciphertext); + const promise = (async () => { + Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); + + const iv = await this.getIv(); + if (!this.encryptCipher) { + this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, iv); + } + + var plaintextBuffer = bufferUtils.toBuffer(plaintext); + var plaintextLength = plaintextBuffer.length, + paddedLength = getPaddedLength(plaintextLength); + var cipherOut = this.encryptCipher.update( + Buffer.concat([plaintextBuffer, pkcs5Padding[paddedLength - plaintextLength]]) + ); + var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]); + return ciphertext; + })(); + + Utils.whenPromiseSettles(promise, callback); } async decrypt(ciphertext: InputCiphertext): Promise { - var blockLength = this.blockLength, - decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, blockLength)), - plaintext = toBuffer(decryptCipher.update(ciphertext.slice(blockLength))), + var decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, DEFAULT_BLOCKLENGTH)), + plaintext = toBuffer(decryptCipher.update(ciphertext.slice(DEFAULT_BLOCKLENGTH))), final = decryptCipher.final(); if (final && final.length) plaintext = Buffer.concat([plaintext, toBuffer(final)]); return plaintext; } - getIv() { + async getIv() { if (this.iv) { var iv = this.iv; this.iv = null; return iv; } - var randomBlock = generateRandom(DEFAULT_BLOCKLENGTH); - /* Since the iv for a new block is the ciphertext of the last, this - * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as - * returning it */ - return toBuffer(this.encryptCipher.update(randomBlock)); + var randomBlock = await generateRandom(DEFAULT_BLOCKLENGTH); + + if (!this.encryptCipher) { + return randomBlock; + } else { + /* Since the iv for a new block is the ciphertext of the last, this + * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as + * returning it */ + return toBuffer(this.encryptCipher.update(randomBlock)); + } } } From 32d02a6e96c7da48c486e3b5c523112790ad3ff0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 11:26:16 +0000 Subject: [PATCH 310/468] Convert web crypto `getIv` to use promises --- src/platform/web/lib/util/crypto.ts | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 0fa0ffb90a..2dcb1c0644 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -6,7 +6,6 @@ import ICipher from '../../../../common/types/ICipher'; import { CryptoDataTypes } from '../../../../common/types/cryptoDataTypes'; import BufferUtils, { Bufferlike, Output as BufferUtilsOutput } from './bufferutils'; import { IPlatformConfig } from 'common/types/IPlatformConfig'; -import * as Utils from '../../../../common/lib/util/utils'; // The type to which ./msgpack.ts deserializes elements of the `bin` or `ext` type type MessagePackBinaryType = ArrayBuffer; @@ -255,16 +254,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); const encryptAsync = async () => { - const iv = await new Promise((resolve: (iv: IV) => void, reject: (error: Error) => void) => { - this.getIv((error, iv) => { - if (error) { - reject(error); - } else { - resolve(iv!); - } - }); - }); - + const iv = await this.getIv(); const cryptoKey = await crypto.subtle.importKey('raw', this.key, this.webCryptoAlgorithm, false, ['encrypt']); const ciphertext = await crypto.subtle.encrypt({ name: this.webCryptoAlgorithm, iv }, cryptoKey, plaintext); @@ -291,21 +281,15 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B return crypto.subtle.decrypt({ name: this.webCryptoAlgorithm, iv }, cryptoKey, ciphertextBody); } - getIv(callback: (error: Error | null, iv: ArrayBuffer | null) => void) { + async getIv(): Promise { if (this.iv) { var iv = this.iv; this.iv = null; - callback(null, iv); - return; + return iv; } - Utils.whenPromiseSettles(generateRandom(DEFAULT_BLOCKLENGTH), function (err, randomBlock) { - if (err) { - callback(err as Error, null); - return; - } - callback(null, bufferUtils.toArrayBuffer(randomBlock!)); - }); + const randomBlock = await generateRandom(DEFAULT_BLOCKLENGTH); + return bufferUtils.toArrayBuffer(randomBlock); } } From 2f19925408cabb3159fcc5ff9bddf1b6c78e99e6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 11:33:14 +0000 Subject: [PATCH 311/468] Convert ICipher.encrypt to use promises Resolves #1532. --- src/common/lib/types/message.ts | 15 ++++-------- src/common/types/ICipher.ts | 2 +- src/platform/nodejs/lib/util/crypto.ts | 33 +++++++++++--------------- src/platform/web/lib/util/crypto.ts | 20 ++++------------ 4 files changed, 24 insertions(+), 46 deletions(-) diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 98e0fa2265..837ab7c0fd 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -111,17 +111,10 @@ async function encrypt(msg: T, options: Cip data = Platform.BufferUtils.utf8Encode(String(data)); encoding = encoding + 'utf-8/'; } - return new Promise((resolve, reject) => { - cipher.encrypt(data, function (err: Error, data: unknown) { - if (err) { - reject(err); - return; - } - msg.data = data; - msg.encoding = encoding + 'cipher+' + cipher.algorithm; - resolve(msg); - }); - }); + const ciphertext = await cipher.encrypt(data); + msg.data = ciphertext; + msg.encoding = encoding + 'cipher+' + cipher.algorithm; + return msg; } export async function encode(msg: T, options: CipherOptions): Promise { diff --git a/src/common/types/ICipher.ts b/src/common/types/ICipher.ts index 83a7653a78..c508a6cca7 100644 --- a/src/common/types/ICipher.ts +++ b/src/common/types/ICipher.ts @@ -1,5 +1,5 @@ export default interface ICipher { algorithm: string; - encrypt: (plaintext: InputPlaintext, callback: (error: Error | null, data?: OutputCiphertext) => void) => void; + encrypt: (plaintext: InputPlaintext) => Promise; decrypt: (ciphertext: InputCiphertext) => Promise; } diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index 9a3010f47a..330f70a598 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -9,7 +9,6 @@ import { CryptoDataTypes } from '../../../../common/types/cryptoDataTypes'; import { Cipher as NodeCipher, CipherKey as NodeCipherKey } from 'crypto'; import BufferUtils, { Bufferlike, Output as BufferUtilsOutput } from './bufferutils'; import util from 'util'; -import * as Utils from '../../../../common/lib/util/utils'; // The type to which ably-forks/msgpack-js deserializes elements of the `bin` or `ext` type type MessagePackBinaryType = Buffer; @@ -223,26 +222,22 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { this.iv = iv; } - encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data?: OutputCiphertext) => void) { - const promise = (async () => { - Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); + async encrypt(plaintext: InputPlaintext): Promise { + Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - const iv = await this.getIv(); - if (!this.encryptCipher) { - this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, iv); - } - - var plaintextBuffer = bufferUtils.toBuffer(plaintext); - var plaintextLength = plaintextBuffer.length, - paddedLength = getPaddedLength(plaintextLength); - var cipherOut = this.encryptCipher.update( - Buffer.concat([plaintextBuffer, pkcs5Padding[paddedLength - plaintextLength]]) - ); - var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]); - return ciphertext; - })(); + const iv = await this.getIv(); + if (!this.encryptCipher) { + this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, iv); + } - Utils.whenPromiseSettles(promise, callback); + var plaintextBuffer = bufferUtils.toBuffer(plaintext); + var plaintextLength = plaintextBuffer.length, + paddedLength = getPaddedLength(plaintextLength); + var cipherOut = this.encryptCipher.update( + Buffer.concat([plaintextBuffer, pkcs5Padding[paddedLength - plaintextLength]]) + ); + var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]); + return ciphertext; } async decrypt(ciphertext: InputCiphertext): Promise { diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 2dcb1c0644..4fa5c34a97 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -250,24 +250,14 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B return output; } - encrypt(plaintext: InputPlaintext, callback: (error: Error | null, data?: OutputCiphertext) => void) { + async encrypt(plaintext: InputPlaintext): Promise { Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', ''); - const encryptAsync = async () => { - const iv = await this.getIv(); - const cryptoKey = await crypto.subtle.importKey('raw', this.key, this.webCryptoAlgorithm, false, ['encrypt']); - const ciphertext = await crypto.subtle.encrypt({ name: this.webCryptoAlgorithm, iv }, cryptoKey, plaintext); + const iv = await this.getIv(); + const cryptoKey = await crypto.subtle.importKey('raw', this.key, this.webCryptoAlgorithm, false, ['encrypt']); + const ciphertext = await crypto.subtle.encrypt({ name: this.webCryptoAlgorithm, iv }, cryptoKey, plaintext); - return this.concat(iv, ciphertext); - }; - - encryptAsync() - .then((ciphertext) => { - callback(null, ciphertext); - }) - .catch((error) => { - callback(error); - }); + return this.concat(iv, ciphertext); } async decrypt(ciphertext: InputCiphertext): Promise { From 509b5c400bdecc07aebd1f891c5a86fd6347bc19 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 12:04:22 +0000 Subject: [PATCH 312/468] Convert IPlatformConfig.getRandomValues to use promises --- src/common/types/IPlatformConfig.d.ts | 2 +- src/platform/nativescript/config.js | 5 +---- src/platform/nodejs/config.ts | 6 +----- src/platform/web/config.ts | 5 +---- src/platform/web/lib/util/crypto.ts | 7 ++----- 5 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index d6fb7447b4..63fcaad550 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -23,7 +23,7 @@ export interface ICommonPlatformConfig { */ export interface ISpecificPlatformConfig { addEventListener?: typeof window.addEventListener | typeof global.addEventListener | null; - getRandomValues?: (arr: ArrayBufferView, callback?: (error: Error | null) => void) => void; + getRandomValues?: (arr: ArrayBufferView) => Promise; userAgent?: string | null; inherits?: typeof import('util').inherits; currentUrl?: string; diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index ccdece4dd5..9b9f444d79 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -45,14 +45,11 @@ var Config = { }, TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder, - getRandomValues: function (arr, callback) { + getRandomValues: async function (arr) { var bytes = randomBytes(arr.length); for (var i = 0; i < arr.length; i++) { arr[i] = bytes[i]; } - if (callback) { - callback(null); - } }, }; diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 4947994239..27952ff76b 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -17,17 +17,13 @@ const Config: IPlatformConfig = { stringByteSize: Buffer.byteLength, inherits: util.inherits, addEventListener: null, - getRandomValues: function (arr: ArrayBufferView, callback?: (err: Error | null) => void): void { + getRandomValues: async function (arr: ArrayBufferView): Promise { const bytes = crypto.randomBytes(arr.byteLength); const dataView = new DataView(arr.buffer, arr.byteOffset, arr.byteLength); for (let i = 0; i < bytes.length; i++) { dataView.setUint8(i, bytes[i]); } - - if (callback) { - callback(null); - } }, }; diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 24ae69d451..5c3bac6eed 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -71,11 +71,8 @@ const Config: IPlatformConfig = { if (crypto === undefined) { return undefined; } - return function (arr: ArrayBufferView, callback?: (error: Error | null) => void) { + return async function (arr: ArrayBufferView) { crypto.getRandomValues(arr); - if (callback) { - callback(null); - } }; })(globalObject.crypto || msCrypto), isWebworker: isWebWorkerContext(), diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index 4fa5c34a97..c16eeed601 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -40,11 +40,8 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); var words = bytes / 4, nativeArray = words == DEFAULT_BLOCKLENGTH_WORDS ? blockRandomArray : new Uint32Array(words); - return new Promise((resolve, reject) => { - config.getRandomValues!(nativeArray, (err) => - err ? reject(err) : resolve(bufferUtils.toArrayBuffer(nativeArray)) - ); - }); + await config.getRandomValues!(nativeArray); + return bufferUtils.toArrayBuffer(nativeArray); }; } else { generateRandom = async function (bytes) { From b429c136125426856c9d3d3b4b9ed1b4049b9d4b Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Fri, 5 Jan 2024 12:11:14 +0000 Subject: [PATCH 313/468] Convert IPlatformConfig.getRandomArrayBuffer to use promises Resolves #1530. --- src/common/types/IPlatformConfig.d.ts | 5 +---- src/platform/react-native/config.ts | 8 +++++--- src/platform/web/lib/util/crypto.ts | 6 +----- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 63fcaad550..1517f485f1 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -36,10 +36,7 @@ export interface ISpecificPlatformConfig { atob?: typeof atob | null; TextEncoder?: typeof TextEncoder; TextDecoder?: typeof TextDecoder; - getRandomArrayBuffer?: ( - byteLength: number, - callback: (err: Error | null, result: ArrayBuffer | null) => void - ) => void; + getRandomArrayBuffer?: (byteLength: number) => Promise; isWebworker?: boolean; } diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index 6a2f0107b8..72a928a875 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -31,9 +31,11 @@ export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder, getRandomArrayBuffer: (function (RNRandomBytes) { - return function (byteLength: number, callback: (err: Error | null, result: ArrayBuffer | null) => void) { - RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { - callback(err, base64String ? bufferUtils.toArrayBuffer(bufferUtils.base64Decode(base64String)) : null); + return async function (byteLength: number) { + return new Promise((resolve, reject) => { + RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { + err ? reject(err) : resolve(bufferUtils.toArrayBuffer(bufferUtils.base64Decode(base64String!))); + }); }); }; // Installing @types/react-native would fix this but conflicts with @types/node diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index c16eeed601..c54c565a3d 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -30,11 +30,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B */ var generateRandom: (byteLength: number) => Promise; if (config.getRandomArrayBuffer) { - generateRandom = async (byteLength) => { - return new Promise((resolve, reject) => { - config.getRandomArrayBuffer!(byteLength, (err, result) => (err ? reject(err) : resolve(result!))); - }); - }; + generateRandom = config.getRandomArrayBuffer; } else if (typeof Uint32Array !== 'undefined' && config.getRandomValues) { generateRandom = async function (bytes) { var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); From cddc7b5866b722dfa651135bee2f3b52d368a50d Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 19 Dec 2023 16:08:20 +0000 Subject: [PATCH 314/468] build: use `Ably` as UMD lib name --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index fc544006dc..425e90e9e9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -119,7 +119,7 @@ module.exports = function (grunt) { sourcemap: true, format: 'umd', banner: { js: '/*' + banner + '*/' }, - plugins: [umdWrapper.default()], + plugins: [umdWrapper.default({ libraryName: 'Ably' })], target: 'es6', }; } From b9d468cb93891cda4fc1383bc24bdbd1c5af793d Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Thu, 1 Feb 2024 23:37:00 +0000 Subject: [PATCH 315/468] Changes "esbuild-plugin-umd-wrapper" dependency to use ably's fork --- package-lock.json | 15 +++++++-------- package.json | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77aadfbf46..2f5f5c2f02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "cli-table": "^0.3.11", "cors": "^2.8.5", "esbuild": "^0.18.10", - "esbuild-plugin-umd-wrapper": "^1.0.7", + "esbuild-plugin-umd-wrapper": "ably-forks/esbuild-plugin-umd-wrapper#1.0.7-optional-amd-named-module", "esbuild-runner": "^2.2.2", "eslint": "^7.13.0", "eslint-plugin-import": "^2.28.0", @@ -5370,9 +5370,9 @@ }, "node_modules/esbuild-plugin-umd-wrapper": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/esbuild-plugin-umd-wrapper/-/esbuild-plugin-umd-wrapper-1.0.7.tgz", - "integrity": "sha512-Jo1S3rczXupUzPGt4eXF643FF/J7nDkwwwHH2PS6ic1l0ye7kTS0plAJQoD9u349ybLyt4wyG9fFa/RCuI2dXw==", - "dev": true + "resolved": "git+ssh://git@github.com/ably-forks/esbuild-plugin-umd-wrapper.git#6eef42a607a9192960706d31e444a2c79a2daeb0", + "dev": true, + "license": "MIT" }, "node_modules/esbuild-runner": { "version": "2.2.2", @@ -16350,10 +16350,9 @@ "optional": true }, "esbuild-plugin-umd-wrapper": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/esbuild-plugin-umd-wrapper/-/esbuild-plugin-umd-wrapper-1.0.7.tgz", - "integrity": "sha512-Jo1S3rczXupUzPGt4eXF643FF/J7nDkwwwHH2PS6ic1l0ye7kTS0plAJQoD9u349ybLyt4wyG9fFa/RCuI2dXw==", - "dev": true + "version": "git+ssh://git@github.com/ably-forks/esbuild-plugin-umd-wrapper.git#6eef42a607a9192960706d31e444a2c79a2daeb0", + "dev": true, + "from": "esbuild-plugin-umd-wrapper@ably-forks/esbuild-plugin-umd-wrapper#1.0.7-optional-amd-named-module" }, "esbuild-runner": { "version": "2.2.2", diff --git a/package.json b/package.json index 097711b2e8..ae57d86b04 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "cli-table": "^0.3.11", "cors": "^2.8.5", "esbuild": "^0.18.10", - "esbuild-plugin-umd-wrapper": "^1.0.7", + "esbuild-plugin-umd-wrapper": "ably-forks/esbuild-plugin-umd-wrapper#1.0.7-optional-amd-named-module", "esbuild-runner": "^2.2.2", "eslint": "^7.13.0", "eslint-plugin-import": "^2.28.0", From 41686c0668169bc017ed97b72346c4a197860a0f Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Thu, 1 Feb 2024 23:37:53 +0000 Subject: [PATCH 316/468] Changes UMD wrapper to use anonymous bundles for AMD resolution Resolves #1607 --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 425e90e9e9..9ee91b1382 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -119,7 +119,7 @@ module.exports = function (grunt) { sourcemap: true, format: 'umd', banner: { js: '/*' + banner + '*/' }, - plugins: [umdWrapper.default({ libraryName: 'Ably' })], + plugins: [umdWrapper.default({ libraryName: 'Ably', amdNamedModule: false })], target: 'es6', }; } From a364a39818d530d6c81fa56a0d4c0d074603f543 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Wed, 7 Feb 2024 12:02:18 +0000 Subject: [PATCH 317/468] Revert "Remove Logger.deprecated* methods" This reverts commit 23354329c8a7c3c89abd52029e2438e9326b1ddb. no need to remove it just because it happens to currently not be used --- src/common/lib/util/logger.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/common/lib/util/logger.ts b/src/common/lib/util/logger.ts index c2a9f406c2..aea27ebb25 100644 --- a/src/common/lib/util/logger.ts +++ b/src/common/lib/util/logger.ts @@ -116,6 +116,18 @@ class Logger { } } + static deprecated = function (original: string, replacement: string) { + Logger.deprecatedWithMsg(original, "Please use '" + replacement + "' instead."); + }; + + static deprecatedWithMsg = (funcName: string, msg: string) => { + if (Logger.shouldLog(LogLevels.Error)) { + Logger.logErrorHandler( + "Ably: Deprecation warning - '" + funcName + "' is deprecated and will be removed from a future version. " + msg + ); + } + }; + /* Where a logging operation is expensive, such as serialisation of data, use shouldLog will prevent the object being serialised if the log level will not output the message */ static shouldLog = (level: LogLevels) => { From 717b060122ace7c7495e6dbade4be25ff260b598 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Wed, 7 Feb 2024 10:46:45 +0000 Subject: [PATCH 318/468] Typings: update v2 typings to use createRecoveryKey() instead of recoveryKey --- ably.d.ts | 10 +++------- src/common/lib/client/connection.ts | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 0f761197a5..fe7320be0b 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2386,17 +2386,13 @@ export declare interface Connection */ id?: string; /** - * A unique private connection key used to recover or resume a connection, assigned by Ably. When recovering a connection explicitly, the `recoveryKey` is used in the recover client options as it contains both the key and the last message serial. This private connection key can also be used by other REST clients to publish on behalf of this client. See the [publishing over REST on behalf of a realtime client docs](https://ably.com/docs/rest/channels#publish-on-behalf) for more info. + * A unique private connection key used to recover or resume a connection, assigned by Ably. This private connection key can also be used by other REST clients to publish on behalf of this client. See the [publishing over REST on behalf of a realtime client docs](https://ably.com/docs/rest/channels#publish-on-behalf) for more info. (If you want to explicitly recover a connection in a different SDK instance, see createRecoveryKey() instead) */ key?: string; /** - * The recovery key string can be used by another client to recover this connection's state in the recover client options property. See [connection state recover options](https://ably.com/docs/realtime/connection#connection-state-recover-options) for more information. + * createRecoveryKey method returns a string that can be used by another client to recover this connection's state in the recover client options property. See [connection state recover options](https://ably.com/docs/connect/states?lang=javascript#connection-state-recovery) for more information. */ - recoveryKey: string | null; - /** - * The serial number of the last message to be received on this connection, used automatically by the library when recovering or resuming a connection. When recovering a connection explicitly, the `recoveryKey` is used in the recover client options as it contains both the key and the last message serial. - */ - serial: number; + createRecoveryKey(): string | null; /** * The current {@link ConnectionState} of the connection. */ diff --git a/src/common/lib/client/connection.ts b/src/common/lib/client/connection.ts index 0f822ee1ec..7cb511d2ad 100644 --- a/src/common/lib/client/connection.ts +++ b/src/common/lib/client/connection.ts @@ -65,6 +65,7 @@ class Connection extends EventEmitter { } get recoveryKey(): string | null { + Logger.deprecated('Connection.recoveryKey attribute', 'Connection.createRecoveryKey() method'); return this.createRecoveryKey(); } From aadae0d9b0b780ff3d46e708f3f61c3022ebbba6 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 8 Feb 2024 12:21:15 -0300 Subject: [PATCH 319/468] =?UTF-8?q?Highlight=20that=20whenPromiseSettles?= =?UTF-8?q?=20doesn=E2=80=99t=20guarantee=20error=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When I added whenPromiseSettles in cd6c2d8, I didn’t realise that TypeScript types the `catch` callback’s argument as `any`, which doesn’t make much sense to me given that the argument of a `catch` clause is typed as `unknown` (the latter seeming more appropriate). So, make it explicit that we’re performing a type assertion on the error. --- src/common/lib/util/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 0d68a8b041..034c73577c 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -455,8 +455,9 @@ export function whenPromiseSettles( .then((result) => { callback?.(null, result); }) - .catch((err) => { - callback?.(err); + .catch((err: unknown) => { + // We make no guarantees about the type of the error that gets passed to the callback. Issue https://github.com/ably/ably-js/issues/1617 will think about how to correctly handle error types. + callback?.(err as E); }); } From e46ba18b51b214d20f671cc6f7a69754bef986d0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 1 Feb 2024 15:01:56 +0000 Subject: [PATCH 320/468] Create "resolved" option variables in requestToken The compiler knows that these are non-nullish, which will come in handy. --- src/common/lib/client/auth.ts | 227 +++++++++++++++++----------------- 1 file changed, 116 insertions(+), 111 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 7cd89f0acb..9450990208 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -378,38 +378,38 @@ class Auth { async requestToken(tokenParams?: API.TokenParams | null, authOptions?: any): Promise { /* RSA8e: if authOptions passed in, they're used instead of stored, don't merge them */ - authOptions = authOptions || this.authOptions; - tokenParams = tokenParams || Utils.copy(this.tokenParams); + const resolvedAuthOptions = authOptions || this.authOptions; + const resolvedTokenParams = tokenParams || Utils.copy(this.tokenParams); /* first set up whatever callback will be used to get signed * token requests */ let tokenRequestCallback: any, client = this.client; - if (authOptions.authCallback) { + if (resolvedAuthOptions.authCallback) { Logger.logAction(Logger.LOG_MINOR, 'Auth.requestToken()', 'using token auth with authCallback'); - tokenRequestCallback = authOptions.authCallback; - } else if (authOptions.authUrl) { + tokenRequestCallback = resolvedAuthOptions.authCallback; + } else if (resolvedAuthOptions.authUrl) { Logger.logAction(Logger.LOG_MINOR, 'Auth.requestToken()', 'using token auth with authUrl'); tokenRequestCallback = (params: Record, cb: Function) => { - const authHeaders = Utils.mixin({ accept: 'application/json, text/plain' }, authOptions.authHeaders) as Record< - string, - string - >; - const usePost = authOptions.authMethod && authOptions.authMethod.toLowerCase() === 'post'; + const authHeaders = Utils.mixin( + { accept: 'application/json, text/plain' }, + resolvedAuthOptions.authHeaders + ) as Record; + const usePost = resolvedAuthOptions.authMethod && resolvedAuthOptions.authMethod.toLowerCase() === 'post'; let providedQsParams; /* Combine authParams with any qs params given in the authUrl */ - const queryIdx = authOptions.authUrl.indexOf('?'); + const queryIdx = resolvedAuthOptions.authUrl.indexOf('?'); if (queryIdx > -1) { - providedQsParams = Utils.parseQueryString(authOptions.authUrl.slice(queryIdx)); - authOptions.authUrl = authOptions.authUrl.slice(0, queryIdx); + providedQsParams = Utils.parseQueryString(resolvedAuthOptions.authUrl.slice(queryIdx)); + resolvedAuthOptions.authUrl = resolvedAuthOptions.authUrl.slice(0, queryIdx); if (!usePost) { /* In case of conflict, authParams take precedence over qs params in the authUrl */ - authOptions.authParams = Utils.mixin(providedQsParams, authOptions.authParams); + resolvedAuthOptions.authParams = Utils.mixin(providedQsParams, resolvedAuthOptions.authParams); } } /* RSA8c2 */ - const authParams = Utils.mixin({}, authOptions.authParams || {}, params) as RequestParams; + const authParams = Utils.mixin({}, resolvedAuthOptions.authParams || {}, params) as RequestParams; const authUrlRequestCallback = function ( err: ErrorInfo, body: string, @@ -471,7 +471,7 @@ class Auth { Logger.LOG_MICRO, 'Auth.requestToken().tokenRequestCallback', 'Requesting token from ' + - authOptions.authUrl + + resolvedAuthOptions.authUrl + '; Params: ' + JSON.stringify(authParams) + '; method: ' + @@ -484,7 +484,7 @@ class Auth { const body = Utils.toQueryString(authParams).slice(1); /* slice is to remove the initial '?' */ this.client.http.doUri( HttpMethods.Post, - authOptions.authUrl, + resolvedAuthOptions.authUrl, headers, body, providedQsParams as Record, @@ -493,7 +493,7 @@ class Auth { } else { this.client.http.doUri( HttpMethods.Get, - authOptions.authUrl, + resolvedAuthOptions.authUrl, authHeaders || {}, null, authParams, @@ -501,10 +501,10 @@ class Auth { ); } }; - } else if (authOptions.key) { + } else if (resolvedAuthOptions.key) { Logger.logAction(Logger.LOG_MINOR, 'Auth.requestToken()', 'using token auth with client-side signing'); tokenRequestCallback = (params: any, cb: StandardCallback) => { - Utils.whenPromiseSettles(this.createTokenRequest(params, authOptions), cb); + Utils.whenPromiseSettles(this.createTokenRequest(params, resolvedAuthOptions), cb); }; } else { const msg = @@ -518,8 +518,10 @@ class Auth { } /* normalise token params */ - if ('capability' in (tokenParams as Record)) - (tokenParams as Record).capability = c14n((tokenParams as Record).capability); + if ('capability' in (resolvedTokenParams as Record)) + (resolvedTokenParams as Record).capability = c14n( + (resolvedTokenParams as Record).capability + ); const tokenRequest = (signedTokenParams: Record, tokenCb: Function) => { const keyName = signedTokenParams.keyName, @@ -529,7 +531,7 @@ class Auth { }; const requestHeaders = Defaults.defaultPostHeaders(this.client.options); - if (authOptions.requestHeaders) Utils.mixin(requestHeaders, authOptions.requestHeaders); + if (resolvedAuthOptions.requestHeaders) Utils.mixin(requestHeaders, resolvedAuthOptions.requestHeaders); Logger.logAction( Logger.LOG_MICRO, 'Auth.requestToken().requestToken', @@ -555,105 +557,108 @@ class Auth { reject(new ErrorInfo(msg, 40170, 401)); }, timeoutLength); - tokenRequestCallback(tokenParams, function (err: ErrorInfo, tokenRequestOrDetails: any, contentType: string) { - if (tokenRequestCallbackTimeoutExpired) return; - clearTimeout(tokenRequestCallbackTimeout); + tokenRequestCallback( + resolvedTokenParams, + function (err: ErrorInfo, tokenRequestOrDetails: any, contentType: string) { + if (tokenRequestCallbackTimeoutExpired) return; + clearTimeout(tokenRequestCallbackTimeout); - if (err) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.requestToken()', - 'token request signing call returned error; err = ' + Utils.inspectError(err) - ); - reject(normaliseAuthcallbackError(err)); - return; - } - /* the response from the callback might be a token string, a signed request or a token details */ - if (typeof tokenRequestOrDetails === 'string') { - if (tokenRequestOrDetails.length === 0) { - reject(new ErrorInfo('Token string is empty', 40170, 401)); - } else if (tokenRequestOrDetails.length > MAX_TOKEN_LENGTH) { - reject( - new ErrorInfo( - 'Token string exceeded max permitted length (was ' + tokenRequestOrDetails.length + ' bytes)', - 40170, - 401 - ) + if (err) { + Logger.logAction( + Logger.LOG_ERROR, + 'Auth.requestToken()', + 'token request signing call returned error; err = ' + Utils.inspectError(err) ); - } else if (tokenRequestOrDetails === 'undefined' || tokenRequestOrDetails === 'null') { - /* common failure mode with poorly-implemented authCallbacks */ - reject(new ErrorInfo('Token string was literal null/undefined', 40170, 401)); - } else if ( - tokenRequestOrDetails[0] === '{' && - !(contentType && contentType.indexOf('application/jwt') > -1) - ) { + reject(normaliseAuthcallbackError(err)); + return; + } + /* the response from the callback might be a token string, a signed request or a token details */ + if (typeof tokenRequestOrDetails === 'string') { + if (tokenRequestOrDetails.length === 0) { + reject(new ErrorInfo('Token string is empty', 40170, 401)); + } else if (tokenRequestOrDetails.length > MAX_TOKEN_LENGTH) { + reject( + new ErrorInfo( + 'Token string exceeded max permitted length (was ' + tokenRequestOrDetails.length + ' bytes)', + 40170, + 401 + ) + ); + } else if (tokenRequestOrDetails === 'undefined' || tokenRequestOrDetails === 'null') { + /* common failure mode with poorly-implemented authCallbacks */ + reject(new ErrorInfo('Token string was literal null/undefined', 40170, 401)); + } else if ( + tokenRequestOrDetails[0] === '{' && + !(contentType && contentType.indexOf('application/jwt') > -1) + ) { + reject( + new ErrorInfo( + "Token was double-encoded; make sure you're not JSON-encoding an already encoded token request or details", + 40170, + 401 + ) + ); + } else { + resolve({ token: tokenRequestOrDetails } as API.TokenDetails); + } + return; + } + if (typeof tokenRequestOrDetails !== 'object') { + const msg = + 'Expected token request callback to call back with a token string or token request/details object, but got a ' + + typeof tokenRequestOrDetails; + Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); + reject(new ErrorInfo(msg, 40170, 401)); + return; + } + const objectSize = JSON.stringify(tokenRequestOrDetails).length; + if (objectSize > MAX_TOKEN_LENGTH && !resolvedAuthOptions.suppressMaxLengthCheck) { reject( new ErrorInfo( - "Token was double-encoded; make sure you're not JSON-encoding an already encoded token request or details", + 'Token request/details object exceeded max permitted stringified size (was ' + objectSize + ' bytes)', 40170, 401 ) ); - } else { - resolve({ token: tokenRequestOrDetails } as API.TokenDetails); + return; } - return; - } - if (typeof tokenRequestOrDetails !== 'object') { - const msg = - 'Expected token request callback to call back with a token string or token request/details object, but got a ' + - typeof tokenRequestOrDetails; - Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); - reject(new ErrorInfo(msg, 40170, 401)); - return; - } - const objectSize = JSON.stringify(tokenRequestOrDetails).length; - if (objectSize > MAX_TOKEN_LENGTH && !authOptions.suppressMaxLengthCheck) { - reject( - new ErrorInfo( - 'Token request/details object exceeded max permitted stringified size (was ' + objectSize + ' bytes)', - 40170, - 401 - ) + if ('issued' in tokenRequestOrDetails) { + /* a tokenDetails object */ + resolve(tokenRequestOrDetails); + return; + } + if (!('keyName' in tokenRequestOrDetails)) { + const msg = + 'Expected token request callback to call back with a token string, token request object, or token details object'; + Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); + reject(new ErrorInfo(msg, 40170, 401)); + return; + } + /* it's a token request, so make the request */ + tokenRequest( + tokenRequestOrDetails, + function ( + err?: ErrorInfo | ErrnoException | null, + tokenResponse?: API.TokenDetails | string, + headers?: Record, + unpacked?: boolean + ) { + if (err) { + Logger.logAction( + Logger.LOG_ERROR, + 'Auth.requestToken()', + 'token request API call returned error; err = ' + Utils.inspectError(err) + ); + reject(normaliseAuthcallbackError(err)); + return; + } + if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); + Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); + resolve(tokenResponse as API.TokenDetails); + } ); - return; } - if ('issued' in tokenRequestOrDetails) { - /* a tokenDetails object */ - resolve(tokenRequestOrDetails); - return; - } - if (!('keyName' in tokenRequestOrDetails)) { - const msg = - 'Expected token request callback to call back with a token string, token request object, or token details object'; - Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); - reject(new ErrorInfo(msg, 40170, 401)); - return; - } - /* it's a token request, so make the request */ - tokenRequest( - tokenRequestOrDetails, - function ( - err?: ErrorInfo | ErrnoException | null, - tokenResponse?: API.TokenDetails | string, - headers?: Record, - unpacked?: boolean - ) { - if (err) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.requestToken()', - 'token request API call returned error; err = ' + Utils.inspectError(err) - ); - reject(normaliseAuthcallbackError(err)); - return; - } - if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); - Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); - resolve(tokenResponse as API.TokenDetails); - } - ); - }); + ); }); } From 410ec16ff0d302cdd77b98280d0335337817dad5 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 1 Feb 2024 12:10:38 +0000 Subject: [PATCH 321/468] =?UTF-8?q?Tighten=20type=20of=20requestToken?= =?UTF-8?q?=E2=80=99s=20authOptions=20arg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/lib/client/auth.ts | 43 +++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 9450990208..40920c313c 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -73,7 +73,7 @@ function c14n(capability?: string | Record>) { return JSON.stringify(c14nCapability); } -function logAndValidateTokenAuthMethod(authOptions: API.AuthOptions) { +function logAndValidateTokenAuthMethod(authOptions: AuthOptions) { if (authOptions.authCallback) { Logger.logAction(Logger.LOG_MINOR, 'Auth()', 'using token auth with authCallback'); } else if (authOptions.authUrl) { @@ -111,13 +111,23 @@ function getTokenRequestId() { return trId++; } +/** + * Auth options used only for testing. + */ +type PrivateAuthOptions = { + requestHeaders?: Record; + suppressMaxLengthCheck?: boolean; +}; + +type AuthOptions = API.AuthOptions & PrivateAuthOptions; + class Auth { client: BaseClient; tokenParams: API.TokenParams; currentTokenRequestId: number | null; waitingForTokenRequest: MulticasterInstance | null; // This initialization is always overwritten and only used to prevent a TypeScript compiler error - authOptions: API.AuthOptions = {} as API.AuthOptions; + authOptions: AuthOptions = {} as AuthOptions; tokenDetails?: API.TokenDetails | null; method?: string; key?: string; @@ -238,11 +248,11 @@ class Auth { * - requestHeaders (optional, unsupported, for testing only) extra headers to add to the * requestToken request */ - async authorize(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null): Promise; + async authorize(tokenParams: API.TokenParams | null, authOptions: AuthOptions | null): Promise; async authorize( tokenParams?: Record | null, - authOptions?: API.AuthOptions | null + authOptions?: AuthOptions | null ): Promise { /* RSA10a: authorize() call implies token auth. If a key is passed it, we * just check if it doesn't clash and assume we're generating a token from it */ @@ -284,7 +294,7 @@ class Auth { * effect on the connection as #authorize does */ async _forceNewToken( tokenParams: API.TokenParams | null, - authOptions: API.AuthOptions | null + authOptions: AuthOptions | null ): Promise { /* get rid of current token even if still valid */ this.tokenDetails = null; @@ -374,9 +384,9 @@ class Auth { * - requestHeaders (optional, unsupported, for testing only) extra headers to add to the * requestToken request */ - async requestToken(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions): Promise; + async requestToken(tokenParams: API.TokenParams | null, authOptions: AuthOptions): Promise; - async requestToken(tokenParams?: API.TokenParams | null, authOptions?: any): Promise { + async requestToken(tokenParams?: API.TokenParams | null, authOptions?: AuthOptions): Promise { /* RSA8e: if authOptions passed in, they're used instead of stored, don't merge them */ const resolvedAuthOptions = authOptions || this.authOptions; const resolvedTokenParams = tokenParams || Utils.copy(this.tokenParams); @@ -399,13 +409,16 @@ class Auth { const usePost = resolvedAuthOptions.authMethod && resolvedAuthOptions.authMethod.toLowerCase() === 'post'; let providedQsParams; /* Combine authParams with any qs params given in the authUrl */ - const queryIdx = resolvedAuthOptions.authUrl.indexOf('?'); + const queryIdx = resolvedAuthOptions.authUrl!.indexOf('?'); if (queryIdx > -1) { - providedQsParams = Utils.parseQueryString(resolvedAuthOptions.authUrl.slice(queryIdx)); - resolvedAuthOptions.authUrl = resolvedAuthOptions.authUrl.slice(0, queryIdx); + providedQsParams = Utils.parseQueryString(resolvedAuthOptions.authUrl!.slice(queryIdx)); + resolvedAuthOptions.authUrl = resolvedAuthOptions.authUrl!.slice(0, queryIdx); if (!usePost) { /* In case of conflict, authParams take precedence over qs params in the authUrl */ - resolvedAuthOptions.authParams = Utils.mixin(providedQsParams, resolvedAuthOptions.authParams); + resolvedAuthOptions.authParams = Utils.mixin( + providedQsParams, + resolvedAuthOptions.authParams + ) as typeof resolvedAuthOptions.authParams; } } /* RSA8c2 */ @@ -484,7 +497,7 @@ class Auth { const body = Utils.toQueryString(authParams).slice(1); /* slice is to remove the initial '?' */ this.client.http.doUri( HttpMethods.Post, - resolvedAuthOptions.authUrl, + resolvedAuthOptions.authUrl!, headers, body, providedQsParams as Record, @@ -493,7 +506,7 @@ class Auth { } else { this.client.http.doUri( HttpMethods.Get, - resolvedAuthOptions.authUrl, + resolvedAuthOptions.authUrl!, authHeaders || {}, null, authParams, @@ -805,7 +818,7 @@ class Auth { return this.client.serverTimeOffset !== null; } - _saveBasicOptions(authOptions: API.AuthOptions) { + _saveBasicOptions(authOptions: AuthOptions) { this.method = 'basic'; this.key = authOptions.key; this.basicKey = Utils.toBase64(authOptions.key as string); @@ -815,7 +828,7 @@ class Auth { } } - _saveTokenOptions(tokenParams: API.TokenParams | null, authOptions: API.AuthOptions | null) { + _saveTokenOptions(tokenParams: API.TokenParams | null, authOptions: AuthOptions | null) { this.method = 'token'; if (tokenParams) { From af2e91677ff077fa9cebd6ebb2b6983e5c40c459 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Thu, 15 Feb 2024 19:29:33 +0000 Subject: [PATCH 322/468] fix: use 'ably' as import path from react-hooks This reapplies 7af5a4b07721da42c42b46983fd89d2aa5ed3a03 which apparently got lost at some point during merge commits from `main` into `integration/v2`. Resolves #1619 --- src/platform/react-hooks/src/AblyReactHooks.ts | 2 +- src/platform/react-hooks/src/hooks/useChannel.test.tsx | 2 +- src/platform/react-hooks/src/hooks/useChannel.ts | 2 +- src/platform/react-hooks/src/hooks/useChannelStateListener.ts | 2 +- .../react-hooks/src/hooks/useConnectionStateListener.ts | 2 +- src/platform/react-hooks/src/hooks/useEventListener.ts | 2 +- src/platform/react-hooks/src/hooks/useStateErrors.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 33059f438c..72f25fc7a2 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -1,4 +1,4 @@ -import * as Ably from '../../../../ably.js'; +import * as Ably from 'ably'; export type ChannelNameAndOptions = { channelName: string; diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index 0fe50da284..b10db60cfb 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -3,7 +3,7 @@ import { it, beforeEach, describe, expect, vi } from 'vitest'; import { useChannel } from './useChannel.js'; import { render, screen, waitFor } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; -import * as Ably from '../../../../../ably.js'; +import * as Ably from 'ably'; import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index 55abe27bb3..b0934fe26e 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -1,4 +1,4 @@ -import * as Ably from '../../../../../ably.js'; +import * as Ably from 'ably'; import { useEffect, useMemo, useRef } from 'react'; import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts index 06638172c6..d7aacac200 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import * as Ably from '../../../../../ably.js'; +import * as Ably from 'ably'; import { ChannelNameAndId, ChannelNameAndOptions, channelOptionsWithAgent } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; diff --git a/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts b/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts index d862970791..7c6e5a80f3 100644 --- a/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useConnectionStateListener.ts @@ -1,4 +1,4 @@ -import * as Ably from '../../../../../ably.js'; +import * as Ably from 'ably'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; diff --git a/src/platform/react-hooks/src/hooks/useEventListener.ts b/src/platform/react-hooks/src/hooks/useEventListener.ts index 44e93094e9..755ba08e02 100644 --- a/src/platform/react-hooks/src/hooks/useEventListener.ts +++ b/src/platform/react-hooks/src/hooks/useEventListener.ts @@ -1,4 +1,4 @@ -import * as Ably from '../../../../../ably.js'; +import * as Ably from 'ably'; import { useEffect, useRef } from 'react'; type EventListener = (stateChange: T) => any; diff --git a/src/platform/react-hooks/src/hooks/useStateErrors.ts b/src/platform/react-hooks/src/hooks/useStateErrors.ts index 4387bb225e..807282a62d 100644 --- a/src/platform/react-hooks/src/hooks/useStateErrors.ts +++ b/src/platform/react-hooks/src/hooks/useStateErrors.ts @@ -1,4 +1,4 @@ -import { ErrorInfo } from '../../../../../ably.js'; +import { ErrorInfo } from 'ably'; import { useState } from 'react'; import { useConnectionStateListener } from './useConnectionStateListener.js'; import { useChannelStateListener } from './useChannelStateListener.js'; From d9cd505c4f2a3803e2c85f015375691d61be161e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 1 Feb 2024 12:52:34 +0000 Subject: [PATCH 323/468] =?UTF-8?q?Tighten=20type=20of=20requestToken?= =?UTF-8?q?=E2=80=99s=20tokenRequestCallback=20variable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/lib/client/auth.ts | 211 ++++++++++++++++++---------------- 1 file changed, 111 insertions(+), 100 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 40920c313c..905d224b14 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -4,7 +4,6 @@ import Multicaster, { MulticasterInstance } from '../util/multicaster'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; import { ErrnoException, RequestCallback, RequestParams } from '../../types/http'; import * as API from '../../../../ably'; -import { StandardCallback } from '../../types/utils'; import BaseClient from './baseclient'; import BaseRealtime from './baserealtime'; import ClientOptions from '../../types/ClientOptions'; @@ -393,7 +392,14 @@ class Auth { /* first set up whatever callback will be used to get signed * token requests */ - let tokenRequestCallback: any, + let tokenRequestCallback: ( + data: API.TokenParams, + callback: ( + error: API.ErrorInfo | string | null, + tokenRequestOrDetails: API.TokenDetails | API.TokenRequest | string | null, + contentType?: string + ) => void + ) => void, client = this.client; if (resolvedAuthOptions.authCallback) { @@ -401,7 +407,7 @@ class Auth { tokenRequestCallback = resolvedAuthOptions.authCallback; } else if (resolvedAuthOptions.authUrl) { Logger.logAction(Logger.LOG_MINOR, 'Auth.requestToken()', 'using token auth with authUrl'); - tokenRequestCallback = (params: Record, cb: Function) => { + tokenRequestCallback = (params, cb) => { const authHeaders = Utils.mixin( { accept: 'application/json, text/plain' }, resolvedAuthOptions.authHeaders @@ -447,7 +453,7 @@ class Auth { if (err || unpacked) return cb(err, body); if (Platform.BufferUtils.isBuffer(body)) body = body.toString(); if (!contentType) { - cb(new ErrorInfo('authUrl response is missing a content-type header', 40170, 401)); + cb(new ErrorInfo('authUrl response is missing a content-type header', 40170, 401), null); return; } const json = contentType.indexOf('application/json') > -1, @@ -460,20 +466,26 @@ class Auth { ', should be either text/plain, application/jwt or application/json', 40170, 401 - ) + ), + null ); return; } if (json) { if (body.length > MAX_TOKEN_LENGTH) { - cb(new ErrorInfo('authUrl response exceeded max permitted length', 40170, 401)); + cb(new ErrorInfo('authUrl response exceeded max permitted length', 40170, 401), null); return; } try { body = JSON.parse(body); } catch (e) { cb( - new ErrorInfo('Unexpected error processing authURL response; err = ' + (e as Error).message, 40170, 401) + new ErrorInfo( + 'Unexpected error processing authURL response; err = ' + (e as Error).message, + 40170, + 401 + ), + null ); return; } @@ -516,8 +528,10 @@ class Auth { }; } else if (resolvedAuthOptions.key) { Logger.logAction(Logger.LOG_MINOR, 'Auth.requestToken()', 'using token auth with client-side signing'); - tokenRequestCallback = (params: any, cb: StandardCallback) => { - Utils.whenPromiseSettles(this.createTokenRequest(params, resolvedAuthOptions), cb); + tokenRequestCallback = (params, cb) => { + Utils.whenPromiseSettles(this.createTokenRequest(params, resolvedAuthOptions), (err, result) => + cb(err as string | ErrorInfo | null, result ?? null) + ); }; } else { const msg = @@ -570,108 +584,105 @@ class Auth { reject(new ErrorInfo(msg, 40170, 401)); }, timeoutLength); - tokenRequestCallback( - resolvedTokenParams, - function (err: ErrorInfo, tokenRequestOrDetails: any, contentType: string) { - if (tokenRequestCallbackTimeoutExpired) return; - clearTimeout(tokenRequestCallbackTimeout); + tokenRequestCallback!(resolvedTokenParams, function (err, tokenRequestOrDetails, contentType) { + if (tokenRequestCallbackTimeoutExpired) return; + clearTimeout(tokenRequestCallbackTimeout); - if (err) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.requestToken()', - 'token request signing call returned error; err = ' + Utils.inspectError(err) + if (err) { + Logger.logAction( + Logger.LOG_ERROR, + 'Auth.requestToken()', + 'token request signing call returned error; err = ' + Utils.inspectError(err) + ); + reject(normaliseAuthcallbackError(err)); + return; + } + /* the response from the callback might be a token string, a signed request or a token details */ + if (typeof tokenRequestOrDetails === 'string') { + if (tokenRequestOrDetails.length === 0) { + reject(new ErrorInfo('Token string is empty', 40170, 401)); + } else if (tokenRequestOrDetails.length > MAX_TOKEN_LENGTH) { + reject( + new ErrorInfo( + 'Token string exceeded max permitted length (was ' + tokenRequestOrDetails.length + ' bytes)', + 40170, + 401 + ) ); - reject(normaliseAuthcallbackError(err)); - return; - } - /* the response from the callback might be a token string, a signed request or a token details */ - if (typeof tokenRequestOrDetails === 'string') { - if (tokenRequestOrDetails.length === 0) { - reject(new ErrorInfo('Token string is empty', 40170, 401)); - } else if (tokenRequestOrDetails.length > MAX_TOKEN_LENGTH) { - reject( - new ErrorInfo( - 'Token string exceeded max permitted length (was ' + tokenRequestOrDetails.length + ' bytes)', - 40170, - 401 - ) - ); - } else if (tokenRequestOrDetails === 'undefined' || tokenRequestOrDetails === 'null') { - /* common failure mode with poorly-implemented authCallbacks */ - reject(new ErrorInfo('Token string was literal null/undefined', 40170, 401)); - } else if ( - tokenRequestOrDetails[0] === '{' && - !(contentType && contentType.indexOf('application/jwt') > -1) - ) { - reject( - new ErrorInfo( - "Token was double-encoded; make sure you're not JSON-encoding an already encoded token request or details", - 40170, - 401 - ) - ); - } else { - resolve({ token: tokenRequestOrDetails } as API.TokenDetails); - } - return; - } - if (typeof tokenRequestOrDetails !== 'object') { - const msg = - 'Expected token request callback to call back with a token string or token request/details object, but got a ' + - typeof tokenRequestOrDetails; - Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); - reject(new ErrorInfo(msg, 40170, 401)); - return; - } - const objectSize = JSON.stringify(tokenRequestOrDetails).length; - if (objectSize > MAX_TOKEN_LENGTH && !resolvedAuthOptions.suppressMaxLengthCheck) { + } else if (tokenRequestOrDetails === 'undefined' || tokenRequestOrDetails === 'null') { + /* common failure mode with poorly-implemented authCallbacks */ + reject(new ErrorInfo('Token string was literal null/undefined', 40170, 401)); + } else if ( + tokenRequestOrDetails[0] === '{' && + !(contentType && contentType.indexOf('application/jwt') > -1) + ) { reject( new ErrorInfo( - 'Token request/details object exceeded max permitted stringified size (was ' + objectSize + ' bytes)', + "Token was double-encoded; make sure you're not JSON-encoding an already encoded token request or details", 40170, 401 ) ); - return; - } - if ('issued' in tokenRequestOrDetails) { - /* a tokenDetails object */ - resolve(tokenRequestOrDetails); - return; - } - if (!('keyName' in tokenRequestOrDetails)) { - const msg = - 'Expected token request callback to call back with a token string, token request object, or token details object'; - Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); - reject(new ErrorInfo(msg, 40170, 401)); - return; + } else { + resolve({ token: tokenRequestOrDetails } as API.TokenDetails); } - /* it's a token request, so make the request */ - tokenRequest( - tokenRequestOrDetails, - function ( - err?: ErrorInfo | ErrnoException | null, - tokenResponse?: API.TokenDetails | string, - headers?: Record, - unpacked?: boolean - ) { - if (err) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.requestToken()', - 'token request API call returned error; err = ' + Utils.inspectError(err) - ); - reject(normaliseAuthcallbackError(err)); - return; - } - if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); - Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); - resolve(tokenResponse as API.TokenDetails); - } + return; + } + if (typeof tokenRequestOrDetails !== 'object' || tokenRequestOrDetails === null) { + const msg = + 'Expected token request callback to call back with a token string or token request/details object, but got a ' + + typeof tokenRequestOrDetails; + Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); + reject(new ErrorInfo(msg, 40170, 401)); + return; + } + const objectSize = JSON.stringify(tokenRequestOrDetails).length; + if (objectSize > MAX_TOKEN_LENGTH && !resolvedAuthOptions.suppressMaxLengthCheck) { + reject( + new ErrorInfo( + 'Token request/details object exceeded max permitted stringified size (was ' + objectSize + ' bytes)', + 40170, + 401 + ) ); + return; } - ); + if ('issued' in tokenRequestOrDetails) { + /* a tokenDetails object */ + resolve(tokenRequestOrDetails); + return; + } + if (!('keyName' in tokenRequestOrDetails)) { + const msg = + 'Expected token request callback to call back with a token string, token request object, or token details object'; + Logger.logAction(Logger.LOG_ERROR, 'Auth.requestToken()', msg); + reject(new ErrorInfo(msg, 40170, 401)); + return; + } + /* it's a token request, so make the request */ + tokenRequest( + tokenRequestOrDetails, + function ( + err?: ErrorInfo | ErrnoException | null, + tokenResponse?: API.TokenDetails | string, + headers?: Record, + unpacked?: boolean + ) { + if (err) { + Logger.logAction( + Logger.LOG_ERROR, + 'Auth.requestToken()', + 'token request API call returned error; err = ' + Utils.inspectError(err) + ); + reject(normaliseAuthcallbackError(err)); + return; + } + if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); + Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); + resolve(tokenResponse as API.TokenDetails); + } + ); + }); }); } From aeac6e2785c7a8d0cf9f8808b31aa1f000341f22 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 7 Feb 2024 09:36:08 -0300 Subject: [PATCH 324/468] =?UTF-8?q?Tighten=20type=20of=20requestToken?= =?UTF-8?q?=E2=80=99s=20tokenRequest=20variable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/lib/client/auth.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 905d224b14..ac90d88ce2 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -550,7 +550,15 @@ class Auth { (resolvedTokenParams as Record).capability ); - const tokenRequest = (signedTokenParams: Record, tokenCb: Function) => { + const tokenRequest = ( + signedTokenParams: Record, + tokenCb: ( + err?: ErrorInfo | ErrnoException | null, + tokenResponse?: API.TokenDetails | string, + headers?: Record, + unpacked?: boolean + ) => void + ) => { const keyName = signedTokenParams.keyName, path = '/keys/' + keyName + '/requestToken', tokenUri = function (host: string) { @@ -663,10 +671,10 @@ class Auth { tokenRequest( tokenRequestOrDetails, function ( - err?: ErrorInfo | ErrnoException | null, - tokenResponse?: API.TokenDetails | string, - headers?: Record, - unpacked?: boolean + err, + tokenResponse, + headers, + unpacked, ) { if (err) { Logger.logAction( From 5714753e6f092a76a58b5b5d6ec28fd75b87120c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 31 Jan 2024 12:18:02 +0000 Subject: [PATCH 325/468] =?UTF-8?q?Make=20RequestCallback=E2=80=99s=20erro?= =?UTF-8?q?r=20param=20non-optional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For consistency with our other callbacks. --- src/common/lib/client/auth.ts | 36 ++++++++++++++--------------------- src/common/types/http.ts | 2 +- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index ac90d88ce2..a0ce7c6995 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -553,7 +553,7 @@ class Auth { const tokenRequest = ( signedTokenParams: Record, tokenCb: ( - err?: ErrorInfo | ErrnoException | null, + err: ErrorInfo | ErrnoException | null, tokenResponse?: API.TokenDetails | string, headers?: Record, unpacked?: boolean @@ -668,28 +668,20 @@ class Auth { return; } /* it's a token request, so make the request */ - tokenRequest( - tokenRequestOrDetails, - function ( - err, - tokenResponse, - headers, - unpacked, - ) { - if (err) { - Logger.logAction( - Logger.LOG_ERROR, - 'Auth.requestToken()', - 'token request API call returned error; err = ' + Utils.inspectError(err) - ); - reject(normaliseAuthcallbackError(err)); - return; - } - if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); - Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); - resolve(tokenResponse as API.TokenDetails); + tokenRequest(tokenRequestOrDetails, function (err, tokenResponse, headers, unpacked) { + if (err) { + Logger.logAction( + Logger.LOG_ERROR, + 'Auth.requestToken()', + 'token request API call returned error; err = ' + Utils.inspectError(err) + ); + reject(normaliseAuthcallbackError(err)); + return; } - ); + if (!unpacked) tokenResponse = JSON.parse(tokenResponse as string); + Logger.logAction(Logger.LOG_MINOR, 'Auth.getToken()', 'token received'); + resolve(tokenResponse as API.TokenDetails); + }); }); }); } diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 4f92b7b684..fc36e25158 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -11,7 +11,7 @@ export type PathParameter = string | ((host: string) => string); export type RequestCallbackHeaders = Partial>; export type RequestCallbackError = ErrnoException | IPartialErrorInfo; export type RequestCallback = ( - error?: RequestCallbackError | null, + error: RequestCallbackError | null, body?: unknown, headers?: RequestCallbackHeaders, unpacked?: boolean, From e6d6bb575a48255a87e54c2e971516e72fd8d3cb Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 Jan 2024 09:14:38 +0000 Subject: [PATCH 326/468] Convert IPlatformHttp.checkConnectivity to use promises --- src/common/types/http.ts | 4 +-- src/platform/nodejs/lib/util/http.ts | 33 ++++++++--------- src/platform/web/lib/http/http.ts | 54 +++++++++++++++------------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/common/types/http.ts b/src/common/types/http.ts index fc36e25158..c3d1916e05 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -42,7 +42,7 @@ export interface IPlatformHttp { params: RequestParams, callback?: RequestCallback ): void; - checkConnectivity?: (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => void; + checkConnectivity?: () => Promise; /** * @param error An error returned by {@link doUri}’s callback. @@ -119,7 +119,7 @@ export class Http { this.checkConnectivity = this.platformHttp.checkConnectivity ? (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => - this.platformHttp.checkConnectivity!(callback) + Utils.whenPromiseSettles(this.platformHttp.checkConnectivity!(), callback) : undefined; } diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 1dfb0752c3..db81b1b16b 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -141,29 +141,30 @@ const Http: IPlatformHttpStatic = class { }); } - checkConnectivity = (callback: (errorInfo: ErrorInfo | null, connected?: boolean) => void): void => { + checkConnectivity = async (): Promise => { if (this.client?.options.disableConnectivityCheck) { - callback(null, true); - return; + return true; } const connectivityCheckUrl = this.client?.options.connectivityCheckUrl || Defaults.connectivityCheckUrl; const connectivityCheckParams = this.client?.options.connectivityCheckParams ?? null; const connectivityUrlIsDefault = !this.client?.options.connectivityCheckUrl; - this.doUri( - HttpMethods.Get, - connectivityCheckUrl, - null, - null, - connectivityCheckParams, - function (err, responseText, headers, unpacked, statusCode) { - if (!err && !connectivityUrlIsDefault) { - callback(null, isSuccessCode(statusCode as number)); - return; + return new Promise((resolve) => { + this.doUri( + HttpMethods.Get, + connectivityCheckUrl, + null, + null, + connectivityCheckParams, + function (err, responseText, headers, unpacked, statusCode) { + if (!err && !connectivityUrlIsDefault) { + resolve(isSuccessCode(statusCode as number)); + return; + } + resolve(!err && (responseText as Buffer | string)?.toString().trim() === 'yes'); } - callback(null, !err && (responseText as Buffer | string)?.toString().trim() === 'yes'); - } - ); + ); + }); }; shouldFallback(err: RequestCallbackError) { diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index 5071f66668..c15bce8d89 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -71,33 +71,35 @@ const Http = class { return req; }; if (client?.options.disableConnectivityCheck) { - this.checkConnectivity = function (callback: (err: null, connectivity: true) => void) { - callback(null, true); + this.checkConnectivity = async function () { + return true; }; } else { - this.checkConnectivity = function (callback: (err: ErrorInfo | null, connectivity: boolean) => void) { + this.checkConnectivity = async function () { Logger.logAction( Logger.LOG_MICRO, '(XHRRequest)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl ); - this.doUri( - HttpMethods.Get, - connectivityCheckUrl, - null, - null, - connectivityCheckParams, - function (err, responseText, headers, unpacked, statusCode) { - let result = false; - if (!connectivityUrlIsDefault) { - result = !err && isSuccessCode(statusCode as number); - } else { - result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; + return new Promise((resolve) => { + this.doUri( + HttpMethods.Get, + connectivityCheckUrl, + null, + null, + connectivityCheckParams, + function (err, responseText, headers, unpacked, statusCode) { + let result = false; + if (!connectivityUrlIsDefault) { + result = !err && isSuccessCode(statusCode as number); + } else { + result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; + } + Logger.logAction(Logger.LOG_MICRO, '(XHRRequest)Http.checkConnectivity()', 'Result: ' + result); + resolve(result); } - Logger.logAction(Logger.LOG_MICRO, '(XHRRequest)Http.checkConnectivity()', 'Result: ' + result); - callback(null, result); - } - ); + ); + }); }; } } else if (Platform.Config.fetchSupported && fetchRequestImplementation) { @@ -105,12 +107,14 @@ const Http = class { this.Request = (method, uri, headers, params, body, callback) => { fetchRequestImplementation(method, client ?? null, uri, headers, params, body, callback); }; - this.checkConnectivity = function (callback: (err: ErrorInfo | null, connectivity: boolean) => void) { + this.checkConnectivity = async function () { Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl); - this.doUri(HttpMethods.Get, connectivityCheckUrl, null, null, null, function (err, responseText) { - const result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; - Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Result: ' + result); - callback(null, result); + return new Promise((resolve) => { + this.doUri(HttpMethods.Get, connectivityCheckUrl, null, null, null, function (err, responseText) { + const result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; + Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Result: ' + result); + resolve(result); + }); }); }; } else { @@ -147,7 +151,7 @@ const Http = class { callback: RequestCallback ) => void; - checkConnectivity?: (callback: (err: ErrorInfo | null, connectivity?: boolean) => void) => void = undefined; + checkConnectivity?: () => Promise = undefined; supportsAuthHeaders = false; supportsLinkHeaders = false; From 865f2249ddf2a03c0e8e7093a27cc8a37da6d127 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 Jan 2024 09:15:17 +0000 Subject: [PATCH 327/468] Convert Http.checkConnectivity to use promises --- src/common/lib/transport/connectionmanager.ts | 41 +++---- src/common/types/http.ts | 7 +- test/realtime/connectivity.test.js | 100 ++++++++++-------- 3 files changed, 83 insertions(+), 65 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 1e34a0852c..48fec7bd78 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1657,26 +1657,29 @@ class ConnectionManager extends EventEmitter { giveUp(new PartialErrorInfo('Internal error: Http.checkConnectivity not set', null, 500)); return; } - this.realtime.http.checkConnectivity((err?: ErrorInfo | null, connectivity?: boolean) => { - if (connectCount !== this.connectCounter) { - return; - } - /* we know err won't happen but handle it here anyway */ - if (err) { - giveUp(err); - return; - } - if (!connectivity) { - /* the internet isn't reachable, so don't try the fallback hosts */ - giveUp(new ErrorInfo('Unable to connect (network unreachable)', 80003, 404)); - return; + Utils.whenPromiseSettles( + this.realtime.http.checkConnectivity(), + (err?: ErrorInfo | null, connectivity?: boolean) => { + if (connectCount !== this.connectCounter) { + return; + } + /* we know err won't happen but handle it here anyway */ + if (err) { + giveUp(err); + return; + } + if (!connectivity) { + /* the internet isn't reachable, so don't try the fallback hosts */ + giveUp(new ErrorInfo('Unable to connect (network unreachable)', 80003, 404)); + return; + } + /* the network is there, so there's a problem with the main host, or + * its dns. Try the fallback hosts. We could try them simultaneously but + * that would potentially cause a huge spike in load on the load balancer */ + transportParams.host = Utils.arrPopRandomElement(candidateHosts); + this.tryATransport(transportParams, this.baseTransport, hostAttemptCb); } - /* the network is there, so there's a problem with the main host, or - * its dns. Try the fallback hosts. We could try them simultaneously but - * that would potentially cause a huge spike in load on the load balancer */ - transportParams.host = Utils.arrPopRandomElement(candidateHosts); - this.tryATransport(transportParams, this.baseTransport, hostAttemptCb); - }); + ); }; if (this.forceFallbackHost && candidateHosts.length) { diff --git a/src/common/types/http.ts b/src/common/types/http.ts index c3d1916e05..b6204f1e1a 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -3,7 +3,7 @@ import Platform from 'common/platform'; import BaseRealtime from 'common/lib/client/baserealtime'; import HttpMethods from '../constants/HttpMethods'; import BaseClient from '../lib/client/baseclient'; -import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; +import { IPartialErrorInfo } from '../lib/types/errorinfo'; import Logger from 'common/lib/util/logger'; import * as Utils from 'common/lib/util/utils'; @@ -112,14 +112,13 @@ function logRequest(method: HttpMethods, uri: string, body: RequestBody | null, export class Http { private readonly platformHttp: IPlatformHttp; - checkConnectivity?: (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => void; + checkConnectivity?: () => Promise; constructor(private readonly client?: BaseClient) { this.platformHttp = new Platform.Http(client); this.checkConnectivity = this.platformHttp.checkConnectivity - ? (callback: (err?: ErrorInfo | null, connected?: boolean) => void) => - Utils.whenPromiseSettles(this.platformHttp.checkConnectivity!(), callback) + ? () => this.platformHttp.checkConnectivity!() : undefined; } diff --git a/test/realtime/connectivity.test.js b/test/realtime/connectivity.test.js index cea8ab6606..6dcad60551 100644 --- a/test/realtime/connectivity.test.js +++ b/test/realtime/connectivity.test.js @@ -5,6 +5,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var closeAndFinish = helper.closeAndFinish; var monitorConnection = helper.monitorConnection; var utils = helper.Utils; + var whenPromiseSettles = helper.whenPromiseSettles; describe('realtime/connectivity', function () { this.timeout(60 * 1000); @@ -22,7 +23,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { * Connect with available http transports; internet connectivity check should work */ it('http_connectivity_check', function (done) { - new Ably.Realtime._Http().checkConnectivity(function (err, res) { + whenPromiseSettles(new Ably.Realtime._Http().checkConnectivity(), function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; } catch (err) { @@ -47,30 +48,36 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { } it('succeeds with scheme', function (done) { - new helper.AblyRealtime(options(urlScheme + successUrl)).http.checkConnectivity(function (err, res) { - try { - expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; - } catch (err) { - done(err); - return; + whenPromiseSettles( + new helper.AblyRealtime(options(urlScheme + successUrl)).http.checkConnectivity(), + function (err, res) { + try { + expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; + } catch (err) { + done(err); + return; + } + done(); } - done(); - }); + ); }); it('fails with scheme', function (done) { - new helper.AblyRealtime(options(urlScheme + failUrl)).http.checkConnectivity(function (err, res) { - try { - expect(!res, 'Connectivity check expected to return false').to.be.ok; - done(); - } catch (err) { - done(err); + whenPromiseSettles( + new helper.AblyRealtime(options(urlScheme + failUrl)).http.checkConnectivity(), + function (err, res) { + try { + expect(!res, 'Connectivity check expected to return false').to.be.ok; + done(); + } catch (err) { + done(err); + } } - }); + ); }); it('succeeds with querystring', function (done) { - new helper.AblyRealtime(options(successUrl)).http.checkConnectivity(function (err, res) { + whenPromiseSettles(new helper.AblyRealtime(options(successUrl)).http.checkConnectivity(), function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; done(); @@ -81,7 +88,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('fails with querystring', function (done) { - new helper.AblyRealtime(options(failUrl)).http.checkConnectivity(function (err, res) { + whenPromiseSettles(new helper.AblyRealtime(options(failUrl)).http.checkConnectivity(), function (err, res) { try { expect(!res, 'Connectivity check expected to return false').to.be.ok; done(); @@ -92,40 +99,49 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); it('succeeds with plain url', function (done) { - new helper.AblyRealtime(options('sandbox-rest.ably.io/time')).http.checkConnectivity(function (err, res) { - try { - expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; - done(); - } catch (err) { - done(err); + whenPromiseSettles( + new helper.AblyRealtime(options('sandbox-rest.ably.io/time')).http.checkConnectivity(), + function (err, res) { + try { + expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; + done(); + } catch (err) { + done(err); + } } - }); + ); }); it('fails with plain url', function (done) { - new helper.AblyRealtime(options('echo.ably.io')).http.checkConnectivity(function (err, res) { - try { - expect(!res, 'Connectivity check expected to return false').to.be.ok; - done(); - } catch (err) { - done(err); + whenPromiseSettles( + new helper.AblyRealtime(options('echo.ably.io')).http.checkConnectivity(), + function (err, res) { + try { + expect(!res, 'Connectivity check expected to return false').to.be.ok; + done(); + } catch (err) { + done(err); + } } - }); + ); }); }); it('disable_connectivity_check', function (done) { - new helper.AblyRealtime({ - connectivityCheckUrl: 'notarealhost', - disableConnectivityCheck: true, - }).http.checkConnectivity(function (err, res) { - try { - expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; - done(); - } catch (err) { - done(err); + whenPromiseSettles( + new helper.AblyRealtime({ + connectivityCheckUrl: 'notarealhost', + disableConnectivityCheck: true, + }).http.checkConnectivity(), + function (err, res) { + try { + expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; + done(); + } catch (err) { + done(err); + } } - }); + ); }); }); }); From 6f0040906703bc72d898a02ce5285a3ab3926b32 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 Jan 2024 16:13:58 +0000 Subject: [PATCH 328/468] Convert IPlatformHttp.doUri to use promises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The approach taken here is the same as that taken in 2001675, in order to be able to emit both an error _and_ the response body. Note that in the "just in case" handling of thrown errors I’ve just typed the thrown error as `any` and not worried about the consequences of doing so; we can figure out how to handle this properly and consistently in https://github.com/ably/ably-js/issues/1617. --- src/common/lib/util/utils.ts | 6 ++- src/common/types/http.ts | 29 ++++++++++-- src/platform/nodejs/lib/util/http.ts | 71 ++++++++++++---------------- src/platform/web/lib/http/http.ts | 66 +++++++++++++------------- 4 files changed, 92 insertions(+), 80 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 034c73577c..c211e833d4 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -577,6 +577,10 @@ export function arrEquals(a: any[], b: any[]) { ); } +export function createMissingModuleError(moduleName: keyof ModulesMap): ErrorInfo { + return new ErrorInfo(`${moduleName} module not provided`, 40019, 400); +} + export function throwMissingModuleError(moduleName: keyof ModulesMap): never { - throw new ErrorInfo(`${moduleName} module not provided`, 40019, 400); + throw createMissingModuleError(moduleName); } diff --git a/src/common/types/http.ts b/src/common/types/http.ts index b6204f1e1a..03c5202426 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -17,6 +17,18 @@ export type RequestCallback = ( unpacked?: boolean, statusCode?: number ) => void; + +/** + * The `body`, `headers`, `unpacked`, and `statusCode` properties of a `RequestResult` may be populated even if its `error` property is non-null. + */ +export type RequestResult = { + error: RequestCallbackError | null; + body?: unknown; + headers?: RequestCallbackHeaders; + unpacked?: boolean; + statusCode?: number; +}; + export type RequestParams = Record | null; export type RequestBody = | Buffer // only on Node @@ -34,18 +46,21 @@ export interface IPlatformHttp { supportsAuthHeaders: boolean; supportsLinkHeaders: boolean; + /** + * This method should not throw any errors; rather, it should communicate any error by populating the {@link RequestResult.error} property of the returned {@link RequestResult}. + */ doUri( method: HttpMethods, uri: string, headers: Record | null, body: RequestBody | null, - params: RequestParams, - callback?: RequestCallback - ): void; + params: RequestParams + ): Promise; + checkConnectivity?: () => Promise; /** - * @param error An error returned by {@link doUri}’s callback. + * @param error An error from the {@link RequestResult.error} property of a result returned by {@link doUri}. */ shouldFallback(error: RequestCallbackError): boolean; } @@ -227,7 +242,11 @@ export class Http { callback = logResponseHandler(callback, method, uri, params); } - this.platformHttp.doUri(method, uri, headers, body, params, callback); + Utils.whenPromiseSettles(this.platformHttp.doUri(method, uri, headers, body, params), (err: any, result) => + err + ? callback?.(err) // doUri isn’t meant to throw an error, but handle any just in case + : callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode) + ); } } diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index db81b1b16b..2306d50856 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -5,9 +5,9 @@ import { ErrnoException, RequestBody, IPlatformHttpStatic, - RequestCallback, RequestCallbackError, RequestParams, + RequestResult, } from '../../../../common/types/http'; import HttpMethods from '../../../../common/constants/HttpMethods'; import got, { Response, Options, CancelableRequest, Agents } from 'got'; @@ -16,7 +16,7 @@ import https from 'https'; import BaseClient from 'common/lib/client/baseclient'; import { RestAgentOptions } from 'common/types/ClientOptions'; import { isSuccessCode } from 'common/constants/HttpStatusCodes'; -import { shallowEquals, throwMissingModuleError } from 'common/lib/util/utils'; +import { createMissingModuleError, shallowEquals } from 'common/lib/util/utils'; /*************************************************** * @@ -34,11 +34,10 @@ import { shallowEquals, throwMissingModuleError } from 'common/lib/util/utils'; const globalAgentPool: Array<{ options: RestAgentOptions; agents: Agents }> = []; -const handler = function (uri: string, params: unknown, client: BaseClient | null, callback?: RequestCallback) { +const handler = function (uri: string, params: unknown, client: BaseClient | null) { return function (err: ErrnoException | null, response?: Response, body?: unknown) { if (err) { - callback?.(err); - return; + return { error: err }; } const statusCode = (response as Response).statusCode, headers = (response as Response).headers; @@ -49,7 +48,7 @@ const handler = function (uri: string, params: unknown, client: BaseClient | nul break; case 'application/x-msgpack': if (!client?._MsgPack) { - throwMissingModuleError('MsgPack'); + return { error: createMissingModuleError('MsgPack') }; } body = client._MsgPack.decode(body as Buffer); } @@ -61,10 +60,9 @@ const handler = function (uri: string, params: unknown, client: BaseClient | nul Number(headers['x-ably-errorcode']), statusCode ); - callback?.(error, body, headers, true, statusCode); - return; + return { error, body, headers, unpacked: true, statusCode }; } - callback?.(null, body, headers, false, statusCode); + return { error: null, body, headers, unpacked: false, statusCode }; }; }; @@ -81,14 +79,13 @@ const Http: IPlatformHttpStatic = class { this.client = client ?? null; } - doUri( + async doUri( method: HttpMethods, uri: string, headers: Record | null, body: RequestBody | null, - params: RequestParams, - callback: RequestCallback - ): void { + params: RequestParams + ): Promise { /* Will generally be making requests to one or two servers exclusively * (Ably and perhaps an auth server), so for efficiency, use the * foreverAgent to keep the TCP stream alive between requests where possible */ @@ -128,17 +125,15 @@ const Http: IPlatformHttpStatic = class { // the same endpoint, inappropriately retrying 429s, etc doOptions.retry = { limit: 0 }; - (got[method](doOptions) as CancelableRequest) - .then((res: Response) => { - handler(uri, params, this.client, callback)(null, res, res.body); - }) - .catch((err: ErrnoException) => { - if (err instanceof got.HTTPError) { - handler(uri, params, this.client, callback)(null, err.response, err.response.body); - return; - } - handler(uri, params, this.client, callback)(err); - }); + try { + const res = await (got[method](doOptions) as CancelableRequest); + return handler(uri, params, this.client)(null, res, res.body); + } catch (err) { + if (err instanceof got.HTTPError) { + return handler(uri, params, this.client)(null, err.response, err.response.body); + } + return handler(uri, params, this.client)(err as ErrnoException); + } } checkConnectivity = async (): Promise => { @@ -149,22 +144,18 @@ const Http: IPlatformHttpStatic = class { const connectivityCheckParams = this.client?.options.connectivityCheckParams ?? null; const connectivityUrlIsDefault = !this.client?.options.connectivityCheckUrl; - return new Promise((resolve) => { - this.doUri( - HttpMethods.Get, - connectivityCheckUrl, - null, - null, - connectivityCheckParams, - function (err, responseText, headers, unpacked, statusCode) { - if (!err && !connectivityUrlIsDefault) { - resolve(isSuccessCode(statusCode as number)); - return; - } - resolve(!err && (responseText as Buffer | string)?.toString().trim() === 'yes'); - } - ); - }); + const { error, statusCode, body } = await this.doUri( + HttpMethods.Get, + connectivityCheckUrl, + null, + null, + connectivityCheckParams + ); + + if (!error && !connectivityUrlIsDefault) { + return isSuccessCode(statusCode as number); + } + return !error && (body as Buffer | string)?.toString().trim() === 'yes'; }; shouldFallback(err: RequestCallbackError) { diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index c15bce8d89..c997562bdf 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -1,7 +1,7 @@ import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestBody, RequestCallback, RequestCallbackError, RequestParams } from 'common/types/http'; +import { RequestBody, RequestCallback, RequestCallbackError, RequestParams, RequestResult } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import XHRStates from 'common/constants/XHRStates'; @@ -81,25 +81,24 @@ const Http = class { '(XHRRequest)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl ); - return new Promise((resolve) => { - this.doUri( - HttpMethods.Get, - connectivityCheckUrl, - null, - null, - connectivityCheckParams, - function (err, responseText, headers, unpacked, statusCode) { - let result = false; - if (!connectivityUrlIsDefault) { - result = !err && isSuccessCode(statusCode as number); - } else { - result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; - } - Logger.logAction(Logger.LOG_MICRO, '(XHRRequest)Http.checkConnectivity()', 'Result: ' + result); - resolve(result); - } - ); - }); + + const requestResult = await this.doUri( + HttpMethods.Get, + connectivityCheckUrl, + null, + null, + connectivityCheckParams + ); + + let result = false; + if (!connectivityUrlIsDefault) { + result = !requestResult.error && isSuccessCode(requestResult.statusCode as number); + } else { + result = !requestResult.error && (requestResult.body as string)?.replace(/\n/, '') == 'yes'; + } + + Logger.logAction(Logger.LOG_MICRO, '(XHRRequest)Http.checkConnectivity()', 'Result: ' + result); + return result; }; } } else if (Platform.Config.fetchSupported && fetchRequestImplementation) { @@ -109,13 +108,10 @@ const Http = class { }; this.checkConnectivity = async function () { Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl); - return new Promise((resolve) => { - this.doUri(HttpMethods.Get, connectivityCheckUrl, null, null, null, function (err, responseText) { - const result = !err && (responseText as string)?.replace(/\n/, '') == 'yes'; - Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Result: ' + result); - resolve(result); - }); - }); + const requestResult = await this.doUri(HttpMethods.Get, connectivityCheckUrl, null, null, null); + const result = !requestResult.error && (requestResult.body as string)?.replace(/\n/, '') == 'yes'; + Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Result: ' + result); + return result; }; } else { this.Request = (method, uri, headers, params, body, callback) => { @@ -127,19 +123,21 @@ const Http = class { } } - doUri( + async doUri( method: HttpMethods, uri: string, headers: Record | null, body: RequestBody | null, - params: RequestParams, - callback: RequestCallback - ): void { + params: RequestParams + ): Promise { if (!this.Request) { - callback(new PartialErrorInfo('Request invoked before assigned to', null, 500)); - return; + return { error: new PartialErrorInfo('Request invoked before assigned to', null, 500) }; } - this.Request(method, uri, headers, params, body, callback); + return new Promise((resolve) => { + this.Request!(method, uri, headers, params, body, (error, resBody, resHeaders, unpacked, statusCode) => + resolve({ error, body: resBody, headers: resHeaders, unpacked, statusCode }) + ); + }); } private Request?: ( From c66335200496bbac10689ffe8419ddf8055a61dc Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 Jan 2024 16:14:43 +0000 Subject: [PATCH 329/468] Convert Http.doUri to use promises --- src/common/lib/client/auth.ts | 44 +++++++--- src/common/types/http.ts | 157 +++++++++++++++++++--------------- test/realtime/auth.test.js | 8 +- test/rest/message.test.js | 17 ++-- 4 files changed, 132 insertions(+), 94 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index a0ce7c6995..e46c2ba9bd 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -507,22 +507,38 @@ class Auth { const headers = authHeaders || {}; headers['content-type'] = 'application/x-www-form-urlencoded'; const body = Utils.toQueryString(authParams).slice(1); /* slice is to remove the initial '?' */ - this.client.http.doUri( - HttpMethods.Post, - resolvedAuthOptions.authUrl!, - headers, - body, - providedQsParams as Record, - authUrlRequestCallback as RequestCallback + Utils.whenPromiseSettles( + this.client.http.doUri( + HttpMethods.Post, + resolvedAuthOptions.authUrl!, + headers, + body, + providedQsParams as Record + ), + (err: any, result) => + err + ? (authUrlRequestCallback as RequestCallback)(err) // doUri isn’t meant to throw an error, but handle any just in case + : (authUrlRequestCallback as RequestCallback)( + result!.error, + result!.body, + result!.headers, + result!.unpacked, + result!.statusCode + ) ); } else { - this.client.http.doUri( - HttpMethods.Get, - resolvedAuthOptions.authUrl!, - authHeaders || {}, - null, - authParams, - authUrlRequestCallback as RequestCallback + Utils.whenPromiseSettles( + this.client.http.doUri(HttpMethods.Get, resolvedAuthOptions.authUrl!, authHeaders || {}, null, authParams), + (err: any, result) => + err + ? (authUrlRequestCallback as RequestCallback)(err) // doUri isn’t meant to throw an error, but handle any just in case + : (authUrlRequestCallback as RequestCallback)( + result!.error, + result!.body, + result!.headers, + result!.unpacked, + result!.statusCode + ) ); } }; diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 03c5202426..79e3e95f77 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -3,7 +3,7 @@ import Platform from 'common/platform'; import BaseRealtime from 'common/lib/client/baserealtime'; import HttpMethods from '../constants/HttpMethods'; import BaseClient from '../lib/client/baseclient'; -import { IPartialErrorInfo } from '../lib/types/errorinfo'; +import ErrorInfo, { IPartialErrorInfo } from '../lib/types/errorinfo'; import Logger from 'common/lib/util/logger'; import * as Utils from 'common/lib/util/utils'; @@ -79,37 +79,29 @@ export function appendingParams(uri: string, params: Record | null) return uri + (params ? '?' : '') + paramString(params); } -function logResponseHandler( - callback: RequestCallback | undefined, - method: HttpMethods, - uri: string, - params: Record | null -): RequestCallback { - return (err, body, headers, unpacked, statusCode) => { - if (err) { - Logger.logActionNoStrip( - Logger.LOG_MICRO, - 'Http.' + method + '()', - 'Received Error; ' + appendingParams(uri, params) + '; Error: ' + Utils.inspectError(err) - ); - } else { - Logger.logActionNoStrip( - Logger.LOG_MICRO, - 'Http.' + method + '()', - 'Received; ' + - appendingParams(uri, params) + - '; Headers: ' + - paramString(headers as Record) + - '; StatusCode: ' + - statusCode + - '; Body' + - (Platform.BufferUtils.isBuffer(body) ? ' (Base64): ' + Platform.BufferUtils.base64Encode(body) : ': ' + body) - ); - } - if (callback) { - callback(err, body, headers, unpacked, statusCode); - } - }; +function logResult(result: RequestResult, method: HttpMethods, uri: string, params: Record | null) { + if (result.error) { + Logger.logActionNoStrip( + Logger.LOG_MICRO, + 'Http.' + method + '()', + 'Received Error; ' + appendingParams(uri, params) + '; Error: ' + Utils.inspectError(result.error) + ); + } else { + Logger.logActionNoStrip( + Logger.LOG_MICRO, + 'Http.' + method + '()', + 'Received; ' + + appendingParams(uri, params) + + '; Headers: ' + + paramString(result.headers as Record) + + '; StatusCode: ' + + result.statusCode + + '; Body' + + (Platform.BufferUtils.isBuffer(result.body) + ? ' (Base64): ' + Platform.BufferUtils.base64Encode(result.body) + : ': ' + result.body) + ); + } } function logRequest(method: HttpMethods, uri: string, body: RequestBody | null, params: RequestParams) { @@ -184,15 +176,24 @@ export class Http { if (currentFallback) { if (currentFallback.validUntil > Date.now()) { /* Use stored fallback */ - this.doUri(method, uriFromHost(currentFallback.host), headers, body, params, (err, ...args) => { - if (err && this.platformHttp.shouldFallback(err as ErrnoException)) { - /* unstore the fallback and start from the top with the default sequence */ - client._currentFallback = null; - this.do(method, path, headers, body, params, callback); - return; + Utils.whenPromiseSettles( + this.doUri(method, uriFromHost(currentFallback.host), headers, body, params), + (err: any, result) => { + // doUri isn’t meant to throw an error, but handle any just in case + if (err) { + callback?.(err); + return; + } + + if (result!.error && this.platformHttp.shouldFallback(result!.error as ErrnoException)) { + /* unstore the fallback and start from the top with the default sequence */ + client._currentFallback = null; + this.do(method, path, headers, body, params, callback); + return; + } + callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode); } - callback?.(err, ...args); - }); + ); return; } else { /* Fallback expired; remove it and fallthrough to normal sequence */ @@ -204,49 +205,71 @@ export class Http { /* see if we have one or more than one host */ if (hosts.length === 1) { - this.doUri(method, uriFromHost(hosts[0]), headers, body, params, callback); + Utils.whenPromiseSettles(this.doUri(method, uriFromHost(hosts[0]), headers, body, params), (err: any, result) => + err + ? callback?.(err) // doUri isn’t meant to throw an error, but handle any just in case + : callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode) + ); return; } const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { const host = candidateHosts.shift(); - this.doUri(method, uriFromHost(host as string), headers, body, params, (err, ...args) => { - if (err && this.platformHttp.shouldFallback(err as ErrnoException) && candidateHosts.length) { - tryAHost(candidateHosts, true); - return; - } - if (persistOnSuccess) { - /* RSC15f */ - client._currentFallback = { - host: host as string, - validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, - }; + Utils.whenPromiseSettles( + this.doUri(method, uriFromHost(host as string), headers, body, params), + (err: any, result) => { + // doUri isn’t meant to throw an error, but handle any just in case + if (err) { + callback?.(err); + return; + } + + if ( + result!.error && + this.platformHttp.shouldFallback(result!.error as ErrnoException) && + candidateHosts.length + ) { + tryAHost(candidateHosts, true); + return; + } + if (persistOnSuccess) { + /* RSC15f */ + client._currentFallback = { + host: host as string, + validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, + }; + } + callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode); } - callback?.(err, ...args); - }); + ); }; tryAHost(hosts); } - doUri( + /** + * This method will not throw any errors; rather, it will communicate any error by populating the {@link RequestResult.error} property of the returned {@link RequestResult}. + */ + async doUri( method: HttpMethods, uri: string, headers: Record | null, body: RequestBody | null, - params: RequestParams, - callback?: RequestCallback | undefined - ): void { - logRequest(method, uri, body, params); + params: RequestParams + ): Promise { + try { + logRequest(method, uri, body, params); - if (Logger.shouldLog(Logger.LOG_MICRO)) { - callback = logResponseHandler(callback, method, uri, params); - } + const result = await this.platformHttp.doUri(method, uri, headers, body, params); - Utils.whenPromiseSettles(this.platformHttp.doUri(method, uri, headers, body, params), (err: any, result) => - err - ? callback?.(err) // doUri isn’t meant to throw an error, but handle any just in case - : callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode) - ); + if (Logger.shouldLog(Logger.LOG_MICRO)) { + logResult(result, method, uri, params); + } + + return result; + } catch (err) { + // Handle any unexpected error, to ensure we always meet our contract of not throwing any errors + return { error: new ErrorInfo(`Unexpected error in Http.doUri: ${Utils.inspectError(err)}`, 500, 50000) }; + } } } diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 115173d70a..47e4ee60f2 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -22,11 +22,11 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async */ function getJWT(params, callback) { var authUrl = echoServer + '/createJWT'; - http.doUri('get', authUrl, null, null, params, function (err, body) { - if (err) { - callback(err, null); + whenPromiseSettles(http.doUri('get', authUrl, null, null, params), function (err, result) { + if (result.error) { + callback(result.error, null); } - callback(null, body.toString()); + callback(null, result.body.toString()); }); } diff --git a/test/rest/message.test.js b/test/rest/message.test.js index b6cd469bef..71cb1826d4 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -157,16 +157,15 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return originalPublish.apply(channel, arguments); }; - Ably.Rest._Http.doUri = function (method, uri, headers, body, params, callback) { - originalDoUri(method, uri, headers, body, params, function (err) { - if (err) { - callback(err); - return; - } - /* Fake a publish error from realtime */ - callback({ message: 'moo', code: 50300, statusCode: 503 }); - }); + Ably.Rest._Http.doUri = async function (method, uri, headers, body, params) { + const resultPromise = originalDoUri(method, uri, headers, body, params); Ably.Rest._Http.doUri = originalDoUri; + const result = await resultPromise; + if (result.error) { + return { error: result.error }; + } + /* Fake a publish error from realtime */ + return { error: { message: 'moo', code: 50300, statusCode: 503 } }; }; await channel.publish([{ name: 'one' }, { name: 'two' }]); From f3e09ef40c7c4897e617481467a8c0a7236f4de7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 8 Feb 2024 09:35:26 -0300 Subject: [PATCH 330/468] Fix an argument name --- src/common/lib/client/rest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 20a8e9b737..1d0b508e01 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -75,13 +75,13 @@ export class Rest { headers, null, params as RequestParams, - (err, res, headers, unpacked) => { + (err, body, headers, unpacked) => { if (err) { reject(err); return; } - if (!unpacked) res = JSON.parse(res as string); - const time = (res as number[])[0]; + if (!unpacked) body = JSON.parse(body as string); + const time = (body as number[])[0]; if (!time) { reject(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); return; From 9e15e4c2ffbb03a2af36308e837fa3202e50eb1e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 15 Jan 2024 11:01:31 +0000 Subject: [PATCH 331/468] Convert Http.do to use promises --- src/common/lib/client/auth.ts | 19 +++-- src/common/lib/client/resource.ts | 15 +--- src/common/lib/client/rest.ts | 44 +++++----- src/common/types/http.ts | 137 ++++++++++++------------------ test/browser/modules.test.js | 6 +- test/rest/http.test.js | 4 +- test/rest/request.test.js | 3 +- 7 files changed, 96 insertions(+), 132 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index e46c2ba9bd..87a93e2de1 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -588,13 +588,18 @@ class Auth { 'Auth.requestToken().requestToken', 'Sending POST to ' + path + '; Token params: ' + JSON.stringify(signedTokenParams) ); - this.client.http.do( - HttpMethods.Post, - tokenUri, - requestHeaders, - JSON.stringify(signedTokenParams), - null, - tokenCb as RequestCallback + Utils.whenPromiseSettles( + this.client.http.do(HttpMethods.Post, tokenUri, requestHeaders, JSON.stringify(signedTokenParams), null), + (err: any, result) => + err + ? (tokenCb as RequestCallback)(err) // doUri isn’t meant to throw an error, but handle any just in case + : (tokenCb as RequestCallback)( + result!.error, + result!.body, + result!.headers, + result!.unpacked, + result!.statusCode + ) ); }; diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 90a114810a..129caed95f 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -12,7 +12,6 @@ import { appendingParams as urlFromPathAndParams, paramString, } from 'common/types/http'; -import { ErrnoException } from '../../types/http'; async function withAuthDetails( client: BaseClient, @@ -327,19 +326,7 @@ class Resource { ); } - type HttpResult = { - error?: ErrnoException | IPartialErrorInfo | null; - body?: unknown; - headers?: RequestCallbackHeaders; - unpacked?: boolean; - statusCode?: number; - }; - - const httpResult = await new Promise((resolve) => { - client.http.do(method, path, headers, body, params, function (error, body, headers, unpacked, statusCode) { - resolve({ error, body, headers, unpacked, statusCode }); - }); - }); + const httpResult = await client.http.do(method, path, headers, body, params); if (httpResult.error && Auth.isTokenErr(httpResult.error as ErrorInfo)) { /* token has expired, so get a new one */ diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 1d0b508e01..c7f506f5d3 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -68,30 +68,26 @@ export class Rest { const timeUri = (host: string) => { return this.client.baseUri(host) + '/time'; }; - return new Promise((resolve, reject) => { - this.client.http.do( - HttpMethods.Get, - timeUri, - headers, - null, - params as RequestParams, - (err, body, headers, unpacked) => { - if (err) { - reject(err); - return; - } - if (!unpacked) body = JSON.parse(body as string); - const time = (body as number[])[0]; - if (!time) { - reject(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); - return; - } - /* calculate time offset only once for this device by adding to the prototype */ - this.client.serverTimeOffset = time - Utils.now(); - resolve(time); - } - ); - }); + + let { error, body, unpacked } = await this.client.http.do( + HttpMethods.Get, + timeUri, + headers, + null, + params as RequestParams + ); + + if (error) { + throw error; + } + if (!unpacked) body = JSON.parse(body as string); + const time = (body as number[])[0]; + if (!time) { + throw new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500); + } + /* calculate time offset only once for this device by adding to the prototype */ + this.client.serverTimeOffset = time - Utils.now(); + return time; } async request( diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 79e3e95f77..9d50be5f0b 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -151,99 +151,74 @@ export class Http { return Defaults.getHosts(client.options); } - do( + /** + * This method will not throw any errors; rather, it will communicate any error by populating the {@link RequestResult.error} property of the returned {@link RequestResult}. + */ + async do( method: HttpMethods, path: PathParameter, headers: Record | null, body: RequestBody | null, - params: RequestParams, - callback?: RequestCallback | undefined - ): void { - /* Unlike for doUri, the presence of `this.client` here is mandatory, as it's used to generate the hosts */ - const client = this.client; - if (!client) { - throw new Error('http.do called without client'); - } - - const uriFromHost = - typeof path === 'function' - ? path - : function (host: string) { - return client.baseUri(host) + path; - }; + params: RequestParams + ): Promise { + try { + /* Unlike for doUri, the presence of `this.client` here is mandatory, as it's used to generate the hosts */ + const client = this.client; + if (!client) { + return { error: new ErrorInfo('http.do called without client', 50000, 500) }; + } - const currentFallback = client._currentFallback; - if (currentFallback) { - if (currentFallback.validUntil > Date.now()) { - /* Use stored fallback */ - Utils.whenPromiseSettles( - this.doUri(method, uriFromHost(currentFallback.host), headers, body, params), - (err: any, result) => { - // doUri isn’t meant to throw an error, but handle any just in case - if (err) { - callback?.(err); - return; - } + const uriFromHost = + typeof path === 'function' + ? path + : function (host: string) { + return client.baseUri(host) + path; + }; - if (result!.error && this.platformHttp.shouldFallback(result!.error as ErrnoException)) { - /* unstore the fallback and start from the top with the default sequence */ - client._currentFallback = null; - this.do(method, path, headers, body, params, callback); - return; - } - callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode); + const currentFallback = client._currentFallback; + if (currentFallback) { + if (currentFallback.validUntil > Date.now()) { + /* Use stored fallback */ + const result = await this.doUri(method, uriFromHost(currentFallback.host), headers, body, params); + if (result.error && this.platformHttp.shouldFallback(result.error as ErrnoException)) { + /* unstore the fallback and start from the top with the default sequence */ + client._currentFallback = null; + return this.do(method, path, headers, body, params); } - ); - return; - } else { - /* Fallback expired; remove it and fallthrough to normal sequence */ - client._currentFallback = null; + return result; + } else { + /* Fallback expired; remove it and fallthrough to normal sequence */ + client._currentFallback = null; + } } - } - const hosts = this._getHosts(client); + const hosts = this._getHosts(client); - /* see if we have one or more than one host */ - if (hosts.length === 1) { - Utils.whenPromiseSettles(this.doUri(method, uriFromHost(hosts[0]), headers, body, params), (err: any, result) => - err - ? callback?.(err) // doUri isn’t meant to throw an error, but handle any just in case - : callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode) - ); - return; - } - - const tryAHost = (candidateHosts: Array, persistOnSuccess?: boolean) => { - const host = candidateHosts.shift(); - Utils.whenPromiseSettles( - this.doUri(method, uriFromHost(host as string), headers, body, params), - (err: any, result) => { - // doUri isn’t meant to throw an error, but handle any just in case - if (err) { - callback?.(err); - return; - } + /* see if we have one or more than one host */ + if (hosts.length === 1) { + return this.doUri(method, uriFromHost(hosts[0]), headers, body, params); + } - if ( - result!.error && - this.platformHttp.shouldFallback(result!.error as ErrnoException) && - candidateHosts.length - ) { - tryAHost(candidateHosts, true); - return; - } - if (persistOnSuccess) { - /* RSC15f */ - client._currentFallback = { - host: host as string, - validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, - }; - } - callback?.(result!.error, result!.body, result!.headers, result!.unpacked, result!.statusCode); + const tryAHost = async (candidateHosts: Array, persistOnSuccess?: boolean): Promise => { + const host = candidateHosts.shift(); + const result = await this.doUri(method, uriFromHost(host as string), headers, body, params); + if (result.error && this.platformHttp.shouldFallback(result.error as ErrnoException) && candidateHosts.length) { + return tryAHost(candidateHosts, true); + } + if (persistOnSuccess) { + /* RSC15f */ + client._currentFallback = { + host: host as string, + validUntil: Date.now() + client.options.timeouts.fallbackRetryTimeout, + }; } - ); - }; - tryAHost(hosts); + return result; + }; + return tryAHost(hosts); + } catch (err) { + // Handle any unexpected error, to ensure we always meet our contract of not throwing any errors + return { error: new ErrorInfo(`Unexpected error in Http.do: ${Utils.inspectError(err)}`, 500, 50000) }; + } } /** diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index d4ab644f28..74e82ec8c9 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -394,12 +394,12 @@ function registerAblyModulesTests(helper, registerDeltaTests) { const channelName = 'channel'; const channel = rest.channels.get(channelName); const contentTypeUsedForPublishPromise = new Promise((resolve, reject) => { - rest.http.do = (method, path, headers, body, params, callback) => { + rest.http.do = async (method, path, headers, body, params) => { if (!(method == 'post' && path == `/channels/${channelName}/messages`)) { - return; + return new Promise(() => {}); } resolve(headers['content-type']); - callback(null); + return { error: null }; }; }); diff --git a/test/rest/http.test.js b/test/rest/http.test.js index f02ed488de..416d4f3617 100644 --- a/test/rest/http.test.js +++ b/test/rest/http.test.js @@ -25,7 +25,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var originalDo = rest.http.do; // Intercept Http.do with test - function testRequestHandler(method, path, headers, body, params, callback) { + async function testRequestHandler(method, path, headers, body, params) { expect('X-Ably-Version' in headers, 'Verify version header exists').to.be.ok; expect('Ably-Agent' in headers, 'Verify agent header exists').to.be.ok; @@ -47,7 +47,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { expect(headers['Ably-Agent'].indexOf('nodejs') > -1, 'Verify agent').to.be.ok; } - originalDo.call(rest.http, method, path, headers, body, params, callback); + return originalDo.call(rest.http, method, path, headers, body, params); } rest.http.do = testRequestHandler; diff --git a/test/rest/request.test.js b/test/rest/request.test.js index cfeb081b33..7aa04f9db9 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -25,7 +25,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async restTestOnJsonMsgpack('request_version', function (rest) { const version = 150; // arbitrarily chosen - function testRequestHandler(_, __, headers) { + async function testRequestHandler(_, __, headers) { try { expect('X-Ably-Version' in headers, 'Verify version header exists').to.be.ok; expect(headers['X-Ably-Version']).to.equal(version.toString(), 'Verify version number sent in request'); @@ -33,6 +33,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } catch (err) { done(err); } + return new Promise(() => {}); } rest.http.do = testRequestHandler; From b23610702e659daf1468933bea58b368e27a475e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 15 Jan 2024 10:32:04 +0000 Subject: [PATCH 332/468] Convert web HTTP `Request` to use promises --- src/platform/web/lib/http/http.ts | 72 +++++++++++++++++++------------ 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index c997562bdf..f6b64161eb 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -1,7 +1,7 @@ import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestBody, RequestCallback, RequestCallbackError, RequestParams, RequestResult } from 'common/types/http'; +import { RequestBody, RequestCallbackError, RequestParams, RequestResult } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import XHRStates from 'common/constants/XHRStates'; @@ -49,26 +49,35 @@ const Http = class { if (Platform.Config.xhrSupported && xhrRequestImplementation) { this.supportsAuthHeaders = true; - this.Request = function ( + this.Request = async function ( method: HttpMethods, uri: string, headers: Record | null, params: RequestParams, - body: RequestBody | null, - callback: RequestCallback + body: RequestBody | null ) { - const req = xhrRequestImplementation.createRequest( - uri, - headers, - params, - body, - XHRStates.REQ_SEND, - (client && client.options.timeouts) ?? null, - method - ); - req.once('complete', callback); - req.exec(); - return req; + return new Promise((resolve) => { + const req = xhrRequestImplementation.createRequest( + uri, + headers, + params, + body, + XHRStates.REQ_SEND, + (client && client.options.timeouts) ?? null, + method + ); + req.once( + 'complete', + ( + error: RequestResult['error'], + body: RequestResult['body'], + headers: RequestResult['headers'], + unpacked: RequestResult['unpacked'], + statusCode: RequestResult['statusCode'] + ) => resolve({ error, body, headers, unpacked, statusCode }) + ); + req.exec(); + }); }; if (client?.options.disableConnectivityCheck) { this.checkConnectivity = async function () { @@ -103,8 +112,20 @@ const Http = class { } } else if (Platform.Config.fetchSupported && fetchRequestImplementation) { this.supportsAuthHeaders = true; - this.Request = (method, uri, headers, params, body, callback) => { - fetchRequestImplementation(method, client ?? null, uri, headers, params, body, callback); + this.Request = async (method, uri, headers, params, body) => { + return new Promise((resolve) => { + fetchRequestImplementation( + method, + client ?? null, + uri, + headers, + params, + body, + (error, body, headers, unpacked, statusCode) => { + resolve({ error, body, headers, unpacked, statusCode }); + } + ); + }); }; this.checkConnectivity = async function () { Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl); @@ -114,11 +135,11 @@ const Http = class { return result; }; } else { - this.Request = (method, uri, headers, params, body, callback) => { + this.Request = async () => { const error = hasImplementation ? new PartialErrorInfo('no supported HTTP transports available', null, 400) : createMissingImplementationError(); - callback(error, null); + return { error }; }; } } @@ -133,11 +154,7 @@ const Http = class { if (!this.Request) { return { error: new PartialErrorInfo('Request invoked before assigned to', null, 500) }; } - return new Promise((resolve) => { - this.Request!(method, uri, headers, params, body, (error, resBody, resHeaders, unpacked, statusCode) => - resolve({ error, body: resBody, headers: resHeaders, unpacked, statusCode }) - ); - }); + return this.Request(method, uri, headers, params, body); } private Request?: ( @@ -145,9 +162,8 @@ const Http = class { uri: string, headers: Record | null, params: RequestParams, - body: RequestBody | null, - callback: RequestCallback - ) => void; + body: RequestBody | null + ) => Promise; checkConnectivity?: () => Promise = undefined; From 3004cccf7111951e2a89c6dd3be3ac8895e64697 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 15 Jan 2024 15:45:07 +0000 Subject: [PATCH 333/468] Convert fetchRequest to use promises Resolves #1533. --- scripts/moduleReport.ts | 2 +- src/platform/web/lib/http/http.ts | 14 +-- .../web/lib/http/request/fetchrequest.ts | 95 +++++++++++-------- 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 440ab77bcf..80665b205f 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -6,7 +6,7 @@ import { gzip } from 'zlib'; import Table from 'cli-table'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) -const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 95, gzip: 29 }; +const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 96, gzip: 29 }; const baseClientNames = ['BaseRest', 'BaseRealtime']; diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index f6b64161eb..88d1f88fd8 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -113,19 +113,7 @@ const Http = class { } else if (Platform.Config.fetchSupported && fetchRequestImplementation) { this.supportsAuthHeaders = true; this.Request = async (method, uri, headers, params, body) => { - return new Promise((resolve) => { - fetchRequestImplementation( - method, - client ?? null, - uri, - headers, - params, - body, - (error, body, headers, unpacked, statusCode) => { - resolve({ error, body, headers, unpacked, statusCode }); - } - ); - }); + return fetchRequestImplementation(method, client ?? null, uri, headers, params, body); }; this.checkConnectivity = async function () { Logger.logAction(Logger.LOG_MICRO, '(Fetch)Http.checkConnectivity()', 'Sending; ' + connectivityCheckUrl); diff --git a/src/platform/web/lib/http/request/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts index 9bd0ce42b6..cbe6b826b9 100644 --- a/src/platform/web/lib/http/request/fetchrequest.ts +++ b/src/platform/web/lib/http/request/fetchrequest.ts @@ -1,7 +1,13 @@ import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestBody, RequestCallback, RequestCallbackHeaders, RequestParams } from 'common/types/http'; +import { + RequestBody, + RequestCallbackError, + RequestCallbackHeaders, + RequestParams, + RequestResult, +} from 'common/types/http'; import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import * as Utils from 'common/lib/util/utils'; @@ -26,27 +32,29 @@ function convertHeaders(headers: Headers) { return result; } -export default function fetchRequest( +export default async function fetchRequest( method: HttpMethods, client: BaseClient | null, uri: string, headers: Record | null, params: RequestParams, - body: RequestBody | null, - callback: RequestCallback -) { + body: RequestBody | null +): Promise { const fetchHeaders = new Headers(headers || {}); const _method = method ? method.toUpperCase() : Utils.isEmptyArg(body) ? 'GET' : 'POST'; const controller = new AbortController(); - const timeout = setTimeout( - () => { - controller.abort(); - callback(new PartialErrorInfo('Request timed out', null, 408)); - }, - client ? client.options.timeouts.httpRequestTimeout : Defaults.TIMEOUTS.httpRequestTimeout - ); + let timeout: ReturnType; // This way we don’t have to worry about the fact that the TypeScript compiler is — for reasons I haven’t looked into — picking up the signature of the Node version of setTimeout, which has a different return type to the web one + const timeoutPromise: Promise = new Promise((resolve) => { + timeout = setTimeout( + () => { + controller.abort(); + resolve({ error: new PartialErrorInfo('Request timed out', null, 408) }); + }, + client ? client.options.timeouts.httpRequestTimeout : Defaults.TIMEOUTS.httpRequestTimeout + ); + }); const requestInit: RequestInit = { method: _method, @@ -58,38 +66,43 @@ export default function fetchRequest( requestInit.credentials = fetchHeaders.has('authorization') ? 'include' : 'same-origin'; } - Utils.getGlobalObject() - .fetch(uri + '?' + new URLSearchParams(params || {}), requestInit) - .then((res) => { - clearTimeout(timeout); + const resultPromise = (async (): Promise => { + try { + const res = await Utils.getGlobalObject().fetch(uri + '?' + new URLSearchParams(params || {}), requestInit); + + clearTimeout(timeout!); + const contentType = res.headers.get('Content-Type'); - let prom; + let body; if (contentType && contentType.indexOf('application/x-msgpack') > -1) { - prom = res.arrayBuffer(); + body = await res.arrayBuffer(); } else if (contentType && contentType.indexOf('application/json') > -1) { - prom = res.json(); + body = await res.json(); } else { - prom = res.text(); + body = await res.text(); } - prom.then((body) => { - const unpacked = !!contentType && contentType.indexOf('application/x-msgpack') === -1; - const headers = convertHeaders(res.headers); - if (!res.ok) { - const err = - getAblyError(body, res.headers) || - new PartialErrorInfo( - 'Error response received from server: ' + res.status + ' body was: ' + Platform.Config.inspect(body), - null, - res.status - ); - callback(err, body, headers, unpacked, res.status); - } else { - callback(null, body, headers, unpacked, res.status); - } - }); - }) - .catch((err) => { - clearTimeout(timeout); - callback(err); - }); + + const unpacked = !!contentType && contentType.indexOf('application/x-msgpack') === -1; + const headers = convertHeaders(res.headers); + + if (!res.ok) { + const error = + getAblyError(body, res.headers) || + new PartialErrorInfo( + 'Error response received from server: ' + res.status + ' body was: ' + Platform.Config.inspect(body), + null, + res.status + ); + + return { error, body, headers, unpacked, statusCode: res.status }; + } else { + return { error: null, body, headers, unpacked, statusCode: res.status }; + } + } catch (error) { + clearTimeout(timeout!); + return { error: error as RequestCallbackError }; + } + })(); + + return Promise.race([timeoutPromise, resultPromise]); } From d0568d7cd547d29961045a5a57014e0cb6e6302e Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 Jan 2024 17:28:13 +0000 Subject: [PATCH 334/468] Remove RequestCallback type assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type assertions masked quite a few inconsistencies, which I’ve attempted to handle here. --- src/common/lib/client/auth.ts | 83 +++++++++++++++-------------------- src/common/platform.ts | 2 +- src/common/types/http.ts | 7 --- 3 files changed, 36 insertions(+), 56 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 87a93e2de1..933260049b 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -2,14 +2,14 @@ import Logger from '../util/logger'; import * as Utils from '../util/utils'; import Multicaster, { MulticasterInstance } from '../util/multicaster'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; -import { ErrnoException, RequestCallback, RequestParams } from '../../types/http'; +import { RequestCallbackError, RequestParams, RequestResult } from '../../types/http'; import * as API from '../../../../ably'; import BaseClient from './baseclient'; import BaseRealtime from './baserealtime'; import ClientOptions from '../../types/ClientOptions'; import HttpMethods from '../../constants/HttpMethods'; import HttpStatusCodes from 'common/constants/HttpStatusCodes'; -import Platform from '../../platform'; +import Platform, { Bufferlike } from '../../platform'; import Defaults from '../util/defaults'; type BatchResult = API.BatchResult; @@ -395,7 +395,7 @@ class Auth { let tokenRequestCallback: ( data: API.TokenParams, callback: ( - error: API.ErrorInfo | string | null, + error: API.ErrorInfo | RequestCallbackError | string | null, tokenRequestOrDetails: API.TokenDetails | API.TokenRequest | string | null, contentType?: string ) => void @@ -429,28 +429,38 @@ class Auth { } /* RSA8c2 */ const authParams = Utils.mixin({}, resolvedAuthOptions.authParams || {}, params) as RequestParams; - const authUrlRequestCallback = function ( - err: ErrorInfo, - body: string, - headers: Record, - unpacked: any - ) { - let contentType; - if (err) { + const authUrlRequestCallback = function (result: RequestResult) { + let body = (result.body ?? null) as string | Bufferlike | API.TokenDetails | API.TokenRequest | null; + + let contentType: string | null = null; + if (result.error) { Logger.logAction( Logger.LOG_MICRO, 'Auth.requestToken().tokenRequestCallback', - 'Received Error: ' + Utils.inspectError(err) + 'Received Error: ' + Utils.inspectError(result.error) ); } else { - contentType = headers['content-type']; + const contentTypeHeaderOrHeaders = result.headers!['content-type'] ?? null; + if (Utils.isArray(contentTypeHeaderOrHeaders)) { + // Combine multiple header values into a comma-separated list per https://datatracker.ietf.org/doc/html/rfc9110#section-5.2; see https://github.com/ably/ably-js/issues/1616 for doing this consistently across the codebase. + contentType = contentTypeHeaderOrHeaders.join(', '); + } else { + contentType = contentTypeHeaderOrHeaders; + } Logger.logAction( Logger.LOG_MICRO, 'Auth.requestToken().tokenRequestCallback', 'Received; content-type: ' + contentType + '; body: ' + Utils.inspectBody(body) ); } - if (err || unpacked) return cb(err, body); + if (result.error) { + cb(result.error, null); + return; + } + if (result.unpacked) { + cb(null, body as Exclude); + return; + } if (Platform.BufferUtils.isBuffer(body)) body = body.toString(); if (!contentType) { cb(new ErrorInfo('authUrl response is missing a content-type header', 40170, 401), null); @@ -472,12 +482,12 @@ class Auth { return; } if (json) { - if (body.length > MAX_TOKEN_LENGTH) { + if ((body as string).length > MAX_TOKEN_LENGTH) { cb(new ErrorInfo('authUrl response exceeded max permitted length', 40170, 401), null); return; } try { - body = JSON.parse(body); + body = JSON.parse(body as string); } catch (e) { cb( new ErrorInfo( @@ -490,7 +500,7 @@ class Auth { return; } } - cb(null, body, contentType); + cb(null, body as Exclude, contentType); }; Logger.logAction( Logger.LOG_MICRO, @@ -517,28 +527,16 @@ class Auth { ), (err: any, result) => err - ? (authUrlRequestCallback as RequestCallback)(err) // doUri isn’t meant to throw an error, but handle any just in case - : (authUrlRequestCallback as RequestCallback)( - result!.error, - result!.body, - result!.headers, - result!.unpacked, - result!.statusCode - ) + ? authUrlRequestCallback(err) // doUri isn’t meant to throw an error, but handle any just in case + : authUrlRequestCallback(result!) ); } else { Utils.whenPromiseSettles( this.client.http.doUri(HttpMethods.Get, resolvedAuthOptions.authUrl!, authHeaders || {}, null, authParams), (err: any, result) => err - ? (authUrlRequestCallback as RequestCallback)(err) // doUri isn’t meant to throw an error, but handle any just in case - : (authUrlRequestCallback as RequestCallback)( - result!.error, - result!.body, - result!.headers, - result!.unpacked, - result!.statusCode - ) + ? authUrlRequestCallback(err) // doUri isn’t meant to throw an error, but handle any just in case + : authUrlRequestCallback(result!) ); } }; @@ -568,12 +566,7 @@ class Auth { const tokenRequest = ( signedTokenParams: Record, - tokenCb: ( - err: ErrorInfo | ErrnoException | null, - tokenResponse?: API.TokenDetails | string, - headers?: Record, - unpacked?: boolean - ) => void + tokenCb: (err: RequestCallbackError | null, tokenResponse?: API.TokenDetails | string, unpacked?: boolean) => void ) => { const keyName = signedTokenParams.keyName, path = '/keys/' + keyName + '/requestToken', @@ -592,14 +585,8 @@ class Auth { this.client.http.do(HttpMethods.Post, tokenUri, requestHeaders, JSON.stringify(signedTokenParams), null), (err: any, result) => err - ? (tokenCb as RequestCallback)(err) // doUri isn’t meant to throw an error, but handle any just in case - : (tokenCb as RequestCallback)( - result!.error, - result!.body, - result!.headers, - result!.unpacked, - result!.statusCode - ) + ? tokenCb(err) // doUri isn’t meant to throw an error, but handle any just in case + : tokenCb(result!.error, result!.body as API.TokenDetails | string | undefined, result!.unpacked) ); }; @@ -689,7 +676,7 @@ class Auth { return; } /* it's a token request, so make the request */ - tokenRequest(tokenRequestOrDetails, function (err, tokenResponse, headers, unpacked) { + tokenRequest(tokenRequestOrDetails, function (err, tokenResponse, unpacked) { if (err) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/platform.ts b/src/common/platform.ts index 725d8beb66..5821fde231 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -10,7 +10,7 @@ import { IUntypedCryptoStatic } from '../common/types/ICryptoStatic'; import TransportName from './constants/TransportName'; import { VcdiffDecoder } from './lib/types/message'; -type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; +export type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; type ToBufferOutput = WebBufferUtils.ToBufferOutput | NodeBufferUtils.ToBufferOutput; diff --git a/src/common/types/http.ts b/src/common/types/http.ts index 9d50be5f0b..d4e6a42d7c 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -10,13 +10,6 @@ import * as Utils from 'common/lib/util/utils'; export type PathParameter = string | ((host: string) => string); export type RequestCallbackHeaders = Partial>; export type RequestCallbackError = ErrnoException | IPartialErrorInfo; -export type RequestCallback = ( - error: RequestCallbackError | null, - body?: unknown, - headers?: RequestCallbackHeaders, - unpacked?: boolean, - statusCode?: number -) => void; /** * The `body`, `headers`, `unpacked`, and `statusCode` properties of a `RequestResult` may be populated even if its `error` property is non-null. From 2cf5a6b510b551de9d3a853cb3aba98df5feb3d9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 30 Jan 2024 17:54:38 +0000 Subject: [PATCH 335/468] Rename types that refer to RequestCallback We removed RequestCallback in d0568d7. --- src/common/lib/client/auth.ts | 6 +++--- src/common/lib/client/paginatedresource.ts | 8 ++++---- src/common/lib/client/resource.ts | 11 +++-------- src/common/types/http.ts | 10 +++++----- src/platform/nodejs/lib/util/http.ts | 4 ++-- src/platform/web/lib/http/http.ts | 4 ++-- src/platform/web/lib/http/request/fetchrequest.ts | 12 +++--------- 7 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index 933260049b..a211fc2ef1 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -2,7 +2,7 @@ import Logger from '../util/logger'; import * as Utils from '../util/utils'; import Multicaster, { MulticasterInstance } from '../util/multicaster'; import ErrorInfo, { IPartialErrorInfo } from '../types/errorinfo'; -import { RequestCallbackError, RequestParams, RequestResult } from '../../types/http'; +import { RequestResultError, RequestParams, RequestResult } from '../../types/http'; import * as API from '../../../../ably'; import BaseClient from './baseclient'; import BaseRealtime from './baserealtime'; @@ -395,7 +395,7 @@ class Auth { let tokenRequestCallback: ( data: API.TokenParams, callback: ( - error: API.ErrorInfo | RequestCallbackError | string | null, + error: API.ErrorInfo | RequestResultError | string | null, tokenRequestOrDetails: API.TokenDetails | API.TokenRequest | string | null, contentType?: string ) => void @@ -566,7 +566,7 @@ class Auth { const tokenRequest = ( signedTokenParams: Record, - tokenCb: (err: RequestCallbackError | null, tokenResponse?: API.TokenDetails | string, unpacked?: boolean) => void + tokenCb: (err: RequestResultError | null, tokenResponse?: API.TokenDetails | string, unpacked?: boolean) => void ) => { const keyName = signedTokenParams.keyName, path = '/keys/' + keyName + '/requestToken', diff --git a/src/common/lib/client/paginatedresource.ts b/src/common/lib/client/paginatedresource.ts index fb56ea0732..b36e7be21a 100644 --- a/src/common/lib/client/paginatedresource.ts +++ b/src/common/lib/client/paginatedresource.ts @@ -3,9 +3,9 @@ import Logger from '../util/logger'; import Resource, { ResourceResult } from './resource'; import { IPartialErrorInfo } from '../types/errorinfo'; import BaseClient from './baseclient'; -import { RequestBody, RequestCallbackHeaders } from 'common/types/http'; +import { RequestBody, ResponseHeaders } from 'common/types/http'; -export type BodyHandler = (body: unknown, headers: RequestCallbackHeaders, unpacked?: boolean) => Promise; +export type BodyHandler = (body: unknown, headers: ResponseHeaders, unpacked?: boolean) => Promise; function getRelParams(linkUrl: string) { const urlMatch = linkUrl.match(/^\.\/(\w+)\?(.*)$/); @@ -176,14 +176,14 @@ export class PaginatedResult { export class HttpPaginatedResponse extends PaginatedResult { statusCode: number; success: boolean; - headers: RequestCallbackHeaders; + headers: ResponseHeaders; errorCode?: number | null; errorMessage?: string | null; constructor( resource: PaginatedResource, items: T[], - headers: RequestCallbackHeaders, + headers: ResponseHeaders, statusCode: number, relParams: any, err: IPartialErrorInfo | null diff --git a/src/common/lib/client/resource.ts b/src/common/lib/client/resource.ts index 129caed95f..3ffd727603 100644 --- a/src/common/lib/client/resource.ts +++ b/src/common/lib/client/resource.ts @@ -6,16 +6,11 @@ import HttpMethods from '../../constants/HttpMethods'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import BaseClient from './baseclient'; import { MsgPack } from 'common/types/msgpack'; -import { - RequestBody, - RequestCallbackHeaders, - appendingParams as urlFromPathAndParams, - paramString, -} from 'common/types/http'; +import { RequestBody, ResponseHeaders, appendingParams as urlFromPathAndParams, paramString } from 'common/types/http'; async function withAuthDetails( client: BaseClient, - headers: RequestCallbackHeaders | undefined, + headers: ResponseHeaders | undefined, params: Record, opCallback: Function ): Promise> { @@ -102,7 +97,7 @@ function logResult(result: ResourceResult, method: HttpMethods, path: stri export interface ResourceResponse { body?: T; - headers?: RequestCallbackHeaders; + headers?: ResponseHeaders; unpacked?: boolean; statusCode?: number; } diff --git a/src/common/types/http.ts b/src/common/types/http.ts index d4e6a42d7c..92cc99f1a5 100644 --- a/src/common/types/http.ts +++ b/src/common/types/http.ts @@ -8,16 +8,16 @@ import Logger from 'common/lib/util/logger'; import * as Utils from 'common/lib/util/utils'; export type PathParameter = string | ((host: string) => string); -export type RequestCallbackHeaders = Partial>; -export type RequestCallbackError = ErrnoException | IPartialErrorInfo; +export type ResponseHeaders = Partial>; +export type RequestResultError = ErrnoException | IPartialErrorInfo; /** * The `body`, `headers`, `unpacked`, and `statusCode` properties of a `RequestResult` may be populated even if its `error` property is non-null. */ export type RequestResult = { - error: RequestCallbackError | null; + error: RequestResultError | null; body?: unknown; - headers?: RequestCallbackHeaders; + headers?: ResponseHeaders; unpacked?: boolean; statusCode?: number; }; @@ -55,7 +55,7 @@ export interface IPlatformHttp { /** * @param error An error from the {@link RequestResult.error} property of a result returned by {@link doUri}. */ - shouldFallback(error: RequestCallbackError): boolean; + shouldFallback(error: RequestResultError): boolean; } export function paramString(params: Record | null) { diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 2306d50856..9db64b7ed9 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -5,7 +5,7 @@ import { ErrnoException, RequestBody, IPlatformHttpStatic, - RequestCallbackError, + RequestResultError, RequestParams, RequestResult, } from '../../../../common/types/http'; @@ -158,7 +158,7 @@ const Http: IPlatformHttpStatic = class { return !error && (body as Buffer | string)?.toString().trim() === 'yes'; }; - shouldFallback(err: RequestCallbackError) { + shouldFallback(err: RequestResultError) { const { code, statusCode } = err as ErrnoException; return ( code === 'ENETUNREACH' || diff --git a/src/platform/web/lib/http/http.ts b/src/platform/web/lib/http/http.ts index 88d1f88fd8..d42392ebe4 100644 --- a/src/platform/web/lib/http/http.ts +++ b/src/platform/web/lib/http/http.ts @@ -1,7 +1,7 @@ import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { RequestBody, RequestCallbackError, RequestParams, RequestResult } from 'common/types/http'; +import { RequestBody, RequestResultError, RequestParams, RequestResult } from 'common/types/http'; import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import XHRStates from 'common/constants/XHRStates'; @@ -158,7 +158,7 @@ const Http = class { supportsAuthHeaders = false; supportsLinkHeaders = false; - shouldFallback(errorInfo: RequestCallbackError) { + shouldFallback(errorInfo: RequestResultError) { const statusCode = errorInfo.statusCode as number; /* 400 + no code = a generic xhr onerror. Browser doesn't give us enough * detail to know whether it's fallback-fixable, but it may be (eg if a diff --git a/src/platform/web/lib/http/request/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts index cbe6b826b9..22cee07069 100644 --- a/src/platform/web/lib/http/request/fetchrequest.ts +++ b/src/platform/web/lib/http/request/fetchrequest.ts @@ -1,13 +1,7 @@ import HttpMethods from 'common/constants/HttpMethods'; import BaseClient from 'common/lib/client/baseclient'; import ErrorInfo, { PartialErrorInfo } from 'common/lib/types/errorinfo'; -import { - RequestBody, - RequestCallbackError, - RequestCallbackHeaders, - RequestParams, - RequestResult, -} from 'common/types/http'; +import { RequestBody, RequestResultError, ResponseHeaders, RequestParams, RequestResult } from 'common/types/http'; import Platform from 'common/platform'; import Defaults from 'common/lib/util/defaults'; import * as Utils from 'common/lib/util/utils'; @@ -23,7 +17,7 @@ function getAblyError(responseBody: unknown, headers: Headers) { } function convertHeaders(headers: Headers) { - const result: RequestCallbackHeaders = {}; + const result: ResponseHeaders = {}; headers.forEach((value, key) => { result[key] = value; @@ -100,7 +94,7 @@ export default async function fetchRequest( } } catch (error) { clearTimeout(timeout!); - return { error: error as RequestCallbackError }; + return { error: error as RequestResultError }; } })(); From 72c131e6876c3a0d571abf0dcd20cfd37665c5d0 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 19 Feb 2024 11:57:35 +0000 Subject: [PATCH 336/468] Adds `exports."./react"` to package.json Also removes "exports" property from package.react.json in react-hooks folder that is copied to `react` build folder. This file now only has `"main": "./cjs/index.js"`, which, for not obvious reason to me, needs to be present in order for `npx attw` check to not show "Resolution failed" error for node10 for "ably/react" path. Resolves #1622 --- package.json | 4 ++++ src/platform/react-hooks/res/package.react.json | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 55068e3945..cbfb751945 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,10 @@ "./modules": { "types": "./modules.d.ts", "default": "./build/modules/index.js" + }, + "./react": { + "require": "./react/cjs/index.js", + "import": "./react/mjs/index.js" } }, "typings": "./ably.d.ts", diff --git a/src/platform/react-hooks/res/package.react.json b/src/platform/react-hooks/res/package.react.json index 09e6f67a7b..d1abb09572 100644 --- a/src/platform/react-hooks/res/package.react.json +++ b/src/platform/react-hooks/res/package.react.json @@ -1,7 +1,3 @@ { - "main": "./cjs/index.js", - "exports": { - "require": "./cjs", - "import": "./mjs" - } + "main": "./cjs/index.js" } From ca887357c8982de126a0a9bbd409019b60511ec2 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 19 Feb 2024 12:38:44 +0000 Subject: [PATCH 337/468] Moves 'handler' function to 'Http._handler' in nodejs http util class --- src/platform/nodejs/lib/util/http.ts | 75 +++++++++++++++------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/platform/nodejs/lib/util/http.ts b/src/platform/nodejs/lib/util/http.ts index 9db64b7ed9..8aff0c2524 100644 --- a/src/platform/nodejs/lib/util/http.ts +++ b/src/platform/nodejs/lib/util/http.ts @@ -34,38 +34,6 @@ import { createMissingModuleError, shallowEquals } from 'common/lib/util/utils'; const globalAgentPool: Array<{ options: RestAgentOptions; agents: Agents }> = []; -const handler = function (uri: string, params: unknown, client: BaseClient | null) { - return function (err: ErrnoException | null, response?: Response, body?: unknown) { - if (err) { - return { error: err }; - } - const statusCode = (response as Response).statusCode, - headers = (response as Response).headers; - if (statusCode >= 300) { - switch (headers['content-type']) { - case 'application/json': - body = JSON.parse(body as string); - break; - case 'application/x-msgpack': - if (!client?._MsgPack) { - return { error: createMissingModuleError('MsgPack') }; - } - body = client._MsgPack.decode(body as Buffer); - } - const error = (body as { error: ErrorInfo }).error - ? ErrorInfo.fromValues((body as { error: ErrorInfo }).error) - : new ErrorInfo( - (headers['x-ably-errormessage'] as string) || - 'Error response received from server: ' + statusCode + ' body was: ' + Platform.Config.inspect(body), - Number(headers['x-ably-errorcode']), - statusCode - ); - return { error, body, headers, unpacked: true, statusCode }; - } - return { error: null, body, headers, unpacked: false, statusCode }; - }; -}; - const Http: IPlatformHttpStatic = class { static methods = [HttpMethods.Get, HttpMethods.Delete, HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch]; static methodsWithoutBody = [HttpMethods.Get, HttpMethods.Delete]; @@ -127,12 +95,12 @@ const Http: IPlatformHttpStatic = class { try { const res = await (got[method](doOptions) as CancelableRequest); - return handler(uri, params, this.client)(null, res, res.body); + return this._handler(null, res, res.body); } catch (err) { if (err instanceof got.HTTPError) { - return handler(uri, params, this.client)(null, err.response, err.response.body); + return this._handler(null, err.response, err.response.body); } - return handler(uri, params, this.client)(err as ErrnoException); + return this._handler(err as ErrnoException); } } @@ -172,6 +140,43 @@ const Http: IPlatformHttpStatic = class { (statusCode >= 500 && statusCode <= 504) ); } + + private _handler(err: ErrnoException | null, response?: Response, body?: unknown) { + if (err) { + return { error: err }; + } + + const statusCode = (response as Response).statusCode, + headers = (response as Response).headers; + + if (statusCode >= 300) { + switch (headers['content-type']) { + case 'application/json': + body = JSON.parse(body as string); + break; + + case 'application/x-msgpack': + if (!this.client?._MsgPack) { + return { error: createMissingModuleError('MsgPack') }; + } + body = this.client._MsgPack.decode(body as Buffer); + break; + } + + const error = (body as { error: ErrorInfo }).error + ? ErrorInfo.fromValues((body as { error: ErrorInfo }).error) + : new ErrorInfo( + (headers['x-ably-errormessage'] as string) || + 'Error response received from server: ' + statusCode + ' body was: ' + Platform.Config.inspect(body), + Number(headers['x-ably-errorcode']), + statusCode + ); + + return { error, body, headers, unpacked: true, statusCode }; + } + + return { error: null, body, headers, unpacked: false, statusCode }; + } }; export default Http; From 7db6fe2d0d7851ad3dd22ac4c042a28f5e925745 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 05:56:06 +0000 Subject: [PATCH 338/468] `npm init playwright@latest -- --ct` in `test/package/browser/template` --- test/package/browser/template/.gitignore | 5 + .../browser/template/package-lock.json | 2912 ++++++++++++++++- test/package/browser/template/package.json | 5 +- .../browser/template/playwright-ct.config.ts | 46 + .../browser/template/playwright/index.html | 12 + .../browser/template/playwright/index.tsx | 2 + 6 files changed, 2823 insertions(+), 159 deletions(-) create mode 100644 test/package/browser/template/.gitignore create mode 100644 test/package/browser/template/playwright-ct.config.ts create mode 100644 test/package/browser/template/playwright/index.html create mode 100644 test/package/browser/template/playwright/index.tsx diff --git a/test/package/browser/template/.gitignore b/test/package/browser/template/.gitignore new file mode 100644 index 0000000000..68c5d18f00 --- /dev/null +++ b/test/package/browser/template/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/test/package/browser/template/package-lock.json b/test/package/browser/template/package-lock.json index 590e78aaa9..3368745aa1 100644 --- a/test/package/browser/template/package-lock.json +++ b/test/package/browser/template/package-lock.json @@ -9,15 +9,413 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { + "@playwright/experimental-ct-react": "^1.41.2", "@playwright/test": "^1.39.0", "@tsconfig/node16": "^16.1.1", "@types/express": "^4.17.20", + "@types/node": "^20.11.19", "esbuild": "^0.19.5", "express": "^4.18.2", "ts-node": "^10.9.1", "typescript": "^5.2.2" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -211,133 +609,907 @@ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", "cpu": [ - "mips64el" + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", + "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", + "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", + "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", + "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", + "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", + "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", + "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", + "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", + "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", + "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@playwright/experimental-ct-core": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.41.2.tgz", + "integrity": "sha512-JtW7gjmBCeHWKmZcOPuoJ15i2ergVX9PnyYUrAvBbWL8l4X9B7Sblro8nE49hO9baANfEGl4BbRUaj+pA67F6w==", + "dev": true, + "dependencies": { + "playwright": "1.41.2", + "playwright-core": "1.41.2", + "vite": "^4.4.12" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/playwright": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", + "dev": true, + "dependencies": { + "playwright-core": "1.41.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/playwright-core": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@playwright/experimental-ct-core/node_modules/vite": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/@playwright/experimental-ct-react": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.41.2.tgz", + "integrity": "sha512-BMcxh2DM3t8nMgUJKWMWAXGIzcYY/4w1ibckGP48edHU/cmZCbI5W7mUXfxMhYCt4mQJ1NuYcFIg5Jt+QVIuRQ==", + "dev": true, + "dependencies": { + "@playwright/experimental-ct-core": "1.41.2", + "@vitejs/plugin-react": "^4.0.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/test": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "dev": true, + "dependencies": { + "playwright": "1.39.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "cpu": [ + "arm64" ], "dev": true, "optional": true, "os": [ - "linux" + "darwin" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", - "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", "cpu": [ - "ppc64" + "x64" ], "dev": true, "optional": true, "os": [ - "linux" + "darwin" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", - "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", "cpu": [ - "riscv64" + "arm" ], "dev": true, "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", - "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", - "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "optional": true, "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", - "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", "cpu": [ - "x64" + "riscv64" ], "dev": true, "optional": true, "os": [ - "netbsd" + "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", - "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", "cpu": [ "x64" ], "dev": true, "optional": true, "os": [ - "openbsd" + "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", - "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", "cpu": [ "x64" ], "dev": true, "optional": true, "os": [ - "sunos" + "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", - "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", "cpu": [ "arm64" ], @@ -346,14 +1518,12 @@ "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", - "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", "cpu": [ "ia32" ], @@ -362,14 +1532,12 @@ "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", - "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", "cpu": [ "x64" ], @@ -378,49 +1546,7 @@ "os": [ "win32" ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@playwright/test": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", - "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", - "dev": true, - "dependencies": { - "playwright": "1.39.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } + "peer": true }, "node_modules/@tsconfig/node10": { "version": "1.0.9", @@ -446,6 +1572,47 @@ "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", "dev": true }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/body-parser": { "version": "1.19.4", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", @@ -465,6 +1632,13 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "peer": true + }, "node_modules/@types/express": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", @@ -502,9 +1676,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", - "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -543,6 +1717,25 @@ "@types/node": "*" } }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -577,6 +1770,18 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -613,6 +1818,38 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -636,6 +1873,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -657,6 +1943,12 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -735,6 +2027,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, + "node_modules/electron-to-chromium": { + "version": "1.4.677", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.677.tgz", + "integrity": "sha512-erDa3CaDzwJOpyvfKhOiJjBVNnMM0qxHq47RheVVwsSQrgBA9ZSGV9kdaOfZDPXcHzhG7lBxhj6A7KvfLJBd6Q==", + "dev": true + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -781,12 +2079,30 @@ "@esbuild/win32-x64": "0.19.5" } }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -897,6 +2213,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -912,6 +2237,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -924,6 +2258,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", @@ -1012,7 +2355,46 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 0.10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/make-error": { @@ -1084,6 +2466,24 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1093,6 +2493,12 @@ "node": ">= 0.6" } }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -1129,6 +2535,12 @@ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/playwright": { "version": "1.39.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", @@ -1159,6 +2571,34 @@ "node": ">=16" } }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1211,6 +2651,48 @@ "node": ">= 0.8" } }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1237,6 +2719,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -1317,6 +2808,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1326,6 +2826,27 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1425,41 +2946,443 @@ "node": ">= 0.8" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", + "dev": true, + "peer": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + } + }, + "@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true + }, + "@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "requires": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "requires": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + } + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dev": true, - "engines": { - "node": ">= 0.4.0" + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", "dev": true, - "engines": { - "node": ">= 0.8" + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dev": true, - "engines": { - "node": ">=6" + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" } - } - }, - "dependencies": { + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1623,12 +3546,29 @@ "dev": true, "optional": true }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -1645,6 +3585,250 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@playwright/experimental-ct-core": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.41.2.tgz", + "integrity": "sha512-JtW7gjmBCeHWKmZcOPuoJ15i2ergVX9PnyYUrAvBbWL8l4X9B7Sblro8nE49hO9baANfEGl4BbRUaj+pA67F6w==", + "dev": true, + "requires": { + "playwright": "1.41.2", + "playwright-core": "1.41.2", + "vite": "^4.4.12" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "dev": true, + "optional": true + }, + "esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "playwright": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.41.2" + } + }, + "playwright-core": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", + "dev": true + }, + "rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "vite": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "dev": true, + "requires": { + "esbuild": "^0.18.10", + "fsevents": "~2.3.2", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + } + } + } + }, + "@playwright/experimental-ct-react": { + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.41.2.tgz", + "integrity": "sha512-BMcxh2DM3t8nMgUJKWMWAXGIzcYY/4w1ibckGP48edHU/cmZCbI5W7mUXfxMhYCt4mQJ1NuYcFIg5Jt+QVIuRQ==", + "dev": true, + "requires": { + "@playwright/experimental-ct-core": "1.41.2", + "@vitejs/plugin-react": "^4.0.0" + } + }, "@playwright/test": { "version": "1.39.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", @@ -1654,6 +3838,110 @@ "playwright": "1.39.0" } }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "dev": true, + "optional": true, + "peer": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", + "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "dev": true, + "optional": true, + "peer": true + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1678,6 +3966,47 @@ "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", "dev": true }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, "@types/body-parser": { "version": "1.19.4", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", @@ -1697,6 +4026,13 @@ "@types/node": "*" } }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "peer": true + }, "@types/express": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", @@ -1734,9 +4070,9 @@ "dev": true }, "@types/node": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", - "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -1775,6 +4111,19 @@ "@types/node": "*" } }, + "@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dev": true, + "requires": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1797,6 +4146,15 @@ "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1829,6 +4187,18 @@ "unpipe": "1.0.0" } }, + "browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1846,6 +4216,38 @@ "set-function-length": "^1.1.1" } }, + "caniuse-lite": { + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -1861,6 +4263,12 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -1923,6 +4331,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, + "electron-to-chromium": { + "version": "1.4.677", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.677.tgz", + "integrity": "sha512-erDa3CaDzwJOpyvfKhOiJjBVNnMM0qxHq47RheVVwsSQrgBA9ZSGV9kdaOfZDPXcHzhG7lBxhj6A7KvfLJBd6Q==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1959,12 +4373,24 @@ "@esbuild/win32-x64": "0.19.5" } }, + "escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2050,6 +4476,12 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, "get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -2062,6 +4494,12 @@ "hasown": "^2.0.0" } }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2071,6 +4509,12 @@ "get-intrinsic": "^1.1.3" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, "has-property-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", @@ -2135,6 +4579,33 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2186,12 +4657,24 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, "object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -2219,6 +4702,12 @@ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "playwright": { "version": "1.39.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", @@ -2235,6 +4724,17 @@ "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", "dev": true }, + "postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2272,6 +4772,36 @@ "unpipe": "1.0.0" } }, + "react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true + }, + "rollup": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", + "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "dev": true, + "peer": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.12.0", + "@rollup/rollup-android-arm64": "4.12.0", + "@rollup/rollup-darwin-arm64": "4.12.0", + "@rollup/rollup-darwin-x64": "4.12.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", + "@rollup/rollup-linux-arm64-gnu": "4.12.0", + "@rollup/rollup-linux-arm64-musl": "4.12.0", + "@rollup/rollup-linux-riscv64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-gnu": "4.12.0", + "@rollup/rollup-linux-x64-musl": "4.12.0", + "@rollup/rollup-win32-arm64-msvc": "4.12.0", + "@rollup/rollup-win32-ia32-msvc": "4.12.0", + "@rollup/rollup-win32-x64-msvc": "4.12.0", + "@types/estree": "1.0.5", + "fsevents": "~2.3.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2284,6 +4814,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, "send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -2354,12 +4890,33 @@ "object-inspect": "^1.9.0" } }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2423,6 +4980,16 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -2441,6 +5008,35 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true }, + "vite": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", + "dev": true, + "peer": true, + "requires": { + "esbuild": "^0.19.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "dependencies": { + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/test/package/browser/template/package.json b/test/package/browser/template/package.json index 8ee502bb86..46eb205749 100644 --- a/test/package/browser/template/package.json +++ b/test/package/browser/template/package.json @@ -8,14 +8,17 @@ "typecheck": "tsc --project src -noEmit", "test-support:server": "ts-node server/server.ts", "test": "playwright test", - "test:install-deps": "playwright install chromium" + "test:install-deps": "playwright install chromium", + "test-ct": "playwright test -c playwright-ct.config.ts" }, "author": "", "license": "ISC", "devDependencies": { + "@playwright/experimental-ct-react": "^1.41.2", "@playwright/test": "^1.39.0", "@tsconfig/node16": "^16.1.1", "@types/express": "^4.17.20", + "@types/node": "^20.11.19", "esbuild": "^0.19.5", "express": "^4.18.2", "ts-node": "^10.9.1", diff --git a/test/package/browser/template/playwright-ct.config.ts b/test/package/browser/template/playwright-ct.config.ts new file mode 100644 index 0000000000..a2c94b7478 --- /dev/null +++ b/test/package/browser/template/playwright-ct.config.ts @@ -0,0 +1,46 @@ +import { defineConfig, devices } from '@playwright/experimental-ct-react'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './', + /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ + snapshotDir: './__snapshots__', + /* Maximum time one test can run for. */ + timeout: 10 * 1000, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Port to use for Playwright component endpoint. */ + ctPort: 3100, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + ], +}); diff --git a/test/package/browser/template/playwright/index.html b/test/package/browser/template/playwright/index.html new file mode 100644 index 0000000000..610ddf8a43 --- /dev/null +++ b/test/package/browser/template/playwright/index.html @@ -0,0 +1,12 @@ + + + + + + Testing Page + + +
+ + + diff --git a/test/package/browser/template/playwright/index.tsx b/test/package/browser/template/playwright/index.tsx new file mode 100644 index 0000000000..ac6de14bf2 --- /dev/null +++ b/test/package/browser/template/playwright/index.tsx @@ -0,0 +1,2 @@ +// Import styles, initialize component theme here. +// import '../src/common.css'; From 4a9970f5e27e73ad38de78d643f0dfc9b7beed31 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 05:57:29 +0000 Subject: [PATCH 339/468] `npm install @playwright/experimental-ct-react@1.39.0` in `test/package/browser/template` Downgrade to 1.39.0 to match installed `@playwright/test` version. Otherwise playwright will throw errors on test run due to version mismatch. --- .../browser/template/package-lock.json | 88 +++++-------------- test/package/browser/template/package.json | 2 +- 2 files changed, 22 insertions(+), 68 deletions(-) diff --git a/test/package/browser/template/package-lock.json b/test/package/browser/template/package-lock.json index 3368745aa1..d0443d1509 100644 --- a/test/package/browser/template/package-lock.json +++ b/test/package/browser/template/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@playwright/experimental-ct-react": "^1.41.2", + "@playwright/experimental-ct-react": "^1.39.0", "@playwright/test": "^1.39.0", "@tsconfig/node16": "^16.1.1", "@types/express": "^4.17.20", @@ -829,14 +829,14 @@ } }, "node_modules/@playwright/experimental-ct-core": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.41.2.tgz", - "integrity": "sha512-JtW7gjmBCeHWKmZcOPuoJ15i2ergVX9PnyYUrAvBbWL8l4X9B7Sblro8nE49hO9baANfEGl4BbRUaj+pA67F6w==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.39.0.tgz", + "integrity": "sha512-1b/qrlB5A/CdEZns8f2RDkWFmSnGkNyec8n72iinmw07+GHsdMP8fIpazeFB0umxWfo+gPLhkjhTGdB3WXJTBw==", "dev": true, "dependencies": { - "playwright": "1.41.2", - "playwright-core": "1.41.2", - "vite": "^4.4.12" + "playwright": "1.39.0", + "playwright-core": "1.39.0", + "vite": "^4.4.10" }, "bin": { "playwright": "cli.js" @@ -1234,36 +1234,6 @@ "@esbuild/win32-x64": "0.18.20" } }, - "node_modules/@playwright/experimental-ct-core/node_modules/playwright": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", - "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", - "dev": true, - "dependencies": { - "playwright-core": "1.41.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/playwright-core": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", - "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", - "dev": true, - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@playwright/experimental-ct-core/node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -1336,12 +1306,12 @@ } }, "node_modules/@playwright/experimental-ct-react": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.41.2.tgz", - "integrity": "sha512-BMcxh2DM3t8nMgUJKWMWAXGIzcYY/4w1ibckGP48edHU/cmZCbI5W7mUXfxMhYCt4mQJ1NuYcFIg5Jt+QVIuRQ==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.39.0.tgz", + "integrity": "sha512-6GaDVVo6x8P9Jdw3yPbVaEP59nesRsETn3kznHAy+2493VsP3IaVATat2G28z0Sivf/K9Tkh5xRYjiawN0ebgw==", "dev": true, "dependencies": { - "@playwright/experimental-ct-core": "1.41.2", + "@playwright/experimental-ct-core": "1.39.0", "@vitejs/plugin-react": "^4.0.0" }, "bin": { @@ -3586,14 +3556,14 @@ } }, "@playwright/experimental-ct-core": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.41.2.tgz", - "integrity": "sha512-JtW7gjmBCeHWKmZcOPuoJ15i2ergVX9PnyYUrAvBbWL8l4X9B7Sblro8nE49hO9baANfEGl4BbRUaj+pA67F6w==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.39.0.tgz", + "integrity": "sha512-1b/qrlB5A/CdEZns8f2RDkWFmSnGkNyec8n72iinmw07+GHsdMP8fIpazeFB0umxWfo+gPLhkjhTGdB3WXJTBw==", "dev": true, "requires": { - "playwright": "1.41.2", - "playwright-core": "1.41.2", - "vite": "^4.4.12" + "playwright": "1.39.0", + "playwright-core": "1.39.0", + "vite": "^4.4.10" }, "dependencies": { "@esbuild/android-arm": { @@ -3780,22 +3750,6 @@ "@esbuild/win32-x64": "0.18.20" } }, - "playwright": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", - "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", - "dev": true, - "requires": { - "fsevents": "2.3.2", - "playwright-core": "1.41.2" - } - }, - "playwright-core": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", - "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", - "dev": true - }, "rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -3820,12 +3774,12 @@ } }, "@playwright/experimental-ct-react": { - "version": "1.41.2", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.41.2.tgz", - "integrity": "sha512-BMcxh2DM3t8nMgUJKWMWAXGIzcYY/4w1ibckGP48edHU/cmZCbI5W7mUXfxMhYCt4mQJ1NuYcFIg5Jt+QVIuRQ==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.39.0.tgz", + "integrity": "sha512-6GaDVVo6x8P9Jdw3yPbVaEP59nesRsETn3kznHAy+2493VsP3IaVATat2G28z0Sivf/K9Tkh5xRYjiawN0ebgw==", "dev": true, "requires": { - "@playwright/experimental-ct-core": "1.41.2", + "@playwright/experimental-ct-core": "1.39.0", "@vitejs/plugin-react": "^4.0.0" } }, diff --git a/test/package/browser/template/package.json b/test/package/browser/template/package.json index 46eb205749..841566d6ee 100644 --- a/test/package/browser/template/package.json +++ b/test/package/browser/template/package.json @@ -14,7 +14,7 @@ "author": "", "license": "ISC", "devDependencies": { - "@playwright/experimental-ct-react": "^1.41.2", + "@playwright/experimental-ct-react": "^1.39.0", "@playwright/test": "^1.39.0", "@tsconfig/node16": "^16.1.1", "@types/express": "^4.17.20", From d3b02ccec91ab89a59fa23bfc250faf2928bcb18 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 06:22:22 +0000 Subject: [PATCH 340/468] Add NPM package import test for `ably/react` Similar to 9976a8455a421b0d901bb609fafac2e0654c4f1a, adds a test for importing from `ably/react` from ably-js NPM package. This test will catch NPM package errors like in #1622. Followed playwright for Components guide [1] to setup this. [1] https://playwright.dev/docs/test-components --- Gruntfile.js | 1 + test/package/browser/template/.gitignore | 1 + .../browser/template/package-lock.json | 1386 +---------------- .../browser/template/playwright-ct.config.ts | 10 +- .../browser/template/playwright/index.html | 2 +- .../browser/template/playwright/index.tsx | 22 +- .../package/browser/template/src/ReactApp.tsx | 50 + .../browser/template/src/tsconfig.json | 5 +- .../template/test-react/ReactApp.spec.tsx | 25 + test/package/browser/template/tsconfig.json | 3 +- 10 files changed, 159 insertions(+), 1346 deletions(-) create mode 100644 test/package/browser/template/src/ReactApp.tsx create mode 100644 test/package/browser/template/test-react/ReactApp.spec.tsx diff --git a/Gruntfile.js b/Gruntfile.js index 9ee91b1382..e537d51233 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -275,6 +275,7 @@ module.exports = function (grunt) { // Test that the code which exercises ably-js behaves as expected await execExternalPromises('npm run test'); + await execExternalPromises('npm run test-ct'); process.chdir(pwd); })() diff --git a/test/package/browser/template/.gitignore b/test/package/browser/template/.gitignore index 68c5d18f00..2911203326 100644 --- a/test/package/browser/template/.gitignore +++ b/test/package/browser/template/.gitignore @@ -1,3 +1,4 @@ +dist node_modules/ /test-results/ /playwright-report/ diff --git a/test/package/browser/template/package-lock.json b/test/package/browser/template/package-lock.json index d0443d1509..f87125086f 100644 --- a/test/package/browser/template/package-lock.json +++ b/test/package/browser/template/package-lock.json @@ -428,757 +428,85 @@ "node": ">=12" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", - "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", - "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", - "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", - "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", - "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", - "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", - "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", - "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", - "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", - "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", - "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", - "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", - "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", - "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", - "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", - "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", - "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", - "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", - "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", - "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", - "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", - "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@playwright/experimental-ct-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.39.0.tgz", - "integrity": "sha512-1b/qrlB5A/CdEZns8f2RDkWFmSnGkNyec8n72iinmw07+GHsdMP8fIpazeFB0umxWfo+gPLhkjhTGdB3WXJTBw==", - "dev": true, - "dependencies": { - "playwright": "1.39.0", - "playwright-core": "1.39.0", - "vite": "^4.4.10" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "node_modules/@esbuild/win32-x64": { + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", + "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", "cpu": [ "x64" ], "dev": true, "optional": true, "os": [ - "linux" + "win32" ], "engines": { "node": ">=12" } }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, - "optional": true, - "os": [ - "openbsd" - ], "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, - "optional": true, - "os": [ - "sunos" - ], "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], + "node_modules/@playwright/experimental-ct-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.39.0.tgz", + "integrity": "sha512-1b/qrlB5A/CdEZns8f2RDkWFmSnGkNyec8n72iinmw07+GHsdMP8fIpazeFB0umxWfo+gPLhkjhTGdB3WXJTBw==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "playwright": "1.39.0", + "playwright-core": "1.39.0", + "vite": "^4.4.10" + }, + "bin": { + "playwright": "cli.js" + }, "engines": { - "node": ">=12" + "node": ">=16" } }, "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-x64": { @@ -1331,178 +659,10 @@ }, "bin": { "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true + }, + "engines": { + "node": ">=16" + } }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.12.0", @@ -2160,20 +1320,6 @@ "node": ">= 0.6" } }, - "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.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3026,21 +2172,6 @@ } } }, - "node_modules/vite/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3362,153 +2493,6 @@ "@jridgewell/trace-mapping": "0.3.9" } }, - "@esbuild/android-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", - "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", - "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", - "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", - "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", - "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", - "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", - "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", - "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", - "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", - "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", - "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", - "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", - "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", - "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", - "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", - "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", - "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", - "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", - "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", - "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", - "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", - "dev": true, - "optional": true - }, "@esbuild/win32-x64": { "version": "0.19.5", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", @@ -3566,153 +2550,6 @@ "vite": "^4.4.10" }, "dependencies": { - "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "dev": true, - "optional": true - }, "@esbuild/win32-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", @@ -3792,102 +2629,6 @@ "playwright": "1.39.0" } }, - "@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", - "dev": true, - "optional": true, - "peer": true - }, "@rollup/rollup-win32-x64-msvc": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", @@ -4417,13 +3158,6 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "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.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4973,16 +3707,6 @@ "fsevents": "~2.3.3", "postcss": "^8.4.35", "rollup": "^4.2.0" - }, - "dependencies": { - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true, - "peer": true - } } }, "yallist": { diff --git a/test/package/browser/template/playwright-ct.config.ts b/test/package/browser/template/playwright-ct.config.ts index a2c94b7478..83a0c9dc64 100644 --- a/test/package/browser/template/playwright-ct.config.ts +++ b/test/package/browser/template/playwright-ct.config.ts @@ -4,7 +4,7 @@ import { defineConfig, devices } from '@playwright/experimental-ct-react'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './', + testDir: './test-react', /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ snapshotDir: './__snapshots__', /* Maximum time one test can run for. */ @@ -34,13 +34,5 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, ], }); diff --git a/test/package/browser/template/playwright/index.html b/test/package/browser/template/playwright/index.html index 610ddf8a43..5d009b1f30 100644 --- a/test/package/browser/template/playwright/index.html +++ b/test/package/browser/template/playwright/index.html @@ -3,7 +3,7 @@ - Testing Page + Ably NPM package test (react export)
diff --git a/test/package/browser/template/playwright/index.tsx b/test/package/browser/template/playwright/index.tsx index ac6de14bf2..340334f77b 100644 --- a/test/package/browser/template/playwright/index.tsx +++ b/test/package/browser/template/playwright/index.tsx @@ -1,2 +1,20 @@ -// Import styles, initialize component theme here. -// import '../src/common.css'; +import { beforeMount } from '@playwright/experimental-ct-react/hooks'; +import * as Ably from 'ably'; +import { AblyProvider } from 'ably/react'; + +import { createSandboxAblyAPIKey } from '../src/sandbox'; + +beforeMount(async ({ App }) => { + const key = await createSandboxAblyAPIKey(); + + const client = new Ably.Realtime({ + key, + environment: 'sandbox', + }); + + return ( + + + + ); +}); diff --git a/test/package/browser/template/src/ReactApp.tsx b/test/package/browser/template/src/ReactApp.tsx new file mode 100644 index 0000000000..e3d83d2f68 --- /dev/null +++ b/test/package/browser/template/src/ReactApp.tsx @@ -0,0 +1,50 @@ +import * as Ably from 'ably'; +import { useChannel } from 'ably/react'; +import { useEffect, useState } from 'react'; + +export function App() { + // check that we can refer to the types exported by Ably. + const [messages, updateMessages] = useState([]); + + // check that we can use ably/react exported members + const { channel, ably } = useChannel({ channelName: 'channel' }, (message) => { + updateMessages((prev) => [...prev, message]); + }); + + const messagePreviews = messages.map((message, idx) => ); + + useEffect(() => { + async function publishMessages() { + try { + // Check that we can use the TypeScript overload that accepts name and data as separate arguments + await channel.publish('message', { foo: 'bar' }); + + // Check that we can use the TypeScript overload that accepts a Message object + await channel.publish({ name: 'message', data: { foo: 'baz' } }); + (window as any).onResult(); + } catch (error) { + (window as any).onResult(error); + } + } + + publishMessages(); + }, [channel]); + + return ( +
+
Ably NPM package test (react export)
+
+

Messages

+
    {messagePreviews}
+
+
+ ); +} + +function MessagePreview({ message }: { message: Ably.Message }) { + return ( +
  • + {message.name}: {message.data.foo} +
  • + ); +} diff --git a/test/package/browser/template/src/tsconfig.json b/test/package/browser/template/src/tsconfig.json index cca98c96fa..e280baa6de 100644 --- a/test/package/browser/template/src/tsconfig.json +++ b/test/package/browser/template/src/tsconfig.json @@ -1,9 +1,10 @@ { - "include": ["**/*.ts"], + "include": ["**/*.ts", "**/*.tsx"], "compilerOptions": { "resolveJsonModule": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "bundler" + "moduleResolution": "bundler", + "jsx": "react-jsx" } } diff --git a/test/package/browser/template/test-react/ReactApp.spec.tsx b/test/package/browser/template/test-react/ReactApp.spec.tsx new file mode 100644 index 0000000000..269230a30d --- /dev/null +++ b/test/package/browser/template/test-react/ReactApp.spec.tsx @@ -0,0 +1,25 @@ +import { expect, test } from '@playwright/experimental-ct-react'; + +import { App } from '../src/ReactApp'; + +test.describe('NPM package', () => { + for (const scenario of [{ name: 'react export' }]) { + test.describe(scenario.name, () => { + test('can be imported and provides access to Ably functionality', async ({ mount, page }) => { + const pageResultPromise = new Promise((resolve, reject) => { + page.exposeFunction('onResult', (error: Error | null) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + + const component = await mount(); + await pageResultPromise; + await expect(component).toContainText('Ably NPM package test (react export)'); + }); + }); + } +}); diff --git a/test/package/browser/template/tsconfig.json b/test/package/browser/template/tsconfig.json index 2f98042715..b72f565555 100644 --- a/test/package/browser/template/tsconfig.json +++ b/test/package/browser/template/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { - "esModuleInterop": true + "esModuleInterop": true, + "jsx": "react-jsx" } } From c04b930cc50e832040b56961a1686f7cb3f12cde Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 06:39:40 +0000 Subject: [PATCH 341/468] `npm install esbuild@0.18.20` in `test/package/browser/template` Had to downgrade version for explicit dev dependency for `esbuild` to match version used by `@playwright/experimental-ct-react` (0.18.20). It was causing 'Error: Expected "0.19.5" but got "0.18.20"' errors in CI, see job log [1]. [1] https://github.com/ably/ably-js/actions/runs/7984893618/job/21802628829 --- .../browser/template/package-lock.json | 1487 ++++++++++++++--- test/package/browser/template/package.json | 2 +- 2 files changed, 1287 insertions(+), 202 deletions(-) diff --git a/test/package/browser/template/package-lock.json b/test/package/browser/template/package-lock.json index f87125086f..30849af9de 100644 --- a/test/package/browser/template/package-lock.json +++ b/test/package/browser/template/package-lock.json @@ -14,7 +14,7 @@ "@tsconfig/node16": "^16.1.1", "@types/express": "^4.17.20", "@types/node": "^20.11.19", - "esbuild": "^0.19.5", + "esbuild": "^0.18.20", "express": "^4.18.2", "ts-node": "^10.9.1", "typescript": "^5.2.2" @@ -428,10 +428,363 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", - "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ "x64" ], @@ -509,59 +862,6 @@ "node": ">=16" } }, - "node_modules/@playwright/experimental-ct-core/node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@playwright/experimental-ct-core/node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, "node_modules/@playwright/experimental-ct-core/node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -1173,9 +1473,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", - "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, "bin": { @@ -1185,28 +1485,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.5", - "@esbuild/android-arm64": "0.19.5", - "@esbuild/android-x64": "0.19.5", - "@esbuild/darwin-arm64": "0.19.5", - "@esbuild/darwin-x64": "0.19.5", - "@esbuild/freebsd-arm64": "0.19.5", - "@esbuild/freebsd-x64": "0.19.5", - "@esbuild/linux-arm": "0.19.5", - "@esbuild/linux-arm64": "0.19.5", - "@esbuild/linux-ia32": "0.19.5", - "@esbuild/linux-loong64": "0.19.5", - "@esbuild/linux-mips64el": "0.19.5", - "@esbuild/linux-ppc64": "0.19.5", - "@esbuild/linux-riscv64": "0.19.5", - "@esbuild/linux-s390x": "0.19.5", - "@esbuild/linux-x64": "0.19.5", - "@esbuild/netbsd-x64": "0.19.5", - "@esbuild/openbsd-x64": "0.19.5", - "@esbuild/sunos-x64": "0.19.5", - "@esbuild/win32-arm64": "0.19.5", - "@esbuild/win32-ia32": "0.19.5", - "@esbuild/win32-x64": "0.19.5" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "node_modules/escalade": { @@ -1320,6 +1620,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1687,6 +2001,20 @@ "node": ">=16" } }, + "node_modules/playwright/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/postcss": { "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", @@ -2107,69 +2435,482 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", + "dev": true, + "peer": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/vite": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", - "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", + "node_modules/vite/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, + "hasInstallScript": true, "peer": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" - }, "bin": { - "vite": "bin/vite.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" + "node": ">=12" }, "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/yallist": { @@ -2493,10 +3234,165 @@ "@jridgewell/trace-mapping": "0.3.9" } }, + "@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "dev": true, + "optional": true + }, "@esbuild/win32-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", - "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "dev": true, "optional": true }, @@ -2550,43 +3446,6 @@ "vite": "^4.4.10" }, "dependencies": { - "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, - "optional": true - }, - "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, "rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -3039,33 +3898,33 @@ "dev": true }, "esbuild": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", - "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.19.5", - "@esbuild/android-arm64": "0.19.5", - "@esbuild/android-x64": "0.19.5", - "@esbuild/darwin-arm64": "0.19.5", - "@esbuild/darwin-x64": "0.19.5", - "@esbuild/freebsd-arm64": "0.19.5", - "@esbuild/freebsd-x64": "0.19.5", - "@esbuild/linux-arm": "0.19.5", - "@esbuild/linux-arm64": "0.19.5", - "@esbuild/linux-ia32": "0.19.5", - "@esbuild/linux-loong64": "0.19.5", - "@esbuild/linux-mips64el": "0.19.5", - "@esbuild/linux-ppc64": "0.19.5", - "@esbuild/linux-riscv64": "0.19.5", - "@esbuild/linux-s390x": "0.19.5", - "@esbuild/linux-x64": "0.19.5", - "@esbuild/netbsd-x64": "0.19.5", - "@esbuild/openbsd-x64": "0.19.5", - "@esbuild/sunos-x64": "0.19.5", - "@esbuild/win32-arm64": "0.19.5", - "@esbuild/win32-ia32": "0.19.5", - "@esbuild/win32-x64": "0.19.5" + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "escalade": { @@ -3158,6 +4017,13 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3404,6 +4270,15 @@ "requires": { "fsevents": "2.3.2", "playwright-core": "1.39.0" + }, + "dependencies": { + "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 + } } }, "playwright-core": { @@ -3707,6 +4582,216 @@ "fsevents": "~2.3.3", "postcss": "^8.4.35", "rollup": "^4.2.0" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "dev": true, + "optional": true, + "peer": true + }, + "esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "peer": true, + "requires": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + } } }, "yallist": { diff --git a/test/package/browser/template/package.json b/test/package/browser/template/package.json index 841566d6ee..1520e97c8b 100644 --- a/test/package/browser/template/package.json +++ b/test/package/browser/template/package.json @@ -19,7 +19,7 @@ "@tsconfig/node16": "^16.1.1", "@types/express": "^4.17.20", "@types/node": "^20.11.19", - "esbuild": "^0.19.5", + "esbuild": "^0.18.20", "express": "^4.18.2", "ts-node": "^10.9.1", "typescript": "^5.2.2" From 0bffeb791219db441281245f02f1189544c2a468 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 06:45:58 +0000 Subject: [PATCH 342/468] Fix package-lock.json optional dependency issue `npm run test-ct` was failing in CI (see job log [1]) with reference to a npm bug [2] and suggested to delete package-lock.json and do `npm install` to rebuild it. This commit has the result of it. Also this commit changes `lockfileVersion` in package-lock.json to 3, which is backwards compatible to npm v7 (bundled with node starting from version 15). We're using node16 in CI for package tests, so it's fine. [1] https://github.com/ably/ably-js/actions/runs/7985041805/job/21802827410?pr=1625 [2] https://github.com/npm/cli/issues/4828 --- .../browser/template/package-lock.json | 2484 +++-------------- 1 file changed, 418 insertions(+), 2066 deletions(-) diff --git a/test/package/browser/template/package-lock.json b/test/package/browser/template/package-lock.json index 30849af9de..701b9122a7 100644 --- a/test/package/browser/template/package-lock.json +++ b/test/package/browser/template/package-lock.json @@ -1,7 +1,7 @@ { "name": "template", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -85,29 +85,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@babel/generator": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", @@ -123,16 +100,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", @@ -379,29 +346,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@babel/types": { "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", @@ -428,6 +372,16 @@ "node": ">=12" } }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", @@ -812,9 +766,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -836,24 +790,24 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@playwright/experimental-ct-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.39.0.tgz", - "integrity": "sha512-1b/qrlB5A/CdEZns8f2RDkWFmSnGkNyec8n72iinmw07+GHsdMP8fIpazeFB0umxWfo+gPLhkjhTGdB3WXJTBw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.41.2.tgz", + "integrity": "sha512-JtW7gjmBCeHWKmZcOPuoJ15i2ergVX9PnyYUrAvBbWL8l4X9B7Sblro8nE49hO9baANfEGl4BbRUaj+pA67F6w==", "dev": true, "dependencies": { - "playwright": "1.39.0", - "playwright-core": "1.39.0", - "vite": "^4.4.10" + "playwright": "1.41.2", + "playwright-core": "1.41.2", + "vite": "^4.4.12" }, "bin": { "playwright": "cli.js" @@ -934,12 +888,12 @@ } }, "node_modules/@playwright/experimental-ct-react": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.39.0.tgz", - "integrity": "sha512-6GaDVVo6x8P9Jdw3yPbVaEP59nesRsETn3kznHAy+2493VsP3IaVATat2G28z0Sivf/K9Tkh5xRYjiawN0ebgw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.41.2.tgz", + "integrity": "sha512-BMcxh2DM3t8nMgUJKWMWAXGIzcYY/4w1ibckGP48edHU/cmZCbI5W7mUXfxMhYCt4mQJ1NuYcFIg5Jt+QVIuRQ==", "dev": true, "dependencies": { - "@playwright/experimental-ct-core": "1.39.0", + "@playwright/experimental-ct-core": "1.41.2", "@vitejs/plugin-react": "^4.0.0" }, "bin": { @@ -950,12 +904,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", - "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", + "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==", "dev": true, "dependencies": { - "playwright": "1.39.0" + "playwright": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -964,6 +918,174 @@ "node": ">=16" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", + "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", + "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", + "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", + "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", + "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", + "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", + "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", + "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", + "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", + "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", + "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", + "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", @@ -1044,9 +1166,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", - "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -1054,9 +1176,9 @@ } }, "node_modules/@types/connect": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", - "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "dependencies": { "@types/node": "*" @@ -1070,9 +1192,9 @@ "peer": true }, "node_modules/@types/express": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", - "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -1082,9 +1204,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.39", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", - "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "dev": true, "dependencies": { "@types/node": "*", @@ -1094,15 +1216,15 @@ } }, "node_modules/@types/http-errors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, "node_modules/@types/mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "node_modules/@types/node": { @@ -1115,21 +1237,21 @@ } }, "node_modules/@types/qs": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "node_modules/@types/send": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", - "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -1137,9 +1259,9 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", - "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", "dev": true, "dependencies": { "@types/http-errors": "*", @@ -1180,9 +1302,9 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1192,9 +1314,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1248,6 +1370,21 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/browserslist": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", @@ -1290,14 +1427,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1401,26 +1543,37 @@ "dev": true }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "ms": "2.0.0" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/depd": { @@ -1472,6 +1625,27 @@ "node": ">= 0.8" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", @@ -1584,6 +1758,21 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -1602,6 +1791,21 @@ "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1621,9 +1825,9 @@ } }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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, @@ -1653,16 +1857,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1698,21 +1906,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -1734,9 +1942,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -1891,9 +2099,9 @@ } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "node_modules/nanoid": { @@ -1972,12 +2180,12 @@ "dev": true }, "node_modules/playwright": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", - "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", "dev": true, "dependencies": { - "playwright-core": "1.39.0" + "playwright-core": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -1990,9 +2198,9 @@ } }, "node_modules/playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -2001,20 +2209,6 @@ "node": ">=16" } }, - "node_modules/playwright/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/postcss": { "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", @@ -2196,6 +2390,21 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2218,15 +2427,17 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -2239,14 +2450,18 @@ "dev": true }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2301,9 +2516,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -2363,9 +2578,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -2913,6 +3128,21 @@ "@esbuild/win32-x64": "0.19.12" } }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -2928,1883 +3158,5 @@ "node": ">=6" } } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - } - }, - "@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true - }, - "@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, - "requires": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", - "dev": true, - "requires": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" - } - }, - "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", - "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", - "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" - } - }, - "@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, - "optional": true - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@playwright/experimental-ct-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.39.0.tgz", - "integrity": "sha512-1b/qrlB5A/CdEZns8f2RDkWFmSnGkNyec8n72iinmw07+GHsdMP8fIpazeFB0umxWfo+gPLhkjhTGdB3WXJTBw==", - "dev": true, - "requires": { - "playwright": "1.39.0", - "playwright-core": "1.39.0", - "vite": "^4.4.10" - }, - "dependencies": { - "rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", - "dev": true, - "requires": { - "esbuild": "^0.18.10", - "fsevents": "~2.3.2", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - } - } - } - }, - "@playwright/experimental-ct-react": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.39.0.tgz", - "integrity": "sha512-6GaDVVo6x8P9Jdw3yPbVaEP59nesRsETn3kznHAy+2493VsP3IaVATat2G28z0Sivf/K9Tkh5xRYjiawN0ebgw==", - "dev": true, - "requires": { - "@playwright/experimental-ct-core": "1.39.0", - "@vitejs/plugin-react": "^4.0.0" - } - }, - "@playwright/test": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", - "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", - "dev": true, - "requires": { - "playwright": "1.39.0" - } - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", - "dev": true, - "optional": true, - "peer": true - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tsconfig/node16": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.1.tgz", - "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", - "dev": true - }, - "@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@types/body-parser": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", - "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", - "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "peer": true - }, - "@types/express": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", - "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.39", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", - "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "@types/http-errors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", - "dev": true - }, - "@types/mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", - "dev": true - }, - "@types/node": { - "version": "20.11.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", - "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", - "dev": true, - "requires": { - "undici-types": "~5.26.4" - } - }, - "@types/qs": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", - "dev": true - }, - "@types/send": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", - "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", - "dev": true, - "requires": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "@types/serve-static": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", - "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", - "dev": true, - "requires": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", - "dev": true, - "requires": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true - }, - "acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, - "requires": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" - } - }, - "caniuse-lite": { - "version": "1.0.30001588", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", - "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.677", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.677.tgz", - "integrity": "sha512-erDa3CaDzwJOpyvfKhOiJjBVNnMM0qxHq47RheVVwsSQrgBA9ZSGV9kdaOfZDPXcHzhG7lBxhj6A7KvfLJBd6Q==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true - }, - "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, - "requires": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.2" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true - }, - "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "playwright": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", - "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", - "dev": true, - "requires": { - "fsevents": "2.3.2", - "playwright-core": "1.39.0" - }, - "dependencies": { - "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 - } - } - }, - "playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", - "dev": true - }, - "postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", - "dev": true, - "requires": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dev": true - }, - "rollup": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", - "dev": true, - "peer": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.12.0", - "@rollup/rollup-android-arm64": "4.12.0", - "@rollup/rollup-darwin-arm64": "4.12.0", - "@rollup/rollup-darwin-x64": "4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", - "@rollup/rollup-linux-arm64-gnu": "4.12.0", - "@rollup/rollup-linux-arm64-musl": "4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-musl": "4.12.0", - "@rollup/rollup-win32-arm64-msvc": "4.12.0", - "@rollup/rollup-win32-ia32-msvc": "4.12.0", - "@rollup/rollup-win32-x64-msvc": "4.12.0", - "@types/estree": "1.0.5", - "fsevents": "~2.3.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, - "requires": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true - }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - } - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true - }, - "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true - }, - "vite": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", - "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", - "dev": true, - "peer": true, - "requires": { - "esbuild": "^0.19.3", - "fsevents": "~2.3.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" - }, - "dependencies": { - "@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "dev": true, - "optional": true, - "peer": true - }, - "@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "dev": true, - "optional": true, - "peer": true - }, - "esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "peer": true, - "requires": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - } - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } } } From 80403b0095c570936e76950ff0f4345f5888b166 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 23 Feb 2024 00:49:30 +0000 Subject: [PATCH 343/468] Improve script names and file structure for `package` tests --- Gruntfile.js | 1 - test/package/browser/template/package.json | 7 ++++--- ...{playwright-ct.config.ts => playwright-hooks.config.ts} | 7 +++++-- .../{playwright.config.js => playwright-lib.config.js} | 7 ++++++- .../template/{test-react => test/hooks}/ReactApp.spec.tsx | 2 +- .../browser/template/test/{ => lib}/package.test.ts | 0 6 files changed, 16 insertions(+), 8 deletions(-) rename test/package/browser/template/{playwright-ct.config.ts => playwright-hooks.config.ts} (84%) rename test/package/browser/template/{playwright.config.js => playwright-lib.config.js} (57%) rename test/package/browser/template/{test-react => test/hooks}/ReactApp.spec.tsx (94%) rename test/package/browser/template/test/{ => lib}/package.test.ts (100%) diff --git a/Gruntfile.js b/Gruntfile.js index e537d51233..9ee91b1382 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -275,7 +275,6 @@ module.exports = function (grunt) { // Test that the code which exercises ably-js behaves as expected await execExternalPromises('npm run test'); - await execExternalPromises('npm run test-ct'); process.chdir(pwd); })() diff --git a/test/package/browser/template/package.json b/test/package/browser/template/package.json index 1520e97c8b..7a3bf1038d 100644 --- a/test/package/browser/template/package.json +++ b/test/package/browser/template/package.json @@ -7,9 +7,10 @@ "build": "esbuild --bundle src/index-default.ts --outdir=dist && esbuild --bundle src/index-modules.ts --outdir=dist", "typecheck": "tsc --project src -noEmit", "test-support:server": "ts-node server/server.ts", - "test": "playwright test", - "test:install-deps": "playwright install chromium", - "test-ct": "playwright test -c playwright-ct.config.ts" + "test": "npm run test:lib && npm run test:hooks", + "test:lib": "playwright test -c playwright-lib.config.js", + "test:hooks": "playwright test -c playwright-hooks.config.ts", + "test:install-deps": "playwright install chromium" }, "author": "", "license": "ISC", diff --git a/test/package/browser/template/playwright-ct.config.ts b/test/package/browser/template/playwright-hooks.config.ts similarity index 84% rename from test/package/browser/template/playwright-ct.config.ts rename to test/package/browser/template/playwright-hooks.config.ts index 83a0c9dc64..afe7f537fa 100644 --- a/test/package/browser/template/playwright-ct.config.ts +++ b/test/package/browser/template/playwright-hooks.config.ts @@ -1,10 +1,13 @@ import { defineConfig, devices } from '@playwright/experimental-ct-react'; /** - * See https://playwright.dev/docs/test-configuration. + * Playwright config for running ably-js react hooks NPM package tests. + * Based on https://playwright.dev/docs/test-components. + * + * See config options: https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './test-react', + testDir: './test/hooks', /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ snapshotDir: './__snapshots__', /* Maximum time one test can run for. */ diff --git a/test/package/browser/template/playwright.config.js b/test/package/browser/template/playwright-lib.config.js similarity index 57% rename from test/package/browser/template/playwright.config.js rename to test/package/browser/template/playwright-lib.config.js index 6cd19e6687..4e12fe01b7 100644 --- a/test/package/browser/template/playwright.config.js +++ b/test/package/browser/template/playwright-lib.config.js @@ -1,7 +1,12 @@ import { defineConfig } from '@playwright/test'; +/** + * Playwright config for running ably-js lib NPM package tests. + * + * See config options: https://playwright.dev/docs/test-configuration. + */ export default defineConfig({ - testDir: 'test', + testDir: './test/lib', webServer: { command: 'npm run test-support:server', url: 'http://localhost:4567', diff --git a/test/package/browser/template/test-react/ReactApp.spec.tsx b/test/package/browser/template/test/hooks/ReactApp.spec.tsx similarity index 94% rename from test/package/browser/template/test-react/ReactApp.spec.tsx rename to test/package/browser/template/test/hooks/ReactApp.spec.tsx index 269230a30d..177dee63c9 100644 --- a/test/package/browser/template/test-react/ReactApp.spec.tsx +++ b/test/package/browser/template/test/hooks/ReactApp.spec.tsx @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/experimental-ct-react'; -import { App } from '../src/ReactApp'; +import { App } from '../../src/ReactApp'; test.describe('NPM package', () => { for (const scenario of [{ name: 'react export' }]) { diff --git a/test/package/browser/template/test/package.test.ts b/test/package/browser/template/test/lib/package.test.ts similarity index 100% rename from test/package/browser/template/test/package.test.ts rename to test/package/browser/template/test/lib/package.test.ts From 62618ae2358e98807b6ad3ddf8bd32f5b09a06eb Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 23 Feb 2024 01:06:05 +0000 Subject: [PATCH 344/468] Update README for `package` test to include `ably/react` testing info --- test/package/browser/template/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/package/browser/template/README.md b/test/package/browser/template/README.md index 2a576916ac..ad3c029e7f 100644 --- a/test/package/browser/template/README.md +++ b/test/package/browser/template/README.md @@ -5,21 +5,26 @@ This directory is intended to be used for testing the following aspects of the a - that its exports are correctly configured and provide access to ably-js’s functionality - that its TypeScript typings are correctly configured and can be successfully used from a TypeScript-based app that imports the package -It contains two files, each of which import ably-js in different manners, and which export a function which briefly exercises its functionality: +It contains three files, each of which import ably-js in different manners, and provide a way to briefly exercise its functionality: - `src/index-default.ts` imports the default ably-js package (`import { Realtime } from 'ably'`). - `src/index-modules.ts` imports the tree-shakable ably-js package (`import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'`). +- `src/ReactApp.tsx` imports React hooks from the ably-js package (`import { useChannel } from 'ably/react'`). ## Why is `ably` not in `package.json`? The `ably` dependency gets added when we run the repository’s `test:package` package script. That script copies the contents of this `template` directory to a new temporary directory, and then adds the `ably` dependency to the copy. We do this so that we can check this directory’s `package-lock.json` into Git, without needing to modify it whenever ably-js’s dependencies change. +## React hooks tests + +To test hooks imported from `ably/react` in React components, we used [Playwright for components](https://playwright.dev/docs/test-components). The main logic sits in `src/ReactApp.tsx`, and `AblyProvider` is configured in `playwright/index.tsx` file based on [this guide](https://playwright.dev/docs/test-components#hooks). + ## Package scripts This directory exposes three package scripts that are to be used for testing: - `build`: Uses esbuild to create: - 1. a bundle containing `src/index-default.ts` and ably-js; - 2. a bundle containing `src/index-modules.ts` and ably-js. -- `test`: Using the bundles created by `build`, tests that the code that exercises ably-js’s functionality is working correctly in a browser. + 1. a bundle containing `src/index-default.ts` and ably-js; + 2. a bundle containing `src/index-modules.ts` and ably-js. +- `test`: Using the bundles created by `build` and playwright components setup, tests that the code that exercises ably-js’s functionality is working correctly in a browser. - `typecheck`: Type-checks the code that imports ably-js. From 4d657fb741f7f6f35f713c7d0a3db4a67204e4e3 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:15:49 +0000 Subject: [PATCH 345/468] Removes IE8 related comment --- src/common/lib/transport/connectionmanager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 48fec7bd78..19ba4b3125 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -346,7 +346,6 @@ class ConnectionManager extends EventEmitter { if (addEventListener) { /* intercept close event in browser to persist connection id if requested */ if (haveSessionStorage() && typeof options.recover === 'function') { - /* Usually can't use bind as not supported in IE8, but IE doesn't support sessionStorage, so... */ addEventListener('beforeunload', this.persistConnection.bind(this)); } From e55a33b713c30859af62db7794790522fb816ff8 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:16:48 +0000 Subject: [PATCH 346/468] Removes Date.now polyfill for IE8 --- src/common/lib/util/utils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index c211e833d4..1fd2d64c28 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -357,12 +357,7 @@ export function parseQueryString(query: string): Record { return result; } -export const now = - Date.now || - function () { - /* IE 8 */ - return new Date().getTime(); - }; +export const now = Date.now; export function isErrorInfoOrPartialErrorInfo(err: unknown): err is ErrorInfo | PartialErrorInfo { return typeof err == 'object' && err !== null && (err instanceof ErrorInfo || err instanceof PartialErrorInfo); From 29984da7b7cb5be7ac1e73b4e106d3aedc35cbee Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:18:19 +0000 Subject: [PATCH 347/468] Removes `Utils.now` function --- src/common/lib/client/auth.ts | 2 +- src/common/lib/client/realtimepresence.ts | 2 +- src/common/lib/client/rest.ts | 2 +- src/common/lib/transport/connectionmanager.ts | 12 ++++++------ src/common/lib/transport/transport.ts | 4 ++-- src/common/lib/util/utils.ts | 2 -- src/platform/nativescript/lib/util/webstorage.js | 5 ++--- src/platform/web/lib/util/webstorage.ts | 5 ++--- test/realtime/presence.test.js | 4 ++-- test/realtime/resume.test.js | 2 +- test/rest/auth.test.js | 2 +- test/rest/fallbacks.test.js | 3 +-- test/rest/time.test.js | 3 +-- 13 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index a211fc2ef1..b0d819921c 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -830,7 +830,7 @@ class Auth { } getTimestampUsingOffset() { - return Utils.now() + (this.client.serverTimeOffset || 0); + return Date.now() + (this.client.serverTimeOffset || 0); } isTimeOffsetSet() { diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 435ecf5c87..3b7098c9b2 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -452,7 +452,7 @@ class RealtimePresence extends EventEmitter { clientId: item.clientId, data: item.data, encoding: item.encoding, - timestamp: Utils.now(), + timestamp: Date.now(), }); subscriptions.emit('leave', presence); }); diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index c7f506f5d3..b8ab1d66d5 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -86,7 +86,7 @@ export class Rest { throw new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500); } /* calculate time offset only once for this device by adding to the prototype */ - this.client.serverTimeOffset = time - Utils.now(); + this.client.serverTimeOffset = time - Date.now(); return time; } diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 19ba4b3125..5d6b357d6f 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1131,7 +1131,7 @@ class ConnectionManager extends EventEmitter { return; } - const sinceLast = Utils.now() - this.lastActivity; + const sinceLast = Date.now() - this.lastActivity; if (sinceLast > this.connectionStateTtl + (this.maxIdleInterval as number)) { Logger.logAction( Logger.LOG_MINOR, @@ -1153,7 +1153,7 @@ class ConnectionManager extends EventEmitter { if (recoveryKey) { setSessionRecoverData({ recoveryKey: recoveryKey, - disconnectedAt: Utils.now(), + disconnectedAt: Date.now(), location: globalObject.location, clientId: this.realtime.auth.clientId, }); @@ -1357,11 +1357,11 @@ class ConnectionManager extends EventEmitter { if (retryImmediately) { const autoReconnect = () => { if (this.state === this.states.disconnected) { - this.lastAutoReconnectAttempt = Utils.now(); + this.lastAutoReconnectAttempt = Date.now(); this.requestState({ state: 'connecting' }); } }; - const sinceLast = this.lastAutoReconnectAttempt && Utils.now() - this.lastAutoReconnectAttempt + 1; + const sinceLast = this.lastAutoReconnectAttempt && Date.now() - this.lastAutoReconnectAttempt + 1; if (sinceLast && sinceLast < 1000) { Logger.logAction( Logger.LOG_MICRO, @@ -2058,14 +2058,14 @@ class ConnectionManager extends EventEmitter { callback(new ErrorInfo('Timeout waiting for heartbeat response', 50000, 500)); }; - const pingStart = Utils.now(), + const pingStart = Date.now(), id = Utils.cheapRandStr(); const onHeartbeat = function (responseId: string) { if (responseId === id) { transport.off('heartbeat', onHeartbeat); clearTimeout(timer); - const responseTime = Utils.now() - pingStart; + const responseTime = Date.now() - pingStart; callback(null, responseTime); } }; diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 5153cd63bf..a18bfcb830 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -257,7 +257,7 @@ abstract class Transport extends EventEmitter { if (!this.maxIdleInterval) { return; } - this.lastActivity = this.connectionManager.lastActivity = Utils.now(); + this.lastActivity = this.connectionManager.lastActivity = Date.now(); this.setIdleTimer(this.maxIdleInterval + 100); } @@ -274,7 +274,7 @@ abstract class Transport extends EventEmitter { throw new Error('Transport.onIdleTimerExpire(): lastActivity/maxIdleInterval not set'); } this.idleTimer = null; - const sinceLast = Utils.now() - this.lastActivity; + const sinceLast = Date.now() - this.lastActivity; const timeRemaining = this.maxIdleInterval - sinceLast; if (timeRemaining <= 0) { const msg = 'No activity seen from realtime in ' + sinceLast + 'ms; assuming connection has dropped'; diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 1fd2d64c28..153dd6a686 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -357,8 +357,6 @@ export function parseQueryString(query: string): Record { return result; } -export const now = Date.now; - export function isErrorInfoOrPartialErrorInfo(err: unknown): err is ErrorInfo | PartialErrorInfo { return typeof err == 'object' && err !== null && (err instanceof ErrorInfo || err instanceof PartialErrorInfo); } diff --git a/src/platform/nativescript/lib/util/webstorage.js b/src/platform/nativescript/lib/util/webstorage.js index cfb874d02c..90d169a615 100644 --- a/src/platform/nativescript/lib/util/webstorage.js +++ b/src/platform/nativescript/lib/util/webstorage.js @@ -1,4 +1,3 @@ -import * as Utils from '../../../../common/lib/util/utils'; import appSettings from '@nativescript/core/application-settings'; var WebStorage = (function () { @@ -7,7 +6,7 @@ var WebStorage = (function () { function set(name, value, ttl) { var wrappedValue = { value: value }; if (ttl) { - wrappedValue.expires = Utils.now() + ttl; + wrappedValue.expires = Date.now() + ttl; } return appSettings.setString(name, JSON.stringify(wrappedValue)); } @@ -16,7 +15,7 @@ var WebStorage = (function () { var rawItem = appSettings.getString(name); if (!rawItem) return null; var wrappedValue = JSON.parse(rawItem); - if (wrappedValue.expires && wrappedValue.expires < Utils.now()) { + if (wrappedValue.expires && wrappedValue.expires < Date.now()) { appSettings.remove(name); return null; } diff --git a/src/platform/web/lib/util/webstorage.ts b/src/platform/web/lib/util/webstorage.ts index 010bff419b..a87300417a 100644 --- a/src/platform/web/lib/util/webstorage.ts +++ b/src/platform/web/lib/util/webstorage.ts @@ -1,4 +1,3 @@ -import * as Utils from 'common/lib/util/utils'; import IWebStorage from 'common/types/IWebStorage'; const test = 'ablyjs-storage-test'; @@ -59,7 +58,7 @@ class Webstorage implements IWebStorage { private _set(name: string, value: string, ttl: number | undefined, session: any) { const wrappedValue: Record = { value: value }; if (ttl) { - wrappedValue.expires = Utils.now() + ttl; + wrappedValue.expires = Date.now() + ttl; } return this.storageInterface(session).setItem(name, JSON.stringify(wrappedValue)); } @@ -70,7 +69,7 @@ class Webstorage implements IWebStorage { const rawItem = this.storageInterface(session).getItem(name); if (!rawItem) return null; const wrappedValue = JSON.parse(rawItem); - if (wrappedValue.expires && wrappedValue.expires < Utils.now()) { + if (wrappedValue.expires && wrappedValue.expires < Date.now()) { this.storageInterface(session).removeItem(name); return null; } diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 1046e2d43e..137b56f69a 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1810,7 +1810,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async action: 14, id: 'messageid:0', connectionId: 'connid', - timestamp: utils.now(), + timestamp: Date.now(), presence: [ { clientId: goneClientId, @@ -1899,7 +1899,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async action: 14, id: 'messageid:0', connectionId: 'connid', - timestamp: utils.now(), + timestamp: Date.now(), presence: [ { clientId: fakeClientId, diff --git a/test/realtime/resume.test.js b/test/realtime/resume.test.js index 26104d9aa2..ba1931765b 100644 --- a/test/realtime/resume.test.js +++ b/test/realtime/resume.test.js @@ -553,7 +553,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { connectionManager = connection.connectionManager; connection.once('connected', function () { - connectionManager.lastActivity = helper.Utils.now() - 10000000; + connectionManager.lastActivity = Date.now() - 10000000; /* noop-out onProtocolMessage so that a DISCONNECTED message doesn't * reset the last activity timer */ connectionManager.activeProtocol.getTransport().onProtocolMessage = function () {}; diff --git a/test/rest/auth.test.js b/test/rest/auth.test.js index 0824bcca8d..8f735add2a 100644 --- a/test/rest/auth.test.js +++ b/test/rest/auth.test.js @@ -59,7 +59,7 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, helper, as }); it('Token generation with invalid timestamp', async function () { - var badTime = utils.now() - 30 * 60 * 1000; + var badTime = Date.now() - 30 * 60 * 1000; try { var tokenDetails = await rest.auth.requestToken({ timestamp: badTime }); } catch (err) { diff --git a/test/rest/fallbacks.test.js b/test/rest/fallbacks.test.js index 209b519291..bef0f13184 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -2,7 +2,6 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var expect = chai.expect; - var utils = helper.Utils; var goodHost; describe('rest/fallbacks', function () { @@ -43,7 +42,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'Check validUntil is the same (implying currentFallback has not been re-set)' ); /* set the validUntil to the past and check that the stored fallback is forgotten */ - var now = utils.now(); + var now = Date.now(); rest._currentFallback.validUntil = now - 1000; var serverTime = await rest.time(); expect(serverTime, 'Check serverTime returned').to.be.ok; diff --git a/test/rest/time.test.js b/test/rest/time.test.js index 71d0ee28ab..43b3799a99 100644 --- a/test/rest/time.test.js +++ b/test/rest/time.test.js @@ -2,7 +2,6 @@ define(['shared_helper', 'chai'], function (helper, chai) { var rest; - var utils = helper.Utils; var expect = chai.expect; describe('rest/time', function () { @@ -19,7 +18,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { it('time0', async function () { var serverTime = await rest.time(); - var localFiveMinutesAgo = utils.now() - 5 * 60 * 1000; + var localFiveMinutesAgo = Date.now() - 5 * 60 * 1000; expect( serverTime > localFiveMinutesAgo, 'Verify returned time matches current local time with 5 minute leeway for badly synced local clocks' From 2a9fd381ce33f2e2e2613394a8a8993ccf962a25 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:25:52 +0000 Subject: [PATCH 348/468] Removes IE10 specific needJsonEnvelope check from web xhrrequest --- .../web/lib/http/request/xhrrequest.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/platform/web/lib/http/request/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts index 99801578fd..5c10ec38b1 100644 --- a/src/platform/web/lib/http/request/xhrrequest.ts +++ b/src/platform/web/lib/http/request/xhrrequest.ts @@ -19,27 +19,10 @@ function getAblyError(responseBody: unknown, headers: Record) { } } -declare const global: { - XDomainRequest: unknown; -}; - const noop = function () {}; let idCounter = 0; const pendingRequests: Record = {}; -const isIE = typeof global !== 'undefined' && global.XDomainRequest; - -function ieVersion() { - const match = navigator.userAgent.toString().match(/MSIE\s([\d.]+)/); - return match && Number(match[1]); -} - -function needJsonEnvelope() { - /* IE 10 xhr bug: http://stackoverflow.com/a/16320339 */ - let version; - return isIE && (version = ieVersion()) && version === 10; -} - function getHeader(xhr: XMLHttpRequest, header: string) { return xhr.getResponseHeader && xhr.getResponseHeader(header); } @@ -91,7 +74,6 @@ class XHRRequest extends EventEmitter implements IXHRRequest { super(); params = params || {}; params.rnd = Utils.cheapRandStr(); - if (needJsonEnvelope() && !params.envelope) params.envelope = 'json'; this.uri = uri + Utils.toQueryString(params); this.headers = headers || {}; this.body = body; From c92c2837e72a397103b871f10152f418695a1f5f Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:27:16 +0000 Subject: [PATCH 349/468] Removes IE specific statusCode check from web xhrrequest --- src/platform/web/lib/http/request/xhrrequest.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platform/web/lib/http/request/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts index 5c10ec38b1..d4f2aaa85d 100644 --- a/src/platform/web/lib/http/request/xhrrequest.ts +++ b/src/platform/web/lib/http/request/xhrrequest.ts @@ -309,8 +309,6 @@ class XHRRequest extends EventEmitter implements IXHRRequest { if (xhr.status !== 0) { if (statusCode === undefined) { statusCode = xhr.status; - /* IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 */ - if (statusCode === 1223) statusCode = 204; onResponse(); } if (readyState == 3 && streaming) { From 3b68aeb249ffa9510dadfc952868471292d3bcf2 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:32:39 +0000 Subject: [PATCH 350/468] Removes IE specific console checks from logger --- src/common/lib/util/logger.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/common/lib/util/logger.ts b/src/common/lib/util/logger.ts index c2a9f406c2..c3dee7f129 100644 --- a/src/common/lib/util/logger.ts +++ b/src/common/lib/util/logger.ts @@ -45,27 +45,19 @@ const getDefaultLoggers = (): [Function, Function] => { let consoleLogger; let errorLogger; - /* Can't just check for console && console.log; fails in IE <=9 */ - if ( - (typeof Window === 'undefined' && typeof WorkerGlobalScope === 'undefined') /* node */ || - typeof globalObject?.console?.log?.apply === 'function' /* sensible browsers */ - ) { + // we expect ably-js to be run in environments which have `console` object available with its `log` function + if (typeof globalObject?.console?.log === 'function') { consoleLogger = function (...args: unknown[]) { console.log.apply(console, args); }; + errorLogger = console.warn ? function (...args: unknown[]) { console.warn.apply(console, args); } : consoleLogger; - } else if (globalObject?.console.log as unknown) { - /* IE <= 9 with the console open -- console.log does not - * inherit from Function, so has no apply method */ - consoleLogger = errorLogger = function () { - Function.prototype.apply.call(console.log, console, arguments); - }; } else { - /* IE <= 9 when dev tools are closed - window.console not even defined */ + // otherwise we should fallback to noop for log functions consoleLogger = errorLogger = function () {}; } From c49fc760516b312ad935e5790be241f8a774bdd0 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:33:55 +0000 Subject: [PATCH 351/468] Removes IE msCrypto from web config --- src/platform/web/config.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 6e6a4b8867..54720764b8 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -4,8 +4,6 @@ import * as Utils from 'common/lib/util/utils'; // Workaround for salesforce lightning locker compat const globalObject = Utils.getGlobalObject(); -declare var msCrypto: typeof crypto; // for IE11 - if (typeof Window === 'undefined' && typeof WorkerGlobalScope === 'undefined') { console.log( "Warning: this distribution of Ably is intended for browsers. On nodejs, please use the 'ably' package on npm" @@ -78,7 +76,7 @@ const Config: IPlatformConfig = { return async function (arr: ArrayBufferView) { crypto.getRandomValues(arr); }; - })(globalObject.crypto || msCrypto), + })(globalObject.crypto), isWebworker: isWebWorkerContext(), }; From 2fb51287afc5f832cf92104a2b32714047e04a09 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 07:38:00 +0000 Subject: [PATCH 352/468] Removes IE8 only `noUpgrade` Platform.Config option --- src/common/types/IPlatformConfig.d.ts | 1 - src/platform/nativescript/config.js | 1 - src/platform/react-native/config.ts | 1 - src/platform/web/config.ts | 1 - src/platform/web/index.ts | 8 -------- src/platform/web/modules.ts | 8 -------- 6 files changed, 20 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 1517f485f1..9663325a91 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -27,7 +27,6 @@ export interface ISpecificPlatformConfig { userAgent?: string | null; inherits?: typeof import('util').inherits; currentUrl?: string; - noUpgrade?: boolean | string; fetchSupported?: boolean; xhrSupported?: boolean; allowComet?: boolean; diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index 256467d846..ffa52e16c6 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -20,7 +20,6 @@ if (global.android) { var Config = { agent: 'nativescript', logTimestamps: true, - noUpgrade: false, binaryType: 'arraybuffer', WebSocket: WebSocket, xhrSupported: XMLHttpRequest, diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index 038682021c..9ce31030bc 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -5,7 +5,6 @@ export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { return { agent: 'reactnative', logTimestamps: true, - noUpgrade: false, binaryType: 'arraybuffer', WebSocket: WebSocket, xhrSupported: true, diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 54720764b8..4d5f3ef411 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -36,7 +36,6 @@ const Config: IPlatformConfig = { logTimestamps: true, userAgent: userAgent, currentUrl: currentUrl, - noUpgrade: userAgent && !!userAgent.match(/MSIE\s8\.0/), binaryType: 'arraybuffer', WebSocket: globalObject.WebSocket, fetchSupported: !!globalObject.fetch, diff --git a/src/platform/web/index.ts b/src/platform/web/index.ts index a598774244..dcb55cd178 100644 --- a/src/platform/web/index.ts +++ b/src/platform/web/index.ts @@ -50,14 +50,6 @@ if (Platform.Config.agent) { Platform.Defaults.agent += ' ' + Platform.Config.agent; } -/* If using IE8, don't attempt to upgrade from xhr_polling to xhr_streaming - - * while it can do streaming, the low max http-connections-per-host limit means - * that the polling transport is crippled during the upgrade process. So just - * leave it at the base transport */ -if (Platform.Config.noUpgrade) { - Platform.Defaults.upgradeTransports = []; -} - export { DefaultRest as Rest, DefaultRealtime as Realtime, msgpack, protocolMessageFromDeserialized }; export default { diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 16c8343efb..33a3aa81a8 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -35,14 +35,6 @@ if (Platform.Config.agent) { Platform.Defaults.agent += ' ' + Platform.Config.agent; } -/* If using IE8, don't attempt to upgrade from xhr_polling to xhr_streaming - - * while it can do streaming, the low max http-connections-per-host limit means - * that the polling transport is crippled during the upgrade process. So just - * leave it at the base transport */ -if (Platform.Config.noUpgrade) { - Platform.Defaults.upgradeTransports = []; -} - export * from './modules/crypto'; export * from './modules/message'; export * from './modules/presencemessage'; From 5106b006944d06e66adc477f66e158a4752d70f1 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:07:28 +0000 Subject: [PATCH 353/468] Make IPlatformConfig.getRandomArrayBuffer non-optional This is in preparation for unifying IPlatformConfig.getRandomValues and IConfig.getRandomArrayBuffer functions into one. `getRandomArrayBuffer` implementation details for each platform: - for `nativescript` - based on nativescript's `getRandomValues` function - for `nodejs` - based on `generateRandom` function from our nodejs crypto code [1] - for `react-native` - was already implemented - for `web` - based on `generateRandom` function from our web crypto code [2]. After fccd4ac950858634ccb7e0120ad5520261be63b8 it is possible to simplify this code by removing `DEFAULT_BLOCKLENGTH_WORDS` and using Uint8Array directly instead of Uint32Array and calculating words [1] https://github.com/ably/ably-js/blob/d0e9fbe4797ebb8520286592ba7b5d82a139eb39/src/platform/nodejs/lib/util/crypto.ts#L32 [2] https://github.com/ably/ably-js/blob/d0e9fbe4797ebb8520286592ba7b5d82a139eb39/src/platform/web/lib/util/crypto.ts#L35 --- src/common/types/IPlatformConfig.d.ts | 2 +- src/platform/nativescript/config.js | 4 ++++ src/platform/nodejs/config.ts | 3 +++ src/platform/react-native/config.ts | 8 ++++++-- src/platform/web/config.ts | 5 +++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 9663325a91..5b0f1cbeec 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -14,6 +14,7 @@ export interface ICommonPlatformConfig { nextTick: process.nextTick; inspect: (value: unknown) => string; stringByteSize: Buffer.byteLength; + getRandomArrayBuffer: (byteLength: number) => Promise; } /** @@ -35,7 +36,6 @@ export interface ISpecificPlatformConfig { atob?: typeof atob | null; TextEncoder?: typeof TextEncoder; TextDecoder?: typeof TextDecoder; - getRandomArrayBuffer?: (byteLength: number) => Promise; isWebworker?: boolean; } diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index ffa52e16c6..c4da80eee0 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -50,6 +50,10 @@ var Config = { arr[i] = bytes[i]; } }, + getRandomArrayBuffer: async function (byteLength) { + var bytes = randomBytes(byteLength); + return bytes; + }, }; export default Config; diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 27952ff76b..69416efbcc 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -25,6 +25,9 @@ const Config: IPlatformConfig = { dataView.setUint8(i, bytes[i]); } }, + getRandomArrayBuffer: async function (byteLength: number): Promise { + return util.promisify(crypto.randomBytes)(byteLength); + }, }; export default Config; diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index 9ce31030bc..7e92eaa59c 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -1,6 +1,10 @@ import { IPlatformConfig } from '../../common/types/IPlatformConfig'; import BufferUtils from '../web/lib/util/bufferutils'; +type RNRandomBytes = { + randomBytes: (byteLength: number, cb: (err: Error | null, base64String: string | null) => void) => void; +}; + export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { return { agent: 'reactnative', @@ -29,10 +33,10 @@ export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { }, TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder, - getRandomArrayBuffer: (function (RNRandomBytes) { + getRandomArrayBuffer: (function (RNRandomBytes: RNRandomBytes) { return async function (byteLength: number) { return new Promise((resolve, reject) => { - RNRandomBytes.randomBytes(byteLength, function (err: Error | null, base64String: string | null) { + RNRandomBytes.randomBytes(byteLength, (err, base64String) => { err ? reject(err) : resolve(bufferUtils.toArrayBuffer(bufferUtils.base64Decode(base64String!))); }); }); diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 4d5f3ef411..51e7bc662c 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -76,6 +76,11 @@ const Config: IPlatformConfig = { crypto.getRandomValues(arr); }; })(globalObject.crypto), + getRandomArrayBuffer: async function (byteLength: number): Promise { + const byteArray = new Uint8Array(byteLength); + globalObject.crypto.getRandomValues(byteArray); + return byteArray.buffer; + }, isWebworker: isWebWorkerContext(), }; From a7a962033ecfd589e8d37f1f22eea58025f14442 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:15:11 +0000 Subject: [PATCH 354/468] Removes fallbacks to `Math.random` in utils and web crypto In #1537 we established browser versions we intend to support in ably-js v2 going forward, and all of them support `Crypto.getRandomValues`, thus there is no need to have fallbacks to `Math.random` anymore. This commit removes such code and changes corresponding places to always call IPlatformConfig.getRandomArrayBuffer instead. Resolves #1332 --- src/common/lib/client/restchannel.ts | 2 +- src/common/lib/util/utils.ts | 20 ++----------- src/platform/web/lib/util/crypto.ts | 43 ++-------------------------- 3 files changed, 7 insertions(+), 58 deletions(-) diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 23c4e9b9d6..be33ac264f 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -88,7 +88,7 @@ class RestChannel { Utils.mixin(headers, options.headers); if (idempotentRestPublishing && allEmptyIds(messages)) { - const msgIdBase = Utils.randomString(MSG_ID_ENTROPY_BYTES); + const msgIdBase = await Utils.randomString(MSG_ID_ENTROPY_BYTES); Utils.arrForEach(messages, function (message, index) { message.id = msgIdBase + ':' + index.toString(); }); diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 153dd6a686..ac91a95998 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -399,23 +399,9 @@ export function cheapRandStr(): string { /* Takes param the minimum number of bytes of entropy the string must * include, not the length of the string. String length produced is not * guaranteed. */ -export const randomString = (numBytes: number): string => { - if (Platform.Config.getRandomValues && typeof Uint8Array !== 'undefined') { - const uIntArr = new Uint8Array(numBytes); - (Platform.Config.getRandomValues as Function)(uIntArr); - return Platform.BufferUtils.base64Encode(uIntArr); - } - /* No secure random generator available; fall back to Math.random. - * TODO we should no longer end up in this scenario — and hence should be able to remove this code — given that all supported platforms should now have a random generator — see https://github.com/ably/ably-js/issues/1332 - */ - const charset = Platform.BufferUtils.base64CharSet; - /* base64 has 33% overhead; round length up */ - const length = Math.round((numBytes * 4) / 3); - let result = ''; - for (let i = 0; i < length; i++) { - result += charset[randomPosn(charset)]; - } - return result; +export const randomString = async (numBytes: number): Promise => { + const buffer = await Platform.Config.getRandomArrayBuffer(numBytes); + return Platform.BufferUtils.base64Encode(buffer); }; /* Pick n elements at random without replacement from an array */ diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index c54c565a3d..f4fa628292 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -21,40 +21,6 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B var DEFAULT_KEYLENGTH = 256; // bits var DEFAULT_MODE = 'cbc'; var DEFAULT_BLOCKLENGTH = 16; // bytes - var DEFAULT_BLOCKLENGTH_WORDS = 4; // 32-bit words - var UINT32_SUP = 0x100000000; - - /** - * Internal: generate an array of secure random data corresponding to the given length of bytes - * @param bytes - */ - var generateRandom: (byteLength: number) => Promise; - if (config.getRandomArrayBuffer) { - generateRandom = config.getRandomArrayBuffer; - } else if (typeof Uint32Array !== 'undefined' && config.getRandomValues) { - generateRandom = async function (bytes) { - var blockRandomArray = new Uint32Array(DEFAULT_BLOCKLENGTH_WORDS); - var words = bytes / 4, - nativeArray = words == DEFAULT_BLOCKLENGTH_WORDS ? blockRandomArray : new Uint32Array(words); - await config.getRandomValues!(nativeArray); - return bufferUtils.toArrayBuffer(nativeArray); - }; - } else { - generateRandom = async function (bytes) { - Logger.logAction( - Logger.LOG_MAJOR, - 'Ably.Crypto.generateRandom()', - 'Warning: the browser you are using does not support secure cryptographically secure randomness generation; falling back to insecure Math.random()' - ); - var words = bytes / 4, - array = new Uint32Array(words); - for (var i = 0; i < words; i++) { - array[i] = Math.floor(Math.random() * UINT32_SUP); - } - - return bufferUtils.toArrayBuffer(array); - }; - } /** * Internal: checks that the cipherParams are a valid combination. Currently @@ -117,10 +83,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B * classes and interfaces here. *- * Secure random data for creation of Initialization Vectors (IVs) and keys - * is obtained from window.crypto.getRandomValues if available, or from - * Math.random() if not. Clients who do not want to depend on Math.random() - * should polyfill window.crypto.getRandomValues with a library that seeds - * a PRNG with real entropy. + * is obtained from window.crypto.getRandomValues. * * Each message payload is encrypted with an IV in CBC mode, and the IV is * concatenated with the resulting raw ciphertext to construct the "ciphertext" @@ -178,7 +141,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B */ static async generateRandomKey(keyLength?: number): Promise { try { - return await generateRandom((keyLength || DEFAULT_KEYLENGTH) / 8); + return config.getRandomArrayBuffer((keyLength || DEFAULT_KEYLENGTH) / 8); } catch (err) { throw new ErrorInfo('Failed to generate random key: ' + (err as Error).message, 400, 50000, err as Error); } @@ -271,7 +234,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B return iv; } - const randomBlock = await generateRandom(DEFAULT_BLOCKLENGTH); + const randomBlock = await config.getRandomArrayBuffer(DEFAULT_BLOCKLENGTH); return bufferUtils.toArrayBuffer(randomBlock); } } From 59ce70a001dc0886453bf253ad4f94cc44c52668 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:20:11 +0000 Subject: [PATCH 355/468] Removes IPlatformConfig.getRandomValues function in favour of getRandomArrayBuffer --- src/common/types/IPlatformConfig.d.ts | 1 - src/platform/nativescript/config.js | 6 ------ src/platform/nodejs/config.ts | 8 -------- src/platform/web/config.ts | 8 -------- 4 files changed, 23 deletions(-) diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 5b0f1cbeec..65b757b7dc 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -24,7 +24,6 @@ export interface ICommonPlatformConfig { */ export interface ISpecificPlatformConfig { addEventListener?: typeof window.addEventListener | typeof global.addEventListener | null; - getRandomValues?: (arr: ArrayBufferView) => Promise; userAgent?: string | null; inherits?: typeof import('util').inherits; currentUrl?: string; diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index c4da80eee0..842b8af527 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -44,12 +44,6 @@ var Config = { }, TextEncoder: global.TextEncoder, TextDecoder: global.TextDecoder, - getRandomValues: async function (arr) { - var bytes = randomBytes(arr.length); - for (var i = 0; i < arr.length; i++) { - arr[i] = bytes[i]; - } - }, getRandomArrayBuffer: async function (byteLength) { var bytes = randomBytes(byteLength); return bytes; diff --git a/src/platform/nodejs/config.ts b/src/platform/nodejs/config.ts index 69416efbcc..fc116ce086 100644 --- a/src/platform/nodejs/config.ts +++ b/src/platform/nodejs/config.ts @@ -17,14 +17,6 @@ const Config: IPlatformConfig = { stringByteSize: Buffer.byteLength, inherits: util.inherits, addEventListener: null, - getRandomValues: async function (arr: ArrayBufferView): Promise { - const bytes = crypto.randomBytes(arr.byteLength); - const dataView = new DataView(arr.buffer, arr.byteOffset, arr.byteLength); - - for (let i = 0; i < bytes.length; i++) { - dataView.setUint8(i, bytes[i]); - } - }, getRandomArrayBuffer: async function (byteLength: number): Promise { return util.promisify(crypto.randomBytes)(byteLength); }, diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 51e7bc662c..977ae102c0 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -68,14 +68,6 @@ const Config: IPlatformConfig = { }, TextEncoder: globalObject.TextEncoder, TextDecoder: globalObject.TextDecoder, - getRandomValues: (function (crypto) { - if (crypto === undefined) { - return undefined; - } - return async function (arr: ArrayBufferView) { - crypto.getRandomValues(arr); - }; - })(globalObject.crypto), getRandomArrayBuffer: async function (byteLength: number): Promise { const byteArray = new Uint8Array(byteLength); globalObject.crypto.getRandomValues(byteArray); From 103f8efc64b6405617ea63c7f89f446480a5b19c Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:20:26 +0000 Subject: [PATCH 356/468] Fix comment --- src/platform/web/lib/util/crypto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/lib/util/crypto.ts b/src/platform/web/lib/util/crypto.ts index f4fa628292..a324f3557b 100644 --- a/src/platform/web/lib/util/crypto.ts +++ b/src/platform/web/lib/util/crypto.ts @@ -81,7 +81,7 @@ var createCryptoClass = function (config: IPlatformConfig, bufferUtils: typeof B * but supporting other keylengths. Other algorithms and chaining modes are * not supported directly, but supportable by extending/implementing the base * classes and interfaces here. - *- + * * Secure random data for creation of Initialization Vectors (IVs) and keys * is obtained from window.crypto.getRandomValues. * From 07601b5ece84c0d3445d59a242fc65f874474d4a Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:38:22 +0000 Subject: [PATCH 357/468] Removes unnecessary hasOwnProperty is nullish check --- src/common/lib/util/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index ac91a95998..9b5063e62a 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -22,9 +22,9 @@ export function mixin( if (!source) { break; } - const hasOwnProperty = Object.prototype.hasOwnProperty; + for (const key in source) { - if (!hasOwnProperty || hasOwnProperty.call(source, key)) { + if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = (source as Record)[key]; } } From ad0857e02a5b3bc2f16956971f09bdba27162cfa Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:42:59 +0000 Subject: [PATCH 358/468] Improves comments in utils --- src/common/lib/util/utils.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 9b5063e62a..daef5703fe 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -7,11 +7,12 @@ function randomPosn(arrOrStr: Array | string) { return Math.floor(Math.random() * arrOrStr.length); } -/* +/** * Add a set of properties to a target object - * target: the target object - * props: an object whose enumerable properties are - * added, by reference only + * + * @param target the target object + * @param args objects, which enumerable properties are added to target, by reference only + * @returns target object with added properties */ export function mixin( target: Record, @@ -32,11 +33,11 @@ export function mixin( return target; } -/* - * Add a set of properties to a target object - * target: the target object - * props: an object whose enumerable properties are - * added, by reference only +/** + * Creates a copy of enumerable properties of the source object + * + * @param src object to copy + * @returns copy of src */ export function copy>(src: T | Record | null | undefined): T { return mixin({}, src as Record) as T; From b88ec14354731cc62875920ada280d220301f7e1 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:45:44 +0000 Subject: [PATCH 359/468] Remove polyfill for Array.isArray in utils --- src/common/lib/util/utils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index daef5703fe..43200f6362 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -47,11 +47,7 @@ export function copy>(src: T | Record { - return Object.prototype.toString.call(value) == '[object Array]'; - }; +export const isArray = Array.isArray; /* * Ensures that an Array object is always returned From 397523baa8739089239930b2f4f17fe795010343 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:49:30 +0000 Subject: [PATCH 360/468] Remove Utils.isArray function in favour of Array.isArray --- src/common/lib/client/auth.ts | 2 +- src/common/lib/client/realtimechannel.ts | 6 +++--- src/common/lib/client/rest.ts | 2 +- src/common/lib/client/restchannel.ts | 2 +- src/common/lib/types/devicedetails.ts | 2 +- src/common/lib/types/message.ts | 2 +- src/common/lib/types/pushchannelsubscription.ts | 2 +- src/common/lib/util/eventemitter.ts | 10 +++++----- src/common/lib/util/utils.ts | 10 ++-------- .../nodejs/lib/transport/nodecomettransport.js | 2 +- src/platform/web/lib/http/request/xhrrequest.ts | 2 +- test/rest/push.test.js | 6 +++--- test/rest/request.test.js | 2 +- 13 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/common/lib/client/auth.ts b/src/common/lib/client/auth.ts index b0d819921c..806930e822 100644 --- a/src/common/lib/client/auth.ts +++ b/src/common/lib/client/auth.ts @@ -441,7 +441,7 @@ class Auth { ); } else { const contentTypeHeaderOrHeaders = result.headers!['content-type'] ?? null; - if (Utils.isArray(contentTypeHeaderOrHeaders)) { + if (Array.isArray(contentTypeHeaderOrHeaders)) { // Combine multiple header values into a comma-separated list per https://datatracker.ietf.org/doc/html/rfc9110#section-5.2; see https://github.com/ably/ably-js/issues/1616 for doing this consistently across the codebase. contentType = contentTypeHeaderOrHeaders.join(', '); } else { diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index 5306ff15e5..c53290a229 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -45,7 +45,7 @@ function validateChannelOptions(options?: API.ChannelOptions) { return new ErrorInfo('options.params must be an object', 40000, 400); } if (options && 'modes' in options) { - if (!Utils.isArray(options.modes)) { + if (!Array.isArray(options.modes)) { return new ErrorInfo('options.modes must be an array', 40000, 400); } for (let i = 0; i < options.modes.length; i++) { @@ -222,7 +222,7 @@ class RealtimeChannel extends EventEmitter { } if (argCount == 1) { if (Utils.isObject(messages)) messages = [messageFromValues(messages)]; - else if (Utils.isArray(messages)) messages = messagesFromValuesArray(messages); + else if (Array.isArray(messages)) messages = messagesFromValuesArray(messages); else throw new ErrorInfo( 'The single-argument form of publish() expects a message object or an array of message objects', @@ -467,7 +467,7 @@ class RealtimeChannel extends EventEmitter { const msg = protocolMessageFromValues({ action: actions.PRESENCE, channel: this.name, - presence: Utils.isArray(presence) + presence: Array.isArray(presence) ? this.client._RealtimePresence!.presenceMessagesFromValuesArray(presence) : [this.client._RealtimePresence!.presenceMessageFromValues(presence)], }); diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index b8ab1d66d5..516cb0a51a 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -154,7 +154,7 @@ export class Rest { ): Promise { let requestBodyDTO: BatchPublishSpec[]; let singleSpecMode: boolean; - if (Utils.isArray(specOrSpecs)) { + if (Array.isArray(specOrSpecs)) { requestBodyDTO = specOrSpecs; singleSpecMode = false; } else { diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index be33ac264f..dbbf8725fb 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -63,7 +63,7 @@ class RestChannel { } else if (Utils.isObject(first)) { messages = [messageFromValues(first)]; params = args[1]; - } else if (Utils.isArray(first)) { + } else if (Array.isArray(first)) { messages = messagesFromValuesArray(first); params = args[1]; } else { diff --git a/src/common/lib/types/devicedetails.ts b/src/common/lib/types/devicedetails.ts index 4b93cac560..6027a089d9 100644 --- a/src/common/lib/types/devicedetails.ts +++ b/src/common/lib/types/devicedetails.ts @@ -84,7 +84,7 @@ class DeviceDetails { body = Utils.decodeBody(body, MsgPack, format); } - if (Utils.isArray(body)) { + if (Array.isArray(body)) { return DeviceDetails.fromValuesArray(body); } else { return DeviceDetails.fromValues(body); diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 837ab7c0fd..e52a40a38e 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -123,7 +123,7 @@ export async function encode(msg: T, option typeof data == 'string' || Platform.BufferUtils.isBuffer(data) || data === null || data === undefined; if (!nativeDataType) { - if (Utils.isObject(data) || Utils.isArray(data)) { + if (Utils.isObject(data) || Array.isArray(data)) { msg.data = JSON.stringify(data); msg.encoding = msg.encoding ? msg.encoding + '/json' : 'json'; } else { diff --git a/src/common/lib/types/pushchannelsubscription.ts b/src/common/lib/types/pushchannelsubscription.ts index 48190f25af..8a9d90ff1c 100644 --- a/src/common/lib/types/pushchannelsubscription.ts +++ b/src/common/lib/types/pushchannelsubscription.ts @@ -44,7 +44,7 @@ class PushChannelSubscription { body = Utils.decodeBody(body, MsgPack, format) as Record; } - if (Utils.isArray(body)) { + if (Array.isArray(body)) { return PushChannelSubscription.fromValuesArray(body); } else { return PushChannelSubscription.fromValues(body); diff --git a/src/common/lib/util/eventemitter.ts b/src/common/lib/util/eventemitter.ts index 4044343b31..b1a3ece94c 100644 --- a/src/common/lib/util/eventemitter.ts +++ b/src/common/lib/util/eventemitter.ts @@ -32,7 +32,7 @@ function removeListener(targetListeners: any, listener: Function, eventFilter?: listeners = listeners[eventFilter] as Record; } - if (Utils.isArray(listeners)) { + if (Array.isArray(listeners)) { while ((index = Utils.arrIndexOf(listeners, listener)) !== -1) { listeners.splice(index, 1); } @@ -44,7 +44,7 @@ function removeListener(targetListeners: any, listener: Function, eventFilter?: } else if (Utils.isObject(listeners)) { /* events */ for (eventName in listeners) { - if (Object.prototype.hasOwnProperty.call(listeners, eventName) && Utils.isArray(listeners[eventName])) { + if (Object.prototype.hasOwnProperty.call(listeners, eventName) && Array.isArray(listeners[eventName])) { removeListener([listeners], listener, eventName); } } @@ -94,7 +94,7 @@ class EventEmitter { } if (Utils.isEmptyArg(event)) { this.any.push(listener); - } else if (Utils.isArray(event)) { + } else if (Array.isArray(event)) { event.forEach((eventName) => { this.on(eventName, listener); }); @@ -156,7 +156,7 @@ class EventEmitter { return; } - if (Utils.isArray(event)) { + if (Array.isArray(event)) { event.forEach((eventName) => { this.off(eventName, listener); }); @@ -256,7 +256,7 @@ class EventEmitter { throw new Error('EventEmitter.once(): Invalid arguments:' + Platform.Config.inspect(args)); } this.anyOnce.push(secondArg); - } else if (Utils.isArray(firstArg)) { + } else if (Array.isArray(firstArg)) { const self = this; const listenerWrapper = function (this: any) { const innerArgs = Array.prototype.slice.call(arguments); diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 43200f6362..2d55efd561 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -43,12 +43,6 @@ export function copy>(src: T | Record) as T; } -/* - * Determine whether or not a given object is - * an array. - */ -export const isArray = Array.isArray; - /* * Ensures that an Array object is always returned * returning the original Array of obj is an Array @@ -58,7 +52,7 @@ export function ensureArray(obj: Record): unknown[] { if (isEmptyArg(obj)) { return []; } - if (isArray(obj)) { + if (Array.isArray(obj)) { return obj; } return [obj]; @@ -154,7 +148,7 @@ export function containsValue(ob: Record, val: unknown): boolea } export function intersect(arr: Array, ob: K[] | Partial>): K[] { - return isArray(ob) ? arrIntersect(arr, ob) : arrIntersectOb(arr, ob); + return Array.isArray(ob) ? arrIntersect(arr, ob) : arrIntersectOb(arr, ob); } export function arrIntersect(arr1: Array, arr2: Array): Array { diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.js b/src/platform/nodejs/lib/transport/nodecomettransport.js index 9a1c29411d..83236045a4 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.js +++ b/src/platform/nodejs/lib/transport/nodecomettransport.js @@ -265,7 +265,7 @@ var NodeCometTransport = function (transportStorage) { * is contains an error action (hence the nonsuccess statuscode), we can * consider the request to have succeeded, just pass it on to * onProtocolMessage to decide what to do */ - if (statusCode < 400 || Utils.isArray(body)) { + if (statusCode < 400 || Array.isArray(body)) { self.complete(null, body); return; } diff --git a/src/platform/web/lib/http/request/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts index d4f2aaa85d..dd219938c1 100644 --- a/src/platform/web/lib/http/request/xhrrequest.ts +++ b/src/platform/web/lib/http/request/xhrrequest.ts @@ -255,7 +255,7 @@ class XHRRequest extends EventEmitter implements IXHRRequest { * is contains an error action (hence the nonsuccess statuscode), we can * consider the request to have succeeded, just pass it on to * onProtocolMessage to decide what to do */ - if (successResponse || Utils.isArray(parsedResponse)) { + if (successResponse || Array.isArray(parsedResponse)) { this.complete(null, parsedResponse, headers, unpacked, statusCode); return; } diff --git a/test/rest/push.test.js b/test/rest/push.test.js index 9d251ffe68..926d534ef5 100644 --- a/test/rest/push.test.js +++ b/test/rest/push.test.js @@ -315,8 +315,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * includesUnordered(x, y) -> string | true */ function includesUnordered(x, y) { - if (Utils.isArray(x)) { - if (!Utils.isArray(y)) { + if (Array.isArray(x)) { + if (!Array.isArray(y)) { return 'not both arrays'; } @@ -351,7 +351,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return true; } else if (x instanceof Object) { - if (!(x instanceof Object) || Utils.isArray(y)) { + if (!(x instanceof Object) || Array.isArray(y)) { return 'not both objects'; } diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 7aa04f9db9..0af1d35335 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -45,7 +45,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async const res = await rest.request('get', '/time', Defaults.protocolVersion, null, null, null); expect(res.statusCode).to.equal(200, 'Check statusCode'); expect(res.success).to.equal(true, 'Check success'); - expect(utils.isArray(res.items), true, 'Check array returned').to.be.ok; + expect(Array.isArray(res.items), true, 'Check array returned').to.be.ok; expect(res.items.length).to.equal(1, 'Check array was of length 1'); }); From 37fe7381af2f9c15b0b13ec99d720cc5a1bf836a Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:58:49 +0000 Subject: [PATCH 361/468] Change Utils.isEmptyArg to Utils.isNil --- src/common/lib/util/eventemitter.ts | 8 ++++---- src/common/lib/util/utils.ts | 16 ++++++---------- .../web/lib/http/request/fetchrequest.ts | 2 +- src/platform/web/lib/http/request/xhrrequest.ts | 2 +- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/common/lib/util/eventemitter.ts b/src/common/lib/util/eventemitter.ts index b1a3ece94c..2aaa89a54e 100644 --- a/src/common/lib/util/eventemitter.ts +++ b/src/common/lib/util/eventemitter.ts @@ -92,7 +92,7 @@ class EventEmitter { if (typeof listener !== 'function') { throw new Error('EventListener.on(): Invalid arguments: ' + Platform.Config.inspect(args)); } - if (Utils.isEmptyArg(event)) { + if (Utils.isNil(event)) { this.any.push(listener); } else if (Array.isArray(event)) { event.forEach((eventName) => { @@ -126,7 +126,7 @@ class EventEmitter { off(event: string | string[] | null, listener?: Function | null): void; off(...args: unknown[]) { - if (args.length == 0 || (Utils.isEmptyArg(args[0]) && Utils.isEmptyArg(args[1]))) { + if (args.length == 0 || (Utils.isNil(args[0]) && Utils.isNil(args[1]))) { this.any = []; this.events = Object.create(null); this.anyOnce = []; @@ -151,7 +151,7 @@ class EventEmitter { [event, listener] = [firstArg, secondArg]; } - if (listener && Utils.isEmptyArg(event)) { + if (listener && Utils.isNil(event)) { removeListener([this.any, this.events, this.anyOnce, this.eventsOnce], listener); return; } @@ -251,7 +251,7 @@ class EventEmitter { const [firstArg, secondArg] = args; if (args.length === 1 && typeof firstArg === 'function') { this.anyOnce.push(firstArg); - } else if (Utils.isEmptyArg(firstArg)) { + } else if (Utils.isNil(firstArg)) { if (typeof secondArg !== 'function') { throw new Error('EventEmitter.once(): Invalid arguments:' + Platform.Config.inspect(args)); } diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 2d55efd561..c31d903f9f 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -49,7 +49,7 @@ export function copy>(src: T | Record): unknown[] { - if (isEmptyArg(obj)) { + if (isNil(obj)) { return []; } if (Array.isArray(obj)) { @@ -72,16 +72,12 @@ export function isEmpty(ob: Record | unknown[]): boolean { return true; } -/* - * Determine whether or not an argument to an overloaded function is - * undefined (missing) or null. - * This method is useful when constructing functions such as (WebIDL terminology): - * off([TreatUndefinedAs=Null] DOMString? event) - * as you can then confirm the argument using: - * Utils.isEmptyArg(event) +/** + * Checks if `value` is `null` or `undefined`. + * + * Source: https://github.com/lodash/lodash/blob/main/src/isNil.ts */ - -export function isEmptyArg(arg: unknown): arg is null | undefined { +export function isNil(arg: unknown): arg is null | undefined { return arg === null || arg === undefined; } diff --git a/src/platform/web/lib/http/request/fetchrequest.ts b/src/platform/web/lib/http/request/fetchrequest.ts index 22cee07069..5ab1924e0f 100644 --- a/src/platform/web/lib/http/request/fetchrequest.ts +++ b/src/platform/web/lib/http/request/fetchrequest.ts @@ -35,7 +35,7 @@ export default async function fetchRequest( body: RequestBody | null ): Promise { const fetchHeaders = new Headers(headers || {}); - const _method = method ? method.toUpperCase() : Utils.isEmptyArg(body) ? 'GET' : 'POST'; + const _method = method ? method.toUpperCase() : Utils.isNil(body) ? 'GET' : 'POST'; const controller = new AbortController(); diff --git a/src/platform/web/lib/http/request/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts index dd219938c1..b6774a9663 100644 --- a/src/platform/web/lib/http/request/xhrrequest.ts +++ b/src/platform/web/lib/http/request/xhrrequest.ts @@ -77,7 +77,7 @@ class XHRRequest extends EventEmitter implements IXHRRequest { this.uri = uri + Utils.toQueryString(params); this.headers = headers || {}; this.body = body; - this.method = method ? method.toUpperCase() : Utils.isEmptyArg(body) ? 'GET' : 'POST'; + this.method = method ? method.toUpperCase() : Utils.isNil(body) ? 'GET' : 'POST'; this.requestMode = requestMode; this.timeouts = timeouts; this.timedOut = false; From d9ec0af1f11c787745aeb966d679d0b4cc772ac0 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 09:59:23 +0000 Subject: [PATCH 362/468] Change Utils.isNil to shorter check --- src/common/lib/util/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index c31d903f9f..a772ea3735 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -78,7 +78,7 @@ export function isEmpty(ob: Record | unknown[]): boolean { * Source: https://github.com/lodash/lodash/blob/main/src/isNil.ts */ export function isNil(arg: unknown): arg is null | undefined { - return arg === null || arg === undefined; + return arg == null; } /* From fc9698e53f205f8179d7178f2ac9c8d3dbd98a0f Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:08:32 +0000 Subject: [PATCH 363/468] Remove polyfill for Array.prototype.indexOf in Utils --- src/common/lib/util/utils.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index a772ea3735..462fc48eca 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -174,20 +174,9 @@ export function arrSubtract(arr1: Array, arr2: Array): Array { return result; } -export const arrIndexOf = (Array.prototype.indexOf as unknown) - ? function (arr: Array, elem: unknown, fromIndex?: number) { - return arr.indexOf(elem, fromIndex); - } - : function (arr: Array, elem: unknown, fromIndex?: number) { - fromIndex = fromIndex || 0; - const len = arr.length; - for (; fromIndex < len; fromIndex++) { - if (arr[fromIndex] === elem) { - return fromIndex; - } - } - return -1; - }; +export const arrIndexOf = function (arr: Array, elem: unknown, fromIndex?: number) { + return arr.indexOf(elem, fromIndex); +}; export function arrIn(arr: Array, val: unknown): boolean { return arrIndexOf(arr, val) !== -1; From 1836bdd03e66b4eaf4f59d15b059d384887dd1fa Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:12:36 +0000 Subject: [PATCH 364/468] Change Transport.shortName type to TransportName from string --- src/common/lib/transport/transport.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index a18bfcb830..adb8a6f0e6 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -12,6 +12,7 @@ import Auth from '../client/auth'; import * as API from '../../../../ably'; import ConnectionManager, { TransportParams } from './connectionmanager'; import Platform from 'common/platform'; +import TransportName from '../../constants/TransportName'; export type TryConnectCallback = ( wrappedErr: { error: ErrorInfo; event: string } | null, @@ -72,7 +73,7 @@ abstract class Transport extends EventEmitter { this.lastActivity = null; } - abstract shortName: string; + abstract shortName: TransportName; abstract send(message: ProtocolMessage): void; connect(): void {} From bc4a49499e4231fabda0d38b763ddd9c150059ef Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:19:18 +0000 Subject: [PATCH 365/468] Remove Utils.arrIndexOf in favour of native Array.prototype.indexOf --- src/common/lib/transport/connectionmanager.ts | 8 ++++---- src/common/lib/util/eventemitter.ts | 2 +- src/common/lib/util/utils.ts | 12 ++++-------- test/realtime/event_emitter.test.js | 4 ++-- test/realtime/message.test.js | 2 +- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 5d6b357d6f..9f6d361f13 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -44,8 +44,8 @@ function clearSessionRecoverData() { function betterTransportThan(a: Transport, b: Transport) { return ( - Utils.arrIndexOf(Platform.Defaults.transportPreferenceOrder, a.shortName) > - Utils.arrIndexOf(Platform.Defaults.transportPreferenceOrder, b.shortName) + Platform.Defaults.transportPreferenceOrder.indexOf(a.shortName) > + Platform.Defaults.transportPreferenceOrder.indexOf(b.shortName) ); } @@ -1695,7 +1695,7 @@ class ConnectionManager extends EventEmitter { * transport in upgradeTransports (if it's in there - if not, currentSerial * will be -1, so return upgradeTransports.slice(0) == upgradeTransports */ const current = (this.activeProtocol as Protocol).getTransport().shortName; - const currentSerial = Utils.arrIndexOf(this.upgradeTransports, current); + const currentSerial = this.upgradeTransports.indexOf(current); return this.upgradeTransports.slice(currentSerial + 1); } @@ -2036,7 +2036,7 @@ class ConnectionManager extends EventEmitter { // Message came in on a defunct transport. Allow only acks, nacks, & errors for outstanding // messages, no new messages (as sync has been sent on new transport so new messages will // be resent there, or connection has been closed so don't want new messages) - if (Utils.arrIndexOf([actions.ACK, actions.NACK, actions.ERROR], message.action) > -1) { + if ([actions.ACK, actions.NACK, actions.ERROR].includes(message.action!)) { await this.realtime.channels.processChannelMessage(message); } else { Logger.logAction( diff --git a/src/common/lib/util/eventemitter.ts b/src/common/lib/util/eventemitter.ts index 2aaa89a54e..f86ead2bc4 100644 --- a/src/common/lib/util/eventemitter.ts +++ b/src/common/lib/util/eventemitter.ts @@ -33,7 +33,7 @@ function removeListener(targetListeners: any, listener: Function, eventFilter?: } if (Array.isArray(listeners)) { - while ((index = Utils.arrIndexOf(listeners, listener)) !== -1) { + while ((index = listeners.indexOf(listener)) !== -1) { listeners.splice(index, 1); } /* If events object has an event name key with no listeners then diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 462fc48eca..0da1943405 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -151,7 +151,7 @@ export function arrIntersect(arr1: Array, arr2: Array): Array { const result = []; for (let i = 0; i < arr1.length; i++) { const member = arr1[i]; - if (arrIndexOf(arr2, member) != -1) result.push(member); + if (arr2.indexOf(member) != -1) result.push(member); } return result; } @@ -169,21 +169,17 @@ export function arrSubtract(arr1: Array, arr2: Array): Array { const result = []; for (let i = 0; i < arr1.length; i++) { const element = arr1[i]; - if (arrIndexOf(arr2, element) == -1) result.push(element); + if (arr2.indexOf(element) == -1) result.push(element); } return result; } -export const arrIndexOf = function (arr: Array, elem: unknown, fromIndex?: number) { - return arr.indexOf(elem, fromIndex); -}; - export function arrIn(arr: Array, val: unknown): boolean { - return arrIndexOf(arr, val) !== -1; + return arr.indexOf(val) !== -1; } export function arrDeleteValue(arr: Array, val: T): boolean { - const idx = arrIndexOf(arr, val); + const idx = arr.indexOf(val); const res = idx != -1; if (res) arr.splice(idx, 1); return res; diff --git a/test/realtime/event_emitter.test.js b/test/realtime/event_emitter.test.js index 1fac46d249..3c4d9be3ce 100644 --- a/test/realtime/event_emitter.test.js +++ b/test/realtime/event_emitter.test.js @@ -33,7 +33,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { expectedConnectionEvents = ['connecting', 'connected', 'closing', 'closed'], expectedChannelEvents = ['attaching', 'attached', 'detaching', 'detached']; realtime.connection.on(function () { - if ((index = utils.arrIndexOf(expectedConnectionEvents, this.event)) > -1) { + if ((index = expectedConnectionEvents.indexOf(this.event)) > -1) { delete expectedConnectionEvents[index]; if (this.event == 'closed') { done(); @@ -45,7 +45,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { realtime.connection.on('connected', function () { var channel = realtime.channels.get('channel'); channel.on(function () { - if ((index = utils.arrIndexOf(expectedChannelEvents, this.event)) > -1) { + if ((index = expectedChannelEvents.indexOf(this.event)) > -1) { delete expectedChannelEvents[index]; switch (this.event) { case 'detached': diff --git a/test/realtime/message.test.js b/test/realtime/message.test.js index f80d4e1e8e..4905870b70 100644 --- a/test/realtime/message.test.js +++ b/test/realtime/message.test.js @@ -572,7 +572,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* subscribe to event */ recvchannel.subscribe('event0', function (msg) { try { - expect(-1).to.not.equal(utils.arrIndexOf(messagesSent, msg.data), 'Received unexpected message text'); + expect(-1).to.not.equal(messagesSent.indexOf(msg.data), 'Received unexpected message text'); } catch (err) { closeAndFinish(done, realtime, err); return; From e5961ead813a6435988679f22911ec232b41ef9e Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:22:21 +0000 Subject: [PATCH 366/468] Remove Utils.arrIn in favour of native Array.prototype.includes --- src/common/lib/client/baserealtime.ts | 2 +- src/common/lib/client/realtimechannel.ts | 4 ++-- src/common/lib/client/rest.ts | 4 ++-- src/common/lib/transport/comettransport.ts | 2 +- src/common/lib/transport/connectionmanager.ts | 8 ++++---- src/common/lib/util/utils.ts | 4 ---- src/platform/web/lib/http/request/xhrrequest.ts | 2 +- 7 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 2b4acacf80..237f08a1e7 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -154,7 +154,7 @@ class Channels extends EventEmitter { for (const channelId in this.all) { const channel = this.all[channelId]; - if (Utils.arrIn(fromChannelStates, channel.state)) { + if (fromChannelStates.includes(channel.state)) { channel.notifyState(toChannelState, reason); } } diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index c53290a229..87decff1b6 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -53,7 +53,7 @@ function validateChannelOptions(options?: API.ChannelOptions) { if ( !currentMode || typeof currentMode !== 'string' || - !Utils.arrIn(channelModes, String.prototype.toUpperCase.call(currentMode)) + !channelModes.includes(String.prototype.toUpperCase.call(currentMode)) ) { return new ErrorInfo('Invalid channel mode: ' + currentMode, 40000, 400); } @@ -696,7 +696,7 @@ class RealtimeChannel extends EventEmitter { this.clearStateTimer(); // RTP5a1 - if (Utils.arrIn(['detached', 'suspended', 'failed'], state)) { + if (['detached', 'suspended', 'failed'].includes(state)) { this.properties.channelSerial = null; } diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts index 516cb0a51a..b0040c57dd 100644 --- a/src/common/lib/client/rest.ts +++ b/src/common/lib/client/rest.ts @@ -134,11 +134,11 @@ export class Rest { /* useHttpPaginatedResponse: */ true ); - if (!Utils.arrIn(Platform.Http.methods, _method)) { + if (!Platform.Http.methods.includes(_method)) { throw new ErrorInfo('Unsupported method ' + _method, 40500, 405); } - if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { + if (Platform.Http.methodsWithBody.includes(_method)) { return paginatedResource[_method as HttpMethods.Post](params, body as RequestBody) as Promise< HttpPaginatedResponse >; diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index 88e094cd8e..b58e43d831 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -21,7 +21,7 @@ function shouldBeErrorAction(err: ErrorInfo) { const UNRESOLVABLE_ERROR_CODES = [80015, 80017, 80030]; if (err.code) { if (Auth.isTokenErr(err)) return false; - if (Utils.arrIn(UNRESOLVABLE_ERROR_CODES, err.code)) return true; + if (UNRESOLVABLE_ERROR_CODES.includes(err.code)) return true; return err.code >= 40000 && err.code < 50000; } else { /* Likely a network or transport error of some kind. Certainly not fatal to the connection */ diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 9f6d361f13..39ef8aa0b0 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -590,7 +590,7 @@ class ConnectionManager extends EventEmitter { /* if ws and xhrs are connecting in parallel, delay xhrs activation to let ws go ahead */ if ( transport.shortName !== optimalTransport && - Utils.arrIn(this.getUpgradePossibilities(), optimalTransport) && + this.getUpgradePossibilities().includes(optimalTransport) && this.activeProtocol ) { setTimeout(() => { @@ -1558,7 +1558,7 @@ class ConnectionManager extends EventEmitter { const preference = this.getTransportPreference(); let preferenceTimeoutExpired = false; - if (!Utils.arrIn(this.transports, preference)) { + if (!this.transports.includes(preference)) { this.unpersistTransportPreference(); this.connectImpl(transportParams, connectCount); } @@ -2025,7 +2025,7 @@ class ConnectionManager extends EventEmitter { private async processChannelMessage(message: ProtocolMessage, transport: Transport) { const onActiveTransport = this.activeProtocol && transport === this.activeProtocol.getTransport(), - onUpgradeTransport = Utils.arrIn(this.pendingTransports, transport) && this.state == this.states.synchronizing; + onUpgradeTransport = this.pendingTransports.includes(transport) && this.state == this.states.synchronizing; /* As the lib now has a period where the upgrade transport is synced but * before it's become active (while waiting for the old one to become @@ -2123,7 +2123,7 @@ class ConnectionManager extends EventEmitter { } persistTransportPreference(transport: Transport): void { - if (Utils.arrIn(Defaults.upgradeTransports, transport.shortName)) { + if (Defaults.upgradeTransports.includes(transport.shortName)) { this.transportPreference = transport.shortName; if (haveWebStorage()) { Platform.WebStorage?.set?.(transportPreferenceName, transport.shortName); diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 0da1943405..f6e035074a 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -174,10 +174,6 @@ export function arrSubtract(arr1: Array, arr2: Array): Array { return result; } -export function arrIn(arr: Array, val: unknown): boolean { - return arr.indexOf(val) !== -1; -} - export function arrDeleteValue(arr: Array, val: T): boolean { const idx = arr.indexOf(val); const res = idx != -1; diff --git a/src/platform/web/lib/http/request/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts index b6774a9663..7646cfd186 100644 --- a/src/platform/web/lib/http/request/xhrrequest.ts +++ b/src/platform/web/lib/http/request/xhrrequest.ts @@ -10,7 +10,7 @@ import XHRStates from 'common/constants/XHRStates'; import Platform from 'common/platform'; function isAblyError(responseBody: unknown, headers: Record): responseBody is { error?: ErrorInfo } { - return Utils.arrIn(Utils.allToLowerCase(Utils.keysArray(headers)), 'x-ably-errorcode'); + return Utils.allToLowerCase(Utils.keysArray(headers)).includes('x-ably-errorcode'); } function getAblyError(responseBody: unknown, headers: Record) { From 011310a61d3a7f5c5a071135041aabedc3070338 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:29:18 +0000 Subject: [PATCH 367/468] Remove polyfill for Array.prototype.forEach in Utils --- src/common/lib/util/utils.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index f6e035074a..2907ddcc7b 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,16 +227,12 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrForEach = (Array.prototype.forEach as unknown) - ? function (arr: Array, fn: (value: T, index: number, arr: Array) => unknown) { - arr.forEach(fn); - } - : function (arr: Array, fn: (value: T, index: number, arr: Array) => unknown) { - const len = arr.length; - for (let i = 0; i < len; i++) { - fn(arr[i], i, arr); - } - }; +export const arrForEach = function ( + arr: Array, + fn: (value: T, index: number, arr: Array) => unknown +) { + arr.forEach(fn); +}; /* Useful when the function may mutate the array */ export function safeArrForEach( From 42ab6cce77cadd43c7a20dff1b8a1320828036f7 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:34:31 +0000 Subject: [PATCH 368/468] Remove Utils.arrForEach in favour of native Array.prototype.forEach --- src/common/lib/client/realtimepresence.ts | 2 +- src/common/lib/client/restchannel.ts | 2 +- src/common/lib/transport/connectionmanager.ts | 4 ++-- src/common/lib/util/defaults.ts | 2 +- src/common/lib/util/eventemitter.ts | 6 +++--- src/common/lib/util/utils.ts | 9 +-------- test/common/modules/shared_helper.js | 4 ++-- test/realtime/auth.test.js | 2 +- test/realtime/failure.test.js | 4 ++-- test/rest/history.test.js | 4 ++-- test/rest/request.test.js | 2 +- 11 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 3b7098c9b2..0144522e09 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -445,7 +445,7 @@ class RealtimePresence extends EventEmitter { _synthesizeLeaves(items: PresenceMessage[]): void { const subscriptions = this.subscriptions; - Utils.arrForEach(items, function (item) { + items.forEach(function (item) { const presence = presenceMessageFromValues({ action: 'leave', connectionId: item.connectionId, diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index dbbf8725fb..8b5ae6ae57 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -89,7 +89,7 @@ class RestChannel { if (idempotentRestPublishing && allEmptyIds(messages)) { const msgIdBase = await Utils.randomString(MSG_ID_ENTROPY_BYTES); - Utils.arrForEach(messages, function (message, index) { + messages.forEach(function (message, index) { message.id = msgIdBase + ':' + index.toString(); }); } diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 39ef8aa0b0..6431133010 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -415,7 +415,7 @@ class ConnectionManager extends EventEmitter { if (initialiseWebSocketTransport) { initialiseWebSocketTransport(storage); } - Utils.arrForEach(Platform.Transports.order, function (transportName) { + Platform.Transports.order.forEach(function (transportName) { const initFn = implementations[transportName]; if (initFn) { initFn(storage); @@ -1711,7 +1711,7 @@ class ConnectionManager extends EventEmitter { return; } - Utils.arrForEach(upgradePossibilities, (upgradeTransport: TransportName) => { + upgradePossibilities.forEach((upgradeTransport: TransportName) => { /* Note: the transport may mutate the params, so give each transport a fresh one */ const upgradeTransportParams = this.createTransportParams(transportParams.host, 'upgrade'); this.tryATransport(upgradeTransportParams, upgradeTransport, noop); diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 2be603152a..752cc841ab 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -217,7 +217,7 @@ export function normaliseOptions(options: InternalClientOptions, MsgPack: MsgPac const restHost = options.restHost || (production ? Defaults.REST_HOST : environment + '-' + Defaults.REST_HOST); const realtimeHost = getRealtimeHost(options, production, environment); - Utils.arrForEach((options.fallbackHosts || []).concat(restHost, realtimeHost), checkHost); + (options.fallbackHosts || []).concat(restHost, realtimeHost).forEach(checkHost); options.port = options.port || Defaults.PORT; options.tlsPort = options.tlsPort || Defaults.TLS_PORT; diff --git a/src/common/lib/util/eventemitter.ts b/src/common/lib/util/eventemitter.ts index f86ead2bc4..5df3c7ee12 100644 --- a/src/common/lib/util/eventemitter.ts +++ b/src/common/lib/util/eventemitter.ts @@ -215,7 +215,7 @@ class EventEmitter { Array.prototype.push.apply(listeners, eventsListeners); } - Utils.arrForEach(listeners, function (listener) { + listeners.forEach(function (listener) { callListener(eventThis, listener, args); }); } @@ -260,7 +260,7 @@ class EventEmitter { const self = this; const listenerWrapper = function (this: any) { const innerArgs = Array.prototype.slice.call(arguments); - Utils.arrForEach(firstArg, function (eventName) { + firstArg.forEach(function (eventName) { self.off(eventName, listenerWrapper); }); if (typeof secondArg !== 'function') { @@ -268,7 +268,7 @@ class EventEmitter { } secondArg.apply(this, innerArgs); }; - Utils.arrForEach(firstArg, function (eventName) { + firstArg.forEach(function (eventName) { self.on(eventName, listenerWrapper); }); } else { diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 2907ddcc7b..1d3794c096 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,19 +227,12 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrForEach = function ( - arr: Array, - fn: (value: T, index: number, arr: Array) => unknown -) { - arr.forEach(fn); -}; - /* Useful when the function may mutate the array */ export function safeArrForEach( arr: Array, fn: (value: T, index: number, arr: Array) => unknown ): void { - return arrForEach(arr.slice(), fn); + return arr.slice().forEach(fn); } export const arrMap = (Array.prototype.map as unknown) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 6a6cfd43c5..5a836bb04d 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -34,7 +34,7 @@ define([ } function monitorConnection(done, realtime, states) { - utils.arrForEach(states || ['failed', 'suspended'], function (state) { + (states || ['failed', 'suspended']).forEach(function (state) { realtime.connection.on(state, function () { done(new Error('Connection monitoring: state changed to ' + state + ', aborting test')); realtime.close(); @@ -141,7 +141,7 @@ define([ function testOnAllTransports(name, testFn, excludeUpgrade, skip) { var itFn = skip ? it.skip : it; let transports = availableTransports; - utils.arrForEach(transports, function (transport) { + transports.forEach(function (transport) { itFn( name + '_with_' + transport + '_binary_transport', testFn({ transports: [transport], useBinaryProtocol: true }) diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index 47e4ee60f2..a70144a413 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -885,7 +885,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * established, before realtime sends error response. So token error * goes through the same path as a connected transport, so goes to * disconnected first */ - utils.arrForEach(['connected', 'suspended'], function (state) { + ['connected', 'suspended'].forEach(function (state) { realtime.connection.on(state, function () { done(new Error('State changed to ' + state + ', should have gone to failed')); realtime.close(); diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index ef488fc2f7..8a5d47a908 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -191,7 +191,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async expect(value).to.be.below(max); } - utils.arrForEach(availableTransports, function (transport) { + availableTransports.forEach(function (transport) { it('disconnected_backoff_' + transport, function (done) { var disconnectedRetryTimeout = 150; var realtime = helper.AblyRealtime({ @@ -373,7 +373,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); }); - utils.arrForEach(availableTransports, function (transport) { + availableTransports.forEach(function (transport) { it('channel_backoff_' + transport, function (done) { var channelRetryTimeout = 150; var realtime = helper.AblyRealtime({ diff --git a/test/rest/history.test.js b/test/rest/history.test.js index 33b91b11c5..e7e1485e4f 100644 --- a/test/rest/history.test.js +++ b/test/rest/history.test.js @@ -44,7 +44,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { /* verify message ids are unique */ var ids = {}; - utils.arrForEach(messages, function (msg) { + messages.forEach(function (msg) { ids[msg.id] = msg; }); expect(utils.keysArray(ids).length).to.equal( @@ -67,7 +67,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { /* verify message ids are unique */ var ids = {}; - utils.arrForEach(messages, function (msg) { + messages.forEach(function (msg) { ids[msg.id] = msg; }); expect(utils.keysArray(ids).length).to.equal( diff --git a/test/rest/request.test.js b/test/rest/request.test.js index 0af1d35335..da17d252eb 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -167,7 +167,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async ); }); - utils.arrForEach(['put', 'patch', 'delete'], function (method) { + ['put', 'patch', 'delete'].forEach(function (method) { it('check' + method, async function () { var restEcho = helper.AblyRest({ useBinaryProtocol: false, restHost: echoServerHost, tls: true }); var res = await restEcho.request(method, '/methods', Defaults.protocolVersion, {}, {}, {}); From 8a8e93f3caf4416575d49327cbd549fdf5508e02 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:36:01 +0000 Subject: [PATCH 369/468] Remove Utils.safeArrForEach --- src/common/lib/transport/connectionmanager.ts | 21 ++++++++++++------- src/common/lib/util/utils.ts | 8 ------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 6431133010..f8697df962 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -929,9 +929,9 @@ class ConnectionManager extends EventEmitter { } } - /* Terminate any other pending transport(s), and - * abort any not-yet-pending transport attempts */ - Utils.safeArrForEach(this.pendingTransports, (pendingTransport) => { + // terminate any other pending transport(s), and abort any not-yet-pending transport attempts + // need to use .slice() here, since we intend to mutate the array during .forEach() iteration + this.pendingTransports.slice().forEach((pendingTransport) => { if (pendingTransport === transport) { const msg = 'Assumption violated: activating a transport that is still marked as a pending transport; transport = ' + @@ -944,7 +944,8 @@ class ConnectionManager extends EventEmitter { pendingTransport.disconnect(); } }); - Utils.safeArrForEach(this.proposedTransports, (proposedTransport: Transport) => { + // need to use .slice() here, since we intend to mutate the array during .forEach() iteration + this.proposedTransports.slice().forEach((proposedTransport: Transport) => { if (proposedTransport === transport) { Logger.logAction( Logger.LOG_ERROR, @@ -1723,12 +1724,14 @@ class ConnectionManager extends EventEmitter { this.cancelSuspendTimer(); this.startTransitionTimer(this.states.closing); - Utils.safeArrForEach(this.pendingTransports, function (transport) { + // need to use .slice() here, since we intend to mutate the array during .forEach() iteration + this.pendingTransports.slice().forEach(function (transport) { Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.closeImpl()', 'Closing pending transport: ' + transport); if (transport) transport.close(); }); - Utils.safeArrForEach(this.proposedTransports, function (transport) { + // need to use .slice() here, since we intend to mutate the array during .forEach() iteration + this.proposedTransports.slice().forEach(function (transport) { Logger.logAction( Logger.LOG_MICRO, 'ConnectionManager.closeImpl()', @@ -1864,7 +1867,8 @@ class ConnectionManager extends EventEmitter { /* This will prevent any connection procedure in an async part of one of its early stages from continuing */ this.connectCounter++; - Utils.safeArrForEach(this.pendingTransports, function (transport) { + // need to use .slice() here, since we intend to mutate the array during .forEach() iteration + this.pendingTransports.slice().forEach(function (transport) { Logger.logAction( Logger.LOG_MICRO, 'ConnectionManager.disconnectAllTransports()', @@ -1874,7 +1878,8 @@ class ConnectionManager extends EventEmitter { }); this.pendingTransports = []; - Utils.safeArrForEach(this.proposedTransports, function (transport) { + // need to use .slice() here, since we intend to mutate the array during .forEach() iteration + this.proposedTransports.slice().forEach(function (transport) { Logger.logAction( Logger.LOG_MICRO, 'ConnectionManager.disconnectAllTransports()', diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 1d3794c096..75c725e822 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,14 +227,6 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -/* Useful when the function may mutate the array */ -export function safeArrForEach( - arr: Array, - fn: (value: T, index: number, arr: Array) => unknown -): void { - return arr.slice().forEach(fn); -} - export const arrMap = (Array.prototype.map as unknown) ? function (arr: Array, fn: (value: T1, index?: number, arr?: Array) => T2) { return arr.map(fn); From fcda7d31e917d0f592f63d8f91fb694a44241bd6 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:38:17 +0000 Subject: [PATCH 370/468] Remove polyfill for Array.prototype.map in Utils --- src/common/lib/util/utils.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 75c725e822..227b154f6d 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,18 +227,9 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrMap = (Array.prototype.map as unknown) - ? function (arr: Array, fn: (value: T1, index?: number, arr?: Array) => T2) { - return arr.map(fn); - } - : function (arr: Array, fn: (value: T, index?: number, arr?: Array) => unknown) { - const result = []; - const len = arr.length; - for (let i = 0; i < len; i++) { - result.push(fn(arr[i], i, arr)); - } - return result; - }; +export const arrMap = function (arr: Array, fn: (value: T1, index?: number, arr?: Array) => T2) { + return arr.map(fn); +}; export const arrFilter = (Array.prototype.filter as unknown) ? function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { From 7d2ed227748cebdefbd6f26846d9a286a569f00a Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:42:27 +0000 Subject: [PATCH 371/468] Remove Utils.arrMap in favour of native Array.prototype.map --- src/common/lib/util/utils.ts | 4 ---- test/realtime/failure.test.js | 12 ++++++------ test/realtime/history.test.js | 7 ++++--- test/realtime/presence.test.js | 12 ++++++------ test/realtime/sync.test.js | 4 ++-- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 227b154f6d..1b800fd068 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,10 +227,6 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrMap = function (arr: Array, fn: (value: T1, index?: number, arr?: Array) => T2) { - return arr.map(fn); -}; - export const arrFilter = (Array.prototype.filter as unknown) ? function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { return arr.filter(fn); diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index 8a5d47a908..a4e9601ad2 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -56,8 +56,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; }; async.parallel( - utils - .arrMap(availableTransports, function (transport) { + availableTransports + .map(function (transport) { return failure_test([transport]); }) .concat(failure_test(null)), // to test not specifying a transport (so will use upgrade mechanism) @@ -94,8 +94,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; }; async.parallel( - utils - .arrMap(availableTransports, function (transport) { + availableTransports + .map(function (transport) { return break_test([transport]); }) .concat(break_test(null)), // to test not specifying a transport (so will use upgrade mechanism) @@ -169,8 +169,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }; }; async.parallel( - utils - .arrMap(availableTransports, function (transport) { + availableTransports + .map(function (transport) { return lifecycleTest([transport]); }) .concat(lifecycleTest(null)), // to test not specifying a transport (so will use upgrade mechanism) diff --git a/test/realtime/history.test.js b/test/realtime/history.test.js index a32ad2c72a..43368cd456 100644 --- a/test/realtime/history.test.js +++ b/test/realtime/history.test.js @@ -3,10 +3,11 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var expect = chai.expect; var utils = helper.Utils; - var preAttachMessages = utils.arrMap([1, 2, 3, 4, 5], function (i) { + var indexes = [1, 2, 3, 4, 5]; + var preAttachMessages = indexes.map(function (i) { return { name: 'pre-attach-' + i, data: 'some data' }; }); - var postAttachMessages = utils.arrMap([1, 2, 3, 4, 5], function (i) { + var postAttachMessages = indexes.map(function (i) { return { name: 'post-attach-' + i, data: 'some data' }; }); var closeAndFinish = helper.closeAndFinish; @@ -14,7 +15,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { var whenPromiseSettles = helper.whenPromiseSettles; var parallelPublishMessages = function (done, channel, messages, callback) { - var publishTasks = utils.arrMap(messages, function (event) { + var publishTasks = messages.map(function (event) { return function (publishCb) { whenPromiseSettles(channel.publish(event.name, event.data), publishCb); }; diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 137b56f69a..d938fb0dbc 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -10,8 +10,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var whenPromiseSettles = helper.whenPromiseSettles; function extractClientIds(presenceSet) { - return utils - .arrMap(presenceSet, function (presmsg) { + return presenceSet + .map(function (presmsg) { return presmsg.clientId; }) .sort(); @@ -780,8 +780,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var presenceMessages = resultPage.items; expect(presenceMessages.length).to.equal(2, 'Verify correct number of presence messages found'); - var actions = utils - .arrMap(presenceMessages, function (msg) { + var actions = presenceMessages + .map(function (msg) { return msg.action; }) .sort(); @@ -1372,8 +1372,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async function checkPresence(first, second, cb) { whenPromiseSettles(observerChannel.presence.get(), function (err, presenceMembers) { - var clientIds = utils - .arrMap(presenceMembers, function (msg) { + var clientIds = presenceMembers + .map(function (msg) { return msg.clientId; }) .sort(); diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index 7b29fd4d27..e7ac1d92b6 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -22,8 +22,8 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }); function extractClientIds(presenceSet) { - return utils - .arrMap(presenceSet, function (presmsg) { + return presenceSet + .map(function (presmsg) { return presmsg.clientId; }) .sort(); From ea6992c7b95c8c690dee78ec673ea47faca60754 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:43:13 +0000 Subject: [PATCH 372/468] Remove polyfill for Array.prototype.filter in Utils --- src/common/lib/util/utils.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 1b800fd068..8eff63a9cd 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,20 +227,9 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrFilter = (Array.prototype.filter as unknown) - ? function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { - return arr.filter(fn); - } - : function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { - const result = [], - len = arr.length; - for (let i = 0; i < len; i++) { - if (fn(arr[i])) { - result.push(arr[i]); - } - } - return result; - }; +export const arrFilter = function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { + return arr.filter(fn); +}; export const arrEvery = (Array.prototype.every as unknown) ? function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { From 218a6cd130338899e9cf930a12b87e57db9a6dc2 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:46:35 +0000 Subject: [PATCH 373/468] Remove Utils.arrFilter in favour of native Array.prototype.filter --- src/common/lib/util/utils.ts | 4 ---- test/common/modules/shared_helper.js | 17 +---------------- test/realtime/encoding.test.js | 2 +- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index 8eff63a9cd..d586b65ac9 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,10 +227,6 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrFilter = function (arr: Array, fn: (value: T, index?: number, arr?: Array) => boolean) { - return arr.filter(fn); -}; - export const arrEvery = (Array.prototype.every as unknown) ? function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { return arr.every(fn); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 5a836bb04d..965f7206c7 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -50,7 +50,7 @@ define([ return; } if (Object.prototype.toString.call(realtime) == '[object Array]') { - var realtimes = utils.arrFilter(realtime, function (rt) { + var realtimes = realtime.filter(function (rt) { return rt !== undefined; }); closeAndFinishSeveral(done, realtimes, err); @@ -208,20 +208,6 @@ define([ return undefined; }; - var arrFilter = Array.prototype.filter - ? function (arr, predicate) { - return arr.filter(predicate); - } - : function (arr, predicate) { - var res = []; - for (var i = 0; i < arr.length; i++) { - if (predicate(arr[i])) { - res.push(arr[i]); - } - } - return res; - }; - function randomString() { return Math.random().toString().slice(2); } @@ -280,7 +266,6 @@ define([ unroutableHost: unroutableHost, unroutableAddress: unroutableAddress, arrFind: arrFind, - arrFilter: arrFilter, whenPromiseSettles: whenPromiseSettles, randomString: randomString, testMessageEquality: testMessageEquality, diff --git a/test/realtime/encoding.test.js b/test/realtime/encoding.test.js index f7c9e49c10..084702ef75 100644 --- a/test/realtime/encoding.test.js +++ b/test/realtime/encoding.test.js @@ -187,7 +187,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async return; } try { - var msgs = helper.arrFilter(resultPage.items, function (m) { + var msgs = resultPage.items.filter(function (m) { return m.name === name; }); expect(msgs.length).to.equal( From 7f275ce380fa7ee658aef91ad11e519451ebb4b0 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:47:45 +0000 Subject: [PATCH 374/468] Remove Array.prototype.find polyfill from shared_helper --- test/common/modules/shared_helper.js | 16 ---------------- test/realtime/presence.test.js | 2 +- test/realtime/sync.test.js | 2 +- test/rest/presence.test.js | 11 +++++------ 4 files changed, 7 insertions(+), 24 deletions(-) diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 965f7206c7..2f984bec83 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -193,21 +193,6 @@ define([ return !!transport.toString().match(/wss?\:/); } - var arrFind = Array.prototype.find - ? function (arr, predicate) { - return arr.find(predicate); - } - : function (arr, predicate) { - var value; - for (var i = 0; i < arr.length; i++) { - value = arr[i]; - if (predicate(value)) { - return value; - } - } - return undefined; - }; - function randomString() { return Math.random().toString().slice(2); } @@ -265,7 +250,6 @@ define([ isWebsocket: isWebsocket, unroutableHost: unroutableHost, unroutableAddress: unroutableAddress, - arrFind: arrFind, whenPromiseSettles: whenPromiseSettles, randomString: randomString, testMessageEquality: testMessageEquality, diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index d938fb0dbc..a0e327f4ea 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -18,7 +18,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } function extractMember(presenceSet, clientId) { - return helper.arrFind(presenceSet, function (member) { + return presenceSet.find(function (member) { return member.clientId === clientId; }); } diff --git a/test/realtime/sync.test.js b/test/realtime/sync.test.js index e7ac1d92b6..7bd493e1b0 100644 --- a/test/realtime/sync.test.js +++ b/test/realtime/sync.test.js @@ -30,7 +30,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async } function extractMember(presenceSet, clientId) { - return helper.arrFind(presenceSet, function (member) { + return presenceSet.find(function (member) { return member.clientId === clientId; }); } diff --git a/test/rest/presence.test.js b/test/rest/presence.test.js index 1c9640631f..d0e6d98a74 100644 --- a/test/rest/presence.test.js +++ b/test/rest/presence.test.js @@ -6,7 +6,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var expect = chai.expect; var Crypto = Ably.Realtime.Platform.Crypto; var BufferUtils = Ably.Realtime.Platform.BufferUtils; - var arrFind = helper.arrFind; function cipherParamsFromConfig(cipherConfig) { var cipherParams = new Crypto.CipherParams(); @@ -41,16 +40,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async if (presenceMessages.length != 6) { console.log('presenceMessages: ', JSON.stringify(presenceMessages)); } - var encodedMessage = arrFind(presenceMessages, function (msg) { + var encodedMessage = presenceMessages.find(function (msg) { return msg.clientId == 'client_encoded'; }); - var decodedMessage = arrFind(presenceMessages, function (msg) { + var decodedMessage = presenceMessages.find(function (msg) { return msg.clientId == 'client_decoded'; }); - var boolMessage = arrFind(presenceMessages, function (msg) { + var boolMessage = presenceMessages.find(function (msg) { return msg.clientId == 'client_bool'; }); - var intMessage = arrFind(presenceMessages, function (msg) { + var intMessage = presenceMessages.find(function (msg) { return msg.clientId == 'client_int'; }); expect(encodedMessage.data).to.deep.equal(decodedMessage.data, 'Verify message decoding works correctly'); @@ -71,7 +70,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async var channel = rest.channels.get('persisted:presence_fixtures'); var resultPage = await channel.presence.get(); var presenceMessages = resultPage.items; - var presenceBool = arrFind(presenceMessages, function (msg) { + var presenceBool = presenceMessages.find(function (msg) { return msg.clientId == 'client_bool'; }); expect(JSON.parse(JSON.stringify(presenceBool)).action).to.equal(1); // present From e5aa106b5763f42b1d7c84d8c1a83369b8903c8e Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:48:06 +0000 Subject: [PATCH 375/468] Remove polyfill for Array.prototype.every in Utils --- src/common/lib/util/utils.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index d586b65ac9..be62130db8 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,19 +227,9 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrEvery = (Array.prototype.every as unknown) - ? function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { - return arr.every(fn); - } - : function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { - const len = arr.length; - for (let i = 0; i < len; i++) { - if (!fn(arr[i], i, arr)) { - return false; - } - } - return true; - }; +export const arrEvery = function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { + return arr.every(fn); +}; export function allSame(arr: Array>, prop: string): boolean { if (arr.length === 0) { From 2072302bbe7fef76788e32c3cb189c94ad5ebf8c Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 10:49:41 +0000 Subject: [PATCH 376/468] Remove Utils.arrEvery in favour of native Array.prototype.every --- src/common/lib/client/restchannel.ts | 2 +- src/common/lib/transport/connectionmanager.ts | 2 +- src/common/lib/util/utils.ts | 8 ++------ test/common/modules/client_module.js | 2 +- test/realtime/history.test.js | 2 +- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 8b5ae6ae57..4f0d1ace37 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -22,7 +22,7 @@ import { RequestBody } from 'common/types/http'; const MSG_ID_ENTROPY_BYTES = 9; function allEmptyIds(messages: Array) { - return Utils.arrEvery(messages, function (message: Message) { + return messages.every(function (message: Message) { return !message.id; }); } diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index f8697df962..c6ae8914e4 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -75,7 +75,7 @@ function bundleWith(dest: ProtocolMessage, src: ProtocolMessage, maxSize: number return false; } if ( - !Utils.arrEvery(proposed, function (msg: Message) { + !proposed.every(function (msg: Message) { return !msg.id; }) ) { diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index be62130db8..dd527d31ce 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -227,16 +227,12 @@ export function forInOwnNonNullProperties(ob: Record, fn: (prop } } -export const arrEvery = function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { - return arr.every(fn); -}; - export function allSame(arr: Array>, prop: string): boolean { if (arr.length === 0) { return true; } const first = arr[0][prop]; - return arrEvery(arr, function (item) { + return arr.every(function (item) { return item[prop] === first; }); } @@ -461,7 +457,7 @@ export function toBase64(str: string) { export function arrEquals(a: any[], b: any[]) { return ( a.length === b.length && - arrEvery(a, function (val, i) { + a.every(function (val, i) { return val === b[i]; }) ); diff --git a/test/common/modules/client_module.js b/test/common/modules/client_module.js index 88458b2212..5ea3c37ba6 100644 --- a/test/common/modules/client_module.js +++ b/test/common/modules/client_module.js @@ -12,7 +12,7 @@ define(['ably', 'globals', 'test/common/modules/testapp_module'], function (Ably /* Use a default api key if no auth methods provided */ if ( - utils.arrEvery(authMethods, function (method) { + authMethods.every(function (method) { return !(method in clientOptions); }) ) { diff --git a/test/realtime/history.test.js b/test/realtime/history.test.js index 43368cd456..24cdfab606 100644 --- a/test/realtime/history.test.js +++ b/test/realtime/history.test.js @@ -121,7 +121,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { 'Verify right number of messages returned when untilAttached is true' ); expect( - utils.arrEvery(messages, function (message) { + messages.every(function (message) { return message.name.substring(0, 10) == 'pre-attach'; }), 'Verify all returned messages were pre-attach ones' From 335007da4a3dea0b270f2a54fdb4b76f821da915 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 11:02:41 +0000 Subject: [PATCH 377/468] Remove polyfill for String.prototype.trim in Utils --- src/common/lib/util/utils.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index dd527d31ce..e0a8d1de68 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -322,13 +322,9 @@ export function arrChooseN(arr: Array, n: number): Array { return result; } -export const trim = (String.prototype.trim as unknown) - ? function (str: string) { - return str.trim(); - } - : function (str: string) { - return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); - }; +export const trim = function (str: string) { + return str.trim(); +}; /** * Uses a callback to communicate the result of a `Promise`. The first argument passed to the callback will be either an error (when the promise is rejected) or `null` (when the promise is fulfilled). In the case where the promise is fulfilled, the resulting value will be passed to the callback as a second argument. From d5fcd672aea0251c13293aa392de14b5c6eb940f Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 11:04:01 +0000 Subject: [PATCH 378/468] Remove Utils.trim in favour of native String.prototype.trim --- src/common/lib/util/utils.ts | 4 ---- src/platform/web/lib/http/request/xhrrequest.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/common/lib/util/utils.ts b/src/common/lib/util/utils.ts index e0a8d1de68..10c5f5ecf0 100644 --- a/src/common/lib/util/utils.ts +++ b/src/common/lib/util/utils.ts @@ -322,10 +322,6 @@ export function arrChooseN(arr: Array, n: number): Array { return result; } -export const trim = function (str: string) { - return str.trim(); -}; - /** * Uses a callback to communicate the result of a `Promise`. The first argument passed to the callback will be either an error (when the promise is rejected) or `null` (when the promise is fulfilled). In the case where the promise is fulfilled, the resulting value will be passed to the callback as a second argument. */ diff --git a/src/platform/web/lib/http/request/xhrrequest.ts b/src/platform/web/lib/http/request/xhrrequest.ts index 7646cfd186..8e1af6f0ae 100644 --- a/src/platform/web/lib/http/request/xhrrequest.ts +++ b/src/platform/web/lib/http/request/xhrrequest.ts @@ -39,10 +39,10 @@ function isEncodingChunked(xhr: XMLHttpRequest) { } function getHeadersAsObject(xhr: XMLHttpRequest) { - const headerPairs = Utils.trim(xhr.getAllResponseHeaders()).split('\r\n'); + const headerPairs = xhr.getAllResponseHeaders().trim().split('\r\n'); const headers: Record = {}; for (let i = 0; i < headerPairs.length; i++) { - const parts = headerPairs[i].split(':').map(Utils.trim); + const parts = headerPairs[i].split(':').map((x) => x.trim()); headers[parts[0].toLowerCase()] = parts[1]; } return headers; From 85f046919bb8c468850cc8b2f054b2aee684808f Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 21 Feb 2024 11:10:51 +0000 Subject: [PATCH 379/468] Change minimal raw bundle size threshold to 95 KiB --- scripts/moduleReport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 80665b205f..440ab77bcf 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -6,7 +6,7 @@ import { gzip } from 'zlib'; import Table from 'cli-table'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) -const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 96, gzip: 29 }; +const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 95, gzip: 29 }; const baseClientNames = ['BaseRest', 'BaseRealtime']; From 809aced61e9f7254e6a37ec2aa0f75f1666057d3 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 23 Feb 2024 01:11:31 +0000 Subject: [PATCH 380/468] Remove IE specific code from tests --- test/browser/connection.test.js | 8 -------- test/common/modules/testapp_manager.js | 13 +------------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/test/browser/connection.test.js b/test/browser/connection.test.js index d615c133c8..8455cd66c3 100644 --- a/test/browser/connection.test.js +++ b/test/browser/connection.test.js @@ -18,14 +18,6 @@ define(['shared_helper', 'chai'], function (helper, chai) { return false; } - // IE doesn't support creating your own events with new - try { - var testEvent = new Event('foo'); - } catch (e) { - console.log('On IE; skipping connection.test.js'); - return false; - } - return true; } diff --git a/test/common/modules/testapp_manager.js b/test/common/modules/testapp_manager.js index 41724490e3..de8fb32661 100644 --- a/test/common/modules/testapp_manager.js +++ b/test/common/modules/testapp_manager.js @@ -30,17 +30,6 @@ define(['globals', 'ably'], function (ablyGlobals, ably) { } } - function createXHR() { - var result = new XMLHttpRequest(); - if ('withCredentials' in result) return result; - if (typeof XDomainRequest !== 'undefined') { - var xdr = new XDomainRequest(); /* Use IE-specific "CORS" code with XDR */ - xdr.isXDR = true; - return xdr; - } - return null; - } - function toBase64(str) { var bufferUtils = ably.Realtime.Platform.BufferUtils; var buffer = bufferUtils.utf8Encode(str); @@ -70,7 +59,7 @@ define(['globals', 'ably'], function (ablyGlobals, ably) { }; } else if (isBrowser) { return function (options, callback) { - var xhr = createXHR(); + var xhr = new XMLHttpRequest(); var uri; uri = options.scheme + '://' + options.host + ':' + options.port + options.path; From 9a05dc86846c00b3f69e82e16086143db9325dc7 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 23 Feb 2024 01:16:27 +0000 Subject: [PATCH 381/468] Remove "comma-dangle" eslint rule used for old browsers compatibility --- .eslintrc.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2e93a299ed..a45654db34 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,8 +19,6 @@ module.exports = { "plugin:security/recommended", ], rules: { - // comma-dangle used for browser compatibility for browsers that don't support trailing commas - "comma-dangle": ["error", "always-multiline"], "eol-last": "error", // security/detect-object-injection just gives a lot of false positives // see https://github.com/nodesecurity/eslint-plugin-security/issues/21 @@ -35,7 +33,6 @@ module.exports = { { files: ["**/*.{ts,tsx}"], rules: { - "comma-dangle": ["error", "only-multiline"], "@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_" }], // TypeScript already enforces these rules better than any eslint setup can "no-undef": "off", From b94c52bae1cfb4991c0aacdfc6847dc197e676d8 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 12:31:56 +0000 Subject: [PATCH 382/468] Use Node.js 20 in github workflows --- .github/workflows/bundle-report.yml | 4 ++-- .github/workflows/check.yml | 4 ++-- .github/workflows/docs.yml | 4 ++-- .github/workflows/publish-cdn.yml | 4 ++-- .github/workflows/react.yml | 2 +- .github/workflows/test-browser.yml | 4 ++-- .github/workflows/test-package.yml | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/bundle-report.yml b/.github/workflows/bundle-report.yml index 269019c3c7..67a81e6eee 100644 --- a/.github/workflows/bundle-report.yml +++ b/.github/workflows/bundle-report.yml @@ -17,10 +17,10 @@ jobs: run: > git config --global url."https://github.com/".insteadOf ssh://git@github.com/ - - name: Use Node.js 14.x + - name: Use Node.js 20.x uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 20.x - run: npm ci - name: Build bundle reports run: | diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3bd17b2148..a051e8f22e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,10 +14,10 @@ jobs: run: > git config --global url."https://github.com/".insteadOf ssh://git@github.com/ - - name: Use Node.js 14.x + - name: Use Node.js 20.x uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 20.x - run: npm ci - run: npm run lint - run: npm run format:check diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8b5492280a..f9e7184297 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,10 +16,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Use Node.js 16.x + - name: Use Node.js 20.x uses: actions/setup-node@v3 with: - node-version: 16.x + node-version: 20.x - name: Install Package Dependencies run: npm ci diff --git a/.github/workflows/publish-cdn.yml b/.github/workflows/publish-cdn.yml index a4fe17b64f..13e21c85fa 100644 --- a/.github/workflows/publish-cdn.yml +++ b/.github/workflows/publish-cdn.yml @@ -23,9 +23,9 @@ jobs: aws-region: us-east-1 role-to-assume: arn:aws:iam::${{ secrets.ABLY_AWS_ACCOUNT_ID_SDK }}:role/prod-ably-sdk-cdn role-session-name: "${{ github.run_id }}-${{ github.run_number }}" - - name: Use Node.js 14.x + - name: Use Node.js 20.x uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 20.x - run: npm ci - run: node scripts/cdn_deploy.js --skipCheckout --tag=${{ github.event.inputs.version }} diff --git a/.github/workflows/react.yml b/.github/workflows/react.yml index 2c48513e7d..ef14e4fb83 100644 --- a/.github/workflows/react.yml +++ b/.github/workflows/react.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 16 + node-version: 20 - run: npm ci - run: npm run format:check - run: npm run test:react diff --git a/.github/workflows/test-browser.yml b/.github/workflows/test-browser.yml index eaa2f3be41..c434c2e582 100644 --- a/.github/workflows/test-browser.yml +++ b/.github/workflows/test-browser.yml @@ -20,10 +20,10 @@ jobs: run: > git config --global url."https://github.com/".insteadOf ssh://git@github.com/ - - name: Use Node.js 16.x + - name: Use Node.js 20.x uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 20.x - run: npm ci - name: Install Playwright browsers and dependencies run: npx playwright install --with-deps diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 98fd5b58a2..8e622413a9 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -12,9 +12,9 @@ jobs: - uses: actions/checkout@v2 with: submodules: true - - name: Use Node.js 16.x + - name: Use Node.js 20.x uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 20.x - run: npm ci - run: npm run test:package From f982d684a16038b3798462b6104d39628b3d00d5 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 12:32:25 +0000 Subject: [PATCH 383/468] Run CI tests for node on Node.js 16, 18, 20 --- .github/workflows/test-node.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-node.yml b/.github/workflows/test-node.yml index 7efb047890..39ccf4d0e6 100644 --- a/.github/workflows/test-node.yml +++ b/.github/workflows/test-node.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [12.x, 14.x, 16.x] + node-version: [16.x, 18.x, 20.x] steps: - uses: actions/checkout@v2 with: From cc963f8425e254bca10db8b14b2fb0d36e273236 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 12:33:04 +0000 Subject: [PATCH 384/468] Update `engines` in `package.json` to `node >=16` --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f220fd36de..0484fe5a1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,7 @@ "webpack-node-externals": "^3.0.0" }, "engines": { - "node": ">=5.10.x" + "node": ">=16" }, "peerDependencies": { "react": ">=16.8.0", diff --git a/package.json b/package.json index 55068e3945..9ee2ecdb14 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "webpack-node-externals": "^3.0.0" }, "engines": { - "node": ">=5.10.x" + "node": ">=16" }, "repository": "ably/ably-js", "jspm": { From c6ff4264d00d320e69a250130ac8a8d7bb607401 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 9 Feb 2024 23:48:45 +0000 Subject: [PATCH 385/468] WIP!: `ChannelProvider` implementation draft BREAKING CHANGE: introduce `ChannelProvider` to setup channel options --- .../react-hooks/sample-app/src/script.tsx | 6 +- src/platform/react-hooks/src/AblyProvider.tsx | 25 ++++++-- .../react-hooks/src/AblyReactHooks.ts | 1 - .../react-hooks/src/ChannelProvider.tsx | 59 +++++++++++++++++++ src/platform/react-hooks/src/hooks/useAbly.ts | 2 +- .../react-hooks/src/hooks/useChannel.ts | 15 +---- .../src/hooks/useChannelStateListener.ts | 23 +------- .../react-hooks/src/hooks/usePresence.ts | 19 +----- src/platform/react-hooks/src/index.ts | 1 + src/platform/react-hooks/src/utils/utils.ts | 36 +++++++++++ 10 files changed, 128 insertions(+), 59 deletions(-) create mode 100644 src/platform/react-hooks/src/ChannelProvider.tsx create mode 100644 src/platform/react-hooks/src/utils/utils.ts diff --git a/src/platform/react-hooks/sample-app/src/script.tsx b/src/platform/react-hooks/sample-app/src/script.tsx index 14853ee6e6..11a381cd78 100644 --- a/src/platform/react-hooks/sample-app/src/script.tsx +++ b/src/platform/react-hooks/sample-app/src/script.tsx @@ -5,7 +5,7 @@ import { createRoot } from 'react-dom/client'; import * as Ably from 'ably'; import App from './App.js'; -import { AblyProvider } from '../../src/index.js'; +import { AblyProvider, ChannelProvider } from '../../src/index.js'; const container = document.getElementById('root')!; @@ -22,7 +22,9 @@ const root = createRoot(container); root.render( - + + + ); diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index 603f83e86f..5fd072ca52 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -1,5 +1,5 @@ import * as Ably from 'ably'; -import React, { useMemo } from 'react'; +import React, { useMemo, useRef } from 'react'; const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'function'; @@ -9,7 +9,12 @@ interface AblyProviderProps { id?: string; } -type AblyContextType = React.Context; +interface AblyContextProps { + client: Ably.RealtimeClient; + channelToOptions: Record; +} + +type AblyContextType = React.Context; // An object is appended to `React.createContext` which stores all contexts // indexed by id, which is used by useAbly to find the correct context when an @@ -24,16 +29,24 @@ export function getContext(ctxId = 'default'): AblyContextType { } export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderProps) => { + const channelToOptionsRef = useRef>({}); + + const value: AblyContextProps = useMemo( + () => ({ + client, + channelToOptions: channelToOptionsRef.current, + }), + [client] + ); + if (!client) { throw new Error('AblyProvider: the `client` prop is required'); } - const realtime = useMemo(() => client, [client]); - let context = getContext(id); if (!context) { - context = ctxMap[id] = React.createContext(realtime); + context = ctxMap[id] = React.createContext({ client, channelToOptions: {} }); } - return {children}; + return {children}; }; diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 72f25fc7a2..eb2f0d6039 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -2,7 +2,6 @@ import * as Ably from 'ably'; export type ChannelNameAndOptions = { channelName: string; - options?: Ably.ChannelOptions; deriveOptions?: Ably.DeriveOptions; id?: string; subscribeOnly?: boolean; diff --git a/src/platform/react-hooks/src/ChannelProvider.tsx b/src/platform/react-hooks/src/ChannelProvider.tsx new file mode 100644 index 0000000000..d33ca7df8e --- /dev/null +++ b/src/platform/react-hooks/src/ChannelProvider.tsx @@ -0,0 +1,59 @@ +import React, { useEffect } from 'react'; +import * as Ably from 'ably'; +import { getContext } from './AblyProvider.js'; +import * as Utils from './utils/utils.js'; +import { channelOptionsWithAgent } from './AblyReactHooks.js'; + +interface ChannelProviderProps { + id?: string; + channelName: string; + options?: Ably.Types.ChannelOptions; + children?: React.ReactNode | React.ReactNode[] | null; +} + +export const ChannelProvider = ({ id = 'default', channelName, options, children }: ChannelProviderProps) => { + const { client, channelToOptions } = React.useContext(getContext(id)); + + const channel = client.channels.get(channelName); + + useEffect(() => { + if (channelToOptions[channelName]) { + throw new Error('You can not use more than one `ChannelProvider` with the same channel name'); + } + channel.setOptions(channelOptionsWithAgent(options)); + channelToOptions[channelName] = options ?? {}; + return () => { + delete channelToOptions[channelName]; + }; + }, [channel, channelName, options, channelToOptions]); + + useEffect(() => { + const handleChannelAttached = () => { + if (hasOptionsChanged(channel, options)) { + throw new Error('You can not use `RealtimeChannel.setOption()` along with `ChannelProvider`'); + } + }; + channel.on('attached', handleChannelAttached); + return () => { + channel.off('attached', handleChannelAttached); + }; + }, [channel, options]); + + return {children}; +}; + +const hasOptionsChanged = (channel: Ably.Types.RealtimeChannelPromise, options: Ably.Types.ChannelOptions) => { + // Don't check against the `agent` param - it isn't returned in the ATTACHED message + const requestedParams = Utils.omitAgent(options.params); + const existingParams = Utils.omitAgent(channel.params); + + if (Object.keys(requestedParams).length !== Object.keys(existingParams).length) { + return true; + } + + if (!Utils.shallowEquals(existingParams, requestedParams)) { + return true; + } + + return !Utils.arrEquals(options.modes ?? [], channel.modes ?? []); +}; diff --git a/src/platform/react-hooks/src/hooks/useAbly.ts b/src/platform/react-hooks/src/hooks/useAbly.ts index 50d3afc69e..0e2970c49c 100644 --- a/src/platform/react-hooks/src/hooks/useAbly.ts +++ b/src/platform/react-hooks/src/hooks/useAbly.ts @@ -3,7 +3,7 @@ import { getContext } from '../AblyProvider.js'; import * as API from 'ably'; export function useAbly(id = 'default'): API.RealtimeClient { - const client = React.useContext(getContext(id)) as API.RealtimeClient; + const client = React.useContext(getContext(id)).client; if (!client) { throw new Error( diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index b0934fe26e..93909fe4fb 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -37,33 +37,22 @@ export function useChannel( const ably = useAbly(channelHookOptions.id); - const { channelName, options: channelOptions, deriveOptions, skip } = channelHookOptions; + const { channelName, deriveOptions, skip } = channelHookOptions; const channelEvent = typeof eventOrCallback === 'string' ? eventOrCallback : null; const ablyMessageCallback = typeof eventOrCallback === 'string' ? callback : eventOrCallback; const deriveOptionsRef = useRef(deriveOptions); - const channelOptionsRef = useRef(channelOptions); const ablyMessageCallbackRef = useRef(ablyMessageCallback); const channel = useMemo(() => { const derived = deriveOptionsRef.current; - const withAgent = channelOptionsWithAgent(channelOptionsRef.current); - const channel = derived - ? ably.channels.getDerived(channelName, derived, withAgent) - : ably.channels.get(channelName, withAgent); + const channel = derived ? ably.channels.getDerived(channelName, derived) : ably.channels.get(channelName); return channel; }, [ably, channelName]); const { connectionError, channelError } = useStateErrors(channelHookOptions); - useEffect(() => { - if (channelOptionsRef.current !== channelOptions && channelOptions) { - channel.setOptions(channelOptionsWithAgent(channelOptions)); - } - channelOptionsRef.current = channelOptions; - }, [channel, channelOptions]); - useEffect(() => { deriveOptionsRef.current = deriveOptions; }, [deriveOptions]); diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts index d7aacac200..063d655529 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts @@ -1,6 +1,5 @@ -import { useEffect, useRef } from 'react'; import * as Ably from 'ably'; -import { ChannelNameAndId, ChannelNameAndOptions, channelOptionsWithAgent } from '../AblyReactHooks.js'; +import { ChannelNameAndId, ChannelNameAndOptions } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; @@ -23,26 +22,10 @@ export function useChannelStateListener( typeof channelNameOrNameAndId === 'object' ? channelNameOrNameAndId : { channelName: channelNameOrNameAndId }; const id = (channelNameOrNameAndId as ChannelNameAndId)?.id; - const { channelName, options: channelOptions } = channelHookOptions; + const { channelName } = channelHookOptions; const ably = useAbly(id); - const channel = ably.channels.get(channelName, channelOptionsWithAgent(channelOptions)); - - const channelOptionsRef = useRef(channelOptions); - - useEffect(() => { - if (channelOptionsRef.current !== channelOptions && channelOptions) { - channel.setOptions(channelOptionsWithAgent(channelOptions)); - } - channelOptionsRef.current = channelOptions; - }, [channel, channelOptions]); - - useEffect(() => { - if (channelOptionsRef.current !== channelOptions && channelOptions) { - channel.setOptions(channelOptionsWithAgent(channelOptions)); - } - channelOptionsRef.current = channelOptions; - }, [channel, channelOptions]); + const channel = ably.channels.get(channelName); const _listener = typeof listener === 'function' ? listener : (stateOrListener as ChannelStateListener); diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index 5c7c715fdc..738eab3f21 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,6 +1,6 @@ import type * as Ably from 'ably'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; @@ -30,24 +30,11 @@ export function usePresence( const subscribeOnly = typeof channelNameOrNameAndOptions === 'string' ? false : params.subscribeOnly; - const channelOptions = params.options; - const channelOptionsRef = useRef(channelOptions); - - const channel = useMemo( - () => ably.channels.get(params.channelName, channelOptionsWithAgent(channelOptionsRef.current)), - [ably, params.channelName] - ); + const channel = useMemo(() => ably.channels.get(params.channelName), [ably, params.channelName]); const skip = params.skip; const { connectionError, channelError } = useStateErrors(params); - useEffect(() => { - if (channelOptionsRef.current !== channelOptions && channelOptions) { - channel.setOptions(channelOptionsWithAgent(channelOptions)); - } - channelOptionsRef.current = channelOptions; - }, [channel, channelOptions]); - const [presenceData, updatePresenceData] = useState>>([]); const updatePresence = async (message?: Ably.PresenceMessage) => { diff --git a/src/platform/react-hooks/src/index.ts b/src/platform/react-hooks/src/index.ts index 10454f0986..f9dc0f1102 100644 --- a/src/platform/react-hooks/src/index.ts +++ b/src/platform/react-hooks/src/index.ts @@ -5,3 +5,4 @@ export * from './hooks/useAbly.js'; export * from './AblyProvider.js'; export * from './hooks/useChannelStateListener.js'; export * from './hooks/useConnectionStateListener.js'; +export { ChannelProvider } from './ChannelProvider.js'; diff --git a/src/platform/react-hooks/src/utils/utils.ts b/src/platform/react-hooks/src/utils/utils.ts new file mode 100644 index 0000000000..69caac5a59 --- /dev/null +++ b/src/platform/react-hooks/src/utils/utils.ts @@ -0,0 +1,36 @@ +import * as API from 'ably'; + +export const arrEvery = (Array.prototype.every as unknown) + ? function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { + return arr.every(fn); + } + : function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { + const len = arr.length; + for (let i = 0; i < len; i++) { + if (!fn(arr[i], i, arr)) { + return false; + } + } + return true; + }; + +export function shallowEquals(source: Record, target: Record) { + return ( + Object.keys(source).every((key) => source[key] === target[key]) && + Object.keys(target).every((key) => target[key] === source[key]) + ); +} + +export function arrEquals(a: any[], b: any[]) { + return ( + a.length === b.length && + arrEvery(a, function (val, i) { + return val === b[i]; + }) + ); +} + +export function omitAgent(channelParams?: API.Types.ChannelParams) { + const { agent: _, ...paramsWithoutAgent } = channelParams || {}; + return paramsWithoutAgent; +} From ab365de3123344c37977089bacf3393006aa3021 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Feb 2024 13:05:23 +0000 Subject: [PATCH 386/468] chore: rename `channelToOptions` to show it's internal --- src/platform/react-hooks/src/AblyProvider.tsx | 10 ++++------ src/platform/react-hooks/src/ChannelProvider.tsx | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index 5fd072ca52..8c8df94a96 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -1,5 +1,5 @@ import * as Ably from 'ably'; -import React, { useMemo, useRef } from 'react'; +import React, { useMemo } from 'react'; const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'function'; @@ -11,7 +11,7 @@ interface AblyProviderProps { interface AblyContextProps { client: Ably.RealtimeClient; - channelToOptions: Record; + _channelToOptions: Record; } type AblyContextType = React.Context; @@ -29,12 +29,10 @@ export function getContext(ctxId = 'default'): AblyContextType { } export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderProps) => { - const channelToOptionsRef = useRef>({}); - const value: AblyContextProps = useMemo( () => ({ client, - channelToOptions: channelToOptionsRef.current, + _channelToOptions: {}, }), [client] ); @@ -45,7 +43,7 @@ export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderP let context = getContext(id); if (!context) { - context = ctxMap[id] = React.createContext({ client, channelToOptions: {} }); + context = ctxMap[id] = React.createContext({ client, _channelToOptions: {} }); } return {children}; diff --git a/src/platform/react-hooks/src/ChannelProvider.tsx b/src/platform/react-hooks/src/ChannelProvider.tsx index d33ca7df8e..bcea423ee2 100644 --- a/src/platform/react-hooks/src/ChannelProvider.tsx +++ b/src/platform/react-hooks/src/ChannelProvider.tsx @@ -12,20 +12,20 @@ interface ChannelProviderProps { } export const ChannelProvider = ({ id = 'default', channelName, options, children }: ChannelProviderProps) => { - const { client, channelToOptions } = React.useContext(getContext(id)); + const { client, _channelToOptions } = React.useContext(getContext(id)); const channel = client.channels.get(channelName); useEffect(() => { - if (channelToOptions[channelName]) { + if (_channelToOptions[channelName]) { throw new Error('You can not use more than one `ChannelProvider` with the same channel name'); } channel.setOptions(channelOptionsWithAgent(options)); - channelToOptions[channelName] = options ?? {}; + _channelToOptions[channelName] = options ?? {}; return () => { - delete channelToOptions[channelName]; + delete _channelToOptions[channelName]; }; - }, [channel, channelName, options, channelToOptions]); + }, [channel, channelName, options, _channelToOptions]); useEffect(() => { const handleChannelAttached = () => { From 5b7a34034a3ace318df71a029cb91a7028843a53 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Feb 2024 15:53:13 +0000 Subject: [PATCH 387/468] chore: make `ChannelProvider` mandatory --- .../react-hooks/src/ChannelProvider.tsx | 31 ++++--- src/platform/react-hooks/src/fakes/ably.ts | 24 +++++- .../react-hooks/src/hooks/useChannel.test.tsx | 84 ++++++++++++------- .../react-hooks/src/hooks/useChannel.ts | 4 +- .../src/hooks/useChannelProviderCheck.ts | 16 ++++ .../hooks/useChannelStateListener.test.tsx | 7 +- .../src/hooks/useChannelStateListener.ts | 2 + .../src/hooks/usePresence.test.tsx | 30 +++++-- .../react-hooks/src/hooks/usePresence.ts | 2 + 9 files changed, 147 insertions(+), 53 deletions(-) create mode 100644 src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts diff --git a/src/platform/react-hooks/src/ChannelProvider.tsx b/src/platform/react-hooks/src/ChannelProvider.tsx index bcea423ee2..1d7f946b46 100644 --- a/src/platform/react-hooks/src/ChannelProvider.tsx +++ b/src/platform/react-hooks/src/ChannelProvider.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useLayoutEffect, useEffect } from 'react'; import * as Ably from 'ably'; import { getContext } from './AblyProvider.js'; import * as Utils from './utils/utils.js'; @@ -8,24 +8,33 @@ interface ChannelProviderProps { id?: string; channelName: string; options?: Ably.Types.ChannelOptions; + deriveOptions?: Ably.Types.DeriveOptions; children?: React.ReactNode | React.ReactNode[] | null; } -export const ChannelProvider = ({ id = 'default', channelName, options, children }: ChannelProviderProps) => { +export const ChannelProvider = ({ + id = 'default', + channelName, + options, + deriveOptions, + children, +}: ChannelProviderProps) => { const { client, _channelToOptions } = React.useContext(getContext(id)); - const channel = client.channels.get(channelName); + const channel = deriveOptions + ? client.channels.getDerived(channelName, deriveOptions) + : client.channels.get(channelName); - useEffect(() => { - if (_channelToOptions[channelName]) { + useLayoutEffect(() => { + if (_channelToOptions[channel.name]) { throw new Error('You can not use more than one `ChannelProvider` with the same channel name'); } channel.setOptions(channelOptionsWithAgent(options)); - _channelToOptions[channelName] = options ?? {}; + _channelToOptions[channel.name] = options ?? {}; return () => { - delete _channelToOptions[channelName]; + delete _channelToOptions[channel.name]; }; - }, [channel, channelName, options, _channelToOptions]); + }, [channel, options, _channelToOptions]); useEffect(() => { const handleChannelAttached = () => { @@ -42,9 +51,9 @@ export const ChannelProvider = ({ id = 'default', channelName, options, children return {children}; }; -const hasOptionsChanged = (channel: Ably.Types.RealtimeChannelPromise, options: Ably.Types.ChannelOptions) => { +const hasOptionsChanged = (channel: Ably.Types.RealtimeChannelPromise, options?: Ably.Types.ChannelOptions) => { // Don't check against the `agent` param - it isn't returned in the ATTACHED message - const requestedParams = Utils.omitAgent(options.params); + const requestedParams = Utils.omitAgent(options?.params); const existingParams = Utils.omitAgent(channel.params); if (Object.keys(requestedParams).length !== Object.keys(existingParams).length) { @@ -55,5 +64,5 @@ const hasOptionsChanged = (channel: Ably.Types.RealtimeChannelPromise, options: return true; } - return !Utils.arrEquals(options.modes ?? [], channel.modes ?? []); + return !Utils.arrEquals(options?.modes ?? [], channel.modes ?? []); }; diff --git a/src/platform/react-hooks/src/fakes/ably.ts b/src/platform/react-hooks/src/fakes/ably.ts index 1face874b7..ef6e1f7e0f 100644 --- a/src/platform/react-hooks/src/fakes/ably.ts +++ b/src/platform/react-hooks/src/fakes/ably.ts @@ -104,7 +104,7 @@ export class ClientChannelsCollection { if (channelConnection) { return channelConnection; } else { - channelConnection = new ClientSingleChannelConnection(this.client, this.channels.get(name)); + channelConnection = new ClientSingleChannelConnection(this.client, this.channels.get(name), name); this._channelConnections.set(name, channelConnection); return channelConnection; } @@ -115,7 +115,7 @@ export class ClientChannelsCollection { if (channelConnection) return channelConnection as ClientSingleDerivedChannelConnection; const channel = this.channels.get(name); - channelConnection = new ClientSingleDerivedChannelConnection(this.client, channel, options); + channelConnection = new ClientSingleDerivedChannelConnection(this.client, channel, options, name); this._channelConnections.set(name, channelConnection); return channelConnection; } @@ -127,13 +127,15 @@ export class ClientSingleChannelConnection extends EventEmitter { public presence: any; public state: string; + public name: string; - constructor(client: FakeAblySdk, channel: Channel) { + constructor(client: FakeAblySdk, channel: Channel, name: string) { super(); this.client = client; this.channel = channel; this.presence = new ClientPresenceConnection(this.client, this.channel.presence); this.state = 'attached'; + this.name = name; } publish(messages: any, callback?: Ably.errorCallback): void; @@ -157,18 +159,24 @@ export class ClientSingleChannelConnection extends EventEmitter { public detach() { this.channel.subscriptionsPerClient.delete(this.client.clientId); } + + public async setOptions() { + // do nothing + } } export class ClientSingleDerivedChannelConnection extends EventEmitter { private client: FakeAblySdk; private channel: Channel; private deriveOpts: Ably.DeriveOptions; + public name?: string; - constructor(client: FakeAblySdk, channel: Channel, deriveOptions?: Ably.DeriveOptions) { + constructor(client: FakeAblySdk, channel: Channel, deriveOptions?: Ably.DeriveOptions, name?: string) { super(); this.client = client; this.channel = channel; this.deriveOpts = deriveOptions; + this.name = name; } public async subscribe( @@ -183,6 +191,10 @@ export class ClientSingleDerivedChannelConnection extends EventEmitter { public unsubscribe() { this.channel.subscriptionsPerClient.delete(this.client.clientId); } + + public async setOptions() { + // do nothing + } } export class ClientPresenceConnection { @@ -328,6 +340,10 @@ export class Channel { subs.push(callback); } } + + public async setOptions() { + // do nothing + } } export class ChannelPresence { diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index b10db60cfb..ee96ae4499 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -6,9 +6,14 @@ import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; import * as Ably from 'ably'; import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; +import { ChannelProvider } from '../ChannelProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render( + + {children} + + ); } describe('useChannel', () => { @@ -58,7 +63,9 @@ describe('useChannel', () => { renderInCtxProvider( ablyClient, - + + + ); @@ -277,15 +284,19 @@ describe('useChannel with deriveOptions', () => { render( - + + + + + ); @@ -314,10 +325,12 @@ describe('useChannel with deriveOptions', () => { renderInCtxProvider( ablyClient, - + + + ); const channelErrorElem = screen.getByRole('channelError'); @@ -337,10 +350,12 @@ describe('useChannel with deriveOptions', () => { renderInCtxProvider( ablyClient, - + + + ); const channelErrorElem = screen.getByRole('connectionError'); @@ -396,11 +411,16 @@ describe('useChannel with deriveOptions', () => { renderInCtxProvider( ablyClient, - callbackCount++} - /> + > + callbackCount++} + /> + ); act(() => { @@ -445,12 +465,14 @@ describe('useChannel with deriveOptions', () => { renderInCtxProvider( ablyClient, - + + + ); await waitFor(() => expect(channel.subscribe).toHaveBeenCalledWith(eventName, expect.any(Function))); @@ -529,7 +551,11 @@ const UseDerivedChannelComponent = ({ channelName, deriveOptions, skip = false } const messagePreviews = messages.map((msg, index) =>
  • {msg.data.text}
  • ); - return
      {messagePreviews}
    ; + return ( + +
      {messagePreviews}
    +
    + ); }; interface UseChannelStateErrorsComponentProps { diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index 93909fe4fb..8740d528e8 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -1,8 +1,9 @@ import * as Ably from 'ably'; import { useEffect, useMemo, useRef } from 'react'; -import { channelOptionsWithAgent, ChannelParameters } from '../AblyReactHooks.js'; +import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; +import { useChannelProviderCheck } from './useChannelProviderCheck.js'; export type AblyMessageCallback = Ably.messageCallback; @@ -36,6 +37,7 @@ export function useChannel( : { channelName: channelNameOrNameAndOptions }; const ably = useAbly(channelHookOptions.id); + useChannelProviderCheck(channelHookOptions); const { channelName, deriveOptions, skip } = channelHookOptions; diff --git a/src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts b/src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts new file mode 100644 index 0000000000..150148a642 --- /dev/null +++ b/src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts @@ -0,0 +1,16 @@ +import React, { useEffect } from 'react'; +import { getContext } from '../AblyProvider.js'; +import { ChannelNameAndOptions } from '../AblyReactHooks.js'; + +export function useChannelProviderCheck(options: ChannelNameAndOptions) { + const channelToOptions = React.useContext(getContext(options.id))._channelToOptions; + + useEffect(() => { + const hasChannelProvider = Object.keys(channelToOptions).includes(options.channelName); + if (!hasChannelProvider) { + throw new Error( + `Could not find options for a channel. Make sure your channel based hooks (usePresnce, useChannel, useChannelStateListener) are called inside an ` + ); + } + }); +} diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx index 156604a5b0..d3149bc2eb 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.test.tsx @@ -7,9 +7,14 @@ import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; import * as Ably from 'ably'; import { act } from 'react-dom/test-utils'; import { AblyProvider } from '../AblyProvider.js'; +import { ChannelProvider } from '../ChannelProvider.js'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render( + + {children} + + ); } describe('useChannelStateListener', () => { diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts index 063d655529..c3ef9c5c78 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts @@ -2,6 +2,7 @@ import * as Ably from 'ably'; import { ChannelNameAndId, ChannelNameAndOptions } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; +import { useChannelProviderCheck } from './useChannelProviderCheck.js'; type ChannelStateListener = (stateChange: Ably.ChannelStateChange) => any; @@ -25,6 +26,7 @@ export function useChannelStateListener( const { channelName } = channelHookOptions; const ably = useAbly(id); + useChannelProviderCheck(channelHookOptions); const channel = ably.channels.get(channelName); const _listener = typeof listener === 'function' ? listener : (stateOrListener as ChannelStateListener); diff --git a/src/platform/react-hooks/src/hooks/usePresence.test.tsx b/src/platform/react-hooks/src/hooks/usePresence.test.tsx index fa37d0f13a..0f5d06b533 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.test.tsx +++ b/src/platform/react-hooks/src/hooks/usePresence.test.tsx @@ -5,13 +5,18 @@ import { usePresence } from './usePresence.js'; import { render, screen, act, waitFor } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; import { AblyProvider } from '../AblyProvider.js'; +import { ChannelProvider } from '../ChannelProvider.js'; + +const testChannelName = 'testChannel'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render({children}); + return render( + + {children} + + ); } -const testChannelName = 'testChannel'; - describe('usePresence', () => { let channels: FakeAblyChannels; let ablyClient: FakeAblySdk; @@ -73,7 +78,12 @@ describe('usePresence', () => { }); it('presence API works with type information provided', async () => { - renderInCtxProvider(ablyClient, ); + renderInCtxProvider( + ablyClient, + + + + ); await act(async () => { await wait(2); @@ -98,7 +108,9 @@ describe('usePresence', () => { renderInCtxProvider( ablyClient, - + + + ); @@ -119,7 +131,9 @@ describe('usePresence', () => { renderInCtxProvider( ablyClient, - + + + ); const channelErrorElem = screen.getByRole('channelError'); @@ -143,7 +157,9 @@ describe('usePresence', () => { renderInCtxProvider( ablyClient, - + + + ); const connectionErrorElem = screen.getByRole('connectionError'); diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index 738eab3f21..7ad2a8fce1 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; +import { useChannelProviderCheck } from './useChannelProviderCheck.js'; export interface PresenceResult { presenceData: PresenceMessage[]; @@ -27,6 +28,7 @@ export function usePresence( : { channelName: channelNameOrNameAndOptions }; const ably = useAbly(params.id); + useChannelProviderCheck(params); const subscribeOnly = typeof channelNameOrNameAndOptions === 'string' ? false : params.subscribeOnly; From baf68c809adac1de5e32abb9fa7277eebb7795d2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Feb 2024 21:32:36 +0000 Subject: [PATCH 388/468] refactor: use React context to store channel instance Start storing the channel instance inside React context. It helps with dealing with channels with derived options (these channels have different names in ably-js internals and should be accessed via the special channels#getDerived() method). --- .../react-hooks/sample-app/src/App.tsx | 8 ++- .../react-hooks/sample-app/src/script.tsx | 25 ++++++-- src/platform/react-hooks/src/AblyProvider.tsx | 8 +-- .../react-hooks/src/AblyReactHooks.ts | 1 - .../react-hooks/src/ChannelProvider.tsx | 57 ++++++------------- .../react-hooks/src/hooks/useChannel.test.tsx | 51 +++++++++-------- .../react-hooks/src/hooks/useChannel.ts | 19 ++----- .../src/hooks/useChannelInstance.ts | 14 +++++ .../src/hooks/useChannelProviderCheck.ts | 16 ------ .../src/hooks/useChannelStateListener.ts | 7 +-- .../react-hooks/src/hooks/usePresence.ts | 5 +- 11 files changed, 96 insertions(+), 115 deletions(-) create mode 100644 src/platform/react-hooks/src/hooks/useChannelInstance.ts delete mode 100644 src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts diff --git a/src/platform/react-hooks/sample-app/src/App.tsx b/src/platform/react-hooks/sample-app/src/App.tsx index dcb1e31fbd..e5fa70a40f 100644 --- a/src/platform/react-hooks/sample-app/src/App.tsx +++ b/src/platform/react-hooks/sample-app/src/App.tsx @@ -22,7 +22,7 @@ function App() { useChannel( { channelName: 'your-derived-channel-name', - deriveOptions: { filter: 'headers.email == `"rob.pike@domain.com"` || headers.company == `"domain"`' }, + id: 'rob', }, (message) => { updateDerivedChannelMessages((prev) => [...prev, message]); @@ -32,14 +32,16 @@ function App() { useChannel( { channelName: 'your-derived-channel-name', - deriveOptions: { filter: 'headers.role == `"front-office"` || headers.company == `"domain"`' }, + id: 'frontOffice', }, (message) => { updateFrontOfficeOnlyMessages((prev) => [...prev, message]); } ); - const { channel: anotherChannelPublisher } = useChannel({ channelName: 'your-derived-channel-name' }); + const { channel: anotherChannelPublisher } = useChannel({ + channelName: 'your-derived-channel-name', + }); const { presenceData, updateStatus } = usePresence( { channelName: 'your-channel-name', skip }, diff --git a/src/platform/react-hooks/sample-app/src/script.tsx b/src/platform/react-hooks/sample-app/src/script.tsx index 11a381cd78..d345d7630f 100644 --- a/src/platform/react-hooks/sample-app/src/script.tsx +++ b/src/platform/react-hooks/sample-app/src/script.tsx @@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import * as Ably from 'ably'; - import App from './App.js'; import { AblyProvider, ChannelProvider } from '../../src/index.js'; @@ -22,9 +21,27 @@ const root = createRoot(container); root.render( - - - + + + + + + + + + + + + + ); diff --git a/src/platform/react-hooks/src/AblyProvider.tsx b/src/platform/react-hooks/src/AblyProvider.tsx index 8c8df94a96..0d26093b2d 100644 --- a/src/platform/react-hooks/src/AblyProvider.tsx +++ b/src/platform/react-hooks/src/AblyProvider.tsx @@ -9,9 +9,9 @@ interface AblyProviderProps { id?: string; } -interface AblyContextProps { +export interface AblyContextProps { client: Ably.RealtimeClient; - _channelToOptions: Record; + _channelNameToInstance: Record; } type AblyContextType = React.Context; @@ -32,7 +32,7 @@ export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderP const value: AblyContextProps = useMemo( () => ({ client, - _channelToOptions: {}, + _channelNameToInstance: {}, }), [client] ); @@ -43,7 +43,7 @@ export const AblyProvider = ({ client, children, id = 'default' }: AblyProviderP let context = getContext(id); if (!context) { - context = ctxMap[id] = React.createContext({ client, _channelToOptions: {} }); + context = ctxMap[id] = React.createContext({ client, _channelNameToInstance: {} }); } return {children}; diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index eb2f0d6039..a4689e750c 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -2,7 +2,6 @@ import * as Ably from 'ably'; export type ChannelNameAndOptions = { channelName: string; - deriveOptions?: Ably.DeriveOptions; id?: string; subscribeOnly?: boolean; skip?: boolean; diff --git a/src/platform/react-hooks/src/ChannelProvider.tsx b/src/platform/react-hooks/src/ChannelProvider.tsx index 1d7f946b46..b41aad58d0 100644 --- a/src/platform/react-hooks/src/ChannelProvider.tsx +++ b/src/platform/react-hooks/src/ChannelProvider.tsx @@ -1,7 +1,6 @@ -import React, { useLayoutEffect, useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import * as Ably from 'ably'; -import { getContext } from './AblyProvider.js'; -import * as Utils from './utils/utils.js'; +import { type AblyContextProps, getContext } from './AblyProvider.js'; import { channelOptionsWithAgent } from './AblyReactHooks.js'; interface ChannelProviderProps { @@ -19,50 +18,30 @@ export const ChannelProvider = ({ deriveOptions, children, }: ChannelProviderProps) => { - const { client, _channelToOptions } = React.useContext(getContext(id)); + const context = getContext(id); + const { client, _channelNameToInstance } = React.useContext(context); + + if (_channelNameToInstance[channelName]) { + throw new Error('You can not use more than one `ChannelProvider` with the same channel name'); + } const channel = deriveOptions ? client.channels.getDerived(channelName, deriveOptions) : client.channels.get(channelName); - useLayoutEffect(() => { - if (_channelToOptions[channel.name]) { - throw new Error('You can not use more than one `ChannelProvider` with the same channel name'); - } - channel.setOptions(channelOptionsWithAgent(options)); - _channelToOptions[channel.name] = options ?? {}; - return () => { - delete _channelToOptions[channel.name]; + const value: AblyContextProps = useMemo(() => { + return { + client, + _channelNameToInstance: { + ..._channelNameToInstance, + [channelName]: channel, + }, }; - }, [channel, options, _channelToOptions]); + }, [client, channel, channelName, _channelNameToInstance]); useEffect(() => { - const handleChannelAttached = () => { - if (hasOptionsChanged(channel, options)) { - throw new Error('You can not use `RealtimeChannel.setOption()` along with `ChannelProvider`'); - } - }; - channel.on('attached', handleChannelAttached); - return () => { - channel.off('attached', handleChannelAttached); - }; + channel.setOptions(channelOptionsWithAgent(options)); }, [channel, options]); - return {children}; -}; - -const hasOptionsChanged = (channel: Ably.Types.RealtimeChannelPromise, options?: Ably.Types.ChannelOptions) => { - // Don't check against the `agent` param - it isn't returned in the ATTACHED message - const requestedParams = Utils.omitAgent(options?.params); - const existingParams = Utils.omitAgent(channel.params); - - if (Object.keys(requestedParams).length !== Object.keys(existingParams).length) { - return true; - } - - if (!Utils.shallowEquals(existingParams, requestedParams)) { - return true; - } - - return !Utils.arrEquals(options?.modes ?? [], channel.modes ?? []); + return {children}; }; diff --git a/src/platform/react-hooks/src/hooks/useChannel.test.tsx b/src/platform/react-hooks/src/hooks/useChannel.test.tsx index ee96ae4499..526e3310b9 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.test.tsx +++ b/src/platform/react-hooks/src/hooks/useChannel.test.tsx @@ -196,7 +196,9 @@ describe('useChannel with deriveOptions', () => { it('component can use "useChannel" with "deriveOptions" and renders nothing by default', async () => { renderInCtxProvider( ablyClient, - + + + ); const messageUl = screen.getAllByRole('derived-channel-messages')[0]; @@ -206,10 +208,12 @@ describe('useChannel with deriveOptions', () => { it('component updates when new message arrives', async () => { renderInCtxProvider( ablyClient, - + + + ); act(() => { @@ -226,10 +230,12 @@ describe('useChannel with deriveOptions', () => { it('component will not update if message filtered out', async () => { renderInCtxProvider( ablyClient, - + > + + ); act(() => { @@ -245,10 +251,12 @@ describe('useChannel with deriveOptions', () => { it('component will update with only those messages that qualify', async () => { renderInCtxProvider( ablyClient, - + > + + ); act(() => { @@ -372,10 +380,9 @@ describe('useChannel with deriveOptions', () => { it('wildcard filter', async () => { renderInCtxProvider( ablyClient, - + + + ); act(() => { @@ -390,11 +397,9 @@ describe('useChannel with deriveOptions', () => { it('skip param', async () => { renderInCtxProvider( ablyClient, - + + + ); act(() => { @@ -542,20 +547,16 @@ interface UseDerivedChannelComponentProps { skip?: boolean; } -const UseDerivedChannelComponent = ({ channelName, deriveOptions, skip = false }: UseDerivedChannelComponentProps) => { +const UseDerivedChannelComponent = ({ channelName, skip = false }: UseDerivedChannelComponentProps) => { const [messages, setMessages] = useState([]); - useChannel({ channelName, deriveOptions, skip }, (message) => { + useChannel({ channelName, skip }, (message) => { setMessages((prev) => [...prev, message]); }); const messagePreviews = messages.map((msg, index) =>
  • {msg.data.text}
  • ); - return ( - -
      {messagePreviews}
    -
    - ); + return
      {messagePreviews}
    ; }; interface UseChannelStateErrorsComponentProps { diff --git a/src/platform/react-hooks/src/hooks/useChannel.ts b/src/platform/react-hooks/src/hooks/useChannel.ts index 8740d528e8..6bd3acc96e 100644 --- a/src/platform/react-hooks/src/hooks/useChannel.ts +++ b/src/platform/react-hooks/src/hooks/useChannel.ts @@ -1,9 +1,9 @@ import * as Ably from 'ably'; -import { useEffect, useMemo, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; -import { useChannelProviderCheck } from './useChannelProviderCheck.js'; +import { useChannelInstance } from './useChannelInstance.js'; export type AblyMessageCallback = Ably.messageCallback; @@ -37,28 +37,17 @@ export function useChannel( : { channelName: channelNameOrNameAndOptions }; const ably = useAbly(channelHookOptions.id); - useChannelProviderCheck(channelHookOptions); + const { channelName, skip } = channelHookOptions; - const { channelName, deriveOptions, skip } = channelHookOptions; + const channel = useChannelInstance(channelHookOptions.id, channelName); const channelEvent = typeof eventOrCallback === 'string' ? eventOrCallback : null; const ablyMessageCallback = typeof eventOrCallback === 'string' ? callback : eventOrCallback; - const deriveOptionsRef = useRef(deriveOptions); const ablyMessageCallbackRef = useRef(ablyMessageCallback); - const channel = useMemo(() => { - const derived = deriveOptionsRef.current; - const channel = derived ? ably.channels.getDerived(channelName, derived) : ably.channels.get(channelName); - return channel; - }, [ably, channelName]); - const { connectionError, channelError } = useStateErrors(channelHookOptions); - useEffect(() => { - deriveOptionsRef.current = deriveOptions; - }, [deriveOptions]); - useEffect(() => { ablyMessageCallbackRef.current = ablyMessageCallback; }, [ablyMessageCallback]); diff --git a/src/platform/react-hooks/src/hooks/useChannelInstance.ts b/src/platform/react-hooks/src/hooks/useChannelInstance.ts new file mode 100644 index 0000000000..dd2a0f6daa --- /dev/null +++ b/src/platform/react-hooks/src/hooks/useChannelInstance.ts @@ -0,0 +1,14 @@ +import React from 'react'; +import { getContext } from '../AblyProvider.js'; + +export function useChannelInstance(id: string, channelName: string) { + const channel = React.useContext(getContext(id))._channelNameToInstance[channelName]; + + if (!channel) { + throw new Error( + `Could not find channel instance for name="${channelName}". Make sure your channel based hooks (usePresence, useChannel, useChannelStateListener) are called inside an ` + ); + } + + return channel; +} diff --git a/src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts b/src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts deleted file mode 100644 index 150148a642..0000000000 --- a/src/platform/react-hooks/src/hooks/useChannelProviderCheck.ts +++ /dev/null @@ -1,16 +0,0 @@ -import React, { useEffect } from 'react'; -import { getContext } from '../AblyProvider.js'; -import { ChannelNameAndOptions } from '../AblyReactHooks.js'; - -export function useChannelProviderCheck(options: ChannelNameAndOptions) { - const channelToOptions = React.useContext(getContext(options.id))._channelToOptions; - - useEffect(() => { - const hasChannelProvider = Object.keys(channelToOptions).includes(options.channelName); - if (!hasChannelProvider) { - throw new Error( - `Could not find options for a channel. Make sure your channel based hooks (usePresnce, useChannel, useChannelStateListener) are called inside an ` - ); - } - }); -} diff --git a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts index c3ef9c5c78..79b74f315d 100644 --- a/src/platform/react-hooks/src/hooks/useChannelStateListener.ts +++ b/src/platform/react-hooks/src/hooks/useChannelStateListener.ts @@ -1,8 +1,7 @@ import * as Ably from 'ably'; import { ChannelNameAndId, ChannelNameAndOptions } from '../AblyReactHooks.js'; -import { useAbly } from './useAbly.js'; import { useEventListener } from './useEventListener.js'; -import { useChannelProviderCheck } from './useChannelProviderCheck.js'; +import { useChannelInstance } from './useChannelInstance.js'; type ChannelStateListener = (stateChange: Ably.ChannelStateChange) => any; @@ -25,9 +24,7 @@ export function useChannelStateListener( const { channelName } = channelHookOptions; - const ably = useAbly(id); - useChannelProviderCheck(channelHookOptions); - const channel = ably.channels.get(channelName); + const channel = useChannelInstance(id, channelName); const _listener = typeof listener === 'function' ? listener : (stateOrListener as ChannelStateListener); diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index 7ad2a8fce1..6b7a88cf4b 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; -import { useChannelProviderCheck } from './useChannelProviderCheck.js'; +import { useChannelInstance } from './useChannelInstance.js'; export interface PresenceResult { presenceData: PresenceMessage[]; @@ -28,11 +28,10 @@ export function usePresence( : { channelName: channelNameOrNameAndOptions }; const ably = useAbly(params.id); - useChannelProviderCheck(params); + const channel = useChannelInstance(params.id, params.channelName); const subscribeOnly = typeof channelNameOrNameAndOptions === 'string' ? false : params.subscribeOnly; - const channel = useMemo(() => ably.channels.get(params.channelName), [ably, params.channelName]); const skip = params.skip; const { connectionError, channelError } = useStateErrors(params); From 5324354c8f9b3faf071055973c7f255ce7d7ac62 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Feb 2024 10:33:58 +0000 Subject: [PATCH 389/468] chore: update example App, wording for channel without provider exception Also get rid of channel options check, looks like there is a lot of corner cases it's better to add this check later --- .../react-hooks/sample-app/src/script.tsx | 2 +- .../react-hooks/src/ChannelProvider.tsx | 4 +- .../react-hooks/src/hooks/useChannel.test.tsx | 72 ++++++++----------- .../src/hooks/useChannelInstance.ts | 2 +- .../react-hooks/src/hooks/usePresence.ts | 2 +- src/platform/react-hooks/src/utils/utils.ts | 36 ---------- 6 files changed, 35 insertions(+), 83 deletions(-) delete mode 100644 src/platform/react-hooks/src/utils/utils.ts diff --git a/src/platform/react-hooks/sample-app/src/script.tsx b/src/platform/react-hooks/sample-app/src/script.tsx index d345d7630f..2eacdc6287 100644 --- a/src/platform/react-hooks/sample-app/src/script.tsx +++ b/src/platform/react-hooks/sample-app/src/script.tsx @@ -23,7 +23,7 @@ root.render( - + { it('component can use "useChannel" with "deriveOptions" and renders nothing by default', async () => { renderInCtxProvider( ablyClient, - - + + ); const messageUl = screen.getAllByRole('derived-channel-messages')[0]; @@ -208,11 +208,11 @@ describe('useChannel with deriveOptions', () => { it('component updates when new message arrives', async () => { renderInCtxProvider( ablyClient, - - + + ); @@ -292,16 +292,25 @@ describe('useChannel with deriveOptions', () => { render( - - + + @@ -420,11 +429,7 @@ describe('useChannel with deriveOptions', () => { channelName={Channels.tasks} deriveOptions={{ filter: 'headers.user == `"robert.pike@domain.io"` || headers.company == `"domain"`' }} > - callbackCount++} - /> + callbackCount++} /> ); @@ -470,13 +475,8 @@ describe('useChannel with deriveOptions', () => { renderInCtxProvider( ablyClient, - - + + ); @@ -518,7 +518,6 @@ interface UseDerivedChannelComponentMultipleClientsProps { channelName: string; anotherClientId: string; anotherChannelName: string; - deriveOptions: Ably.DeriveOptions; } const UseDerivedChannelComponentMultipleClients = ({ @@ -526,13 +525,12 @@ const UseDerivedChannelComponentMultipleClients = ({ clientId, anotherClientId, anotherChannelName, - deriveOptions, }: UseDerivedChannelComponentMultipleClientsProps) => { const [messages, setMessages] = useState([]); - useChannel({ id: clientId, channelName, deriveOptions }, (message) => { + useChannel({ id: clientId, channelName }, (message) => { setMessages((prev) => [...prev, message]); }); - useChannel({ id: anotherClientId, channelName: anotherChannelName, deriveOptions }, (message) => { + useChannel({ id: anotherClientId, channelName: anotherChannelName }, (message) => { setMessages((prev) => [...prev, message]); }); @@ -543,7 +541,6 @@ const UseDerivedChannelComponentMultipleClients = ({ interface UseDerivedChannelComponentProps { channelName: string; - deriveOptions: Ably.DeriveOptions; skip?: boolean; } @@ -563,16 +560,14 @@ interface UseChannelStateErrorsComponentProps { onConnectionError?: (err: Ably.ErrorInfo) => unknown; onChannelError?: (err: Ably.ErrorInfo) => unknown; channelName?: string; - deriveOptions?: Ably.DeriveOptions; } const UseChannelStateErrorsComponent = ({ onConnectionError, onChannelError, channelName = 'blah', - deriveOptions, }: UseChannelStateErrorsComponentProps) => { - const opts = { channelName, deriveOptions, onConnectionError, onChannelError }; + const opts = { channelName, onConnectionError, onChannelError }; const { connectionError, channelError } = useChannel(opts); return ( @@ -585,18 +580,13 @@ const UseChannelStateErrorsComponent = ({ interface LatestMessageCallbackComponentProps { channelName: string; - deriveOptions?: Ably.DeriveOptions; callback: () => any; } -const LatestMessageCallbackComponent = ({ - channelName, - deriveOptions, - callback, -}: LatestMessageCallbackComponentProps) => { +const LatestMessageCallbackComponent = ({ channelName, callback }: LatestMessageCallbackComponentProps) => { const [count, setCount] = React.useState(0); - useChannel({ channelName, deriveOptions }, () => { + useChannel({ channelName }, () => { callback(); setCount((count) => count + 1); }); @@ -607,7 +597,6 @@ const LatestMessageCallbackComponent = ({ interface ChangingEventComponentProps { newEventName: string; channelName?: string; - deriveOptions?: Ably.DeriveOptions; eventName?: string; } @@ -615,11 +604,10 @@ const ChangingEventComponent = ({ channelName = 'blah', eventName = 'event1', newEventName, - deriveOptions, }: ChangingEventComponentProps) => { const [currentEventName, setCurrentEventName] = useState(eventName); - useChannel({ channelName, deriveOptions }, currentEventName, vi.fn()); + useChannel({ channelName }, currentEventName, vi.fn()); useEffect(() => { const timeoutId = setTimeout(() => { diff --git a/src/platform/react-hooks/src/hooks/useChannelInstance.ts b/src/platform/react-hooks/src/hooks/useChannelInstance.ts index dd2a0f6daa..03fa36013c 100644 --- a/src/platform/react-hooks/src/hooks/useChannelInstance.ts +++ b/src/platform/react-hooks/src/hooks/useChannelInstance.ts @@ -6,7 +6,7 @@ export function useChannelInstance(id: string, channelName: string) { if (!channel) { throw new Error( - `Could not find channel instance for name="${channelName}". Make sure your channel based hooks (usePresence, useChannel, useChannelStateListener) are called inside an ` + `Could not find a parent ChannelProvider in the component tree for name="${channelName}". Make sure your channel based hooks (usePresence, useChannel, useChannelStateListener) are called inside a component` ); } diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index 6b7a88cf4b..35139e85b8 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,5 +1,5 @@ import type * as Ably from 'ably'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useStateErrors } from './useStateErrors.js'; diff --git a/src/platform/react-hooks/src/utils/utils.ts b/src/platform/react-hooks/src/utils/utils.ts deleted file mode 100644 index 69caac5a59..0000000000 --- a/src/platform/react-hooks/src/utils/utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as API from 'ably'; - -export const arrEvery = (Array.prototype.every as unknown) - ? function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { - return arr.every(fn); - } - : function (arr: Array, fn: (value: T, index: number, arr: Array) => boolean) { - const len = arr.length; - for (let i = 0; i < len; i++) { - if (!fn(arr[i], i, arr)) { - return false; - } - } - return true; - }; - -export function shallowEquals(source: Record, target: Record) { - return ( - Object.keys(source).every((key) => source[key] === target[key]) && - Object.keys(target).every((key) => target[key] === source[key]) - ); -} - -export function arrEquals(a: any[], b: any[]) { - return ( - a.length === b.length && - arrEvery(a, function (val, i) { - return val === b[i]; - }) - ); -} - -export function omitAgent(channelParams?: API.Types.ChannelParams) { - const { agent: _, ...paramsWithoutAgent } = channelParams || {}; - return paramsWithoutAgent; -} From c3da4909181487c7b5f255253cdd2c147f822b43 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 26 Feb 2024 19:33:48 +0000 Subject: [PATCH 390/468] chore: add `ChannelProvider` to the playwright check --- test/package/browser/template/playwright/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/package/browser/template/playwright/index.tsx b/test/package/browser/template/playwright/index.tsx index 340334f77b..5b17f5d566 100644 --- a/test/package/browser/template/playwright/index.tsx +++ b/test/package/browser/template/playwright/index.tsx @@ -1,6 +1,6 @@ import { beforeMount } from '@playwright/experimental-ct-react/hooks'; import * as Ably from 'ably'; -import { AblyProvider } from 'ably/react'; +import { AblyProvider, ChannelProvider } from 'ably/react'; import { createSandboxAblyAPIKey } from '../src/sandbox'; @@ -14,7 +14,9 @@ beforeMount(async ({ App }) => { return ( - + + + ); }); From 4cb3b00dd347cd29c2fdbe8ae3f0c7e4efc16fe4 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 22:40:12 +0000 Subject: [PATCH 391/468] Add `console` and `pageerror` listeners to react-hooks package test By default playwright doesn't output console messages and errors happened during rendering React components to stdout of node.js test process. This means we're getting a generic "Test timeout of 10000ms exceeded." error and nothing else when running react-hooks package test and there is a rendering error [1]. This commit adds listeners for 'console' and 'pageerror' events on playwright page, so we can output actual errors to node.js stdout. So we also get error messages like here [2]. [1] https://github.com/ably/ably-js/actions/runs/8056450454/job/22005521704?pr=1639 [2] https://github.com/ably/ably-js/actions/runs/8056604349/job/22005998807?pr=1639 --- .../browser/template/test/hooks/ReactApp.spec.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/package/browser/template/test/hooks/ReactApp.spec.tsx b/test/package/browser/template/test/hooks/ReactApp.spec.tsx index 177dee63c9..6a734da0a1 100644 --- a/test/package/browser/template/test/hooks/ReactApp.spec.tsx +++ b/test/package/browser/template/test/hooks/ReactApp.spec.tsx @@ -6,6 +6,16 @@ test.describe('NPM package', () => { for (const scenario of [{ name: 'react export' }]) { test.describe(scenario.name, () => { test('can be imported and provides access to Ably functionality', async ({ mount, page }) => { + page.on('console', (message) => { + if (['error', 'warning'].includes(message.type())) { + console.log(`Console ${message.type()}:`, message); + } + }); + + page.on('pageerror', (err) => { + console.log('Uncaught exception:', err); + }); + const pageResultPromise = new Promise((resolve, reject) => { page.exposeFunction('onResult', (error: Error | null) => { if (error) { @@ -17,7 +27,7 @@ test.describe('NPM package', () => { }); const component = await mount(); - await pageResultPromise; + await expect(pageResultPromise).resolves.not.toThrow(); await expect(component).toContainText('Ably NPM package test (react export)'); }); }); From 2fc88a33cd6ef5fbf8b5147460bf13fabbcc7e1c Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 27 Feb 2024 13:29:15 +0000 Subject: [PATCH 392/468] Remove node 0.8.x specific code from node platform crypto --- src/platform/nodejs/lib/util/crypto.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/platform/nodejs/lib/util/crypto.ts b/src/platform/nodejs/lib/util/crypto.ts index 330f70a598..e785ad8557 100644 --- a/src/platform/nodejs/lib/util/crypto.ts +++ b/src/platform/nodejs/lib/util/crypto.ts @@ -76,15 +76,6 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { var pkcs5Padding = [filledBuffer(16, 16)]; for (var i = 1; i <= 16; i++) pkcs5Padding.push(filledBuffer(i, i)); - /** - * Internal: convert a binary string to Buffer (for node 0.8.x) - * @param bufferOrString - * @returns {Buffer} - */ - function toBuffer(bufferOrString: Buffer | string) { - return typeof bufferOrString == 'string' ? Buffer.from(bufferOrString, 'binary') : bufferOrString; - } - /** * A class encapsulating the client-specifiable parameters for * the cipher. @@ -236,19 +227,19 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { var cipherOut = this.encryptCipher.update( Buffer.concat([plaintextBuffer, pkcs5Padding[paddedLength - plaintextLength]]) ); - var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]); + var ciphertext = Buffer.concat([iv, cipherOut]); return ciphertext; } async decrypt(ciphertext: InputCiphertext): Promise { var decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, DEFAULT_BLOCKLENGTH)), - plaintext = toBuffer(decryptCipher.update(ciphertext.slice(DEFAULT_BLOCKLENGTH))), + plaintext = decryptCipher.update(ciphertext.slice(DEFAULT_BLOCKLENGTH)), final = decryptCipher.final(); - if (final && final.length) plaintext = Buffer.concat([plaintext, toBuffer(final)]); + if (final && final.length) plaintext = Buffer.concat([plaintext, final]); return plaintext; } - async getIv() { + async getIv(): Promise { if (this.iv) { var iv = this.iv; this.iv = null; @@ -263,7 +254,7 @@ var createCryptoClass = function (bufferUtils: typeof BufferUtils) { /* Since the iv for a new block is the ciphertext of the last, this * sets a new iv (= aes(randomBlock XOR lastCipherText)) as well as * returning it */ - return toBuffer(this.encryptCipher.update(randomBlock)); + return this.encryptCipher.update(randomBlock); } } } From 8996ff53634017439ecac08ad085409be342d644 Mon Sep 17 00:00:00 2001 From: Evgeny Khokhlov Date: Tue, 27 Feb 2024 13:29:49 +0000 Subject: [PATCH 393/468] Update src/platform/react-hooks/src/hooks/useChannelInstance.ts Co-authored-by: Andrew Bulat --- src/platform/react-hooks/src/hooks/useChannelInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/react-hooks/src/hooks/useChannelInstance.ts b/src/platform/react-hooks/src/hooks/useChannelInstance.ts index 03fa36013c..42a2abfdcb 100644 --- a/src/platform/react-hooks/src/hooks/useChannelInstance.ts +++ b/src/platform/react-hooks/src/hooks/useChannelInstance.ts @@ -6,7 +6,7 @@ export function useChannelInstance(id: string, channelName: string) { if (!channel) { throw new Error( - `Could not find a parent ChannelProvider in the component tree for name="${channelName}". Make sure your channel based hooks (usePresence, useChannel, useChannelStateListener) are called inside a component` + `Could not find a parent ChannelProvider in the component tree for channelName="${channelName}". Make sure your channel based hooks (usePresence, useChannel, useChannelStateListener) are called inside a component` ); } From c4fc54431f6b38b2e5358304dab37a03cc7a7d7d Mon Sep 17 00:00:00 2001 From: owenpearson Date: Thu, 29 Feb 2024 11:37:18 +0000 Subject: [PATCH 394/468] ci: don't check formatting in react workflow this is already checked in the lint workflow so no need to do it here --- .github/workflows/react.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/react.yml b/.github/workflows/react.yml index 2c48513e7d..51734cb38a 100644 --- a/.github/workflows/react.yml +++ b/.github/workflows/react.yml @@ -16,5 +16,4 @@ jobs: with: node-version: 16 - run: npm ci - - run: npm run format:check - run: npm run test:react From 581a6fd705318d2a46ebff171bc61f8041f71f25 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 13:08:29 +0000 Subject: [PATCH 395/468] Update platform compatibility statements in READMEs --- CONTRIBUTING.md | 6 +++--- README.md | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81ac97069e..8aa3a0afb7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ ## Building the library -To build the library, simply run `npm run build`. Building the library currently requires NodeJS <= v16. +To build the library, simply run `npm run build`. Building the library currently requires NodeJS >= v16. Since webpack builds are slow, commands are also available to only build the output for specific platforms (eg `npm run build:node`), see [package.json](./package.json) for the full list of available commands @@ -51,11 +51,11 @@ Or run just one test file Or run just one test - npm run test:node -- --file=test/rest/status.test.js --grep=test_name_here + npm run test:node -- --file=test/rest/status.test.js --grep=test_name_here Or run test skipping the build - npm run test:node:skip-build -- --file=test/rest/status.test.js --grep=test_name_here + npm run test:node:skip-build -- --file=test/rest/status.test.js --grep=test_name_here ### Debugging the mocha tests locally with a debugger diff --git a/README.md b/README.md index 65ecbc379f..1c242aad7f 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,19 @@ This library currently targets the [Ably client library features spec](https://w This SDK supports the following platforms: -**Browsers:** All major desktop and mobile browsers, including (but not limited to) Chrome, Firefox, IE (only version 9 or newer), Safari on iOS and macOS, Opera, and Android browsers. +**Browsers:** All major desktop and mobile browsers, including (but not limited to) Chrome, Firefox, Edge, Safari on iOS and macOS, Opera, and Android browsers. IE is not supported. See compatibility table below for more information on minimum supported versions for major browsers: + +| Browser | Minimum supported version | Release date | +| ------------- |:-------------------------:| -------------:| +| Chrome | 58 | Apr 19, 2017 | +| Firefox | 52 | Mar 7, 2017 | +| Edge | 79 | Dec 15, 2020 | +| Safari | 11 | Sep 19, 2017 | +| Opera | 45 | May 10, 2017 | **Webpack:** see [using Webpack in browsers](#using-webpack), or [our guide for serverside Webpack](#serverside-usage-with-webpack) -**Node.js:** version 8.17 or newer. (1.1.x versions work on Node.js 4.5 or newer). We do not currently provide an ESM bundle, please [contact us](https://www.ably.com/contact) if you would would like to use ably-js in a NodeJS ESM project. +**Node.js:** version 16.x or newer. (1.1.x versions work on Node.js 4.5 or newer, 1.2.x versions work on Node.js 8.17 or newer). We do not currently provide an ESM bundle, please [contact us](https://www.ably.com/contact) if you would would like to use ably-js in a NodeJS ESM project. **React (release candidate):** We offer a set of React Hooks which make it seamless to use ably-js in your React application. See the [React Hooks documentation](./docs/react.md) for more details. @@ -30,12 +38,11 @@ This SDK supports the following platforms: **WebWorkers:** The browser bundle supports running in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) context. You can also use the [modular variant](#modular-tree-shakable-variant) of the library in Web Workers. -We regression-test the library against a selection of those (which will change over time, but usually consists of the versions that are supported upstream, plus old versions of IE). +We test the library against a selection of browsers using their latest versions. Please refer to [the test-browser GitHub workflow](./.github/workflows/test-browser.yml) for the set of browsers that currently undergo CI testing. -However, we aim to be compatible with a much wider set of platforms and browsers than we can possibly test on. That means we'll happily support (and investigate reported problems with) any reasonably-widely-used browser. So if you find any compatibility issues, please do [raise an issue](https://github.com/ably/ably-js/issues) in this repository or [contact Ably customer support](https://support.ably.com) for advice. +We regression-test the library against a selection of Node.js versions, which will change over time. We will always support and test against current LTS Node.js versions, and optionally some older versions that are still supported by upstream dependencies. We reserve the right to drop support for non-LTS versions in a non-major release. We will update the `engines` field in [package.json](./package.json) whenever we change the Node.js versions supported by the project. Please refer to [the test-node GitHub workflow](./.github/workflows/test-node.yml) for the set of versions that currently undergo CI testing. -Ably-js has fallback mechanisms in order to be able to support older browsers; specifically it supports comet-based connections for browsers that do not support websockets. Each of these fallback transport mechanisms is supported and tested on all the browsers we test against, even when those browsers do not themselves require those fallbacks. These mean that the library should be compatible with nearly any browser on most platforms. -Known browser incompatibilities will be documented as an issue in this repository using the ["compatibility" label](https://github.com/ably/ably-js/issues?q=is%3Aissue+is%3Aopen+label%3A%22compatibility%22). +However, we aim to be compatible with a much wider set of platforms and browsers than we can possibly test on. That means we'll happily support (and investigate reported problems with) any reasonably-widely-used browser. So if you find any compatibility issues, please do [raise an issue](https://github.com/ably/ably-js/issues) in this repository or [contact Ably customer support](https://support.ably.com) for advice. For complete API documentation, see the [Ably documentation](https://www.ably.com/docs). From 54d4c3908cda2c2fb7b3e9f357b875a7882f1bb2 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 13:13:25 +0000 Subject: [PATCH 396/468] Change build/bundle ES target to ES2017 --- Gruntfile.js | 2 +- src/platform/react-hooks/tsconfig.json | 2 +- tsconfig.json | 4 ++-- webpack.config.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 9ee91b1382..088f5bcd43 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -120,7 +120,7 @@ module.exports = function (grunt) { format: 'umd', banner: { js: '/*' + banner + '*/' }, plugins: [umdWrapper.default({ libraryName: 'Ably', amdNamedModule: false })], - target: 'es6', + target: 'es2017', }; } diff --git a/src/platform/react-hooks/tsconfig.json b/src/platform/react-hooks/tsconfig.json index ed956b2d1d..b4fcca7644 100644 --- a/src/platform/react-hooks/tsconfig.json +++ b/src/platform/react-hooks/tsconfig.json @@ -2,7 +2,7 @@ "include": ["./src/**/*.ts", "./ably.d.ts"], "exclude": ["./src/**/*.test.tsx", "./src/fakes/**/*.ts"], "compilerOptions": { - "target": "es6", + "target": "ES2017", "rootDir": "./src", "sourceMap": true, "strict": false, diff --git a/tsconfig.json b/tsconfig.json index c0a2b4227c..e1d81bdb0c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "es5", + "target": "ES2017", "module": "commonjs", - "lib": ["ES5", "DOM", "DOM.Iterable", "webworker"], + "lib": ["ES2017", "DOM", "DOM.Iterable", "webworker"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, diff --git a/webpack.config.js b/webpack.config.js index 7ed67c6bb9..57ea291278 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,7 +27,7 @@ const baseConfig = { { test: /\.ts$/, loader: 'ts-loader' }, ], }, - target: ['web', 'es5'], + target: ['web', 'es2017'], externals: { request: false, ws: false, @@ -54,7 +54,7 @@ const nodeConfig = { ...baseConfig.output, filename: 'ably-node.js', }, - target: ['node', 'es5'], + target: ['node', 'es2017'], externals: { got: true, ws: true, From b4457294450971f0d4074671905d642c9c9135b9 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 15:25:47 +0000 Subject: [PATCH 397/468] Convert NodeCometTransport in nodejs platform to ES6 classes After switching to ES2017 build target in 54d4c3908cda2c2fb7b3e9f357b875a7882f1bb2, ES5 classes in NodeCometTransport were causing "Uncaught TypeError: Class constructor CometTransport cannot be invoked without 'new'" errors in CI [1]. [1] https://github.com/ably/ably-js/actions/runs/8049569753/job/21984635358?pr=1633 --- .../lib/transport/nodecomettransport.js | 101 +++++++++--------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.js b/src/platform/nodejs/lib/transport/nodecomettransport.js index 9a1c29411d..616166b9c9 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.js +++ b/src/platform/nodejs/lib/transport/nodecomettransport.js @@ -12,29 +12,24 @@ import url from 'url'; import util from 'util'; import { TransportNames } from '../../../../common/constants/TransportName'; -var NodeCometTransport = function (transportStorage) { - var noop = function () {}; - var shortName = TransportNames.Comet; - - /* - * A transport to use with nodejs - * to simulate an XHR transport for test purposes - */ - function NodeCometTransport(connectionManager, auth, params) { - CometTransport.call(this, connectionManager, auth, params); - this.httpAgent = null; - this.httpsAgent = null; - this.pendingRequests = 0; - this.shortName = shortName; - } - util.inherits(NodeCometTransport, CometTransport); - - NodeCometTransport.isAvailable = function () { +var noop = function () {}; +var shortName = TransportNames.Comet; + +/* + * A transport to use with nodejs + * to simulate an XHR transport for test purposes + */ +class NodeCometTransport extends CometTransport { + httpAgent = null; + httpsAgent = null; + pendingRequests = 0; + shortName = shortName; + + static isAvailable() { return true; - }; - transportStorage.supportedTransports[shortName] = NodeCometTransport; + } - NodeCometTransport.prototype.toString = function () { + toString() { return ( 'NodeCometTransport; uri=' + this.baseUri + @@ -45,58 +40,61 @@ var NodeCometTransport = function (transportStorage) { '; stream=' + this.stream ); - }; + } - NodeCometTransport.prototype.getAgent = function (tls) { + getAgent(tls) { var prop = tls ? 'httpsAgent' : 'httpAgent', agent = this[prop]; if (!agent) agent = this[prop] = new (tls ? https : http).Agent({ keepAlive: true }); return agent; - }; + } - NodeCometTransport.prototype.dispose = function () { + dispose() { var self = this; this.onceNoPending(function () { if (self.httpAgent) self.httpAgent.destroy(); if (self.httpsAgent) self.httpsAgent.destroy(); }); CometTransport.prototype.dispose.call(this); - }; + } /* valid in non-streaming mode only, or data only contains last update */ - NodeCometTransport.prototype.request = function (uri, params, body, requestMode, callback) { + request(uri, params, body, requestMode, callback) { var req = this.createRequest(uri, params, body, requestMode); req.once('complete', callback); req.exec(); return req; - }; + } - NodeCometTransport.prototype.createRequest = function (uri, headers, params, body, requestMode) { + createRequest(uri, headers, params, body, requestMode) { return new Request(uri, headers, params, body, requestMode, this.format, this.timeouts, this); - }; + } - NodeCometTransport.prototype.addPending = function () { + addPending() { ++this.pendingRequests; - }; + } - NodeCometTransport.prototype.removePending = function () { + removePending() { if (--this.pendingRequests <= 0) { this.emit('nopending'); } - }; + } - NodeCometTransport.prototype.onceNoPending = function (listener) { + onceNoPending(listener) { if (this.pendingRequests == 0) { listener(); return; } this.once('nopending', listener); - }; + } +} + +class Request extends EventEmitter { + constructor(uri, headers, params, body, requestMode, format, timeouts, transport) { + super(); - function Request(uri, headers, params, body, requestMode, format, timeouts, transport) { - EventEmitter.call(this); if (typeof uri == 'string') uri = url.parse(uri); var tls = uri.protocol == 'https:'; this.client = tls ? https : http; @@ -131,9 +129,8 @@ var NodeCometTransport = function (transportStorage) { }); if (transport) requestOptions.agent = transport.getAgent(tls); } - Utils.inherits(Request, EventEmitter); - Request.prototype.exec = function () { + exec() { var timeout = this.requestMode == XHRStates.REQ_SEND ? this.timeouts.httpRequestTimeout : this.timeouts.recvTimeout, self = this; @@ -184,9 +181,9 @@ var NodeCometTransport = function (transportStorage) { if (this.transport) this.transport.addPending(); req.end(this.body); - }; + } - Request.prototype.readStream = function () { + readStream() { var res = this.res, self = this; @@ -236,9 +233,9 @@ var NodeCometTransport = function (transportStorage) { self.complete(); }); }); - }; + } - Request.prototype.readFully = function () { + readFully() { var res = this.res, chunks = [], self = this; @@ -281,9 +278,9 @@ var NodeCometTransport = function (transportStorage) { self.complete(err); }); }); - }; + } - Request.prototype.complete = function (err, body) { + complete(err, body) { if (!this.requestComplete) { this.requestComplete = true; if (body) this.emit('data', body); @@ -298,9 +295,9 @@ var NodeCometTransport = function (transportStorage) { this.transport.removePending(); } } - }; + } - Request.prototype.abort = function () { + abort() { Logger.logAction(Logger.LOG_MINOR, 'NodeCometTransport.Request.abort()', ''); var timer = this.timer; if (timer) { @@ -316,9 +313,13 @@ var NodeCometTransport = function (transportStorage) { this.req = null; } this.complete({ statusCode: 400, code: 80003, message: 'Cancelled' }); - }; + } +} + +var initialiseNodeCometTransport = function (transportStorage) { + transportStorage.supportedTransports[shortName] = NodeCometTransport; return NodeCometTransport; }; -export default NodeCometTransport; +export default initialiseNodeCometTransport; From 81f1bceb79c527827d67fa671d0d5f21743116d2 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Mon, 26 Feb 2024 14:31:46 +0000 Subject: [PATCH 398/468] Document continued support for ably-js v1 in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1c242aad7f..9046dac4d6 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ We regression-test the library against a selection of Node.js versions, which wi However, we aim to be compatible with a much wider set of platforms and browsers than we can possibly test on. That means we'll happily support (and investigate reported problems with) any reasonably-widely-used browser. So if you find any compatibility issues, please do [raise an issue](https://github.com/ably/ably-js/issues) in this repository or [contact Ably customer support](https://support.ably.com) for advice. +If you require support for older browsers and Node.js, you can use the security-maintained version 1 of the library. Install version 1 via [CDN link](https://cdn.ably.com/lib/ably.min-1.js), or from npm with `npm install ably@1 --save`. It supports IE versions 9 or newer, older versions of major browsers, and Node.js 8.17 or newer. Note that version 1 will only receive security updates and critical bug fixes, and won't include any new features. + For complete API documentation, see the [Ably documentation](https://www.ably.com/docs). ## Installation From cd9c61e9479139e852f18c98b9fa18cb490ba463 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 1 Mar 2024 22:46:40 +0000 Subject: [PATCH 399/468] Document fallback transport mechanisms in the context of network conditions --- README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1c242aad7f..e389738d7b 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ This SDK supports the following platforms: **Browsers:** All major desktop and mobile browsers, including (but not limited to) Chrome, Firefox, Edge, Safari on iOS and macOS, Opera, and Android browsers. IE is not supported. See compatibility table below for more information on minimum supported versions for major browsers: -| Browser | Minimum supported version | Release date | -| ------------- |:-------------------------:| -------------:| -| Chrome | 58 | Apr 19, 2017 | -| Firefox | 52 | Mar 7, 2017 | -| Edge | 79 | Dec 15, 2020 | -| Safari | 11 | Sep 19, 2017 | -| Opera | 45 | May 10, 2017 | +| Browser | Minimum supported version | Release date | +| ------- | :-----------------------: | -----------: | +| Chrome | 58 | Apr 19, 2017 | +| Firefox | 52 | Mar 7, 2017 | +| Edge | 79 | Dec 15, 2020 | +| Safari | 11 | Sep 19, 2017 | +| Opera | 45 | May 10, 2017 | **Webpack:** see [using Webpack in browsers](#using-webpack), or [our guide for serverside Webpack](#serverside-usage-with-webpack) @@ -364,6 +364,14 @@ function sendReaction(emoji) { See https://www.ably.com/docs/realtime/messages#message-interactions for more detail. +### Fallback transport mechanisms + +Ably-js has fallback transport mechanisms to ensure its realtime capabilities can function in network conditions (such as firewalls or proxies) that might prevent the client from establishing a WebSocket connection. + +The default `Ably.Realtime` client includes these mechanisms by default. If you are using modular variant of the library, you may wish to provide the `BaseRealtime` instance with one or more alternative transport modules, namely `XHRStreaming` and/or `XHRPolling`, alongside `WebSocketTransport`, so your connection is less susceptible to these external conditions. For instructions on how to do this, refer to the [modular variant of the library](#modular-tree-shakable-variant) section. + +Each of these fallback transport mechanisms is supported and tested on all the browsers we test against, even when those browsers do not themselves require those fallbacks. + ## Using the REST API This readme gives some basic examples. For our full API documentation, please go to https://www.ably.com/docs . From 60aec7d369b1f941f7ebb41af6afbdf093211a72 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 1 Mar 2024 13:24:26 +0000 Subject: [PATCH 400/468] feat: add `publish` to the `useChannel` hook Using publish method you can send messages to the derived channels (channels with filter qualifier, without attaching to the channel or using other workarounds) --- docs/react.md | 35 ++++++++----------- .../react-hooks/sample-app/src/App.tsx | 12 +++---- src/platform/react-hooks/src/AblyProvider.tsx | 11 ++++-- .../react-hooks/src/ChannelProvider.tsx | 20 ++++++----- src/platform/react-hooks/src/fakes/ably.ts | 6 +++- .../react-hooks/src/hooks/useChannel.test.tsx | 19 +++++++++- .../react-hooks/src/hooks/useChannel.ts | 14 ++++++-- .../src/hooks/useChannelInstance.ts | 10 +++--- .../src/hooks/useChannelStateListener.ts | 2 +- .../react-hooks/src/hooks/usePresence.ts | 2 +- 10 files changed, 81 insertions(+), 50 deletions(-) diff --git a/docs/react.md b/docs/react.md index 58ea659b91..1e27d91fad 100644 --- a/docs/react.md +++ b/docs/react.md @@ -8,7 +8,7 @@ Use Ably in your React application using idiomatic, easy to use, React Hooks! Using this module you can: - Interact with [Ably channels](https://ably.com/docs/channels) using a React Hook. -- [Publish messages](https://ably.com/docs/channels#publish) via Ably using the channel instances the hooks provide +- [Publish messages](https://ably.com/docs/channels#publish) via Ably using publish function the hooks provide - Get notifications of user [presence on channels](https://ably.com/docs/presence-occupancy/presence) - Send presence updates @@ -94,20 +94,27 @@ const messagePreviews = messages.map((msg, index) =>
  • {msg.data.s `useChannel` supports all of the parameter combinations of a regular call to `channel.subscribe`, so you can filter the messages you subscribe to by providing a `message type` to the `useChannel` function: ```javascript -const { channel } = useChannel("your-channel-name", "test-message", (message) => { +useChannel("your-channel-name", "test-message", (message) => { console.log(message); // Only logs messages sent using the `test-message` message type }); ``` -The `channel` instance returned by `useChannel` can be used to send messages to the channel. It's just a regular Ably JavaScript SDK `channel` instance. +#### useChannel `publish` function + +The `publish` function returned by `useChannel` can be used to send messages to the channel. ```javascript -channel.publish("test-message", { text: "message text" }); +const { publish } = useChannel("your-channel-name") +publish("test-message", { text: "message text" }); ``` -Because we're returning the channel instance, and Ably SDK instance from our `useChannel` hook, you can subsequently use these to perform any operations you like on the channel. +#### useChannel `channel` instance + +The `useChannel` hook returns an instance of the channel, which is part of the Ably JavaScript SDK. This allows you to access the standard Ably JavaScript SDK functionalities associated with channels. + +By providing both the channel instance and the Ably SDK instance through our useChannel hook, you gain the flexibility to execute various operations on the channel. -For example, you could retrieve history like this: +For instance, you can easily fetch the history of the channel using the following method: ```javascript const { channel } = useChannel("your-channel-name", (message) => { @@ -134,24 +141,12 @@ const { channel } = useChannel({ channelName: "your-channel-name", options: { pa ```javascript const deriveOptions = { filter: 'headers.email == `"rob.pike@domain.com"` || headers.company == `"domain"`' } -const { channel } = useChannel({ channelName: "your-derived-channel-name", options: { ... }, deriveOptions }, (message) => { +const { publish } = useChannel({ channelName: "your-derived-channel-name", options: { ... }, deriveOptions }, (message) => { ... }); ``` -Please note that attempts to publish to a derived channel (the one created or retrieved with a filter expression) will fail. In order to send messages to the channel called _"your-derived-channel-name"_ from the example above, you will need to create another channel instance without a filter expression. - -```javascript -const channelName = "your-derived-channel-name"; -const options = { ... }; -const deriveOptions = { filter: 'headers.email == `"rob.pike@domain.com"` || headers.company == `"domain"`' } -const callback = (message) => { ... }; - -const { channel: readOnlyChannelInstance } = useChannel({ channelName, options, deriveOptions }, callback); -const { channel: readWriteChannelInstance } = useChannel({ channelName, options }, callback); // NB! No 'deriveOptions' passed here - -readWriteChannelInstance.publish("test-message", { text: "message text" }); -``` +Please note that attempts to publish to a derived channel (the one created or retrieved with a filter expression) using channel instance will fail, since derived channels support only `subscribe` capability. Use `publish` function returned by `useChannel` hook instead. --- diff --git a/src/platform/react-hooks/sample-app/src/App.tsx b/src/platform/react-hooks/sample-app/src/App.tsx index e5fa70a40f..ac00f18b87 100644 --- a/src/platform/react-hooks/sample-app/src/App.tsx +++ b/src/platform/react-hooks/sample-app/src/App.tsx @@ -15,7 +15,7 @@ function App() { const [frontOficeOnlyMessages, updateFrontOfficeOnlyMessages] = useState([]); const [skip, setSkip] = useState(false); - const { channel, ably } = useChannel({ channelName: 'your-channel-name', skip }, (message) => { + const { publish, ably } = useChannel({ channelName: 'your-channel-name', skip }, (message) => { updateMessages((prev) => [...prev, message]); }); @@ -39,7 +39,7 @@ function App() { } ); - const { channel: anotherChannelPublisher } = useChannel({ + const { publish: anotherChannelPublish } = useChannel({ channelName: 'your-derived-channel-name', }); @@ -89,7 +89,7 @@ function App() {
    -
      {presentUsers}
    ); }; const UsePresenceComponentMultipleClients = () => { - const { presenceData: val1, updateStatus: update1 } = usePresence({ channelName: testChannelName }, 'foo'); + const { updateStatus: update1 } = usePresence({ channelName: testChannelName }, 'foo'); const { updateStatus: update2 } = usePresence({ channelName: testChannelName, ablyId: 'otherClient' }, 'bar'); - const presentUsers = val1.map((presence, index) => { - return ( -
  • - {presence.clientId} - {JSON.stringify(presence)} -
  • - ); - }); - return ( <> -
      {presentUsers}
    ); }; @@ -286,11 +266,19 @@ interface MyPresenceType { } const TypedUsePresenceComponent = () => { - const { presenceData } = usePresence('testChannelName', { - foo: 'bar', - }); + const { updateStatus } = usePresence(testChannelName, { foo: 'bar' }); - return
    {JSON.stringify(presenceData)}
    ; + return ( +
    + +
    + ); }; const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index 772ee5a112..a620df526f 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,77 +1,45 @@ import type * as Ably from 'ably'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect } from 'react'; import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; -import { useStateErrors } from './useStateErrors.js'; import { useChannelInstance } from './useChannelInstance.js'; +import { useStateErrors } from './useStateErrors.js'; -interface PresenceMessage extends Ably.PresenceMessage { - data: T; -} - -export interface PresenceResult { - presenceData: PresenceMessage[]; +export interface PresenceEnterResult { updateStatus: (messageOrPresenceObject: T) => void; connectionError: Ably.ErrorInfo | null; channelError: Ably.ErrorInfo | null; } -export type OnPresenceMessageReceived = (presenceData: PresenceMessage) => void; -export type UseStatePresenceUpdate = (presenceData: Ably.PresenceMessage[]) => void; - const INACTIVE_CONNECTION_STATES: Ably.ConnectionState[] = ['suspended', 'closing', 'closed', 'failed']; export function usePresence( channelNameOrNameAndOptions: ChannelParameters, messageOrPresenceObject?: T, - onPresenceUpdated?: OnPresenceMessageReceived, -): PresenceResult { +): PresenceEnterResult { const params = typeof channelNameOrNameAndOptions === 'object' ? channelNameOrNameAndOptions : { channelName: channelNameOrNameAndOptions }; + const skip = params.skip; const ably = useAbly(params.ablyId); const { channel } = useChannelInstance(params.ablyId, params.channelName); - - const subscribeOnly = typeof channelNameOrNameAndOptions === 'string' ? false : params.subscribeOnly; - - const skip = params.skip; - const { connectionError, channelError } = useStateErrors(params); - const [presenceData, updatePresenceData] = useState>>([]); - - const updatePresence = async (message?: Ably.PresenceMessage) => { - const snapshot = await channel.presence.get(); - updatePresenceData(snapshot); - - onPresenceUpdated?.call(this, message); - }; - const onMount = async () => { - channel.presence.subscribe(['enter', 'leave', 'update'], updatePresence); - - if (!subscribeOnly) { - await channel.presence.enter(messageOrPresenceObject); - } - - const snapshot = await channel.presence.get(); - updatePresenceData(snapshot); + await channel.presence.enter(messageOrPresenceObject); }; const onUnmount = () => { // if connection is in one of inactive states, leave call will produce exception if (channel.state === 'attached' && !INACTIVE_CONNECTION_STATES.includes(ably.connection.state)) { - if (!subscribeOnly) { - channel.presence.leave(); - } + channel.presence.leave(); } - channel.presence.unsubscribe(['enter', 'leave', 'update'], updatePresence); }; const useEffectHook = () => { - !skip && onMount(); + if (!skip) onMount(); return () => { onUnmount(); }; @@ -82,14 +50,10 @@ export function usePresence( const updateStatus = useCallback( (messageOrPresenceObject: T) => { - if (!subscribeOnly) { - channel.presence.update(messageOrPresenceObject); - } else { - throw new Error('updateStatus can not be called while using the hook in subscribeOnly mode'); - } + channel.presence.update(messageOrPresenceObject); }, - [subscribeOnly, channel], + [channel], ); - return { presenceData, updateStatus, connectionError, channelError }; + return { updateStatus, connectionError, channelError }; } diff --git a/src/platform/react-hooks/src/hooks/usePresenceListener.test.tsx b/src/platform/react-hooks/src/hooks/usePresenceListener.test.tsx new file mode 100644 index 0000000000..1b2c92bcf5 --- /dev/null +++ b/src/platform/react-hooks/src/hooks/usePresenceListener.test.tsx @@ -0,0 +1,318 @@ +import React from 'react'; +import type * as Ably from 'ably'; +import { it, beforeEach, describe, expect, vi } from 'vitest'; +import { usePresenceListener } from './usePresenceListener.js'; +import { render, screen, act, waitFor } from '@testing-library/react'; +import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; +import { AblyProvider } from '../AblyProvider.js'; +import { ChannelProvider } from '../ChannelProvider.js'; + +const testChannelName = 'testChannel'; + +function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { + return render( + + {children} + , + ); +} + +describe('usePresenceListener', () => { + let channels: FakeAblyChannels; + let ablyClient: FakeAblySdk; + let otherClient: FakeAblySdk; + + beforeEach(() => { + channels = new FakeAblyChannels([testChannelName]); + ablyClient = new FakeAblySdk().connectTo(channels); + otherClient = new FakeAblySdk().connectTo(channels); + }); + + it('presence data is not visible on first render as it runs in an effect', async () => { + // enter presence before rendering the component + ablyClient.channels.get(testChannelName).presence.enter('bar'); + + renderInCtxProvider(ablyClient, ); + + const values = screen.getByRole('presence').innerHTML; + expect(values).toBe(''); + + await act(async () => { + await wait(2); + // To let react run its updates so we don't see warnings in the test output + }); + }); + + it('presence data available after effect runs', async () => { + // enter presence before rendering the component + ablyClient.channels.get(testChannelName).presence.enter('bar'); + + renderInCtxProvider(ablyClient, ); + + await act(async () => { + await wait(2); + }); + + const values = screen.getByRole('presence').innerHTML; + expect(values).toContain(`"bar"`); + }); + + it('presence data in component updates when presence was updated', async () => { + ablyClient.channels.get(testChannelName).presence.enter('bar'); + + renderInCtxProvider(ablyClient, ); + + await act(async () => { + ablyClient.channels.get(testChannelName).presence.update('baz'); + }); + + const values = screen.getByRole('presence').innerHTML; + expect(values).toContain(`"baz"`); + }); + + it('presence data respects updates made by other clients', async () => { + ablyClient.channels.get(testChannelName).presence.enter('bar'); + + renderInCtxProvider(ablyClient, ); + + await act(async () => { + otherClient.channels.get(testChannelName).presence.enter('baz'); + }); + + const presenceElement = screen.getByRole('presence'); + const values = presenceElement.innerHTML; + expect(presenceElement.children.length).toBe(2); + expect(values).toContain(`"bar"`); + expect(values).toContain(`"baz"`); + }); + + it('presence API works with type information provided', async () => { + const data: MyPresenceType = { foo: 'bar' }; + ablyClient.channels.get(testChannelName).presence.enter(data); + + renderInCtxProvider(ablyClient, ); + + await act(async () => { + await wait(2); + }); + + const values = screen.getByRole('presence').innerHTML; + expect(values).toContain(`"data":${JSON.stringify(data)}`); + }); + + it('`skip` param prevents mounting and subscribing to presence events', async () => { + // can't really test 'leave' event, since if 'skip' works as expected then we won't have any data available to check that it's gone + ablyClient.channels.get(testChannelName).presence.enter('bar'); + ablyClient.channels.get(testChannelName).presence.update('baz'); + + renderInCtxProvider(ablyClient, ); + + await act(async () => { + await wait(2); + }); + + const values = screen.getByRole('presence').innerHTML; + expect(values).to.not.contain(`"bar"`); + expect(values).to.not.contain(`"baz"`); + }); + + it('usePresenceListener works with multiple clients', async () => { + ablyClient.channels.get(testChannelName).presence.enter('bar1'); + otherClient.channels.get(testChannelName).presence.enter('bar2'); + + renderInCtxProvider( + ablyClient, + + + + + , + ); + + await act(async () => { + ablyClient.channels.get(testChannelName).presence.update('baz1'); + otherClient.channels.get(testChannelName).presence.update('baz2'); + }); + + const values1 = screen.getByRole('presence1').innerHTML; + expect(values1).toContain(`"data":"baz1"`); + expect(values1).toContain(`"data":"baz2"`); + + const values2 = screen.getByRole('presence2').innerHTML; + expect(values2).toContain(`"data":"baz1"`); + expect(values2).toContain(`"data":"baz2"`); + }); + + it('handles channel errors', async () => { + const onChannelError = vi.fn(); + const reason = { message: 'foo' }; + + renderInCtxProvider( + ablyClient, + + + , + ); + + const channelErrorElem = screen.getByRole('channelError'); + expect(onChannelError).toHaveBeenCalledTimes(0); + expect(channelErrorElem.innerHTML).toEqual(''); + + await act(async () => { + ablyClient.channels.get('blah').emit('failed', { + reason, + }); + }); + + expect(channelErrorElem.innerHTML).toEqual(reason.message); + expect(onChannelError).toHaveBeenCalledTimes(1); + expect(onChannelError).toHaveBeenCalledWith(reason); + }); + + it('handles connection errors', async () => { + const onConnectionError = vi.fn(); + const reason = { message: 'foo' }; + + renderInCtxProvider( + ablyClient, + + + , + ); + + const connectionErrorElem = screen.getByRole('connectionError'); + expect(onConnectionError).toHaveBeenCalledTimes(0); + expect(connectionErrorElem.innerHTML).toEqual(''); + + await act(async () => { + ablyClient.connection.emit('failed', { + reason, + }); + }); + + expect(connectionErrorElem.innerHTML).toEqual(reason.message); + expect(onConnectionError).toHaveBeenCalledTimes(1); + expect(onConnectionError).toHaveBeenCalledWith(reason); + }); + + it('should not affect existing presence listeners when hook unmounts', async () => { + const enterListener = vi.fn(); + ablyClient.channels.get(testChannelName).presence.subscribe('enter', enterListener); + + // enter presence + ablyClient.channels.get(testChannelName).presence.enter('bar'); + + const { unmount } = renderInCtxProvider(ablyClient, ); + + // wait for 'usePresenceListener' to render + await act(async () => { + await wait(2); + }); + + // expect existing listener and component to have presence data + const values = screen.getByRole('presence').innerHTML; + expect(values).toContain(`"bar"`); + expect(enterListener).toHaveBeenCalledWith(expect.objectContaining({ data: 'bar' })); + + unmount(); + + // Wait for `usePresenceListener` to be fully unmounted and effect's clean-ups applied + await act(async () => { + await wait(2); + }); + + ablyClient.channels.get(testChannelName).presence.enter('baz'); + + // Check that listener still exists + await waitFor(() => { + expect(enterListener).toHaveBeenCalledWith(expect.objectContaining({ data: 'baz' })); + }); + }); +}); + +const UsePresenceListenerComponent = ({ skip }: { skip?: boolean }) => { + const { presenceData } = usePresenceListener({ channelName: testChannelName, skip }); + + const presentUsers = presenceData.map((presence, index) => { + return ( +
  • + {/* PresenceMessage type is not correctly resolved and is missing 'clientId' property due to fail to load 'ably' type declarations in this test file */} + {(presence as any).clientId} - {JSON.stringify(presence)} +
  • + ); + }); + + return ( + <> +
      {presentUsers}
    + + ); +}; + +const UsePresenceListenerComponentMultipleClients = () => { + const { presenceData: presenceData1 } = usePresenceListener({ channelName: testChannelName }); + const { presenceData: presenceData2 } = usePresenceListener({ channelName: testChannelName, ablyId: 'otherClient' }); + + const presentUsers1 = presenceData1.map((presence, index) => { + return ( +
  • + {/* PresenceMessage type is not correctly resolved and is missing 'clientId' property due to fail to load 'ably' type declarations in this test file */} + {(presence as any).clientId} - {JSON.stringify(presence)} +
  • + ); + }); + const presentUsers2 = presenceData2.map((presence, index) => { + return ( +
  • + {/* PresenceMessage type is not correctly resolved and is missing 'clientId' property due to fail to load 'ably' type declarations in this test file */} + {(presence as any).clientId} - {JSON.stringify(presence)} +
  • + ); + }); + + return ( + <> +
      {presentUsers1}
    +
      {presentUsers2}
    + + ); +}; + +interface UsePresenceListenerStateErrorsComponentProps { + onConnectionError?: (err: Ably.ErrorInfo) => unknown; + onChannelError?: (err: Ably.ErrorInfo) => unknown; +} + +const UsePresenceListenerStateErrorsComponent = ({ + onConnectionError, + onChannelError, +}: UsePresenceListenerStateErrorsComponentProps) => { + const { connectionError, channelError } = usePresenceListener({ + channelName: 'blah', + onConnectionError, + onChannelError, + }); + + return ( + <> +

    {connectionError?.message}

    +

    {channelError?.message}

    + + ); +}; + +interface MyPresenceType { + foo: string; +} + +const TypedUsePresenceListenerComponent = () => { + const { presenceData } = usePresenceListener(testChannelName); + + return
    {JSON.stringify(presenceData)}
    ; +}; + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/platform/react-hooks/src/hooks/usePresenceListener.ts b/src/platform/react-hooks/src/hooks/usePresenceListener.ts new file mode 100644 index 0000000000..0508ef063f --- /dev/null +++ b/src/platform/react-hooks/src/hooks/usePresenceListener.ts @@ -0,0 +1,61 @@ +import type * as Ably from 'ably'; +import { useEffect, useState } from 'react'; +import { ChannelParameters } from '../AblyReactHooks.js'; +import { useChannelInstance } from './useChannelInstance.js'; +import { useStateErrors } from './useStateErrors.js'; + +interface PresenceMessage extends Ably.PresenceMessage { + data: T; +} + +export interface PresenceListenerResult { + presenceData: PresenceMessage[]; + connectionError: Ably.ErrorInfo | null; + channelError: Ably.ErrorInfo | null; +} + +export type OnPresenceMessageReceived = (presenceData: PresenceMessage) => void; + +export function usePresenceListener( + channelNameOrNameAndOptions: ChannelParameters, + onPresenceMessageReceived?: OnPresenceMessageReceived, +): PresenceListenerResult { + const params = + typeof channelNameOrNameAndOptions === 'object' + ? channelNameOrNameAndOptions + : { channelName: channelNameOrNameAndOptions }; + const skip = params.skip; + + const { channel } = useChannelInstance(params.ablyId, params.channelName); + const { connectionError, channelError } = useStateErrors(params); + const [presenceData, updatePresenceData] = useState>>([]); + + const updatePresence = async (message?: Ably.PresenceMessage) => { + const snapshot = await channel.presence.get(); + updatePresenceData(snapshot); + + onPresenceMessageReceived?.call(this, message); + }; + + const onMount = async () => { + channel.presence.subscribe(['enter', 'leave', 'update'], updatePresence); + const snapshot = await channel.presence.get(); + updatePresenceData(snapshot); + }; + + const onUnmount = () => { + channel.presence.unsubscribe(['enter', 'leave', 'update'], updatePresence); + }; + + const useEffectHook = () => { + if (!skip) onMount(); + return () => { + onUnmount(); + }; + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(useEffectHook, [skip]); + + return { presenceData, connectionError, channelError }; +} diff --git a/src/platform/react-hooks/src/index.ts b/src/platform/react-hooks/src/index.ts index d73bdc6222..c064287468 100644 --- a/src/platform/react-hooks/src/index.ts +++ b/src/platform/react-hooks/src/index.ts @@ -1,6 +1,7 @@ export * from './AblyReactHooks.js'; export * from './hooks/useChannel.js'; export * from './hooks/usePresence.js'; +export * from './hooks/usePresenceListener.js'; export * from './hooks/useAbly.js'; export * from './AblyProvider.js'; export * from './hooks/useChannelStateListener.js'; From fe9d009b9570b8814ecaa565ccec12eae16baf78 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 8 Mar 2024 06:02:00 +0000 Subject: [PATCH 440/468] Update react hooks sample app to use new `usePresence*` hooks --- .../react-hooks/sample-app/src/App.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/platform/react-hooks/sample-app/src/App.tsx b/src/platform/react-hooks/sample-app/src/App.tsx index 11f38d8973..2d547f51b3 100644 --- a/src/platform/react-hooks/sample-app/src/App.tsx +++ b/src/platform/react-hooks/sample-app/src/App.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import { useChannel, usePresence, + usePresenceListener, useConnectionStateListener, useChannelStateListener, useAbly, @@ -15,7 +16,7 @@ function App() { const [frontOficeOnlyMessages, updateFrontOfficeOnlyMessages] = useState([]); const [skip, setSkip] = useState(false); - const { publish, ably } = useChannel({ channelName: 'your-channel-name', skip }, (message) => { + const { channel, publish, ably } = useChannel({ channelName: 'your-channel-name', skip }, (message) => { updateMessages((prev) => [...prev, message]); }); @@ -43,13 +44,10 @@ function App() { channelName: 'your-derived-channel-name', }); - const { presenceData, updateStatus } = usePresence( - { channelName: 'your-channel-name', skip }, - { foo: 'bar' }, - (update) => { - console.log(update); - }, - ); + const { updateStatus } = usePresence({ channelName: 'your-channel-name', skip }, { foo: 'bar' }); + const { presenceData } = usePresenceListener({ channelName: 'your-channel-name', skip }, (update) => { + console.log(update); + }); const [, setConnectionState] = useState(ably.connection.state); @@ -79,7 +77,8 @@ function App() { const presentClients = presenceData.map((msg, index) => (
  • - {msg.clientId}: {JSON.stringify(msg.data)} + {/* PresenceMessage type is not correctly resolved and is missing 'clientId' property due to fail to load 'ably' type declarations in this test file */} + {(msg as any).clientId}: {JSON.stringify(msg.data)}
  • )); @@ -101,7 +100,7 @@ function App() { updateStatus({ foo: 'baz' }); }} > - Update status to hello + Update presence status to baz ); } @@ -230,7 +226,58 @@ interface MyPresenceType { } ``` -`PresenceData` is a good way to store synchronised, per-client metadata, so types here are especially valuable. +--- + +### usePresenceListener + +The usePresenceListener hook [subscribes you to presence 'enter', 'update' and 'leave' events on a channel](https://ably.com/docs/presence-occupancy/presence?lang=javascript#subscribe) - this will allow you to get notified when a user joins or leaves the channel, or updates its presence data. To find out more about Presence, see the [Presence documentation](https://ably.com/docs/presence-occupancy/presence). + +**Please note** that fetching present members is executed as an effect, so it'll load in *after* your component renders for the first time. + +```javascript +const { presenceData } = usePresenceListener("your-channel-name"); + +// Convert presence data to the list of items to render +const membersData = presenceData.map((msg, index) =>
  • {msg.clientId}: {msg.data}
  • ); +``` + +The `usePresenceListener` hook returns an array of presence messages - each message is a regular Ably JavaScript SDK `PresenceMessage` instance. + +If you don't want to use the `presenceData` returned from `usePresenceListener`, you can configure a callback: + +```javascript +usePresenceListener("your-channel-name", (presenceUpdate) => { + console.log(presenceUpdate); +}); +``` + +If you're using `TypeScript` there are type hints to make sure that presence data received are of the same `type` as a provided generic type parameter: + +```tsx +const TypedUsePresenceListenerComponent = () => { + // In this example MyPresenceType will be used for presenceData type hints. + // If that's omitted, `any` will be the default. + + const { presenceData } = usePresenceListener("testChannelName"); + + const membersData = presenceData.map((presenceMsg, index) => { + return ( +
  • + {/* you will have Intellisense for presenceMsg.data of type MyPresenceType here */} + {presenceMsg.clientId} - {presenceMsg.data.foo} +
  • + ); + }); + + return
      {membersData}
    ; +} + +interface MyPresenceType { + foo: string; +} +``` + +`presenceData` is a good way to store synchronised, per-client metadata, so types here are especially valuable. ### useConnectionStateListener @@ -284,7 +331,7 @@ client.authorize(); When using the Ably react hooks, your Ably client may encounter a variety of errors, for example if it doesn't have permissions to attach to a channel it may encounter a channel error, or if it loses connection from the Ably network it may encounter a connection error. -To allow you to handle these errors, the `useChannel` and `usePresence` hooks return connection and channel errors so that you can react to them in your components: +To allow you to handle these errors, the `useChannel`, `usePresence` and `usePresenceListener` hooks return connection and channel errors so that you can react to them in your components: ```jsx const { connectionError, channelError } = useChannel('my_channel', messageHandler); @@ -333,9 +380,11 @@ useChannel({ channelName: "your-channel-name", ablyId }, (message) => { console.log(message); }); -usePresence({ channelName: "your-channel-name", ablyId }, (presenceUpdate) => { - ... -}) +usePresence({ channelName: "your-channel-name", ablyId }, "initial state"); + +usePresenceListener({ channelName: "your-channel-name", ablyId }, (presenceUpdate) => { + // ... +}); ``` ## NextJS warnings From c56b665eca38963c450b071f2a1b9e9cbb4dfcde Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 12 Mar 2024 20:43:57 +0000 Subject: [PATCH 442/468] Add tests for `usePresenceListener` react hook for `onPresenceMessageReceived` callback --- .../src/hooks/usePresenceListener.test.tsx | 86 ++++++++++++++++++- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/src/platform/react-hooks/src/hooks/usePresenceListener.test.tsx b/src/platform/react-hooks/src/hooks/usePresenceListener.test.tsx index 1b2c92bcf5..67f04f0c20 100644 --- a/src/platform/react-hooks/src/hooks/usePresenceListener.test.tsx +++ b/src/platform/react-hooks/src/hooks/usePresenceListener.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type * as Ably from 'ably'; import { it, beforeEach, describe, expect, vi } from 'vitest'; -import { usePresenceListener } from './usePresenceListener.js'; +import { OnPresenceMessageReceived, usePresenceListener } from './usePresenceListener.js'; import { render, screen, act, waitFor } from '@testing-library/react'; import { FakeAblySdk, FakeAblyChannels } from '../fakes/ably.js'; import { AblyProvider } from '../AblyProvider.js'; @@ -10,11 +10,22 @@ import { ChannelProvider } from '../ChannelProvider.js'; const testChannelName = 'testChannel'; function renderInCtxProvider(client: FakeAblySdk, children: React.ReactNode | React.ReactNode[]) { - return render( + const renderResult = render( {children} , ); + + const originalRerender = renderResult.rerender; + renderResult.rerender = (children: React.ReactNode | React.ReactNode[]) => { + return originalRerender( + + {children} + , + ); + }; + + return renderResult; } describe('usePresenceListener', () => { @@ -143,6 +154,67 @@ describe('usePresenceListener', () => { expect(values2).toContain(`"data":"baz2"`); }); + it('calls onPresenceMessageReceived callback on new messages', async () => { + const onPresenceMessageReceived = vi.fn(); + ablyClient.channels.get(testChannelName).presence.enter('bar'); + + renderInCtxProvider( + ablyClient, + , + ); + + await act(async () => { + await wait(2); + }); + + // should not have been called for already existing presence state + expect(onPresenceMessageReceived).toHaveBeenCalledTimes(0); + + await act(async () => { + ablyClient.channels.get(testChannelName).presence.update('baz'); + }); + + expect(onPresenceMessageReceived).toHaveBeenCalledTimes(1); + expect(onPresenceMessageReceived).toHaveBeenCalledWith(expect.objectContaining({ data: 'baz' })); + }); + + it('reacts to onPresenceMessageReceived callback changes', async () => { + let onPresenceMessageReceived = vi.fn(); + ablyClient.channels.get(testChannelName).presence.enter('foo'); + + const { rerender } = renderInCtxProvider( + ablyClient, + , + ); + + await act(async () => { + ablyClient.channels.get(testChannelName).presence.update('bar'); + }); + + expect(onPresenceMessageReceived).toHaveBeenCalledTimes(1); + expect(onPresenceMessageReceived).toHaveBeenCalledWith(expect.objectContaining({ data: 'bar' })); + + // change callback function and rerender + onPresenceMessageReceived = vi.fn(); + rerender( + , + ); + + await act(async () => { + ablyClient.channels.get(testChannelName).presence.update('baz'); + }); + + // new callback should be called once + expect(onPresenceMessageReceived).toHaveBeenCalledTimes(1); + expect(onPresenceMessageReceived).toHaveBeenCalledWith(expect.objectContaining({ data: 'baz' })); + }); + it('handles channel errors', async () => { const onChannelError = vi.fn(); const reason = { message: 'foo' }; @@ -234,8 +306,14 @@ describe('usePresenceListener', () => { }); }); -const UsePresenceListenerComponent = ({ skip }: { skip?: boolean }) => { - const { presenceData } = usePresenceListener({ channelName: testChannelName, skip }); +const UsePresenceListenerComponent = ({ + skip, + onPresenceMessageReceived, +}: { + skip?: boolean; + onPresenceMessageReceived?: OnPresenceMessageReceived; +}) => { + const { presenceData } = usePresenceListener({ channelName: testChannelName, skip }, onPresenceMessageReceived); const presentUsers = presenceData.map((presence, index) => { return ( From 45391c5bbe47ead1172c9cb1a9ab67151af53ee6 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 12 Mar 2024 20:46:36 +0000 Subject: [PATCH 443/468] Fix `usePresenceListener` hook did not react to `onPresenceMessageReceived` changes --- .../src/hooks/usePresenceListener.ts | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/platform/react-hooks/src/hooks/usePresenceListener.ts b/src/platform/react-hooks/src/hooks/usePresenceListener.ts index 0508ef063f..656bcf3339 100644 --- a/src/platform/react-hooks/src/hooks/usePresenceListener.ts +++ b/src/platform/react-hooks/src/hooks/usePresenceListener.ts @@ -1,5 +1,5 @@ import type * as Ably from 'ably'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { ChannelParameters } from '../AblyReactHooks.js'; import { useChannelInstance } from './useChannelInstance.js'; import { useStateErrors } from './useStateErrors.js'; @@ -30,32 +30,39 @@ export function usePresenceListener( const { connectionError, channelError } = useStateErrors(params); const [presenceData, updatePresenceData] = useState>>([]); - const updatePresence = async (message?: Ably.PresenceMessage) => { - const snapshot = await channel.presence.get(); - updatePresenceData(snapshot); + const onPresenceMessageReceivedRef = useRef(onPresenceMessageReceived); + useEffect(() => { + onPresenceMessageReceivedRef.current = onPresenceMessageReceived; + }, [onPresenceMessageReceived]); + + const updatePresence = useCallback( + async (message?: Ably.PresenceMessage) => { + const snapshot = await channel.presence.get(); + updatePresenceData(snapshot); - onPresenceMessageReceived?.call(this, message); - }; + onPresenceMessageReceivedRef.current?.(message); + }, + [channel.presence], + ); - const onMount = async () => { + const onMount = useCallback(async () => { channel.presence.subscribe(['enter', 'leave', 'update'], updatePresence); const snapshot = await channel.presence.get(); updatePresenceData(snapshot); - }; + }, [channel.presence, updatePresence]); - const onUnmount = () => { + const onUnmount = useCallback(async () => { channel.presence.unsubscribe(['enter', 'leave', 'update'], updatePresence); - }; + }, [channel.presence, updatePresence]); - const useEffectHook = () => { - if (!skip) onMount(); + useEffect(() => { + if (skip) return; + + onMount(); return () => { onUnmount(); }; - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(useEffectHook, [skip]); + }, [skip, onMount, onUnmount]); return { presenceData, connectionError, channelError }; } From 2454a287ceebcad3008b3024509b26c1c1804657 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 12 Mar 2024 21:00:10 +0000 Subject: [PATCH 444/468] Refactor onMount/onUnmount functions in `usePresence` hook --- .../react-hooks/src/hooks/usePresence.ts | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index a620df526f..ea0028bd32 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -1,5 +1,5 @@ import type * as Ably from 'ably'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { ChannelParameters } from '../AblyReactHooks.js'; import { useAbly } from './useAbly.js'; import { useChannelInstance } from './useChannelInstance.js'; @@ -26,27 +26,37 @@ export function usePresence( const ably = useAbly(params.ablyId); const { channel } = useChannelInstance(params.ablyId, params.channelName); const { connectionError, channelError } = useStateErrors(params); + // we can't simply add messageOrPresenceObject to dependency list in our useCallback/useEffect hooks, + // since it will most likely cause an infinite loop of updates in cases when user calls this hook + // with an object literal instead of a state or memoized object. + // to prevent this from happening we store messageOrPresenceObject in a ref, and use that instead. + // note that it still prevents us from automatically re-entering presence with new messageOrPresenceObject if it changes. + // one of the options to fix this, is to use deep equals to check if the object has actually changed. see https://github.com/ably/ably-js/issues/1688. + const messageOrPresenceObjectRef = useRef(messageOrPresenceObject); - const onMount = async () => { - await channel.presence.enter(messageOrPresenceObject); - }; + useEffect(() => { + messageOrPresenceObjectRef.current = messageOrPresenceObject; + }, [messageOrPresenceObject]); - const onUnmount = () => { + const onMount = useCallback(async () => { + await channel.presence.enter(messageOrPresenceObjectRef.current); + }, [channel.presence]); + + const onUnmount = useCallback(() => { // if connection is in one of inactive states, leave call will produce exception if (channel.state === 'attached' && !INACTIVE_CONNECTION_STATES.includes(ably.connection.state)) { channel.presence.leave(); } - }; + }, [channel, ably.connection.state]); + + useEffect(() => { + if (skip) return; - const useEffectHook = () => { - if (!skip) onMount(); + onMount(); return () => { onUnmount(); }; - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(useEffectHook, [skip]); + }, [skip, onMount, onUnmount]); const updateStatus = useCallback( (messageOrPresenceObject: T) => { From 735f27bdf133835898b74b1429b159ebfd575321 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 8 Mar 2024 08:27:56 +0000 Subject: [PATCH 445/468] Add documentation for `skip` parameter to React docs Resolves #1673 --- docs/react.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/docs/react.md b/docs/react.md index 0dbc4d2f12..a1fde416fe 100644 --- a/docs/react.md +++ b/docs/react.md @@ -387,6 +387,100 @@ usePresenceListener({ channelName: "your-channel-name", ablyId }, (presenceUpdat }); ``` +### Skip attaching to a channel using `skip` parameter + +By default, `usePresenceEnter` and `usePresenceListener` automatically attach to a channel upon component mount, and `useChannel` does so if a callback for receiving messages is provided. This means that these hooks will attempt to establish a connection to the Ably server using the credentials currently set in the corresponding `Ably.RealtimeClient` from `AblyProvider`. However, there may be scenarios where your user authentication is asynchronous or when certain parts of your application are conditionally accessible (e.g., premium features). In these instances, you might not have a valid auth token yet, and an attempt to attach to a channel would result in an error. + +To address this, the `skip` parameter allows you to control the mounting behavior of the `useChannel`, `usePresenceEnter`, and `usePresenceListener` hooks, specifically determining whether they should attach to a channel upon the component's mount. + +```tsx +const [skip, setSkip] = useState(true); + +const { channel, publish } = useChannel({ channelName: "your-channel-name", skip }, (message) => { + updateMessages((prev) => [...prev, message]); +}); +``` + +By setting the `skip` parameter, you can prevent the hooks from attempting to attach to a channel, thereby avoiding errors and avoiding unnecessary messages being send (which reduces your messages consumption in your Ably app). + +The `skip` parameter accepts a boolean value. When set to `true`, it instructs the hooks not to attach to the channel upon the component's mount. This behavior is dynamically responsive; meaning that, once the conditions change (e.g., the user gets authenticated), updating the `skip` parameter to `false` will trigger the hooks to attach to the channel. + +This parameter is useful in next situations: + +**Asynchronous Authentication**: Users are not immediately authorized upon loading the component, and acquiring a valid auth token is asynchronous. + +Consider a scenario where a component uses the `useChannel` hook, but the user's authentication status is determined asynchronously: + +```tsx +import React, { useEffect, useState } from "react"; +import { useChannel } from "ably/react"; +import * as Ably from "ably"; + +const ChatComponent = () => { + const [isUserAuthenticated, setIsUserAuthenticated] = useState(false); + const [messages, updateMessages] = useState([]); + + // Simulate asynchronous authentication + useEffect(() => { + async function authenticate() { + const isAuthenticated = await someAuthenticationFunction(); + setIsUserAuthenticated(isAuthenticated); + } + authenticate(); + }, []); + + // useChannel with skip parameter + useChannel({ channelName: "your-channel-name", skip: !isUserAuthenticated }, (message) => { + updateMessages((prev) => [...prev, message]); + }); + + if (!isUserAuthenticated) { + return

    Please log in to join the chat.

    ; + } + + return ( +
    + {messages.map((message, index) => ( +

    {message.data.text}

    + ))} +
    + ); +}; +``` + +**Conditional Feature Access**: Certain features or parts of your application are behind a paywall or require specific user privileges that not all users possess. + +In an application with both free and premium features, you might want to conditionally use channels based on the user's subscription status: + +```tsx +import React from "react"; +import { useChannel } from "ably/react"; +import * as Ably from "ably"; + +interface PremiumFeatureComponentProps { + isPremiumUser: boolean; +} + +const PremiumFeatureComponent = ({ isPremiumUser }: PremiumFeatureComponentProps) => { + const [messages, updateMessages] = useState([]); + + // Skip attaching to the channel if the user is not a premium subscriber + useChannel({ channelName: "premium-feature-channel", skip: !isPremiumUser }, (message) => { + updateMessages((prev) => [...prev, message]); + }); + + if (!isPremiumUser) { + return

    This feature is available for premium users only.

    ; + } + + return ( +
    + {/* Render premium feature based on messages */} +
    + ); +}; +``` + ## NextJS warnings Currently, when using our react library with NextJS you may encounter some warnings which arise due to some static checks against subdependencies of the library. From c32f16b5dd9f58871f48acbcab8279eade6f8cc4 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 15 Mar 2024 09:54:39 +0000 Subject: [PATCH 446/468] Change `PresenceEnterResult` to `PresenceResult` in react hooks This was wrongfully renamed in 730c621d6de3f76be727c18e528071937936a967. It is a remnant of a `usePresenceEnter` name for the new hook, which was temporary used during development in https://github.com/ably/ably-js/pull/1674, but we decided to keep the name `usePresence`. --- src/platform/react-hooks/src/hooks/usePresence.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/react-hooks/src/hooks/usePresence.ts b/src/platform/react-hooks/src/hooks/usePresence.ts index ea0028bd32..a22d808ba7 100644 --- a/src/platform/react-hooks/src/hooks/usePresence.ts +++ b/src/platform/react-hooks/src/hooks/usePresence.ts @@ -5,7 +5,7 @@ import { useAbly } from './useAbly.js'; import { useChannelInstance } from './useChannelInstance.js'; import { useStateErrors } from './useStateErrors.js'; -export interface PresenceEnterResult { +export interface PresenceResult { updateStatus: (messageOrPresenceObject: T) => void; connectionError: Ably.ErrorInfo | null; channelError: Ably.ErrorInfo | null; @@ -16,7 +16,7 @@ const INACTIVE_CONNECTION_STATES: Ably.ConnectionState[] = ['suspended', 'closin export function usePresence( channelNameOrNameAndOptions: ChannelParameters, messageOrPresenceObject?: T, -): PresenceEnterResult { +): PresenceResult { const params = typeof channelNameOrNameAndOptions === 'object' ? channelNameOrNameAndOptions From 5a7dd974efb3498c42cdd2696fb4e3bec4c21e60 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 15 Mar 2024 09:09:53 +0000 Subject: [PATCH 447/468] Fix `ably/modular` can't be imported in node environment Resolves #1546 --- .github/workflows/check.yml | 5 +++-- Gruntfile.js | 2 +- package.json | 2 +- scripts/moduleReport.ts | 2 +- test/browser/modular.test.js | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index c1120b6db8..90d1e81ed6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,6 +25,7 @@ jobs: # for some reason, this doesn't work in CI using `npx attw --pack .` - run: npm pack - run: npx attw ably-$(node -e "console.log(require('./package.json').version)").tgz --summary --exclude-entrypoints 'ably/modular' - # TODO understand this unexpected-module-syntax error (https://github.com/ably/ably-js/issues/1546) - - run: npx attw ably-$(node -e "console.log(require('./package.json').version)").tgz --summary --entrypoints 'ably/modular' --ignore-rules unexpected-module-syntax + # see https://github.com/ably/ably-js/issues/1546 for why we ignore 'false-cjs' currently. + # should remove when switched to auto-generated type declaration files for modular variant of the library. + - run: npx attw ably-$(node -e "console.log(require('./package.json').version)").tgz --summary --entrypoints 'ably/modular' --ignore-rules false-cjs - run: npm audit --production diff --git a/Gruntfile.js b/Gruntfile.js index 5b118f1afd..2c5b45099d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -123,7 +123,7 @@ module.exports = function (grunt) { // which we don’t want here. ...createBaseConfig(), entryPoints: ['src/platform/web/modular.ts'], - outfile: 'build/modular/index.js', + outfile: 'build/modular/index.mjs', format: 'esm', plugins: [stripLogsPlugin], }; diff --git a/package.json b/package.json index d57cdbf94b..2dd77fe2af 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "./modular": { "types": "./modular.d.ts", - "default": "./build/modular/index.js" + "import": "./build/modular/index.mjs" }, "./react": { "require": "./react/cjs/index.js", diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 2d72004951..384835d4cb 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -70,7 +70,7 @@ function getBundleInfo(exports: string[]): BundleInfo { const outfile = exports.join(''); const result = esbuild.buildSync({ stdin: { - contents: `export { ${exports.join(', ')} } from './build/modular'`, + contents: `export { ${exports.join(', ')} } from './build/modular/index.mjs'`, resolveDir: '.', }, metafile: true, diff --git a/test/browser/modular.test.js b/test/browser/modular.test.js index 594b14671d..3d8b176989 100644 --- a/test/browser/modular.test.js +++ b/test/browser/modular.test.js @@ -20,7 +20,7 @@ import { FetchRequest, XHRRequest, MessageInteractions, -} from '../../build/modular/index.js'; +} from '../../build/modular/index.mjs'; function registerAblyModularTests(helper) { describe('browser/modular', function () { From 606fd927074809c6e5992179448b937ae140b233 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 18 Mar 2024 17:27:52 -0300 Subject: [PATCH 448/468] Fix stray second arg to modular constructor Missed this in e8c3012. --- test/browser/modular.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/browser/modular.test.js b/test/browser/modular.test.js index 594b14671d..a7bcb9e033 100644 --- a/test/browser/modular.test.js +++ b/test/browser/modular.test.js @@ -46,7 +46,7 @@ function registerAblyModularTests(helper) { for (const clientClass of [BaseRest, BaseRealtime]) { describe(clientClass.name, () => { it('throws an error due to the absence of an HTTP plugin', () => { - expect(() => new clientClass(ablyClientOptions(), {})).to.throw( + expect(() => new clientClass(ablyClientOptions())).to.throw( 'No HTTP request plugin provided. Provide at least one of the FetchRequest or XHRRequest plugins.', ); }); From 673aa5b3319c130a43701110844f983f1a8edfe9 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Mon, 18 Mar 2024 18:01:35 -0300 Subject: [PATCH 449/468] Improve errors for incorrect constructor args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the regression that I introduced in e8c3012, which stopped the user from getting a useful error message when not passing any args to the constructor. I’ve also introduced a wider range of error messages to give the user useful guidance depending on which variant of the library they’re using. Resolves #1700. --- src/common/lib/client/baseclient.ts | 6 ---- src/common/lib/client/baserealtime.ts | 12 +++++-- src/common/lib/client/baserest.ts | 12 +++++-- src/common/lib/client/defaultrealtime.ts | 5 +-- src/common/lib/client/defaultrest.ts | 5 +-- src/common/lib/util/defaults.ts | 44 +++++++++++++++++++++--- test/browser/modular.test.js | 34 ++++++++++++++++++ test/realtime/api.test.js | 6 ++++ test/rest/api.test.js | 6 ++++ 9 files changed, 112 insertions(+), 18 deletions(-) diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 5cfed84b70..f4351902a2 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -48,12 +48,6 @@ class BaseClient { constructor(options: ClientOptions) { this._additionalHTTPRequestImplementations = options.plugins ?? null; - if (!options) { - const msg = 'no options provided'; - Logger.logAction(Logger.LOG_ERROR, 'BaseClient()', msg); - throw new Error(msg); - } - Logger.setLog(options.logLevel, options.logHandler); Logger.logAction( Logger.LOG_MICRO, diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 359ad7bc6f..39fee93e21 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -24,8 +24,16 @@ class BaseRealtime extends BaseClient { _channels: any; connection: Connection; - constructor(options: ClientOptions | string) { - super(Defaults.objectifyOptions(options)); + /* + * The public typings declare that this only accepts an object, but since we want to emit a good error message in the case where a non-TypeScript user does one of these things: + * + * 1. passes a string (which is quite likely if they’re e.g. migrating from the default variant to the modular variant) + * 2. passes no argument at all + * + * tell the compiler that these cases are possible so that it forces us to handle them. + */ + constructor(options?: ClientOptions | string) { + super(Defaults.objectifyOptions(options, false, 'BaseRealtime')); Logger.logAction(Logger.LOG_MINOR, 'Realtime()', ''); this._additionalTransportImplementations = BaseRealtime.transportImplementationsFromPlugins(this.options.plugins); this._RealtimePresence = this.options.plugins?.RealtimePresence ?? null; diff --git a/src/common/lib/client/baserest.ts b/src/common/lib/client/baserest.ts index 5c823755eb..46137a08cb 100644 --- a/src/common/lib/client/baserest.ts +++ b/src/common/lib/client/baserest.ts @@ -9,7 +9,15 @@ import Defaults from '../util/defaults'; It always includes the `Rest` plugin. */ export class BaseRest extends BaseClient { - constructor(options: ClientOptions | string) { - super(Defaults.objectifyOptions(options, { Rest })); + /* + * The public typings declare that this only accepts an object, but since we want to emit a good error message in the case where a non-TypeScript user does one of these things: + * + * 1. passes a string (which is quite likely if they’re e.g. migrating from the default variant to the modular variant) + * 2. passes no argument at all + * + * tell the compiler that these cases are possible so that it forces us to handle them. + */ + constructor(options?: ClientOptions | string) { + super(Defaults.objectifyOptions(options, false, 'BaseRest', { Rest })); } } diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index d1fa3f4ce2..a966a69d32 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -22,14 +22,15 @@ import Defaults from '../util/defaults'; `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. */ export class DefaultRealtime extends BaseRealtime { - constructor(options: ClientOptions | string) { + // The public typings declare that this requires an argument to be passed, but since we want to emit a good error message in the case where a non-TypeScript user does not pass an argument, tell the compiler that this is possible so that it forces us to handle it. + constructor(options?: ClientOptions | string) { const MsgPack = DefaultRealtime._MsgPack; if (!MsgPack) { throw new Error('Expected DefaultRealtime._MsgPack to have been set'); } super( - Defaults.objectifyOptions(options, { + Defaults.objectifyOptions(options, true, 'Realtime', { ...allCommonModularPlugins, Crypto: DefaultRealtime.Crypto ?? undefined, MsgPack, diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index b6359d9bb8..110aef6fa3 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -12,14 +12,15 @@ import Defaults from '../util/defaults'; `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. */ export class DefaultRest extends BaseRest { - constructor(options: ClientOptions | string) { + // The public typings declare that this requires an argument to be passed, but since we want to emit a good error message in the case where a non-TypeScript user does not pass an argument, tell the compiler that this is possible so that it forces us to handle it. + constructor(options?: ClientOptions | string) { const MsgPack = DefaultRest._MsgPack; if (!MsgPack) { throw new Error('Expected DefaultRest._MsgPack to have been set'); } super( - Defaults.objectifyOptions(options, { + Defaults.objectifyOptions(options, true, 'Rest', { ...allCommonModularPlugins, Crypto: DefaultRest.Crypto ?? undefined, MsgPack: DefaultRest._MsgPack ?? undefined, diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index cde1668181..48920ca6cf 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -44,7 +44,12 @@ type CompleteDefaults = IDefaults & { getHosts(options: NormalisedClientOptions): string[]; checkHost(host: string): void; getRealtimeHost(options: ClientOptions, production: boolean, environment: string): string; - objectifyOptions(options: ClientOptions | string, modularPluginsToInclude?: ModularPlugins): ClientOptions; + objectifyOptions( + options: undefined | ClientOptions | string, + allowKeyOrToken: boolean, + sourceForErrorMessage: string, + modularPluginsToInclude?: ModularPlugins, + ): ClientOptions; normaliseOptions(options: ClientOptions, MsgPack: MsgPack | null): NormalisedClientOptions; defaultGetHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record; defaultPostHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record; @@ -183,11 +188,42 @@ export function getAgentString(options: ClientOptions): string { } export function objectifyOptions( - options: ClientOptions | string, + options: undefined | ClientOptions | string, + allowKeyOrToken: boolean, + sourceForErrorMessage: string, modularPluginsToInclude?: ModularPlugins, ): ClientOptions { - let optionsObj = - typeof options === 'string' ? (options.indexOf(':') == -1 ? { token: options } : { key: options }) : options; + if (options === undefined) { + const msg = allowKeyOrToken + ? `${sourceForErrorMessage} must be initialized with either a client options object, an Ably API key, or an Ably Token` + : `${sourceForErrorMessage} must be initialized with a client options object`; + Logger.logAction(Logger.LOG_ERROR, `${sourceForErrorMessage}()`, msg); + throw new Error(msg); + } + + let optionsObj: ClientOptions; + + if (typeof options === 'string') { + if (options.indexOf(':') == -1) { + if (!allowKeyOrToken) { + const msg = `${sourceForErrorMessage} cannot be initialized with just an Ably Token; you must provide a client options object with a \`plugins\` property. (Set this Ably Token as the object’s \`token\` property.)`; + Logger.logAction(Logger.LOG_ERROR, `${sourceForErrorMessage}()`, msg); + throw new Error(msg); + } + + optionsObj = { token: options }; + } else { + if (!allowKeyOrToken) { + const msg = `${sourceForErrorMessage} cannot be initialized with just an Ably API key; you must provide a client options object with a \`plugins\` property. (Set this Ably API key as the object’s \`key\` property.)`; + Logger.logAction(Logger.LOG_ERROR, `${sourceForErrorMessage}()`, msg); + throw new Error(msg); + } + + optionsObj = { key: options }; + } + } else { + optionsObj = options; + } if (modularPluginsToInclude) { optionsObj = { ...optionsObj, plugins: { ...modularPluginsToInclude, ...optionsObj.plugins } }; diff --git a/test/browser/modular.test.js b/test/browser/modular.test.js index 594b14671d..2de10279b2 100644 --- a/test/browser/modular.test.js +++ b/test/browser/modular.test.js @@ -42,6 +42,40 @@ function registerAblyModularTests(helper) { helper.setupApp(done); }); + describe('attempting to initialize with no client options', () => { + for (const clientClass of [BaseRest, BaseRealtime]) { + describe(clientClass.name, () => { + it('throws an error', () => { + expect(() => new clientClass()).to.throw('must be initialized with a client options object'); + }); + }); + } + }); + + describe('attempting to initialize with just an API key', () => { + for (const clientClass of [BaseRest, BaseRealtime]) { + describe(clientClass.name, () => { + it('throws an error', () => { + expect(() => new clientClass('foo:bar')).to.throw( + 'cannot be initialized with just an Ably API key; you must provide a client options object with a `plugins` property', + ); + }); + }); + } + }); + + describe('attempting to initialize with just a token', () => { + for (const clientClass of [BaseRest, BaseRealtime]) { + describe(clientClass.name, () => { + it('throws an error', () => { + expect(() => new clientClass('foo')).to.throw( + 'cannot be initialized with just an Ably Token; you must provide a client options object with a `plugins` property', + ); + }); + }); + } + }); + describe('without any plugins', () => { for (const clientClass of [BaseRest, BaseRealtime]) { describe(clientClass.name, () => { diff --git a/test/realtime/api.test.js b/test/realtime/api.test.js index 951c0d0669..ab5e3695e4 100644 --- a/test/realtime/api.test.js +++ b/test/realtime/api.test.js @@ -8,6 +8,12 @@ define(['ably', 'chai'], function (Ably, chai) { expect(typeof Ably.Realtime).to.equal('function'); }); + it('constructor without any arguments', function () { + expect(() => new Ably.Realtime()).to.throw( + 'must be initialized with either a client options object, an Ably API key, or an Ably Token', + ); + }); + it('Crypto', function () { expect(typeof Ably.Realtime.Crypto).to.equal('function'); expect(typeof Ably.Realtime.Crypto.getDefaultParams).to.equal('function'); diff --git a/test/rest/api.test.js b/test/rest/api.test.js index fcc19aa3df..5bd351b4b0 100644 --- a/test/rest/api.test.js +++ b/test/rest/api.test.js @@ -8,6 +8,12 @@ define(['ably', 'chai'], function (Ably, chai) { expect(typeof Ably.Rest).to.equal('function'); }); + it('constructor without any arguments', function () { + expect(() => new Ably.Rest()).to.throw( + 'must be initialized with either a client options object, an Ably API key, or an Ably Token', + ); + }); + it('Crypto', function () { expect(typeof Ably.Rest.Crypto).to.equal('function'); expect(typeof Ably.Rest.Crypto.getDefaultParams).to.equal('function'); From 36946a4d75357afdc91090c8c16e52c3a9344d84 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 27 Feb 2024 17:33:10 +0000 Subject: [PATCH 450/468] refactor: use TransportName type for `Transport.name` --- src/common/lib/transport/transport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 858b7967b4..0c9cd31f20 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -12,7 +12,7 @@ import Auth from '../client/auth'; import * as API from '../../../../ably'; import ConnectionManager, { TransportParams } from './connectionmanager'; import Platform from 'common/platform'; -import TransportName from '../../constants/TransportName'; +import TransportName from 'common/constants/TransportName'; export type TryConnectCallback = ( wrappedErr: { error: ErrorInfo; event: string } | null, From bee9f1c0b62812af3eb466b800969c0a1c3db86e Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 27 Feb 2024 17:33:10 +0000 Subject: [PATCH 451/468] refactor: normalise tranpsort interface, remove initialiser functions --- src/common/lib/client/defaultrealtime.ts | 4 ++-- src/common/lib/client/modularplugins.ts | 8 ++++---- src/common/lib/transport/connectionmanager.ts | 14 ++++--------- src/common/lib/transport/transport.ts | 20 +++++++++++++------ .../lib/transport/websockettransport.ts | 10 ++-------- src/common/platform.ts | 4 ++-- src/platform/nodejs/lib/transport/index.ts | 9 +++++---- .../lib/transport/nodecomettransport.d.ts | 8 +++++--- .../lib/transport/nodecomettransport.js | 19 ++++++++---------- src/platform/web/lib/transport/index.ts | 12 +++++------ .../web/lib/transport/xhrpollingtransport.ts | 12 +++-------- .../lib/transport/xhrstreamingtransport.ts | 12 +++-------- 12 files changed, 58 insertions(+), 74 deletions(-) diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index a966a69d32..866a963028 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -9,7 +9,7 @@ import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; -import initialiseWebSocketTransport from '../transport/websockettransport'; +import WebSocketTransport from '../transport/websockettransport'; import { FilteredSubscriptions } from './filteredsubscriptions'; import { fromValues as presenceMessageFromValues, @@ -39,7 +39,7 @@ export class DefaultRealtime extends BaseRealtime { presenceMessageFromValues, presenceMessagesFromValuesArray, }, - WebSocketTransport: initialiseWebSocketTransport, + WebSocketTransport, MessageInteractions: FilteredSubscriptions, }), ); diff --git a/src/common/lib/client/modularplugins.ts b/src/common/lib/client/modularplugins.ts index d3189aa85a..8ed795f2a4 100644 --- a/src/common/lib/client/modularplugins.ts +++ b/src/common/lib/client/modularplugins.ts @@ -2,7 +2,6 @@ import { Rest } from './rest'; import { IUntypedCryptoStatic } from '../../types/ICryptoStatic'; import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; -import { TransportInitialiser } from '../transport/connectionmanager'; import XHRRequest from 'platform/web/lib/http/request/xhrrequest'; import fetchRequest from 'platform/web/lib/http/request/fetchrequest'; import { FilteredSubscriptions } from './filteredsubscriptions'; @@ -10,6 +9,7 @@ import { fromValues as presenceMessageFromValues, fromValuesArray as presenceMessagesFromValuesArray, } from '../types/presencemessage'; +import { TransportCtor } from '../transport/transport'; export interface PresenceMessagePlugin { presenceMessageFromValues: typeof presenceMessageFromValues; @@ -25,9 +25,9 @@ export interface ModularPlugins { Crypto?: IUntypedCryptoStatic; MsgPack?: MsgPack; RealtimePresence?: RealtimePresencePlugin; - WebSocketTransport?: TransportInitialiser; - XHRPolling?: TransportInitialiser; - XHRStreaming?: TransportInitialiser; + WebSocketTransport?: TransportCtor; + XHRPolling?: TransportCtor; + XHRStreaming?: TransportCtor; XHRRequest?: typeof XHRRequest; FetchRequest?: typeof fetchRequest; MessageInteractions?: typeof FilteredSubscriptions; diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index cd2def1c19..85e715af12 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -411,14 +411,10 @@ class ConnectionManager extends EventEmitter { private static initTransports(additionalImplementations: TransportImplementations, storage: TransportStorage) { const implementations = { ...Platform.Transports.bundledImplementations, ...additionalImplementations }; - const initialiseWebSocketTransport = implementations[TransportNames.WebSocket]; - if (initialiseWebSocketTransport) { - initialiseWebSocketTransport(storage); - } - Platform.Transports.order.forEach(function (transportName) { - const initFn = implementations[transportName]; - if (initFn) { - initFn(storage); + [TransportNames.WebSocket, ...Platform.Transports.order].forEach((transportName) => { + const transport = implementations[transportName]; + if (transport && transport.isAvailable()) { + storage.supportedTransports[transportName] = transport; } }); } @@ -2196,5 +2192,3 @@ export default ConnectionManager; export interface TransportStorage { supportedTransports: Partial>; } - -export type TransportInitialiser = (transportStorage: TransportStorage) => typeof Transport; diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 0c9cd31f20..9dbb95c935 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -19,12 +19,16 @@ export type TryConnectCallback = ( transport?: Transport, ) => void; -export type TransportCtor = new ( - connectionManager: ConnectionManager, - auth: Auth, - params: TransportParams, - forceJsonProtocol?: boolean, -) => Transport; +export interface TransportCtor { + new ( + connectionManager: ConnectionManager, + auth: Auth, + params: TransportParams, + forceJsonProtocol?: boolean, + ): Transport; + + isAvailable(): boolean; +} const closeMessage = protocolMessageFromValues({ action: actions.CLOSE }); const disconnectMessage = protocolMessageFromValues({ action: actions.DISCONNECT }); @@ -323,6 +327,10 @@ abstract class Transport extends EventEmitter { } onAuthUpdated?: (tokenDetails: API.TokenDetails) => void; + + static isAvailable(): boolean { + throw new ErrorInfo('isAvailable not implemented for transport', 50000, 500); + } } export default Transport; diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 63462c25c1..e51d77c36e 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -9,7 +9,7 @@ import ProtocolMessage, { } from '../types/protocolmessage'; import ErrorInfo from '../types/errorinfo'; import NodeWebSocket from 'ws'; -import ConnectionManager, { TransportParams, TransportStorage } from './connectionmanager'; +import ConnectionManager, { TransportParams } from './connectionmanager'; import Auth from '../client/auth'; import { TransportNames } from 'common/constants/TransportName'; @@ -209,10 +209,4 @@ class WebSocketTransport extends Transport { } } -function initialiseTransport(transportStorage: TransportStorage): typeof WebSocketTransport { - if (WebSocketTransport.isAvailable()) transportStorage.supportedTransports[shortName] = WebSocketTransport; - - return WebSocketTransport; -} - -export default initialiseTransport; +export default WebSocketTransport; diff --git a/src/common/platform.ts b/src/common/platform.ts index b5b1427d20..1f695eb45a 100644 --- a/src/common/platform.ts +++ b/src/common/platform.ts @@ -1,6 +1,5 @@ import { IPlatformConfig } from './types/IPlatformConfig'; import { IPlatformHttpStatic } from './types/http'; -import { TransportInitialiser } from './lib/transport/connectionmanager'; import IDefaults from './types/IDefaults'; import IWebStorage from './types/IWebStorage'; import IBufferUtils from './types/IBufferUtils'; @@ -8,12 +7,13 @@ import * as WebBufferUtils from '../platform/web/lib/util/bufferutils'; import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils'; import { IUntypedCryptoStatic } from '../common/types/ICryptoStatic'; import TransportName from './constants/TransportName'; +import { TransportCtor } from './lib/transport/transport'; export type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike; type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output; type ToBufferOutput = WebBufferUtils.ToBufferOutput | NodeBufferUtils.ToBufferOutput; -export type TransportImplementations = Partial>; +export type TransportImplementations = Partial>; export default class Platform { static Config: IPlatformConfig; diff --git a/src/platform/nodejs/lib/transport/index.ts b/src/platform/nodejs/lib/transport/index.ts index 80ab1d0115..7cd942bd67 100644 --- a/src/platform/nodejs/lib/transport/index.ts +++ b/src/platform/nodejs/lib/transport/index.ts @@ -1,11 +1,12 @@ import { TransportNames } from 'common/constants/TransportName'; -import initialiseNodeCometTransport from './nodecomettransport'; -import { default as initialiseWebSocketTransport } from '../../../../common/lib/transport/websockettransport'; +import NodeCometTransport from './nodecomettransport'; +import { default as WebSocketTransport } from '../../../../common/lib/transport/websockettransport'; +import { TransportCtor } from 'common/lib/transport/transport'; export default { order: [TransportNames.Comet], bundledImplementations: { - [TransportNames.WebSocket]: initialiseWebSocketTransport, - [TransportNames.Comet]: initialiseNodeCometTransport, + [TransportNames.WebSocket]: WebSocketTransport as TransportCtor, + [TransportNames.Comet]: NodeCometTransport as unknown as TransportCtor, }, }; diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.d.ts b/src/platform/nodejs/lib/transport/nodecomettransport.d.ts index f90fa468b0..a069964ba4 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.d.ts +++ b/src/platform/nodejs/lib/transport/nodecomettransport.d.ts @@ -1,5 +1,7 @@ -import { TransportStorage } from '../../../../common/lib/transport/connectionmanager'; import Transport from '../../../../common/lib/transport/transport'; -declare function initialiseNodeCometTransport(transportStorage: TransportStorage): typeof Transport; -export default initialiseNodeCometTransport; +declare class NodeCometTransport extends Transport { + static isAvailable(): boolean; +} + +export default NodeCometTransport; diff --git a/src/platform/nodejs/lib/transport/nodecomettransport.js b/src/platform/nodejs/lib/transport/nodecomettransport.js index fc92aa0d75..9a4eb0a9e8 100644 --- a/src/platform/nodejs/lib/transport/nodecomettransport.js +++ b/src/platform/nodejs/lib/transport/nodecomettransport.js @@ -20,10 +20,13 @@ var shortName = TransportNames.Comet; * to simulate an XHR transport for test purposes */ class NodeCometTransport extends CometTransport { - httpAgent = null; - httpsAgent = null; - pendingRequests = 0; - shortName = shortName; + constructor(connectionManager, auth, params) { + super(connectionManager, auth, params); + this.httpAgent = null; + this.httpsAgent = null; + this.pendingRequests = 0; + this.shortName = shortName; + } static isAvailable() { return true; @@ -316,10 +319,4 @@ class Request extends EventEmitter { } } -var initialiseNodeCometTransport = function (transportStorage) { - transportStorage.supportedTransports[shortName] = NodeCometTransport; - - return NodeCometTransport; -}; - -export default initialiseNodeCometTransport; +export default NodeCometTransport; diff --git a/src/platform/web/lib/transport/index.ts b/src/platform/web/lib/transport/index.ts index 23e62165c2..40ccfcd2fb 100644 --- a/src/platform/web/lib/transport/index.ts +++ b/src/platform/web/lib/transport/index.ts @@ -1,8 +1,8 @@ import TransportName from 'common/constants/TransportName'; import Platform from 'common/platform'; -import initialiseXHRPollingTransport from './xhrpollingtransport'; -import initialiseXHRStreamingTransport from './xhrstreamingtransport'; -import { default as initialiseWebSocketTransport } from '../../../../common/lib/transport/websockettransport'; +import XhrPollingTransport from './xhrpollingtransport'; +import XHRStreamingTransport from './xhrstreamingtransport'; +import WebSocketTransport from '../../../../common/lib/transport/websockettransport'; // For reasons that I don’t understand, if we use [TransportNames.XhrStreaming] and [TransportNames.XhrPolling] for the keys in defaultTransports’s, then defaultTransports does not get tree-shaken. Hence using literals instead. They’re still correctly type-checked. @@ -11,9 +11,9 @@ const order: TransportName[] = ['xhr_polling', 'xhr_streaming']; const defaultTransports: (typeof Platform)['Transports'] = { order, bundledImplementations: { - web_socket: initialiseWebSocketTransport, - xhr_polling: initialiseXHRPollingTransport, - xhr_streaming: initialiseXHRStreamingTransport, + web_socket: WebSocketTransport, + xhr_polling: XhrPollingTransport, + xhr_streaming: XHRStreamingTransport, }, }; diff --git a/src/platform/web/lib/transport/xhrpollingtransport.ts b/src/platform/web/lib/transport/xhrpollingtransport.ts index 3da0b45a9e..12268fed86 100644 --- a/src/platform/web/lib/transport/xhrpollingtransport.ts +++ b/src/platform/web/lib/transport/xhrpollingtransport.ts @@ -1,7 +1,7 @@ import Platform from '../../../../common/platform'; import CometTransport from '../../../../common/lib/transport/comettransport'; import XHRRequest from '../http/request/xhrrequest'; -import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; +import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestBody, RequestParams } from 'common/types/http'; import { TransportNames } from 'common/constants/TransportName'; @@ -16,7 +16,7 @@ class XHRPollingTransport extends CometTransport { } static isAvailable() { - return Platform.Config.xhrSupported && Platform.Config.allowComet; + return !!(Platform.Config.xhrSupported && Platform.Config.allowComet); } toString() { @@ -34,10 +34,4 @@ class XHRPollingTransport extends CometTransport { } } -function initialiseTransport(transportStorage: TransportStorage): typeof XHRPollingTransport { - if (XHRPollingTransport.isAvailable()) transportStorage.supportedTransports[shortName] = XHRPollingTransport; - - return XHRPollingTransport; -} - -export default initialiseTransport; +export default XHRPollingTransport; diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts index fa1343769c..bc0ad0383e 100644 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ b/src/platform/web/lib/transport/xhrstreamingtransport.ts @@ -1,7 +1,7 @@ import CometTransport from '../../../../common/lib/transport/comettransport'; import Platform from '../../../../common/platform'; import XHRRequest from '../http/request/xhrrequest'; -import ConnectionManager, { TransportParams, TransportStorage } from 'common/lib/transport/connectionmanager'; +import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; import Auth from 'common/lib/client/auth'; import { RequestBody, RequestParams } from 'common/types/http'; import { TransportNames } from 'common/constants/TransportName'; @@ -14,7 +14,7 @@ class XHRStreamingTransport extends CometTransport { } static isAvailable() { - return Platform.Config.xhrSupported && Platform.Config.streamingSupported && Platform.Config.allowComet; + return !!(Platform.Config.xhrSupported && Platform.Config.streamingSupported && Platform.Config.allowComet); } toString() { @@ -32,10 +32,4 @@ class XHRStreamingTransport extends CometTransport { } } -function initialiseTransport(transportStorage: TransportStorage): typeof XHRStreamingTransport { - if (XHRStreamingTransport.isAvailable()) transportStorage.supportedTransports[shortName] = XHRStreamingTransport; - - return XHRStreamingTransport; -} - -export default initialiseTransport; +export default XHRStreamingTransport; From 000134fbb905dab2b9b695bb04ef11e0f20d492d Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 27 Feb 2024 17:33:10 +0000 Subject: [PATCH 452/468] refactor!: remove XhrStreaming transport --- README.md | 2 +- ably.d.ts | 2 +- modular.d.ts | 29 +++---------- scripts/moduleReport.ts | 2 - src/common/constants/TransportName.ts | 7 +--- src/common/lib/client/baserealtime.ts | 3 -- src/common/lib/client/modularplugins.ts | 1 - src/platform/web/lib/transport/index.ts | 6 +-- .../lib/transport/xhrstreamingtransport.ts | 35 ---------------- src/platform/web/lib/util/defaults.ts | 8 ++-- src/platform/web/modular/transports.ts | 1 - test/browser/connection.test.js | 41 ++----------------- test/browser/modular.test.js | 2 - test/browser/simple.test.js | 21 ---------- test/realtime/init.test.js | 10 ++--- test/realtime/upgrade.test.js | 1 - 16 files changed, 21 insertions(+), 150 deletions(-) delete mode 100644 src/platform/web/lib/transport/xhrstreamingtransport.ts diff --git a/README.md b/README.md index 8239c933e8..bfb9de6659 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ const client = new BaseRealtime({ You must provide: - at least one HTTP request implementation; that is, one of `FetchRequest` or `XHRRequest`; -- at least one realtime transport implementation; that is, one of `WebSocketTransport`, `XHRStreaming`, or `XHRPolling`. +- at least one realtime transport implementation; that is, one of `WebSocketTransport` or `XHRPolling`. `BaseRealtime` offers the same API as the `Realtime` class described in the rest of this `README`. This means that you can develop an application using the default variant of the SDK and switch to the modular version when you wish to optimize your bundle size. diff --git a/ably.d.ts b/ably.d.ts index 9c48ec563f..6afba3d0f3 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -297,7 +297,7 @@ export type HTTPMethod = HTTPMethods.GET | HTTPMethods.POST; /** * A type which specifies the valid transport names. [See here](https://faqs.ably.com/which-transports-are-supported) for more information. */ -export type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'comet'; +export type Transport = 'web_socket' | 'xhr_polling' | 'comet'; /** * Contains the details of a {@link Channel} or {@link RealtimeChannel} object such as its ID and {@link ChannelStatus}. diff --git a/modular.d.ts b/modular.d.ts index 1e03425c8a..45bd3ece26 100644 --- a/modular.d.ts +++ b/modular.d.ts @@ -141,11 +141,11 @@ export declare const RealtimePresence: unknown; * const realtime = new BaseRealtime({ ...options, plugins: { WebSocketTransport, FetchRequest } }); * ``` * - * Note that network conditions, such as firewalls or proxies, might prevent the client from establishing a WebSocket connection. For this reason, you may wish to provide the `BaseRealtime` instance with the ability to alternatively establish a connection using a transport that is less susceptible to these external conditions. You do this by passing one or more alternative transport plugins, namely {@link XHRStreaming} and/or {@link XHRPolling}, alongside `WebSocketTransport`: + * Note that network conditions, such as firewalls or proxies, might prevent the client from establishing a WebSocket connection. For this reason, you may wish to provide the `BaseRealtime` instance with the ability to alternatively establish a connection using long polling which is less susceptible to these external conditions. You do this by passing in the {@link XHRPolling} module, alongside `WebSocketTransport`: * * ```javascript - * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modular'; - * const realtime = new BaseRealtime({ ...options, plugins: { WebSocketTransport, XHRStreaming, FetchRequest } }); + * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modules'; + * const realtime = new BaseRealtime(options, { WebSocketTransport, XHRPolling, FetchRequest }); * ``` */ export declare const WebSocketTransport: unknown; @@ -153,7 +153,7 @@ export declare const WebSocketTransport: unknown; /** * Provides a {@link BaseRealtime} instance with the ability to establish a connection with the Ably realtime service using the browser’s [XMLHttpRequest API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). * - * `XHRPolling` uses HTTP long polling; that is, it will make a new HTTP request each time a message is received from Ably. This is less efficient than {@link XHRStreaming}, but is also more likely to succeed in the presence of certain network conditions such as firewalls or proxies. + * `XHRPolling` uses HTTP long polling; that is, it will make a new HTTP request each time a message is received from Ably. * * ```javascript * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modular'; @@ -164,20 +164,6 @@ export declare const WebSocketTransport: unknown; */ export declare const XHRPolling: unknown; -/** - * Provides a {@link BaseRealtime} instance with the ability to establish a connection with the Ably realtime service using the browser’s [XMLHttpRequest API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). - * - * `XHRStreaming` uses HTTP streaming; that is, in contrast to {@link XHRPolling}, it does not need to make a new HTTP request each time a message is received from Ably. This is more efficient than `XHRPolling`, but is more likely to be blocked by certain network conditions such as firewalls or proxies. - * - * ```javascript - * import { BaseRealtime, WebSocketTransport, FetchRequest } from 'ably/modular'; - * const realtime = new BaseRealtime({ ...options, plugins: { XHRStreaming, FetchRequest } }); - * ``` - * - * Provide this plugin if, for example, you wish the client to have an alternative mechanism for connecting to Ably if it’s unable to establish a WebSocket connection. - */ -export declare const XHRStreaming: unknown; - /** * Provides a {@link BaseRest} or {@link BaseRealtime} instance with the ability to make HTTP requests using the browser’s [XMLHttpRequest API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). * @@ -250,11 +236,6 @@ export interface ModularPlugins { */ XHRPolling?: typeof XHRPolling; - /** - * See {@link XHRStreaming | documentation for the `XHRStreaming` plugin}. - */ - XHRStreaming?: typeof XHRStreaming; - /** * See {@link XHRRequest | documentation for the `XHRRequest` plugin}. */ @@ -342,7 +323,7 @@ export declare class BaseRealtime implements RealtimeClient { * You must provide: * * - at least one HTTP request implementation; that is, one of {@link FetchRequest} or {@link XHRRequest} — for minimum bundle size, favour `FetchRequest`; - * - at least one realtime transport implementation; that is, one of {@link WebSocketTransport}, {@link XHRStreaming}, or {@link XHRPolling} — for minimum bundle size, favour `WebSocketTransport`. + * - at least one realtime transport implementation; that is, one of {@link WebSocketTransport} or {@link XHRPolling} — for minimum bundle size, favour `WebSocketTransport`. */ constructor(options: ClientOptions); diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index 384835d4cb..c933800afe 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -17,7 +17,6 @@ const pluginNames = [ 'MsgPack', 'RealtimePresence', 'XHRPolling', - 'XHRStreaming', 'WebSocketTransport', 'XHRRequest', 'FetchRequest', @@ -233,7 +232,6 @@ async function checkBaseRealtimeFiles() { // The threshold is chosen pretty arbitrarily. There are some files (e.g. presencemessage.ts) whose bulk should not be included in the BaseRealtime bundle, but which make a small contribution to the bundle (probably because we make use of one exported constant or something; I haven’t looked into it). const allowedFiles = new Set([ 'src/common/constants/HttpStatusCodes.ts', - 'src/common/constants/TransportName.ts', 'src/common/constants/XHRStates.ts', 'src/common/lib/client/auth.ts', 'src/common/lib/client/baseclient.ts', diff --git a/src/common/constants/TransportName.ts b/src/common/constants/TransportName.ts index 29137c7f4c..eda3ba2789 100644 --- a/src/common/constants/TransportName.ts +++ b/src/common/constants/TransportName.ts @@ -1,14 +1,9 @@ export namespace TransportNames { export const WebSocket = 'web_socket' as const; export const Comet = 'comet' as const; - export const XhrStreaming = 'xhr_streaming' as const; export const XhrPolling = 'xhr_polling' as const; } -type TransportName = - | typeof TransportNames.WebSocket - | typeof TransportNames.Comet - | typeof TransportNames.XhrStreaming - | typeof TransportNames.XhrPolling; +type TransportName = typeof TransportNames.WebSocket | typeof TransportNames.Comet | typeof TransportNames.XhrPolling; export default TransportName; diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 39fee93e21..f41ed2d879 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -48,9 +48,6 @@ class BaseRealtime extends BaseClient { if (plugins?.WebSocketTransport) { transports[TransportNames.WebSocket] = plugins.WebSocketTransport; } - if (plugins?.XHRStreaming) { - transports[TransportNames.XhrStreaming] = plugins.XHRStreaming; - } if (plugins?.XHRPolling) { transports[TransportNames.XhrPolling] = plugins.XHRPolling; } diff --git a/src/common/lib/client/modularplugins.ts b/src/common/lib/client/modularplugins.ts index 8ed795f2a4..28fd50b898 100644 --- a/src/common/lib/client/modularplugins.ts +++ b/src/common/lib/client/modularplugins.ts @@ -27,7 +27,6 @@ export interface ModularPlugins { RealtimePresence?: RealtimePresencePlugin; WebSocketTransport?: TransportCtor; XHRPolling?: TransportCtor; - XHRStreaming?: TransportCtor; XHRRequest?: typeof XHRRequest; FetchRequest?: typeof fetchRequest; MessageInteractions?: typeof FilteredSubscriptions; diff --git a/src/platform/web/lib/transport/index.ts b/src/platform/web/lib/transport/index.ts index 40ccfcd2fb..25515a9e77 100644 --- a/src/platform/web/lib/transport/index.ts +++ b/src/platform/web/lib/transport/index.ts @@ -1,19 +1,17 @@ import TransportName from 'common/constants/TransportName'; import Platform from 'common/platform'; import XhrPollingTransport from './xhrpollingtransport'; -import XHRStreamingTransport from './xhrstreamingtransport'; import WebSocketTransport from '../../../../common/lib/transport/websockettransport'; -// For reasons that I don’t understand, if we use [TransportNames.XhrStreaming] and [TransportNames.XhrPolling] for the keys in defaultTransports’s, then defaultTransports does not get tree-shaken. Hence using literals instead. They’re still correctly type-checked. +// For reasons that I don’t understand, if we use [TransportNames.XhrPolling] for the keys in defaultTransports’s, then defaultTransports does not get tree-shaken. Hence using literals instead. They’re still correctly type-checked. -const order: TransportName[] = ['xhr_polling', 'xhr_streaming']; +const order: TransportName[] = ['xhr_polling']; const defaultTransports: (typeof Platform)['Transports'] = { order, bundledImplementations: { web_socket: WebSocketTransport, xhr_polling: XhrPollingTransport, - xhr_streaming: XHRStreamingTransport, }, }; diff --git a/src/platform/web/lib/transport/xhrstreamingtransport.ts b/src/platform/web/lib/transport/xhrstreamingtransport.ts deleted file mode 100644 index bc0ad0383e..0000000000 --- a/src/platform/web/lib/transport/xhrstreamingtransport.ts +++ /dev/null @@ -1,35 +0,0 @@ -import CometTransport from '../../../../common/lib/transport/comettransport'; -import Platform from '../../../../common/platform'; -import XHRRequest from '../http/request/xhrrequest'; -import ConnectionManager, { TransportParams } from 'common/lib/transport/connectionmanager'; -import Auth from 'common/lib/client/auth'; -import { RequestBody, RequestParams } from 'common/types/http'; -import { TransportNames } from 'common/constants/TransportName'; - -const shortName = TransportNames.XhrStreaming; -class XHRStreamingTransport extends CometTransport { - shortName = shortName; - constructor(connectionManager: ConnectionManager, auth: Auth, params: TransportParams) { - super(connectionManager, auth, params); - } - - static isAvailable() { - return !!(Platform.Config.xhrSupported && Platform.Config.streamingSupported && Platform.Config.allowComet); - } - - toString() { - return 'XHRStreamingTransport; uri=' + this.baseUri + '; isConnected=' + this.isConnected; - } - - createRequest( - uri: string, - headers: Record, - params: RequestParams, - body: RequestBody | null, - requestMode: number, - ) { - return XHRRequest.createRequest(uri, headers, params, body, requestMode, this.timeouts); - } -} - -export default XHRStreamingTransport; diff --git a/src/platform/web/lib/util/defaults.ts b/src/platform/web/lib/util/defaults.ts index 3dbd549733..2ff50682b2 100644 --- a/src/platform/web/lib/util/defaults.ts +++ b/src/platform/web/lib/util/defaults.ts @@ -6,10 +6,10 @@ const Defaults: IDefaults = { /* Order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's * supported. */ - defaultTransports: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], - baseTransportOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], - transportPreferenceOrder: [TransportNames.XhrPolling, TransportNames.XhrStreaming, TransportNames.WebSocket], - upgradeTransports: [TransportNames.XhrStreaming, TransportNames.WebSocket], + defaultTransports: [TransportNames.XhrPolling, TransportNames.WebSocket], + baseTransportOrder: [TransportNames.XhrPolling, TransportNames.WebSocket], + transportPreferenceOrder: [TransportNames.XhrPolling, TransportNames.WebSocket], + upgradeTransports: [TransportNames.WebSocket], }; export default Defaults; diff --git a/src/platform/web/modular/transports.ts b/src/platform/web/modular/transports.ts index 37c0a09cf8..86301dfc3d 100644 --- a/src/platform/web/modular/transports.ts +++ b/src/platform/web/modular/transports.ts @@ -1,3 +1,2 @@ export { default as XHRPolling } from '../lib/transport/xhrpollingtransport'; -export { default as XHRStreaming } from '../lib/transport/xhrstreamingtransport'; export { default as WebSocketTransport } from '../../../common/lib/transport/websockettransport'; diff --git a/test/browser/connection.test.js b/test/browser/connection.test.js index 1dd4e69513..36c14c90ba 100644 --- a/test/browser/connection.test.js +++ b/test/browser/connection.test.js @@ -390,14 +390,14 @@ define(['shared_helper', 'chai'], function (helper, chai) { }); it('use_persisted_transport1', function (done) { - window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'xhr_streaming' })); + window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'xhr_polling' })); var realtime = helper.AblyRealtime(); realtime.connection.connectionManager.on(function (transport) { if (this.event === 'transport.active') { try { - expect(transport.shortName).to.equal('xhr_streaming'); + expect(transport.shortName).to.equal('xhr_polling'); } catch (err) { closeAndFinish(done, realtime, err); return; @@ -412,48 +412,13 @@ define(['shared_helper', 'chai'], function (helper, chai) { var realtime = helper.AblyRealtime(); try { expect(realtime.connection.connectionManager.baseTransport).to.equal('xhr_polling'); - expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal([ - 'xhr_streaming', - 'web_socket', - ]); + expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal(['web_socket']); } catch (err) { closeAndFinish(done, realtime, err); return; } closeAndFinish(done, realtime); }); - - /* - * Check behaviour when going through a proxy which doesn't send any xhr streaming - * responses until the stream closes (simulated by a transportparam interpreted by - * realtime) - */ - it('connection behaviour with a proxy through which streaming is broken', function (done) { - const realtime = helper.AblyRealtime({ - transportParams: { - simulateNoStreamingProxy: true, - maxStreamDuration: 7500, - }, - realtimeRequestTimeout: 5000, - transports: ['xhr_polling', 'xhr_streaming'], - }); - realtime.connection.once('connected', function () { - const connectionEvents = []; - realtime.connection.on(function () { - connectionEvents.push(this.event); - }); - // After 10s, we should have remained connected, and still be on xhr_polling; - // xhr_streaming should have failed to connect after 5s, and in particular - // should not have completed the upgrade after 7.5s. - setTimeout(function () { - expect(realtime.connection.state).to.equal('connected'); - const transport = realtime.connection.connectionManager.activeProtocol.getTransport(); - expect(transport.shortName).to.equal('xhr_polling'); - expect(connectionEvents.length).to.equal(0); - closeAndFinish(done, realtime); - }, 10000); - }); - }); }); } }); diff --git a/test/browser/modular.test.js b/test/browser/modular.test.js index 58567d9cd1..82e5b0698a 100644 --- a/test/browser/modular.test.js +++ b/test/browser/modular.test.js @@ -15,7 +15,6 @@ import { decodePresenceMessages, constructPresenceMessage, XHRPolling, - XHRStreaming, WebSocketTransport, FetchRequest, XHRRequest, @@ -666,7 +665,6 @@ function registerAblyModularTests(helper) { for (const scenario of [ { pluginsKey: 'WebSocketTransport', transportPlugin: WebSocketTransport, transportName: 'web_socket' }, { pluginsKey: 'XHRPolling', transportPlugin: XHRPolling, transportName: 'xhr_polling' }, - { pluginsKey: 'XHRStreaming', transportPlugin: XHRStreaming, transportName: 'xhr_streaming' }, ]) { describe(`with the ${scenario.pluginsKey} plugin`, () => { it(`is able to use the ${scenario.transportName} transport`, async () => { diff --git a/test/browser/simple.test.js b/test/browser/simple.test.js index 23e2667e17..5eac642bfb 100644 --- a/test/browser/simple.test.js +++ b/test/browser/simple.test.js @@ -180,27 +180,6 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); } - var xhrStreamingTransport = 'xhr_streaming'; - if (isTransportAvailable(xhrStreamingTransport)) { - it('xhrstreamingbase0', function (done) { - connectionWithTransport(done, xhrStreamingTransport); - }); - - /* - * Publish and subscribe, json transport - */ - it('xhrstreamingpublish0', function (done) { - publishWithTransport(done, xhrStreamingTransport); - }); - - /* - * Check heartbeat - */ - it('xhrstreamingheartbeat0', function (done) { - heartbeatWithTransport(done, xhrStreamingTransport); - }); - } - var xhrPollingTransport = 'xhr_polling'; if (isTransportAvailable(xhrPollingTransport)) { it('xhrpollingbase0', function (done) { diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 1e9c95feda..5298f9af4d 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -18,18 +18,16 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); }); - /* Restrict to websocket or xhr streaming for the v= test as if stream=false the - * recvRequest may not be the connectRequest by the time we check it. All comet - * transports share the same connect uri generation code so should be adequately - * tested by testing xhr_streaming */ - if (helper.bestTransport === 'web_socket' || helper.bestTransport === 'xhr_streaming') { + /* Restrict to websocket or xhr polling for the v= test as if stream=false the + * recvRequest may not be the connectRequest by the time we check it. */ + if (helper.bestTransport === 'web_socket' || helper.bestTransport === 'xhr_polling') { /* * Base init case */ it('initbase0', function (done) { var realtime; try { - realtime = helper.AblyRealtime({ transports: ['web_socket', 'xhr_streaming'] }); + realtime = helper.AblyRealtime({ transports: ['web_socket', 'xhr_polling'] }); realtime.connection.on('connected', function () { /* check api version */ var transport = realtime.connection.connectionManager.activeProtocol.transport; diff --git a/test/realtime/upgrade.test.js b/test/realtime/upgrade.test.js index b4b3a6e9b8..94a081e455 100644 --- a/test/realtime/upgrade.test.js +++ b/test/realtime/upgrade.test.js @@ -660,7 +660,6 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai }, function (cb) { connectionManager.on('transport.pending', function (transport) { - if (!helper.isWebsocket(transport)) return; // in browser, might be xhr_streaming connectionManager.off('transport.pending'); /* Abort comet transport nonfatally */ var baseTransport = connectionManager.activeProtocol.getTransport(); From b54bcf5c486926dbdf003b26b96329652da770c4 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Tue, 27 Feb 2024 17:33:10 +0000 Subject: [PATCH 453/468] feat: replace upgrade with websocket+base mechanism --- src/common/lib/client/realtimechannel.ts | 4 +- src/common/lib/transport/connectionmanager.ts | 781 +++++++----------- src/common/lib/transport/transport.ts | 4 +- .../lib/transport/websockettransport.ts | 2 +- src/common/lib/util/defaults.ts | 15 +- src/common/types/IDefaults.d.ts | 4 +- src/common/types/IPlatformConfig.d.ts | 1 - src/platform/nativescript/config.js | 1 - src/platform/nodejs/lib/util/defaults.ts | 4 +- src/platform/react-native/config.ts | 1 - src/platform/web/config.ts | 1 - src/platform/web/lib/util/defaults.ts | 4 +- test/browser/connection.test.js | 41 +- test/common/modules/shared_helper.js | 16 +- test/realtime/channel.test.js | 8 +- test/realtime/connection.test.js | 2 +- test/realtime/event_emitter.test.js | 5 +- test/realtime/failure.test.js | 8 +- test/realtime/init.test.js | 13 +- test/realtime/presence.test.js | 3 +- test/realtime/resume.test.js | 31 +- test/realtime/transports.test.js | 230 ++++++ test/realtime/upgrade.test.js | 709 ---------------- test/support/browser_file_list.js | 2 +- test/support/root_hooks.js | 1 + 25 files changed, 559 insertions(+), 1332 deletions(-) create mode 100644 test/realtime/transports.test.js delete mode 100644 test/realtime/upgrade.test.js diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index de12a500ff..637c1c508e 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -753,9 +753,7 @@ class RealtimeChannel extends EventEmitter { checkPendingState(): void { /* if can't send events, do nothing */ const cmState = this.connectionManager.state; - /* Allow attach messages to queue up when synchronizing, since this will be - * the state we'll be in when upgrade transport.active triggers a checkpendingstate */ - if (!(cmState.sendEvents || cmState.forceQueueEvents)) { + if (!cmState.sendEvents) { Logger.logAction( Logger.LOG_MINOR, 'RealtimeChannel.checkPendingState', diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 85e715af12..07cd440c41 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -42,13 +42,6 @@ function clearSessionRecoverData() { return haveSessionStorage() && Platform.WebStorage?.removeSession?.(sessionRecoveryName); } -function betterTransportThan(a: Transport, b: Transport) { - return ( - Platform.Defaults.transportPreferenceOrder.indexOf(a.shortName) > - Platform.Defaults.transportPreferenceOrder.indexOf(b.shortName) - ); -} - function bundleWith(dest: ProtocolMessage, src: ProtocolMessage, maxSize: number) { let action; if (dest.channel !== src.channel) { @@ -122,9 +115,6 @@ export class TransportParams { const params = authParams ? Utils.copy(authParams) : {}; const options = this.options; switch (this.mode) { - case 'upgrade': - params.upgrade = this.connectionKey as string; - break; case 'resume': params.resume = this.connectionKey as string; break; @@ -184,7 +174,6 @@ type ConnectionState = { sendEvents?: boolean; failState?: string; retryDelay?: number; - forceQueueEvents?: boolean; retryImmediately?: boolean; error?: IPartialErrorInfo; }; @@ -204,18 +193,18 @@ class ConnectionManager extends EventEmitter { connectionStateTtl: number; maxIdleInterval: number | null; transports: TransportName[]; - baseTransport: TransportName; - upgradeTransports: TransportName[]; + baseTransport?: TransportName; + webSocketTransportAvailable?: true; transportPreference: string | null; httpHosts: string[]; + wsHosts: string[]; activeProtocol: null | Protocol; - proposedTransports: Transport[]; - pendingTransports: Transport[]; + pendingTransport?: Transport; + proposedTransport?: Transport; host: string | null; lastAutoReconnectAttempt: number | null; lastActivity: number | null; forceFallbackHost: boolean; - connectCounter: number; transitionTimer?: number | NodeJS.Timeout | null; suspendTimer?: number | NodeJS.Timeout | null; retryTimer?: number | NodeJS.Timeout | null; @@ -226,6 +215,11 @@ class ConnectionManager extends EventEmitter { // The messages remaining to be processed (excluding any message currently being processed) queue: { message: ProtocolMessage; transport: Transport }[]; } = { isProcessing: false, queue: [] }; + webSocketSlowTimer: NodeJS.Timeout | null; + wsCheckResult: boolean | null; + webSocketGiveUpTimer: NodeJS.Timeout | null; + abandonedWebSocket: boolean; + connectCounter: number; constructor(realtime: BaseRealtime, options: NormalisedClientOptions) { super(); @@ -233,10 +227,10 @@ class ConnectionManager extends EventEmitter { this.initTransports(); this.options = options; const timeouts = options.timeouts; - /* connectingTimeout: leave preferenceConnectTimeout (~6s) to try the - * preference transport, then realtimeRequestTimeout (~10s) to establish + /* connectingTimeout: leave webSocketConnectTimeout (~6s) to try the + * websocket transport, then realtimeRequestTimeout (~10s) to establish * the base transport in case that fails */ - const connectingTimeout = timeouts.preferenceConnectTimeout + timeouts.realtimeRequestTimeout; + const connectingTimeout = timeouts.webSocketConnectTimeout + timeouts.realtimeRequestTimeout; this.states = { initialized: { state: 'initialized', @@ -260,14 +254,6 @@ class ConnectionManager extends EventEmitter { sendEvents: true, failState: 'disconnected', }, - synchronizing: { - state: 'connected', - terminal: false, - queueEvents: true, - sendEvents: false, - forceQueueEvents: true, - failState: 'disconnected', - }, disconnected: { state: 'disconnected', terminal: false, @@ -307,21 +293,29 @@ class ConnectionManager extends EventEmitter { this.maxIdleInterval = null; this.transports = Utils.intersect(options.transports || Defaults.defaultTransports, this.supportedTransports); - /* baseTransports selects the leftmost transport in the Defaults.baseTransportOrder list - * that's both requested and supported. */ - this.baseTransport = Utils.intersect(Defaults.baseTransportOrder, this.transports)[0]; - this.upgradeTransports = Utils.intersect(this.transports, Defaults.upgradeTransports); this.transportPreference = null; + if (this.transports.includes(TransportNames.WebSocket)) { + this.webSocketTransportAvailable = true; + } + if (this.transports.includes(TransportNames.XhrPolling)) { + this.baseTransport = TransportNames.XhrPolling; + } else if (this.transports.includes(TransportNames.Comet)) { + this.baseTransport = TransportNames.Comet; + } + this.httpHosts = Defaults.getHosts(options); + this.wsHosts = Defaults.getHosts(options, true); this.activeProtocol = null; - this.proposedTransports = []; - this.pendingTransports = []; this.host = null; this.lastAutoReconnectAttempt = null; this.lastActivity = null; this.forceFallbackHost = false; this.connectCounter = 0; + this.wsCheckResult = null; + this.webSocketSlowTimer = null; + this.webSocketGiveUpTimer = null; + this.abandonedWebSocket = false; Logger.logAction(Logger.LOG_MINOR, 'Realtime.ConnectionManager()', 'started'); Logger.logAction( @@ -371,10 +365,7 @@ class ConnectionManager extends EventEmitter { this.requestState({ state: 'connecting' }); } else if (this.state == this.states.connecting) { // RTN20c: if 'online' event recieved while CONNECTING, abandon connection attempt and retry - this.pendingTransports.forEach(function (transport) { - // Detach transport listeners to avoid connection state side effects from calling dispose - transport.off(); - }); + this.pendingTransport?.off(); this.disconnectAllTransports(); this.startConnect(); @@ -492,7 +483,7 @@ class ConnectionManager extends EventEmitter { tryATransport(transportParams: TransportParams, candidate: TransportName, callback: Function): void { Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.tryATransport()', 'trying ' + candidate); - Transport.tryConnect( + this.proposedTransport = Transport.tryConnect( this.supportedTransports[candidate]!, this, this.realtime.auth, @@ -577,33 +568,13 @@ class ConnectionManager extends EventEmitter { 'transport = ' + transport + '; mode = ' + mode, ); - Utils.arrDeleteValue(this.proposedTransports, transport); - this.pendingTransports.push(transport); - const optimalTransport = - Platform.Defaults.transportPreferenceOrder[Platform.Defaults.transportPreferenceOrder.length - 1]; - transport.once('connected', (error: ErrorInfo, connectionId: string, connectionDetails: Record) => { - if (mode == 'upgrade' && this.activeProtocol) { - /* if ws and xhrs are connecting in parallel, delay xhrs activation to let ws go ahead */ - if ( - transport.shortName !== optimalTransport && - this.getUpgradePossibilities().includes(optimalTransport) && - this.activeProtocol - ) { - setTimeout(() => { - this.scheduleTransportActivation(error, transport, connectionId, connectionDetails); - }, this.options.timeouts.parallelUpgradeDelay); - } else { - this.scheduleTransportActivation(error, transport, connectionId, connectionDetails); - } - } else { - this.activateTransport(error, transport, connectionId, connectionDetails); + this.pendingTransport = transport; - /* allow connectImpl to start the upgrade process if needed, but allow - * other event handlers, including activating the transport, to run first */ - Platform.Config.nextTick(() => { - this.connectImpl(transportParams); - }); - } + this.cancelWebSocketSlowTimer(); + this.cancelWebSocketGiveUpTimer(); + + transport.once('connected', (error: ErrorInfo, connectionId: string, connectionDetails: Record) => { + this.activateTransport(error, transport, connectionId, connectionDetails); if (mode === 'recover' && this.options.recover) { /* After a successful recovery, we unpersist, as a recovery key cannot @@ -621,166 +592,6 @@ class ConnectionManager extends EventEmitter { this.emit('transport.pending', transport); } - /** - * Called when an upgrade transport is connected, - * to schedule the activation of that transport. - * @param error - * @param transport - * @param connectionId - * @param connectionDetails - */ - scheduleTransportActivation( - error: ErrorInfo, - transport: Transport, - connectionId: string, - connectionDetails: Record, - ): void { - const currentTransport = this.activeProtocol && this.activeProtocol.getTransport(), - abandon = () => { - transport.disconnect(); - Utils.arrDeleteValue(this.pendingTransports, transport); - }; - - if (this.state !== this.states.connected && this.state !== this.states.connecting) { - /* This is most likely to happen for the delayed XHRs, when XHRs and ws are scheduled in parallel*/ - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Current connection state (' + - this.state.state + - (this.state === this.states.synchronizing ? ', but with an upgrade already in progress' : '') + - ') is not valid to upgrade in; abandoning upgrade to ' + - transport.shortName, - ); - abandon(); - return; - } - - if (currentTransport && !betterTransportThan(transport, currentTransport)) { - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Proposed transport ' + - transport.shortName + - ' is no better than current active transport ' + - currentTransport.shortName + - ' - abandoning upgrade', - ); - abandon(); - return; - } - - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Scheduling transport upgrade; transport = ' + transport, - ); - - let oldProtocol: Protocol | null = null; - - if (!transport.isConnected) { - /* This is only possible if the xhr streaming transport was disconnected during the parallelUpgradeDelay */ - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Proposed transport ' + transport.shortName + 'is no longer connected; abandoning upgrade', - ); - abandon(); - return; - } - - if (this.state === this.states.connected) { - Logger.logAction( - Logger.LOG_MICRO, - 'ConnectionManager.scheduleTransportActivation()', - 'Currently connected, so temporarily pausing events until the upgrade is complete', - ); - this.state = this.states.synchronizing; - oldProtocol = this.activeProtocol; - } else if (this.state !== this.states.connecting) { - /* Note: upgrading from the connecting state is valid if the old active - * transport was deactivated after the upgrade transport first connected; - * see logic in deactivateTransport */ - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Current connection state (' + - this.state.state + - (this.state === this.states.synchronizing ? ', but with an upgrade already in progress' : '') + - ') is not valid to upgrade in; abandoning upgrade to ' + - transport.shortName, - ); - abandon(); - return; - } - - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Syncing transport; transport = ' + transport, - ); - - const finishUpgrade = () => { - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Activating transport; transport = ' + transport, - ); - - // Send ACTIVATE to tell the server to make this transport the - // active transport, which suspends channels until we re-attach. - transport.send( - protocolMessageFromValues({ - action: actions.ACTIVATE, - }), - ); - - this.activateTransport(error, transport, connectionId, connectionDetails); - /* Restore pre-sync state. If state has changed in the meantime, - * don't touch it -- since the websocket transport waits a tick before - * disposing itself, it's possible for it to have happily synced - * without err while, unknown to it, the connection has closed in the - * meantime and the ws transport is scheduled for death */ - if (this.state === this.states.synchronizing) { - Logger.logAction( - Logger.LOG_MICRO, - 'ConnectionManager.scheduleTransportActivation()', - 'Pre-upgrade protocol idle, sending queued messages on upgraded transport; transport = ' + transport, - ); - this.state = this.states.connected; - } else { - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.scheduleTransportActivation()', - 'Pre-upgrade protocol idle, but state is now ' + this.state.state + ', so leaving unchanged', - ); - } - if (this.state.sendEvents) { - this.sendQueuedMessages(); - } - }; - - /* Wait until sync is done and old transport is idle before activating new transport. This - * guarantees that messages arrive at realtime in the same order they are sent. - * - * If a message times out on the old transport, since it's still the active transport the - * message will be requeued. deactivateTransport will see the pending transport and notify - * the `connecting` state without starting a new connection, so the new transport can take - * over once deactivateTransport clears the old protocol's queue. - * - * If there is no old protocol, that meant that we weren't in the connected state at the - * beginning of the sync - likely the base transport died just before the sync. So can just - * finish the upgrade. If we're actually in closing/failed rather than connecting, that's - * fine, activatetransport will deal with that. */ - if (oldProtocol) { - /* Most of the time this will be already true: the new-transport sync will have given - * enough time for in-flight messages on the old transport to complete. */ - oldProtocol.onceIdle(finishUpgrade); - } else { - finishUpgrade(); - } - } - /** * Called when a transport is connected, and the connectionmanager decides that * it will now be the active transport. Returns whether or not it activated @@ -835,8 +646,7 @@ class ConnectionManager extends EventEmitter { return false; } - /* remove this transport from pending transports */ - Utils.arrDeleteValue(this.pendingTransports, transport); + delete this.pendingTransport; /* if the transport is not connected then don't activate it */ if (!transport.isConnected) { @@ -879,10 +689,7 @@ class ConnectionManager extends EventEmitter { * error). */ if (existingState.state === this.states.connected.state) { if (error) { - /* if upgrading without error, leave any existing errorReason alone */ this.errorReason = this.realtime.connection.errorReason = error; - /* Only bother emitting an upgrade if there's an error; otherwise it's - * just a transport upgrade, so auth details won't have changed */ this.emit('update', new ConnectionStateChange(connectedState, connectedState, null, error)); } } else { @@ -925,38 +732,6 @@ class ConnectionManager extends EventEmitter { } } - // terminate any other pending transport(s), and abort any not-yet-pending transport attempts - // need to use .slice() here, since we intend to mutate the array during .forEach() iteration - this.pendingTransports.slice().forEach((pendingTransport) => { - if (pendingTransport === transport) { - const msg = - 'Assumption violated: activating a transport that is still marked as a pending transport; transport = ' + - transport.shortName + - '; stack = ' + - new Error().stack; - Logger.logAction(Logger.LOG_ERROR, 'ConnectionManager.activateTransport()', msg); - Utils.arrDeleteValue(this.pendingTransports, transport); - } else { - pendingTransport.disconnect(); - } - }); - // need to use .slice() here, since we intend to mutate the array during .forEach() iteration - this.proposedTransports.slice().forEach((proposedTransport: Transport) => { - if (proposedTransport === transport) { - Logger.logAction( - Logger.LOG_ERROR, - 'ConnectionManager.activateTransport()', - 'Assumption violated: activating a transport that is still marked as a proposed transport; transport = ' + - transport.shortName + - '; stack = ' + - new Error().stack, - ); - Utils.arrDeleteValue(this.proposedTransports, transport); - } else { - proposedTransport.dispose(); - } - }); - return true; } @@ -968,8 +743,7 @@ class ConnectionManager extends EventEmitter { deactivateTransport(transport: Transport, state: string, error: ErrorInfo): void { const currentProtocol = this.activeProtocol, wasActive = currentProtocol && currentProtocol.getTransport() === transport, - wasPending = Utils.arrDeleteValue(this.pendingTransports, transport), - wasProposed = Utils.arrDeleteValue(this.proposedTransports, transport), + wasPending = transport === this.pendingTransport, noTransportsScheduledForActivation = this.noTransportsScheduledForActivation(); Logger.logAction(Logger.LOG_MINOR, 'ConnectionManager.deactivateTransport()', 'transport = ' + transport); @@ -978,7 +752,7 @@ class ConnectionManager extends EventEmitter { 'ConnectionManager.deactivateTransport()', 'state = ' + state + - (wasActive ? '; was active' : wasPending ? '; was pending' : wasProposed ? '; was proposed' : '') + + (wasActive ? '; was active' : wasPending ? '; was pending' : '') + (noTransportsScheduledForActivation ? '' : '; another transport is scheduled for activation'), ); if (error && error.message) @@ -993,13 +767,8 @@ class ConnectionManager extends EventEmitter { ' pending messages', ); this.queuePendingMessages((currentProtocol as Protocol).getPendingMessages()); - /* Clear any messages we requeue to allow the protocol to become idle. - * In case of an upgrade, this will trigger an immediate activation of - * the upgrade transport, so delay a tick so this transport can finish - * deactivating */ - Platform.Config.nextTick(function () { - (currentProtocol as Protocol).clearPendingMessages(); - }); + /* Clear any messages we requeue to allow the protocol to become idle.*/ + (currentProtocol as Protocol).clearPendingMessages(); this.activeProtocol = this.host = null; } @@ -1018,7 +787,7 @@ class ConnectionManager extends EventEmitter { (wasActive && noTransportsScheduledForActivation) || (wasActive && state === 'failed') || state === 'closed' || - (currentProtocol === null && wasPending && this.pendingTransports.length === 0) + (currentProtocol === null && wasPending) ) { /* If we're disconnected with a 5xx we need to try fallback hosts * (RTN14d), but (a) due to how the upgrade sequence works, the @@ -1043,37 +812,13 @@ class ConnectionManager extends EventEmitter { this.notifyState({ state: newConnectionState, error: error }); return; } - - if (wasActive && state === 'disconnected' && this.state !== this.states.synchronizing) { - /* If we were active but there is another transport scheduled for - * activation, go into to the connecting state until that transport - * activates and sets us back to connected. (manually starting the - * transition timers in case that never happens). (If we were in the - * synchronizing state, then that's fine, the old transport just got its - * disconnected before the new one got the sync -- ignore it and keep - * waiting for the sync. If it fails we have a separate sync timer that - * will expire). */ - Logger.logAction( - Logger.LOG_MICRO, - 'ConnectionManager.deactivateTransport()', - 'wasActive but another transport is connected and scheduled for activation, so going into the connecting state until it activates', - ); - this.startSuspendTimer(); - this.startTransitionTimer(this.states.connecting); - this.notifyState({ state: 'connecting', error: error }); - } } /* Helper that returns true if there are no transports which are pending, * have been connected, and are just waiting for onceNoPending to fire before * being activated */ noTransportsScheduledForActivation(): boolean { - return ( - Utils.isEmpty(this.pendingTransports) || - this.pendingTransports.every(function (transport) { - return !transport.isConnected; - }) - ); + return !this.pendingTransport || !this.pendingTransport.isConnected; } setConnection(connectionId: string, connectionDetails: Record, hasConnectionError?: boolean): void { @@ -1293,6 +1038,92 @@ class ConnectionManager extends EventEmitter { } } + startWebSocketSlowTimer() { + this.webSocketSlowTimer = setTimeout(() => { + Logger.logAction(Logger.LOG_MINOR, 'ConnectionManager WebSocket slow timer', 'checking connectivity'); + if (this.wsCheckResult === null) { + this.checkWsConnectivity() + .then(() => { + Logger.logAction( + Logger.LOG_MINOR, + 'ConnectionManager WebSocket slow timer', + 'ws connectivity check succeeded', + ); + this.wsCheckResult = true; + }) + .catch(() => { + Logger.logAction( + Logger.LOG_MAJOR, + 'ConnectionManager WebSocket slow timer', + 'ws connectivity check failed', + ); + this.wsCheckResult = false; + }); + } + if (this.realtime.http.checkConnectivity) { + Utils.whenPromiseSettles(this.realtime.http.checkConnectivity(), (err, connectivity) => { + if (err || !connectivity) { + Logger.logAction( + Logger.LOG_MAJOR, + 'ConnectionManager WebSocket slow timer', + 'http connectivity check failed', + ); + this.cancelWebSocketGiveUpTimer(); + this.notifyState({ + state: 'disconnected', + error: new ErrorInfo("new ErrorInfo('Unable to connect (network unreachable)'", 80003, 404), + }); + } else { + Logger.logAction( + Logger.LOG_MINOR, + 'ConnectionManager WebSocket slow timer', + 'http connectivity check succeeded', + ); + } + }); + } + }, this.options.timeouts.webSocketSlowTimeout); + } + + cancelWebSocketSlowTimer() { + if (this.webSocketSlowTimer) { + clearTimeout(this.webSocketSlowTimer); + this.webSocketSlowTimer = null; + } + } + + startWebSocketGiveUpTimer(transportParams: TransportParams) { + this.webSocketGiveUpTimer = setTimeout(() => { + if (!this.wsCheckResult) { + Logger.logAction( + Logger.LOG_MINOR, + 'ConnectionManager WebSocket give up timer', + 'websocket connection took more than 10s; ' + (this.baseTransport ? 'trying base transport' : ''), + ); + if (this.baseTransport) { + this.abandonedWebSocket = true; + this.proposedTransport?.dispose(); + this.pendingTransport?.dispose(); + this.connectBase(transportParams, ++this.connectCounter); + } else { + // if we don't have a base transport to fallback to, just let the websocket connection attempt time out + Logger.logAction( + Logger.LOG_MAJOR, + 'ConnectionManager WebSocket give up timer', + 'websocket connectivity appears to be unavailable but no other transports to try', + ); + } + } + }, this.options.timeouts.webSocketConnectTimeout); + } + + cancelWebSocketGiveUpTimer() { + if (this.webSocketGiveUpTimer) { + clearTimeout(this.webSocketGiveUpTimer); + this.webSocketGiveUpTimer = null; + } + } + notifyState(indicated: ConnectionState): void { const state = indicated.state; @@ -1307,7 +1138,6 @@ class ConnectionManager extends EventEmitter { const retryImmediately = state === 'disconnected' && (this.state === this.states.connected || - this.state === this.states.synchronizing || indicated.retryImmediately || (this.state === this.states.connecting && indicated.error && @@ -1326,6 +1156,8 @@ class ConnectionManager extends EventEmitter { * state), as these are superseded by this notification */ this.cancelTransitionTimer(); this.cancelRetryTimer(); + this.cancelWebSocketSlowTimer(); + this.cancelWebSocketGiveUpTimer(); this.checkSuspendTimer(indicated.state); if (state === 'suspended' || state === 'connected') { @@ -1415,6 +1247,8 @@ class ConnectionManager extends EventEmitter { if (state == this.state.state) return; /* silently do nothing */ /* kill running timers, as this request supersedes them */ + this.cancelWebSocketSlowTimer(); + this.cancelWebSocketGiveUpTimer(); this.cancelTransitionTimer(); this.cancelRetryTimer(); /* for suspend timer check rather than cancel -- eg requesting a connecting @@ -1507,130 +1341,132 @@ class ConnectionManager extends EventEmitter { } } - /** - * There are three stages in connecting: - * - preference: if there is a cached transport preference, we try to connect - * on that. If that fails or times out we abort the attempt, remove the - * preference and fall back to base. If it succeeds, we try upgrading it if - * needed (will only be in the case where the preference is xhrs and the - * browser supports ws). - * - base: we try to connect with the best transport that we think will - * never fail for this platform. If it doesn't work, we try fallback hosts. - * - upgrade: given a connected transport, we see if there are any better - * ones, and if so, try to upgrade to them. + /* + * there are, at most, two transports available with which a connection may + * be attempted: web_socket and/or a base transport (xhr_polling in browsers, + * comet in nodejs). web_socket is always preferred, and the base transport is + * only used in case web_socket connectivity appears to be unavailable. + * + * connectImpl begins the transport selection process by checking which transports + * are available, and if there is a cached preference. It then defers to the + * transport-specific connect methods: connectWs and connectBase. + * + * It is also responsible for invalidating the cache in the case that a base + * transport preference is stored but web socket connectivity is now available. * - * connectImpl works out what stage you're at (which is purely a function of - * the current connection state and whether there are any stored preferences), - * and dispatches accordingly. After a transport has been set pending, - * tryATransport calls connectImpl to see if there's another stage to be done. - * */ - connectImpl(transportParams: TransportParams, connectCount?: number): void { + * handling of the case where we need to failover from web_socket to the base + * transport is implemented in the connectWs method. + */ + connectImpl(transportParams: TransportParams, connectCount: number): void { const state = this.state.state; - - if (state !== this.states.connecting.state && state !== this.states.connected.state) { + if (state !== this.states.connecting.state) { /* Only keep trying as long as in the 'connecting' state (or 'connected' * for upgrading). Any operation can put us into 'disconnected' to cancel * connection attempts and wait before retrying, or 'failed' to fail. */ Logger.logAction( Logger.LOG_MINOR, 'ConnectionManager.connectImpl()', - 'Must be in connecting state to connect (or connected to upgrade), but was ' + state, + 'Must be in connecting state to connect, but was ' + state, ); - } else if (this.pendingTransports.length) { - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.connectImpl()', - 'Transports ' + this.pendingTransports[0].toString() + ' currently pending; taking no action', - ); - } else if (state == this.states.connected.state) { - this.upgradeIfNeeded(transportParams); - } else if (this.transports.length > 1 && this.getTransportPreference()) { - this.connectPreference(transportParams, connectCount); - } else { - this.connectBase(transportParams, connectCount); + return; } - } - connectPreference(transportParams: TransportParams, connectCount?: number): void { - const preference = this.getTransportPreference(); - let preferenceTimeoutExpired = false; + const transportPreference = this.getTransportPreference(); - if (!this.transports.includes(preference)) { - this.unpersistTransportPreference(); - this.connectImpl(transportParams, connectCount); + // If transport preference is for a non-ws transport but websocket is now available, unpersist the preference for next time + if (transportPreference && transportPreference === this.baseTransport && this.webSocketTransportAvailable) { + this.checkWsConnectivity() + .then(() => { + this.wsCheckResult = true; + this.abandonedWebSocket = false; + this.unpersistTransportPreference(); + if (this.state === this.states.connecting) { + Logger.logAction( + Logger.LOG_MINOR, + 'ConnectionManager.connectImpl():', + 'web socket connectivity available, cancelling connection attempt with ' + this.baseTransport, + ); + this.disconnectAllTransports(); + this.connectWs(transportParams, ++this.connectCounter); + } + }) + .catch(noop); } - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.connectPreference()', - 'Trying to connect with stored transport preference ' + preference, - ); + if ( + (transportPreference && transportPreference === this.baseTransport) || + (this.baseTransport && !this.webSocketTransportAvailable) + ) { + this.connectBase(transportParams, connectCount); + } else { + this.connectWs(transportParams, connectCount); + } + } + + /* + * connectWs starts two timers to monitor the success of a web_socket connection attempt: + * - webSocketSlowTimer: if this timer fires before the connection succeeds, + * cm will simultaneously check websocket and http/xhr connectivity. if the http + * connectivity check fails, we give up the connection sequence entirely and + * transition to disconnected. if the websocket connectivity check fails then + * we assume no ws connectivity and failover to base transport. in the case that + * the checks succeed, we continue with websocket and wait for it to try fallback hosts + * and, if unsuccessful, ultimately transition to disconnected. + * - webSocketGiveUpTimer: if this timer fires, and the preceding websocket + * connectivity check is still pending then we assume that there is an issue + * with the transport and fallback to base transport. + */ + connectWs(transportParams: TransportParams, connectCount: number) { + Logger.logAction(Logger.LOG_DEBUG, 'ConnectionManager.connectWs()'); + this.startWebSocketSlowTimer(); + this.startWebSocketGiveUpTimer(transportParams); - const preferenceTimeout = setTimeout(() => { - preferenceTimeoutExpired = true; - if (!(this.state.state === this.states.connected.state)) { - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.connectPreference()', - 'Shortcircuit connection attempt with ' + preference + ' failed; clearing preference and trying from scratch', - ); - /* Abort all connection attempts. (This also disconnects the active - * protocol, but none exists if we're not in the connected state) */ - this.disconnectAllTransports(); - /* Be quite agressive about clearing the stored preference if ever it doesn't work */ - this.unpersistTransportPreference(); - } - this.connectImpl(transportParams, connectCount); - }, this.options.timeouts.preferenceConnectTimeout); - - /* For connectPreference, just use the main host. If host fallback is needed, do it in connectBase. - * The wstransport it will substitute the httphost for an appropriate wshost */ - transportParams.host = this.httpHosts[0]; - this.tryATransport(transportParams, preference, (fatal: boolean, transport: Transport) => { - clearTimeout(preferenceTimeout); - if (preferenceTimeoutExpired && transport) { - /* Viable, but too late - connectImpl() will already be trying - * connectBase, and we weren't in upgrade mode. Just remove the - * onconnected listener and get rid of it */ - transport.off(); - transport.disconnect(); - Utils.arrDeleteValue(this.pendingTransports, transport); - } else if (!transport && !fatal) { - /* Preference failed in a transport-specific way. Try more */ - this.unpersistTransportPreference(); - this.connectImpl(transportParams, connectCount); - } - /* If suceeded, or failed fatally, nothing to do */ + this.tryTransportWithFallbacks('web_socket', transportParams, true, connectCount, () => { + return this.wsCheckResult !== false && !this.abandonedWebSocket; }); } - /** - * Try to establish a transport on the base transport (the best transport - * such that if it doesn't work, nothing will work) as determined through - * static feature detection, checking for network connectivity and trying - * fallback hosts if applicable. - * @param transportParams - */ - connectBase(transportParams: TransportParams, connectCount?: number): void { + connectBase(transportParams: TransportParams, connectCount: number) { + Logger.logAction(Logger.LOG_DEBUG, 'ConnectionManager.connectBase()'); + if (this.baseTransport) { + this.tryTransportWithFallbacks(this.baseTransport, transportParams, false, connectCount, () => true); + } else { + this.notifyState({ + state: 'disconnected', + error: new ErrorInfo('No transports left to try', 80000, 404), + }); + } + } + + tryTransportWithFallbacks( + transportName: TransportName, + transportParams: TransportParams, + ws: boolean, + connectCount: number, + shouldContinue: () => boolean, + ): void { + Logger.logAction(Logger.LOG_DEBUG, 'ConnectionManager.tryTransportWithFallbacks()', transportName); const giveUp = (err: IPartialErrorInfo) => { this.notifyState({ state: this.states.connecting.failState as string, error: err }); }; - const candidateHosts = this.httpHosts.slice(); + + const candidateHosts = ws ? this.wsHosts.slice() : this.httpHosts.slice(); + const hostAttemptCb = (fatal: boolean, transport: Transport) => { if (connectCount !== this.connectCounter) { return; } + if (!shouldContinue()) { + if (transport) { + transport.dispose(); + } + return; + } if (!transport && !fatal) { tryFallbackHosts(); } }; - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.connectBase()', - 'Trying to connect with base transport ' + this.baseTransport, - ); - /* first try to establish a connection with the priority host with http transport */ const host = candidateHosts.shift(); if (!host) { @@ -1659,6 +1495,9 @@ class ConnectionManager extends EventEmitter { if (connectCount !== this.connectCounter) { return; } + if (!shouldContinue()) { + return; + } /* we know err won't happen but handle it here anyway */ if (err) { giveUp(err); @@ -1673,7 +1512,7 @@ class ConnectionManager extends EventEmitter { * its dns. Try the fallback hosts. We could try them simultaneously but * that would potentially cause a huge spike in load on the load balancer */ transportParams.host = Utils.arrPopRandomElement(candidateHosts); - this.tryATransport(transportParams, this.baseTransport, hostAttemptCb); + this.tryATransport(transportParams, transportName, hostAttemptCb); }, ); }; @@ -1684,35 +1523,7 @@ class ConnectionManager extends EventEmitter { return; } - this.tryATransport(transportParams, this.baseTransport, hostAttemptCb); - } - - getUpgradePossibilities(): TransportName[] { - /* returns the subset of upgradeTransports to the right of the current - * transport in upgradeTransports (if it's in there - if not, currentSerial - * will be -1, so return upgradeTransports.slice(0) == upgradeTransports */ - const current = (this.activeProtocol as Protocol).getTransport().shortName; - const currentSerial = this.upgradeTransports.indexOf(current); - return this.upgradeTransports.slice(currentSerial + 1); - } - - upgradeIfNeeded(transportParams: Record): void { - const upgradePossibilities = this.getUpgradePossibilities(); - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.upgradeIfNeeded()', - 'upgrade possibilities: ' + Platform.Config.inspect(upgradePossibilities), - ); - - if (!upgradePossibilities.length) { - return; - } - - upgradePossibilities.forEach((upgradeTransport: TransportName) => { - /* Note: the transport may mutate the params, so give each transport a fresh one */ - const upgradeTransportParams = this.createTransportParams(transportParams.host, 'upgrade'); - this.tryATransport(upgradeTransportParams, upgradeTransport, noop); - }); + this.tryATransport(transportParams, transportName, hostAttemptCb); } closeImpl(): void { @@ -1720,21 +1531,14 @@ class ConnectionManager extends EventEmitter { this.cancelSuspendTimer(); this.startTransitionTimer(this.states.closing); - // need to use .slice() here, since we intend to mutate the array during .forEach() iteration - this.pendingTransports.slice().forEach(function (transport) { - Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.closeImpl()', 'Closing pending transport: ' + transport); - if (transport) transport.close(); - }); - - // need to use .slice() here, since we intend to mutate the array during .forEach() iteration - this.proposedTransports.slice().forEach(function (transport) { + if (this.pendingTransport) { Logger.logAction( Logger.LOG_MICRO, 'ConnectionManager.closeImpl()', - 'Disposing of proposed transport: ' + transport, + 'Closing pending transport: ' + this.pendingTransport, ); - if (transport) transport.dispose(); - }); + this.pendingTransport.close(); + } if (this.activeProtocol) { Logger.logAction( @@ -1758,23 +1562,6 @@ class ConnectionManager extends EventEmitter { 'ConnectionManager.onAuthUpdated()', 'Sending AUTH message on active transport', ); - /* If there are any proposed/pending transports (eg an upgrade that - * isn't yet scheduled for activation) that hasn't yet started syncing, - * just to get rid of them & restart the upgrade with the new token, to - * avoid a race condition. (If it has started syncing, the AUTH will be - * queued until the upgrade is complete, so everything's fine) */ - if ( - (this.pendingTransports.length || this.proposedTransports.length) && - this.state !== this.states.synchronizing - ) { - this.disconnectAllTransports(/* exceptActive: */ true); - const transportParams = (this.activeProtocol as Protocol).getTransport().params; - Platform.Config.nextTick(() => { - if (this.state.state === 'connected') { - this.upgradeIfNeeded(transportParams); - } - }); - } /* Do any transport-specific new-token action */ const activeTransport = this.activeProtocol?.getTransport(); @@ -1853,39 +1640,33 @@ class ConnectionManager extends EventEmitter { } } - disconnectAllTransports(exceptActive?: boolean): void { - Logger.logAction( - Logger.LOG_MINOR, - 'ConnectionManager.disconnectAllTransports()', - 'Disconnecting all transports' + (exceptActive ? ' except the active transport' : ''), - ); + disconnectAllTransports(): void { + Logger.logAction(Logger.LOG_MINOR, 'ConnectionManager.disconnectAllTransports()', 'Disconnecting all transports'); /* This will prevent any connection procedure in an async part of one of its early stages from continuing */ this.connectCounter++; - // need to use .slice() here, since we intend to mutate the array during .forEach() iteration - this.pendingTransports.slice().forEach(function (transport) { + if (this.pendingTransport) { Logger.logAction( Logger.LOG_MICRO, 'ConnectionManager.disconnectAllTransports()', - 'Disconnecting pending transport: ' + transport, + 'Disconnecting pending transport: ' + this.pendingTransport, ); - if (transport) transport.disconnect(); - }); - this.pendingTransports = []; + this.pendingTransport.disconnect(); + } + delete this.pendingTransport; - // need to use .slice() here, since we intend to mutate the array during .forEach() iteration - this.proposedTransports.slice().forEach(function (transport) { + if (this.proposedTransport) { Logger.logAction( Logger.LOG_MICRO, 'ConnectionManager.disconnectAllTransports()', - 'Disposing of proposed transport: ' + transport, + 'Disconnecting proposed transport: ' + this.pendingTransport, ); - if (transport) transport.dispose(); - }); - this.proposedTransports = []; + this.proposedTransport.disconnect(); + } + delete this.pendingTransport; - if (this.activeProtocol && !exceptActive) { + if (this.activeProtocol) { Logger.logAction( Logger.LOG_MICRO, 'ConnectionManager.disconnectAllTransports()', @@ -1910,7 +1691,7 @@ class ConnectionManager extends EventEmitter { this.sendImpl(new PendingMessage(msg, callback)); return; } - const shouldQueue = (queueEvent && state.queueEvents) || state.forceQueueEvents; + const shouldQueue = queueEvent && state.queueEvents; if (!shouldQueue) { const err = 'rejecting event, queueEvent was ' + queueEvent + ', state was ' + state.state; Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.send()', err); @@ -2009,7 +1790,7 @@ class ConnectionManager extends EventEmitter { this.pendingChannelMessagesState.isProcessing = true; const pendingChannelMessage = this.pendingChannelMessagesState.queue.shift()!; - this.processChannelMessage(pendingChannelMessage.message, pendingChannelMessage.transport) + this.processChannelMessage(pendingChannelMessage.message) .catch((err) => { Logger.logAction( Logger.LOG_ERROR, @@ -2024,29 +1805,8 @@ class ConnectionManager extends EventEmitter { } } - private async processChannelMessage(message: ProtocolMessage, transport: Transport) { - const onActiveTransport = this.activeProtocol && transport === this.activeProtocol.getTransport(), - onUpgradeTransport = this.pendingTransports.includes(transport) && this.state == this.states.synchronizing; - - /* As the lib now has a period where the upgrade transport is synced but - * before it's become active (while waiting for the old one to become - * idle), message can validly arrive on it even though it isn't active */ - if (onActiveTransport || onUpgradeTransport) { - await this.realtime.channels.processChannelMessage(message); - } else { - // Message came in on a defunct transport. Allow only acks, nacks, & errors for outstanding - // messages, no new messages (as sync has been sent on new transport so new messages will - // be resent there, or connection has been closed so don't want new messages) - if ([actions.ACK, actions.NACK, actions.ERROR].includes(message.action!)) { - await this.realtime.channels.processChannelMessage(message); - } else { - Logger.logAction( - Logger.LOG_MICRO, - 'ConnectionManager.onChannelMessage()', - 'received message ' + JSON.stringify(message) + 'on defunct transport; discarding', - ); - } - } + private async processChannelMessage(message: ProtocolMessage) { + await this.realtime.channels.processChannelMessage(message); } ping(transport: Transport | null, callback: Function): void { @@ -2115,20 +1875,14 @@ class ConnectionManager extends EventEmitter { (this.activeProtocol as Protocol).getTransport().fail(error); } - registerProposedTransport(transport: Transport): void { - this.proposedTransports.push(transport); - } - getTransportPreference(): TransportName { return this.transportPreference || (haveWebStorage() && Platform.WebStorage?.get?.(transportPreferenceName)); } persistTransportPreference(transport: Transport): void { - if (Defaults.upgradeTransports.includes(transport.shortName)) { - this.transportPreference = transport.shortName; - if (haveWebStorage()) { - Platform.WebStorage?.set?.(transportPreferenceName, transport.shortName); - } + this.transportPreference = transport.shortName; + if (haveWebStorage()) { + Platform.WebStorage?.set?.(transportPreferenceName, transport.shortName); } } @@ -2185,6 +1939,27 @@ class ConnectionManager extends EventEmitter { this.maxIdleInterval = connectionDetails.maxIdleInterval; this.emit('connectiondetails', connectionDetails); } + + checkWsConnectivity() { + const ws = new Platform.Config.WebSocket(Defaults.wsConnectivityUrl); + return new Promise((resolve, reject) => { + let finished = false; + ws.onopen = () => { + if (!finished) { + finished = true; + resolve(); + ws.close(); + } + }; + + ws.onclose = ws.onerror = () => { + if (!finished) { + finished = true; + reject(); + } + }; + }); + } } export default ConnectionManager; diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 9dbb95c935..78a173688d 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -64,7 +64,6 @@ abstract class Transport extends EventEmitter { params.heartbeats = true; } this.connectionManager = connectionManager; - connectionManager.registerProposedTransport(this); this.auth = auth; this.params = params; this.timeouts = params.options.timeouts; @@ -296,7 +295,7 @@ abstract class Transport extends EventEmitter { auth: Auth, transportParams: TransportParams, callback: TryConnectCallback, - ): void { + ): Transport { const transport = new transportCtor(connectionManager, auth, transportParams); let transportAttemptTimer: NodeJS.Timeout | number; @@ -324,6 +323,7 @@ abstract class Transport extends EventEmitter { callback(null, transport); }); transport.connect(); + return transport; } onAuthUpdated?: (tokenDetails: API.TokenDetails) => void; diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index e51d77c36e..0efe0eba0e 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -29,7 +29,7 @@ class WebSocketTransport extends Transport { super(connectionManager, auth, params); /* If is a browser, can't detect pings, so request protocol heartbeats */ params.heartbeats = Platform.Config.useProtocolHeartbeats; - this.wsHost = Defaults.getHost(params.options, params.host, true); + this.wsHost = params.host as string; } static isAvailable() { diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 48920ca6cf..16d3738112 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -28,8 +28,8 @@ type CompleteDefaults = IDefaults & { connectionStateTtl: number; realtimeRequestTimeout: number; recvTimeout: number; - preferenceConnectTimeout: number; - parallelUpgradeDelay: number; + webSocketConnectTimeout: number; + webSocketSlowTimeout: number; }; httpMaxRetryCount: number; maxMessageSize: number; @@ -41,7 +41,7 @@ type CompleteDefaults = IDefaults & { getHttpScheme(options: ClientOptions): string; environmentFallbackHosts(environment: string): string[]; getFallbackHosts(options: NormalisedClientOptions): string[]; - getHosts(options: NormalisedClientOptions): string[]; + getHosts(options: NormalisedClientOptions, ws?: boolean): string[]; checkHost(host: string): void; getRealtimeHost(options: ClientOptions, production: boolean, environment: string): string; objectifyOptions( @@ -80,8 +80,8 @@ const Defaults = { connectionStateTtl: 120000, realtimeRequestTimeout: 10000, recvTimeout: 90000, - preferenceConnectTimeout: 6000, - parallelUpgradeDelay: 6000, + webSocketConnectTimeout: 10000, + webSocketSlowTimeout: 4000, }, httpMaxRetryCount: 3, maxMessageSize: 65536, @@ -136,8 +136,9 @@ export function getFallbackHosts(options: NormalisedClientOptions): string[] { return fallbackHosts ? Utils.arrChooseN(fallbackHosts, httpMaxRetryCount) : []; } -export function getHosts(options: NormalisedClientOptions): string[] { - return [options.restHost].concat(getFallbackHosts(options)); +export function getHosts(options: NormalisedClientOptions, ws?: boolean): string[] { + const hosts = [options.restHost].concat(getFallbackHosts(options)); + return ws ? hosts.map((host) => getHost(options, host, true)) : hosts; } function checkHost(host: string): void { diff --git a/src/common/types/IDefaults.d.ts b/src/common/types/IDefaults.d.ts index 1fd02bf5da..5e2e12d37d 100644 --- a/src/common/types/IDefaults.d.ts +++ b/src/common/types/IDefaults.d.ts @@ -3,9 +3,7 @@ import { RestAgentOptions } from './ClientOptions'; export default interface IDefaults { connectivityCheckUrl: string; + wsConnectivityUrl: string; defaultTransports: TransportName[]; - baseTransportOrder: TransportName[]; - transportPreferenceOrder: TransportName[]; - upgradeTransports: TransportName[]; restAgentOptions?: RestAgentOptions; } diff --git a/src/common/types/IPlatformConfig.d.ts b/src/common/types/IPlatformConfig.d.ts index 65b757b7dc..7ceeddc1c8 100644 --- a/src/common/types/IPlatformConfig.d.ts +++ b/src/common/types/IPlatformConfig.d.ts @@ -30,7 +30,6 @@ export interface ISpecificPlatformConfig { fetchSupported?: boolean; xhrSupported?: boolean; allowComet?: boolean; - streamingSupported?: boolean; ArrayBuffer?: typeof ArrayBuffer | false; atob?: typeof atob | null; TextEncoder?: typeof TextEncoder; diff --git a/src/platform/nativescript/config.js b/src/platform/nativescript/config.js index 842b8af527..db739aaf30 100644 --- a/src/platform/nativescript/config.js +++ b/src/platform/nativescript/config.js @@ -24,7 +24,6 @@ var Config = { WebSocket: WebSocket, xhrSupported: XMLHttpRequest, allowComet: true, - streamingSupported: false, useProtocolHeartbeats: true, supportsBinary: typeof TextDecoder !== 'undefined' && TextDecoder, preferBinary: false, // Motivation as on web; see `preferBinary` comment there. diff --git a/src/platform/nodejs/lib/util/defaults.ts b/src/platform/nodejs/lib/util/defaults.ts index 8b29953cbd..200d9e8fd6 100644 --- a/src/platform/nodejs/lib/util/defaults.ts +++ b/src/platform/nodejs/lib/util/defaults.ts @@ -3,12 +3,10 @@ import { TransportNames } from '../../../../common/constants/TransportName'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', + wsConnectivityUrl: 'wss://ws-up.ably-realtime.com', /* Note: order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's supported. */ defaultTransports: [TransportNames.WebSocket], - baseTransportOrder: [TransportNames.Comet, TransportNames.WebSocket], - transportPreferenceOrder: [TransportNames.Comet, TransportNames.WebSocket], - upgradeTransports: [TransportNames.WebSocket], restAgentOptions: { maxSockets: 40, keepAlive: true }, }; diff --git a/src/platform/react-native/config.ts b/src/platform/react-native/config.ts index 7e92eaa59c..8a0be88127 100644 --- a/src/platform/react-native/config.ts +++ b/src/platform/react-native/config.ts @@ -13,7 +13,6 @@ export default function (bufferUtils: typeof BufferUtils): IPlatformConfig { WebSocket: WebSocket, xhrSupported: true, allowComet: true, - streamingSupported: true, useProtocolHeartbeats: true, supportsBinary: !!(typeof TextDecoder !== 'undefined' && TextDecoder), preferBinary: false, // Motivation as on web; see `preferBinary` comment there. diff --git a/src/platform/web/config.ts b/src/platform/web/config.ts index 5af56e62e6..ec2c9b6362 100644 --- a/src/platform/web/config.ts +++ b/src/platform/web/config.ts @@ -41,7 +41,6 @@ const Config: IPlatformConfig = { fetchSupported: !!globalObject.fetch, xhrSupported: globalObject.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest(), allowComet: allowComet(), - streamingSupported: true, useProtocolHeartbeats: true, supportsBinary: !!globalObject.TextDecoder, /* Per Paddy (https://ably-real-time.slack.com/archives/CURL4U2FP/p1705674537763479) web intentionally prefers JSON to MessagePack: diff --git a/src/platform/web/lib/util/defaults.ts b/src/platform/web/lib/util/defaults.ts index 2ff50682b2..c2bfd52ecc 100644 --- a/src/platform/web/lib/util/defaults.ts +++ b/src/platform/web/lib/util/defaults.ts @@ -3,13 +3,11 @@ import { TransportNames } from 'common/constants/TransportName'; const Defaults: IDefaults = { connectivityCheckUrl: 'https://internet-up.ably-realtime.com/is-the-internet-up.txt', + wsConnectivityUrl: 'wss://ws-up.ably-realtime.com', /* Order matters here: the base transport is the leftmost one in the * intersection of baseTransportOrder and the transports clientOption that's * supported. */ defaultTransports: [TransportNames.XhrPolling, TransportNames.WebSocket], - baseTransportOrder: [TransportNames.XhrPolling, TransportNames.WebSocket], - transportPreferenceOrder: [TransportNames.XhrPolling, TransportNames.WebSocket], - upgradeTransports: [TransportNames.WebSocket], }; export default Defaults; diff --git a/test/browser/connection.test.js b/test/browser/connection.test.js index 36c14c90ba..870b0a9f78 100644 --- a/test/browser/connection.test.js +++ b/test/browser/connection.test.js @@ -369,50 +369,11 @@ define(['shared_helper', 'chai'], function (helper, chai) { monitorConnection(done, realtime); }); - it('use_persisted_transport0', function (done) { - var transportPreferenceName = 'ably-transport-preference'; - window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'web_socket' })); - - var realtime = helper.AblyRealtime(); - - realtime.connection.connectionManager.on(function (transport) { - if (this.event === 'transport.active') { - try { - expect(transport.shortName).to.equal('web_socket'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); - } - }); - monitorConnection(done, realtime); - }); - - it('use_persisted_transport1', function (done) { - window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: 'xhr_polling' })); - - var realtime = helper.AblyRealtime(); - - realtime.connection.connectionManager.on(function (transport) { - if (this.event === 'transport.active') { - try { - expect(transport.shortName).to.equal('xhr_polling'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); - } - }); - monitorConnection(done, realtime); - }); - it('browser_transports', function (done) { var realtime = helper.AblyRealtime(); try { expect(realtime.connection.connectionManager.baseTransport).to.equal('xhr_polling'); - expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal(['web_socket']); + expect(realtime.connection.connectionManager.webSocketTransportAvailable).to.be.ok; } catch (err) { closeAndFinish(done, realtime, err); return; diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 9f922fc669..0cfd8ce1d1 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -154,7 +154,7 @@ define([ } /* testFn is assumed to be a function of realtimeOptions that returns a mocha test */ - function testOnAllTransports(name, testFn, excludeUpgrade, skip) { + function testOnAllTransports(name, testFn, skip) { var itFn = skip ? it.skip : it; let transports = availableTransports; transports.forEach(function (transport) { @@ -167,18 +167,16 @@ define([ testFn({ transports: [transport], useBinaryProtocol: false }), ); }); - /* Plus one for no transport specified (ie use upgrade mechanism if + /* Plus one for no transport specified (ie use websocket/base mechanism if * present). (we explicitly specify all transports since node only does - * nodecomet+upgrade if comet is explicitly requested + * websocket+nodecomet if comet is explicitly requested) * */ - if (!excludeUpgrade) { - itFn(name + '_with_binary_transport', testFn({ transports, useBinaryProtocol: true })); - itFn(name + '_with_text_transport', testFn({ transports, useBinaryProtocol: false })); - } + itFn(name + '_with_binary_transport', testFn({ transports, useBinaryProtocol: true })); + itFn(name + '_with_text_transport', testFn({ transports, useBinaryProtocol: false })); } - testOnAllTransports.skip = function (name, testFn, excludeUpgrade) { - testOnAllTransports(name, testFn, excludeUpgrade, true); + testOnAllTransports.skip = function (name, testFn) { + testOnAllTransports(name, testFn, true); }; function restTestOnJsonMsgpack(name, testFn, skip) { diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index f8fd8ade1d..c339edef3d 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -282,9 +282,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, true, ); - /* NB upgrade is excluded because realtime now sends an ATTACHED - * post-upgrade, which can race with the DETACHED if the DETACH is only sent - * just after upgrade. Re-include it with 1.1 spec which has IDs in ATTACHs */ /* * Attach with an empty channel and expect a channel error @@ -685,10 +682,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async }, function (cb) { var channelUpdated = false; - // attached or update: in case this is happening in parallel with - // a transport upgrade, we might flip to attaching, meaning it'll come - // through as attached not update - channel._allChannelChanges.on(['attached', 'update'], function () { + channel._allChannelChanges.on(['update'], function () { channelUpdated = true; }); diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index 413902b25e..058b6a646e 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -165,7 +165,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* * Check that a message published on one transport that has not yet been * acked will be republished with the same msgSerial on a new transport (eg - * after a resume or an upgrade), before any new messages are send (and + * after a resume), before any new messages are send (and * without being merged with new messages) */ it('connectionQueuing', function (done) { diff --git a/test/realtime/event_emitter.test.js b/test/realtime/event_emitter.test.js index 3c4d9be3ce..435a7e1ac4 100644 --- a/test/realtime/event_emitter.test.js +++ b/test/realtime/event_emitter.test.js @@ -25,10 +25,7 @@ define(['shared_helper', 'chai'], function (helper, chai) { */ it('attachdetach0', function (done) { try { - /* Note: realtime now sends an ATTACHED post-upgrade, which can race with - * the DETACHED if the DETACH is only sent just after upgrade. Remove - * bestTransport with 1.1 spec which has IDs in ATTACHs */ - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime(), index, expectedConnectionEvents = ['connecting', 'connected', 'closing', 'closed'], expectedChannelEvents = ['attaching', 'attached', 'detaching', 'detached']; diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index fc4d299a5c..9bace8d5bb 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -60,7 +60,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async .map(function (transport) { return failure_test([transport]); }) - .concat(failure_test(null)), // to test not specifying a transport (so will use upgrade mechanism) + .concat(failure_test(null)), // to test not specifying a transport (so will use websocket/base mechanism) function (err, realtimes) { closeAndFinish(done, realtimes, err); }, @@ -98,7 +98,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async .map(function (transport) { return break_test([transport]); }) - .concat(break_test(null)), // to test not specifying a transport (so will use upgrade mechanism) + .concat(break_test(null)), // to test not specifying a transport (so will use websocket/base mechanism) function (err, realtimes) { closeAndFinish(done, realtimes, err); }, @@ -127,7 +127,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * the suspended timeout trips before three connection cycles */ disconnectedRetryTimeout: 1000, realtimeRequestTimeout: 50, - preferenceConnectTimeout: 50, + webSocketConnectTimeout: 50, suspendedRetryTimeout: 1000, connectionStateTtl: 2900, }); @@ -173,7 +173,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async .map(function (transport) { return lifecycleTest([transport]); }) - .concat(lifecycleTest(null)), // to test not specifying a transport (so will use upgrade mechanism) + .concat(lifecycleTest(null)), // to test not specifying a transport (so will use websocket/base mechanism) function (err) { if (err) { done(err); diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 5298f9af4d..bca2f44f81 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -31,7 +31,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { realtime.connection.on('connected', function () { /* check api version */ var transport = realtime.connection.connectionManager.activeProtocol.transport; - var connectUri = helper.isWebsocket(transport) ? transport.uri : transport.recvRequest.uri; + var connectUri = helper.isWebsocket(transport) ? transport.uri : transport.recvRequest.recvUri; try { expect(connectUri.indexOf('v=3') > -1, 'Check uri includes v=3').to.be.ok; } catch (err) { @@ -309,14 +309,14 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { } }); - /* Check base and upgrade transports (nodejs only; browser tests in their own section) */ + /* Check base and websocket transports (nodejs only; browser tests in their own section) */ if (!isBrowser) { it('node_transports', function (done) { var realtime; try { realtime = helper.AblyRealtime({ transports: helper.availableTransports }); expect(realtime.connection.connectionManager.baseTransport).to.equal('comet'); - expect(realtime.connection.connectionManager.upgradeTransports).to.deep.equal(['web_socket']); + expect(realtime.connection.connectionManager.webSocketTransportAvailable).to.be.ok; closeAndFinish(done, realtime); } catch (err) { closeAndFinish(done, realtime, err); @@ -331,9 +331,9 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var keyStr = helper.getTestApp().keys[0].keyStr; var realtime = helper.AblyRealtime({ key: keyStr, useTokenAuth: true }); realtime.connection.connectionManager.once('transport.pending', function (state) { - var transport = realtime.connection.connectionManager.pendingTransports[0], + var transport = realtime.connection.connectionManager.pendingTransport, originalOnProtocolMessage = transport.onProtocolMessage; - realtime.connection.connectionManager.pendingTransports[0].onProtocolMessage = function (message) { + realtime.connection.connectionManager.pendingTransport.onProtocolMessage = function (message) { try { if (message.action === 4) { expect(message.connectionDetails.connectionKey).to.be.ok; @@ -371,13 +371,14 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { var realtime = helper.AblyRealtime({ httpMaxRetryCount: 3, fallbackHosts: ['a', 'b', 'c'], + transport: ['web_socket'], }); realtime.connection.once('connected', function () { try { var hosts = new Ably.Rest._Http()._getHosts(realtime); /* restHost rather than realtimeHost as that's what connectionManager * knows about; converted to realtimeHost by the websocketTransport */ - expect(hosts[0]).to.equal(realtime.options.restHost, 'Check connected realtime host is the first option'); + expect(hosts[0]).to.equal(realtime.options.realtimeHost, 'Check connected realtime host is the first option'); expect(hosts.length).to.equal(4, 'Check also have three fallbacks'); } catch (err) { closeAndFinish(done, realtime, err); diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 1855aeb12f..688d4b6b07 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1708,8 +1708,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Enter ten clients while attaching, finish the attach, check they were all entered correctly */ it('multiple_pending', function (done) { - /* single transport to avoid upgrade stalling due to the stubbed attachImpl */ - var realtime = helper.AblyRealtime({ transports: [helper.bestTransport] }), + var realtime = helper.AblyRealtime(), channel = realtime.channels.get('multiple_pending'), originalAttachImpl = channel.attachImpl; diff --git a/test/realtime/resume.test.js b/test/realtime/resume.test.js index c7f2d19b0c..812db0f668 100644 --- a/test/realtime/resume.test.js +++ b/test/realtime/resume.test.js @@ -138,15 +138,11 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); } - testOnAllTransports( - 'resume_inactive', - function (realtimeOpts) { - return function (done) { - resume_inactive(done, 'resume_inactive' + String(Math.random()), {}, realtimeOpts); - }; - }, - /* excludeUpgrade: */ true, - ); + testOnAllTransports('resume_inactive', function (realtimeOpts) { + return function (done) { + resume_inactive(done, 'resume_inactive' + String(Math.random()), {}, realtimeOpts); + }; + }); /** * Simple resume case @@ -259,15 +255,11 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { }); } - testOnAllTransports( - 'resume_active', - function (realtimeOpts) { - return function (done) { - resume_active(done, 'resume_active' + String(Math.random()), {}, realtimeOpts); - }; - }, - /* excludeUpgrade: */ true, - ); + testOnAllTransports('resume_active', function (realtimeOpts) { + return function (done) { + resume_active(done, 'resume_active' + String(Math.random()), {}, realtimeOpts); + }; + }); /* RTN15c3 * Resume with loss of continuity @@ -547,8 +539,7 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { * connection was > connectionStateTtl ago */ it('no_resume_last_activity', function (done) { - /* Specify a best transport so that upgrade activity doesn't reset the last activity timer */ - var realtime = helper.AblyRealtime({ transports: [bestTransport] }), + var realtime = helper.AblyRealtime(), connection = realtime.connection, connectionManager = connection.connectionManager; diff --git a/test/realtime/transports.test.js b/test/realtime/transports.test.js new file mode 100644 index 0000000000..155e2d563c --- /dev/null +++ b/test/realtime/transports.test.js @@ -0,0 +1,230 @@ +'use strict'; + +define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai, Ably) { + const expect = chai.expect; + const closeAndFinish = helper.closeAndFinish; + const monitorConnection = helper.monitorConnection; + const whenPromiseSettles = helper.whenPromiseSettles; + const Defaults = Ably.Rest.Platform.Defaults; + const originialWsCheckUrl = Defaults.wsConnectivityUrl; + const transportPreferenceName = 'ably-transport-preference'; + const localStorageSupported = globalThis.localStorage; + const urlScheme = 'https://'; + const echoServer = 'echo.ably.io'; + const failUrl = urlScheme + echoServer + '/respondwith?status=500'; + const availableTransports = helper.availableTransports; + const defaultTransports = new Ably.Realtime({ key: 'xxx:yyy', autoConnect: false }).connection.connectionManager + .transports; + const baseTransport = new Ably.Realtime({ key: 'xxx:yyy', autoConnect: false, transports: availableTransports }) + .connection.connectionManager.baseTransport; + const mixin = helper.Utils.mixin; + + function restoreWsConnectivityUrl() { + Defaults.wsConnectivityUrl = originialWsCheckUrl; + } + + const Config = Ably.Rest.Platform.Config; + const oldWs = Config.WebSocket; + + function restoreWebSocketConstructor() { + Config.WebSocket = oldWs; + } + + // drop in replacement for WebSocket which doesn't emit any events (same behaviour as when WebSockets upgrade headers are removed by a proxy) + class FakeWebSocket { + close() {} + } + + describe('realtime/transports', function () { + this.timeout(60 * 1000); + + before(function (done) { + helper.setupApp(function (err) { + if (err) { + done(err); + return; + } + done(); + }); + }); + + afterEach(restoreWsConnectivityUrl); + afterEach(restoreWebSocketConstructor); + + if (helper.availableTransports.length > 1) { + // ensure comet transport is used for nodejs tests + function options(opts) { + return mixin( + { + transports: helper.availableTransports, + }, + opts || {}, + ); + } + + it('websocket_is_default', function (done) { + const realtime = helper.AblyRealtime(options()); + + realtime.connection.on('connected', function () { + try { + expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal('web_socket'); + } catch (err) { + closeAndFinish(done, realtime, err); + } + closeAndFinish(done, realtime); + }); + + monitorConnection(done, realtime); + }); + + it('no_ws_connectivity', function (done) { + Config.WebSocket = FakeWebSocket; + const realtime = helper.AblyRealtime(options({ webSocketSlowTimeout: 1000, webSocketConnectTimeout: 3000 })); + + realtime.connection.on('connected', function () { + try { + expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal(baseTransport); + // check that transport preference is set + if (localStorageSupported) { + expect(window.localStorage.getItem(transportPreferenceName)).to.equal( + JSON.stringify({ value: baseTransport }), + ); + } + } catch (err) { + closeAndFinish(done, realtime, err); + } + closeAndFinish(done, realtime); + }); + + monitorConnection(done, realtime); + }); + + it('ws_primary_host_fails', function (done) { + const goodHost = helper.AblyRest().options.realtimeHost; + const realtime = helper.AblyRealtime( + options({ realtimeHost: helper.unroutableAddress, fallbackHosts: [goodHost] }), + ); + + realtime.connection.on('connected', function () { + expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal('web_socket'); + closeAndFinish(done, realtime); + }); + + monitorConnection(done, realtime); + }); + + it('no_internet_connectivity', function (done) { + Config.WebSocket = FakeWebSocket; + const realtime = helper.AblyRealtime(options({ connectivityCheckUrl: failUrl, webSocketSlowTimeout: 1000 })); + + // expect client to transition to disconnected rather than attempting base transport (which would succeed in this instance) + realtime.connection.on('disconnected', function () { + closeAndFinish(done, realtime); + }); + }); + + it('no_websocket_or_base_transport', function (done) { + Config.WebSocket = FakeWebSocket; + const realtime = helper.AblyRealtime({ + transports: ['web_socket'], + realtimeRequestTimeout: 3000, + webSocketConnectTimeout: 3000, + }); + + realtime.connection.on('disconnected', function () { + closeAndFinish(done, realtime); + }); + }); + + if (localStorageSupported) { + it('base_transport_preference', function (done) { + window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: baseTransport })); + const realtime = helper.AblyRealtime(options()); + + // make ws connectivity check only resolve after connected with base transport. + // prevents a race condition where the wsConnectivity check succeeds before base transport is activated; + // in this case the base transport would be abandoned in favour of websocket + realtime.connection.connectionManager.checkWsConnectivity = function () { + return new Promise((resolve) => { + realtime.connection.once('connected', () => { + resolve(); + }); + }); + }; + + realtime.connection.on('connected', function () { + try { + expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal(baseTransport); + } catch (err) { + closeAndFinish(done, realtime, err); + } + closeAndFinish(done, realtime); + }); + monitorConnection(done, realtime); + }); + + it('transport_preference_reset_while_connecting', function (done) { + window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: baseTransport })); + const realtime = helper.AblyRealtime(options()); + + // make ws connectivity check fast so that it succeeds while base transport is still connecting + realtime.connection.connectionManager.checkWsConnectivity = function () { + return new Promise((resolve) => { + setTimeout(() => resolve(), 1); + }); + }; + + realtime.connection.once('connected', function () { + try { + expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal('web_socket'); + expect(realtime.connection.connectionManager.getTransportPreference()).to.equal('web_socket'); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }); + monitorConnection(done, realtime); + }); + + it('transport_preference_reset_after_connected', function (done) { + window.localStorage.setItem(transportPreferenceName, JSON.stringify({ value: baseTransport })); + const realtime = helper.AblyRealtime(options()); + + // make ws connectivity check only resolve after connected with base transport + realtime.connection.connectionManager.checkWsConnectivity = function () { + return new Promise((resolve) => { + realtime.connection.once('connected', () => { + try { + expect(realtime.connection.connectionManager.activeProtocol.transport.shortName).to.equal( + baseTransport, + ); + resolve(); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + }); + }); + }; + + realtime.connection.once('connected', function () { + // the checkWsConnectivity promise won't execute .then callbacks synchronously upon resolution + // so we need to wait one tick before the transport preference is unpersisted + setTimeout(() => { + try { + // ensure base transport preference is erased + expect(realtime.connection.connectionManager.getTransportPreference()).to.equal(null); + } catch (err) { + closeAndFinish(done, realtime, err); + return; + } + closeAndFinish(done, realtime); + }, 0); + }); + monitorConnection(done, realtime); + }); + } + } + }); +}); diff --git a/test/realtime/upgrade.test.js b/test/realtime/upgrade.test.js deleted file mode 100644 index 94a081e455..0000000000 --- a/test/realtime/upgrade.test.js +++ /dev/null @@ -1,709 +0,0 @@ -'use strict'; - -define(['shared_helper', 'async', 'chai', 'ably'], function (helper, async, chai, Ably) { - var expect = chai.expect; - var rest; - var publishIntervalHelper = function (currentMessageNum, channel, dataFn, onPublish) { - return function (currentMessageNum) { - whenPromiseSettles(channel.publish('event0', dataFn()), function () { - onPublish(); - }); - }; - }; - var publishAtIntervals = function (numMessages, channel, dataFn, onPublish) { - for (var i = numMessages; i > 0; i--) { - setTimeout(publishIntervalHelper(i, channel, dataFn, onPublish), 2 * i); - } - }; - var closeAndFinish = helper.closeAndFinish; - var monitorConnection = helper.monitorConnection; - var bestTransport = helper.bestTransport; - var whenPromiseSettles = helper.whenPromiseSettles; - - if (bestTransport === 'web_socket') { - describe('realtime/upgrade', function () { - this.timeout(60 * 1000); - - before(function (done) { - helper.setupApp(function (err) { - if (err) { - done(err); - return; - } - rest = helper.AblyRest(); - done(); - }); - }); - - afterEach(helper.clearTransportPreference); - - /* - * Publish once with REST, before upgrade, verify message received - */ - it('publishpreupgrade', function (done) { - var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - try { - var realtime = helper.AblyRealtime(transportOpts); - /* connect and attach */ - realtime.connection.on('connected', function () { - //console.log('publishpreupgrade: connected'); - var testMsg = 'Hello world'; - var rtChannel = realtime.channels.get('publishpreupgrade'); - whenPromiseSettles(rtChannel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - - /* subscribe to event */ - rtChannel.subscribe('event0', function (msg) { - try { - expect(msg.data).to.equal(testMsg, 'Unexpected msg text received'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); - }); - - /* publish event */ - var restChannel = rest.channels.get('publishpreupgrade'); - whenPromiseSettles(restChannel.publish('event0', testMsg), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - } - }); - }); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* - * Publish once with REST, after upgrade, verify message received on active transport - */ - it('publishpostupgrade0', function (done) { - var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - try { - var realtime = helper.AblyRealtime(transportOpts); - - /* subscribe to event */ - var rtChannel = realtime.channels.get('publishpostupgrade0'); - rtChannel.subscribe('event0', function (msg) { - try { - expect(msg.data).to.equal(testMsg, 'Unexpected msg text received'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - var closeFn = function () { - closeAndFinish(done, realtime); - }; - if (isBrowser) setTimeout(closeFn, 0); - else process.nextTick(closeFn); - }); - - /* publish event */ - var testMsg = 'Hello world'; - var restChannel = rest.channels.get('publishpostupgrade0'); - var connectionManager = realtime.connection.connectionManager; - connectionManager.on('transport.active', function (transport) { - //console.log('publishpostupgrade0: transport active: transport = ' + transport); - if (transport.toString().match(/wss?\:/)) { - if (rtChannel.state == 'attached') { - //console.log('*** publishpostupgrade0: publishing (channel attached on transport active) ...'); - whenPromiseSettles(restChannel.publish('event0', testMsg), function (err) { - //console.log('publishpostupgrade0: publish returned err = ' + displayError(err)); - if (err) { - closeAndFinish(done, realtime, err); - } - }); - } else { - rtChannel.on('attached', function () { - //console.log('*** publishpostupgrade0: publishing (channel attached after wait) ...'); - whenPromiseSettles(restChannel.publish('event0', testMsg), function (err) { - //console.log('publishpostupgrade0: publish returned err = ' + displayError(err)); - if (err) { - closeAndFinish(done, realtime, err); - } - }); - }); - } - } - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* - * Publish once with REST, after upgrade, verify message not received on inactive transport - */ - it('publishpostupgrade1', function (done) { - var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - try { - var realtime = helper.AblyRealtime(transportOpts); - - /* subscribe to event */ - var rtChannel = realtime.channels.get('publishpostupgrade1'); - rtChannel.subscribe('event0', function (msg) { - try { - expect(msg.data).to.equal(testMsg, 'Unexpected msg text received'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - var closeFn = function () { - closeAndFinish(done, realtime); - }; - if (isBrowser) setTimeout(closeFn, 0); - else process.nextTick(closeFn); - }); - - /* publish event */ - var testMsg = 'Hello world'; - var restChannel = rest.channels.get('publishpostupgrade1'); - var connectionManager = realtime.connection.connectionManager; - connectionManager.on('transport.active', function (transport) { - if (helper.isComet(transport)) { - /* override the processing of incoming messages on this channel - * so we can see if a message arrived. - * NOTE: this relies on knowledge of the internal implementation - * of the transport */ - - var originalOnProtocolMessage = transport.onProtocolMessage; - transport.onProtocolMessage = function (message) { - if (message.messages) { - closeAndFinish(done, realtime, new Error('Message received on comet transport')); - return; - } - originalOnProtocolMessage.apply(this, arguments); - }; - } - }); - connectionManager.on('transport.active', function (transport) { - if (helper.isWebsocket(transport)) { - if (rtChannel.state == 'attached') { - //console.log('*** publishing (channel attached on transport active) ...'); - restChannel.publish('event0', testMsg); - } else { - rtChannel.on('attached', function () { - //console.log('*** publishing (channel attached after wait) ...'); - restChannel.publish('event0', testMsg); - }); - } - } - }); - - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /** - * Publish and subscribe, text protocol - */ - it('upgradepublish0', function (done) { - var count = 10; - var cbCount = 10; - var checkFinish = function () { - if (count <= 0 && cbCount <= 0) { - closeAndFinish(done, realtime); - } - }; - var onPublish = function () { - --cbCount; - checkFinish(); - }; - var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; - var realtime = helper.AblyRealtime(transportOpts); - var channel = realtime.channels.get('upgradepublish0'); - /* subscribe to event */ - whenPromiseSettles( - channel.subscribe('event0', function () { - --count; - checkFinish(); - }), - function () { - var dataFn = function () { - return 'Hello world at: ' + new Date(); - }; - publishAtIntervals(count, channel, dataFn, onPublish); - }, - ); - }); - - /** - * Publish and subscribe, binary protocol - */ - it('upgradepublish1', function (done) { - var count = 10; - var cbCount = 10; - var checkFinish = function () { - if (count <= 0 && cbCount <= 0) { - closeAndFinish(done, realtime); - } - }; - var onPublish = function () { - --cbCount; - checkFinish(); - }; - var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - var realtime = helper.AblyRealtime(transportOpts); - var channel = realtime.channels.get('upgradepublish1'); - /* subscribe to event */ - whenPromiseSettles( - channel.subscribe('event0', function () { - --count; - checkFinish(); - }), - function () { - var dataFn = function () { - return 'Hello world at: ' + new Date(); - }; - publishAtIntervals(count, channel, dataFn, onPublish); - }, - ); - }); - - /* - * Base upgrade case - */ - it('upgradebase0', function (done) { - var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - var cometDeactivated = false; - try { - var realtime = helper.AblyRealtime(transportOpts); - /* check that we see the transport we're interested in get activated, - * and that we see the comet transport deactivated */ - var failTimer = setTimeout(function () { - closeAndFinish(done, realtime, new Error('upgrade heartbeat failed (timer expired)')); - }, 120000); - - var connectionManager = realtime.connection.connectionManager; - connectionManager.once('transport.inactive', function (transport) { - if (transport.toString().indexOf('/comet/') > -1) cometDeactivated = true; - }); - connectionManager.on('transport.active', function (transport) { - if (transport.toString().match(/wss?\:/)) { - clearTimeout(failTimer); - var closeFn = function () { - try { - expect(cometDeactivated).to.be.ok; - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - closeAndFinish(done, realtime); - }; - if (isBrowser) { - setTimeout(closeFn, 0); - } else { - process.nextTick(closeFn); - } - } - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* - * Check active heartbeat, text protocol - */ - it('upgradeheartbeat0', function (done) { - var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; - try { - var realtime = helper.AblyRealtime(transportOpts); - - /* when we see the transport we're interested in get activated, - * listen for the heartbeat event */ - var failTimer; - var connectionManager = realtime.connection.connectionManager; - connectionManager.on('transport.active', function (transport) { - if (transport.toString().match(/wss?\:/)) - transport.on('heartbeat', function () { - transport.off('heartbeat'); - clearTimeout(failTimer); - closeAndFinish(done, realtime); - }); - transport.ping(); - }); - - realtime.connection.on('connected', function () { - failTimer = setTimeout(function () { - closeAndFinish(done, realtime, new Error('upgrade heartbeat failed (timer expired)')); - }, 120000); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* - * Check active heartbeat, binary protocol - */ - it('upgradeheartbeat1', function (done) { - var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - try { - var realtime = helper.AblyRealtime(transportOpts); - - /* when we see the transport we're interested in get activated, - * listen for the heartbeat event */ - var failTimer; - var connectionManager = realtime.connection.connectionManager; - connectionManager.on('transport.active', function (transport) { - if (transport.toString().match(/wss?\:/)) - transport.on('heartbeat', function () { - transport.off('heartbeat'); - clearTimeout(failTimer); - closeAndFinish(done, realtime); - }); - transport.ping(); - }); - - realtime.connection.on('connected', function () { - failTimer = setTimeout(function () { - closeAndFinish(done, realtime, new Error('upgrade heartbeat failed (timer expired)')); - }, 120000); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* - * Check heartbeat does not fire on inactive transport, text protocol - */ - it('upgradeheartbeat2', function (done) { - var transportOpts = { useBinaryProtocol: false, transports: helper.availableTransports }; - try { - var realtime = helper.AblyRealtime(transportOpts); - - /* when we see the transport we're interested in get activated, - * listen for the heartbeat event */ - var failTimer, cometTransport, wsTransport; - var connectionManager = realtime.connection.connectionManager; - connectionManager.on('transport.active', function (transport) { - var transportDescription = transport.toString(); - //console.log('active transport: ' + transportDescription); - if (transportDescription.indexOf('/comet/') > -1) { - cometTransport = transport; - cometTransport.on('heartbeat', function () { - closeAndFinish(done, realtime, new Error('verify heartbeat does not fire on inactive transport')); - }); - } - if (transportDescription.match(/wss?\:/)) { - wsTransport = transport; - wsTransport.on('heartbeat', function () { - clearTimeout(failTimer); - /* wait a couple of seconds to give it time - * in case it might still fire */ - setTimeout(function () { - closeAndFinish(done, realtime); - }, 2000); - }); - wsTransport.ping(); - } - }); - - realtime.connection.on('connected', function () { - failTimer = setTimeout(function () { - closeAndFinish(done, realtime, new Error('upgrade heartbeat failed (timer expired)')); - }, 120000); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* - * Check heartbeat does not fire on inactive transport, binary protocol - */ - it('upgradeheartbeat3', function (done) { - var transportOpts = { useBinaryProtocol: true, transports: helper.availableTransports }; - try { - var realtime = helper.AblyRealtime(transportOpts); - - /* when we see the transport we're interested in get activated, - * listen for the heartbeat event */ - var failTimer, cometTransport, wsTransport; - var connectionManager = realtime.connection.connectionManager; - connectionManager.on('transport.active', function (transport) { - var transportDescription = transport.toString(); - //console.log('active transport: ' + transportDescription); - if (transportDescription.indexOf('/comet/') > -1) { - cometTransport = transport; - cometTransport.on('heartbeat', function () { - closeAndFinish(done, realtime, new Error('verify heartbeat does not fire on inactive transport')); - }); - } - if (transportDescription.match(/wss?\:/)) { - wsTransport = transport; - wsTransport.on('heartbeat', function () { - clearTimeout(failTimer); - /* wait a couple of seconds to give it time - * in case it might still fire */ - setTimeout(function () { - closeAndFinish(done, realtime); - }, 2000); - }); - wsTransport.ping(); - } - }); - - realtime.connection.on('connected', function () { - failTimer = setTimeout(function () { - closeAndFinish(done, realtime, new Error('upgrade heartbeat failed (timer expired)')); - }, 120000); - }); - monitorConnection(done, realtime); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - it('unrecoverableUpgrade', function (done) { - var realtime, - fakeConnectionKey = '_____!ablyjs_test_fake-key____', - fakeConnectionId = 'ablyjs_tes'; - - try { - /* on base transport active */ - realtime = helper.AblyRealtime({ transports: helper.availableTransports }); - realtime.connection.connectionManager.once('transport.active', function (transport) { - expect( - transport.toString().indexOf('/comet/') > -1, - 'assert first transport to become active is a comet transport', - ).to.be.ok; - try { - expect(realtime.connection.errorReason).to.equal(null, 'Check connection.errorReason is initially null'); - /* sabotage the upgrade */ - realtime.connection.connectionManager.connectionKey = fakeConnectionKey; - realtime.connection.connectionManager.connectionId = fakeConnectionId; - } catch (err) { - closeAndFinish(done, realtiem, err); - return; - } - - /* on upgrade failure */ - realtime.connection.once('update', function (stateChange) { - try { - expect(stateChange.reason.code).to.equal(80018, 'Check correct (unrecoverable connection) error'); - expect(stateChange.current).to.equal('connected', 'Check current is connected'); - expect(realtime.connection.errorReason.code).to.equal( - 80018, - 'Check error set in connection.errorReason', - ); - expect(realtime.connection.state).to.equal('connected', 'Check still connected'); - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - - /* Check events not still paused */ - var channel = realtime.channels.get('unrecoverableUpgrade'); - whenPromiseSettles(channel.attach(), function (err) { - if (err) { - closeAndFinish(done, realtime, err); - return; - } - channel.subscribe(function (msg) { - closeAndFinish(done, realtime); - }); - channel.publish('msg', null); - }); - }); - }); - } catch (err) { - closeAndFinish(done, realtime, err); - } - }); - - /* - * Check that a message that fails to publish on a comet transport can be - * seamlessly transferred to the websocket transport and published there - */ - it('message_timeout_stalling_upgrade', function (done) { - var realtime = helper.AblyRealtime({ transports: helper.availableTransports, httpRequestTimeout: 3000 }), - channel = realtime.channels.get('timeout1'), - connectionManager = realtime.connection.connectionManager; - - realtime.connection.once('connected', function () { - /* Sabotage comet sending */ - var transport = connectionManager.activeProtocol.getTransport(); - try { - expect(helper.isComet(transport), 'Check active transport is still comet').to.be.ok; - } catch (err) { - closeAndFinish(done, realtime, err); - return; - } - transport.sendUri = helper.unroutableAddress; - - async.parallel( - [ - function (cb) { - channel.subscribe('event', function () { - cb(); - }); - }, - function (cb) { - whenPromiseSettles(channel.publish('event', null), function (err) { - try { - expect(!err, 'Successfully published message').to.be.ok; - } catch (err) { - cb(err); - return; - } - cb(); - }); - }, - ], - function (err) { - closeAndFinish(done, realtime, err); - }, - ); - }); - }); - - /* - * Check that after a successful upgrade, the transport pref is persisted, - * and subsequent connections do not upgrade - */ - it('persist_transport_prefs', function (done) { - var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), - connection = realtime.connection, - connectionManager = connection.connectionManager; - - async.series( - [ - function (cb) { - connectionManager.once('transport.active', function (transport) { - try { - expect(helper.isComet(transport), 'Check first transport to become active is comet').to.be.ok; - } catch (err) { - cb(err); - return; - } - cb(); - }); - }, - function (cb) { - connectionManager.once('transport.active', function (transport) { - try { - expect(helper.isWebsocket(transport), 'Check second transport to become active is ws').to.be.ok; - } catch (err) { - cb(err); - return; - } - cb(); - }); - }, - function (cb) { - connection.once('closed', function () { - cb(); - }); - Ably.Realtime.Platform.Config.nextTick(function () { - connection.close(); - }); - }, - function (cb) { - connectionManager.once('transport.active', function (transport) { - try { - expect( - helper.isWebsocket(transport), - 'Check first transport to become active the second time round is websocket', - ).to.be.ok; - } catch (err) { - cb(err); - return; - } - cb(); - }); - connection.connect(); - }, - ], - function (err) { - closeAndFinish(done, realtime, err); - }, - ); - }); - - /* - * Check that upgrades succeed even if the original transport dies before the sync - */ - it('upgrade_original_transport_dies', function (done) { - var realtime = helper.AblyRealtime({ transports: helper.availableTransports }), - connection = realtime.connection, - connectionManager = connection.connectionManager; - - async.series( - [ - function (cb) { - connectionManager.once('transport.active', function (transport) { - try { - expect(helper.isComet(transport), 'Check first transport to become active is comet').to.be.ok; - } catch (err) { - cb(err); - return; - } - cb(); - }); - }, - function (cb) { - connectionManager.on('transport.pending', function (transport) { - connectionManager.off('transport.pending'); - /* Abort comet transport nonfatally */ - var baseTransport = connectionManager.activeProtocol.getTransport(); - try { - expect(helper.isComet(baseTransport), 'Check original transport is still comet').to.be.ok; - } catch (err) { - cb(err); - return; - } - /* Check that if we do get a statechange, it's to connecting, not disconnected. */ - var stateChangeListener = function (stateChange) { - try { - expect(stateChange.current).to.equal( - 'connecting', - 'check that deactivateTransport only drops us to connecting as another transport is ready for activation', - ); - } catch (err) { - cb(err); - } - }; - connection.once(stateChangeListener); - connectionManager.once('connectiondetails', function () { - connection.off(stateChangeListener); - /* Check the upgrade completed */ - var newActiveTransport = connectionManager.activeProtocol.getTransport(); - try { - expect(transport).to.equal(newActiveTransport, 'Check the upgrade transport is now active'); - } catch (err) { - cb(err); - return; - } - cb(); - }); - transport.once('connected', function () { - baseTransport.disconnect({ code: 50000, statusCode: 500, message: 'a non-fatal transport error' }); - }); - }); - }, - ], - function (err) { - closeAndFinish(done, realtime, err); - }, - ); - }); - }); - } -}); diff --git a/test/support/browser_file_list.js b/test/support/browser_file_list.js index 03ff54169b..4b2c4df0c0 100644 --- a/test/support/browser_file_list.js +++ b/test/support/browser_file_list.js @@ -43,7 +43,7 @@ window.__testFiles__.files = { 'test/realtime/reauth.test.js': true, 'test/realtime/resume.test.js': true, 'test/realtime/sync.test.js': true, - 'test/realtime/upgrade.test.js': true, + 'test/realtime/transports.test.js': true, 'test/rest/auth.test.js': true, 'test/rest/bufferutils.test.js': true, 'test/rest/capability.test.js': true, diff --git a/test/support/root_hooks.js b/test/support/root_hooks.js index 0576bc9a64..8b3d59e704 100644 --- a/test/support/root_hooks.js +++ b/test/support/root_hooks.js @@ -12,4 +12,5 @@ define(['shared_helper'], function (helper) { afterEach(helper.closeActiveClients); afterEach(helper.logTestResults); + beforeEach(helper.clearTransportPreference); }); From 12131f9d9865412739df2fb9ececa1a2b4bb18c3 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Wed, 13 Mar 2024 17:43:27 +0000 Subject: [PATCH 454/468] build: use esbuild for nodejs bundle --- Gruntfile.js | 61 ++++++++++++----------------------------- grunt/esbuild/build.js | 62 ++++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 21 -------------- 3 files changed, 80 insertions(+), 64 deletions(-) create mode 100644 grunt/esbuild/build.js diff --git a/Gruntfile.js b/Gruntfile.js index 2c5b45099d..41d2f87383 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,11 +4,9 @@ var fs = require('fs'); var path = require('path'); var webpackConfig = require('./webpack.config'); var esbuild = require('esbuild'); -var umdWrapper = require('esbuild-plugin-umd-wrapper'); -var banner = require('./src/fragments/license'); var process = require('process'); -var stripLogsPlugin = require('./grunt/esbuild/strip-logs').default; var MochaServer = require('./test/web_server'); +var esbuildConfig = require('./grunt/esbuild/build'); module.exports = function (grunt) { grunt.loadNpmTasks('grunt-webpack'); @@ -49,7 +47,6 @@ module.exports = function (grunt) { dirs: dirs, webpack: { all: Object.values(webpackConfig), - node: [webpackConfig.node], browser: [webpackConfig.browser, webpackConfig.browserMin, webpackConfig.mochaJUnitReporterBrowser], }, }; @@ -76,11 +73,7 @@ module.exports = function (grunt) { }); }); - grunt.registerTask('build', ['checkGitSubmodules', 'webpack:all', 'build:browser']); - - grunt.registerTask('build:node', ['checkGitSubmodules', 'webpack:node']); - - grunt.registerTask('build:browser', ['checkGitSubmodules', 'webpack:browser']); + grunt.registerTask('build', ['checkGitSubmodules', 'webpack:all', 'build:browser', 'build:node']); grunt.registerTask('all', ['build', 'requirejs']); @@ -99,44 +92,26 @@ module.exports = function (grunt) { }); }); + grunt.registerTask('build:node', function () { + const done = this.async(); + + esbuild + .build(esbuildConfig.nodeConfig) + .then(() => { + done(true); + }) + .catch((err) => { + done(err); + }); + }); + grunt.registerTask('build:browser', function () { var done = this.async(); - function createBaseConfig() { - return { - entryPoints: ['src/platform/web/index.ts'], - outfile: 'build/ably.js', - bundle: true, - sourcemap: true, - format: 'umd', - banner: { js: '/*' + banner + '*/' }, - plugins: [umdWrapper.default({ libraryName: 'Ably', amdNamedModule: false })], - target: 'es2017', - }; - } - - function createModularConfig() { - return { - // We need to create a new copy of the base config, because calling - // esbuild.build() with the base config causes it to mutate the passed - // config’s `banner.js` property to add some weird modules shim code, - // which we don’t want here. - ...createBaseConfig(), - entryPoints: ['src/platform/web/modular.ts'], - outfile: 'build/modular/index.mjs', - format: 'esm', - plugins: [stripLogsPlugin], - }; - } - Promise.all([ - esbuild.build(createBaseConfig()), - esbuild.build({ - ...createBaseConfig(), - outfile: 'build/ably.min.js', - minify: true, - }), - esbuild.build(createModularConfig()), + esbuild.build(esbuildConfig.webConfig), + esbuild.build(esbuildConfig.minifiedWebConfig), + esbuild.build(esbuildConfig.modularConfig), ]).then(() => { console.log('esbuild succeeded'); done(true); diff --git a/grunt/esbuild/build.js b/grunt/esbuild/build.js new file mode 100644 index 0000000000..262644ca53 --- /dev/null +++ b/grunt/esbuild/build.js @@ -0,0 +1,62 @@ +const banner = require('../../src/fragments/license'); +const umdWrapper = require('esbuild-plugin-umd-wrapper'); +const stripLogsPlugin = require('./strip-logs').default; + +// We need to create a new copy of the base config each time, because calling +// esbuild.build() with the base config causes it to mutate the passed +// config’s `banner.js` property to add some weird modules shim code, +// which we don’t want here. +function createBaseConfig() { + return { + bundle: true, + sourcemap: true, + format: 'umd', + banner: { js: '/*' + banner + '*/' }, + plugins: [umdWrapper.default({ libraryName: 'Ably', amdNamedModule: false })], + target: 'es2017', + }; +} + +const webConfig = { + ...createBaseConfig(), + entryPoints: ['src/platform/web/index.ts'], + outfile: 'build/ably.js', +}; + +const minifiedWebConfig = { + ...createBaseConfig(), + entryPoints: ['src/platform/web/index.ts'], + outfile: 'build/ably.min.js', + minify: true, +}; + +const modularConfig = { + ...createBaseConfig(), + entryPoints: ['src/platform/web/modular.ts'], + outfile: 'build/modular/index.mjs', + format: 'esm', + plugins: [stripLogsPlugin], +}; + +const nodeConfig = { + ...createBaseConfig(), + platform: 'node', + entryPoints: ['src/platform/nodejs/index.ts'], + outfile: 'build/ably-node.js', + /* + * in order to support both named, and default exports in commonjs, esbuild + * will export named exports by name on module.exports and default exports on + * module.exports.default. Since we export everything on the default export + * object anyway, we can just ignore named exports and only export the default. + * Without this footer, you would need to require('ably').default.Realtime to + * access client constructors. + */ + footer: { js: 'module.exports = module.exports.default;' }, +}; + +module.exports = { + webConfig, + minifiedWebConfig, + modularConfig, + nodeConfig, +}; diff --git a/webpack.config.js b/webpack.config.js index e32013f942..e1a056a131 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -44,26 +44,6 @@ function platformPath(platform, ...dir) { return path.resolve(__dirname, 'src', 'platform', platform, ...dir); } -const nodeConfig = { - ...baseConfig, - entry: { - index: platformPath('nodejs'), - }, - output: { - ...baseConfig.output, - filename: 'ably-node.js', - }, - target: ['node', 'es2017'], - externals: { - got: true, - ws: true, - }, - optimization: { - minimize: false, - }, - devtool: 'source-map', -}; - const nativeScriptConfig = { ...baseConfig, output: { @@ -157,7 +137,6 @@ function createMochaJUnitReporterConfig() { } module.exports = { - node: nodeConfig, nativeScript: nativeScriptConfig, reactNative: reactNativeConfig, mochaJUnitReporterBrowser: createMochaJUnitReporterConfig(), From f94d587d4c402bb158c15adb9ecb435bff9910de Mon Sep 17 00:00:00 2001 From: owenpearson Date: Wed, 13 Mar 2024 17:44:38 +0000 Subject: [PATCH 455/468] build: catch esbuild errors for web builds --- Gruntfile.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 41d2f87383..a19bc21ea3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -112,10 +112,14 @@ module.exports = function (grunt) { esbuild.build(esbuildConfig.webConfig), esbuild.build(esbuildConfig.minifiedWebConfig), esbuild.build(esbuildConfig.modularConfig), - ]).then(() => { - console.log('esbuild succeeded'); - done(true); - }); + ]) + .then(() => { + console.log('esbuild succeeded'); + done(true); + }) + .catch((err) => { + done(err); + }); }); grunt.registerTask('test:webserver', 'Launch the Mocha test web server on http://localhost:3000/', [ From 7f67f6850487815e978abccaa210f85e13818dd9 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Wed, 20 Mar 2024 11:49:45 +0000 Subject: [PATCH 456/468] build: use module.exports for node --- grunt/esbuild/build.js | 9 --------- src/platform/nodejs/index.ts | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/grunt/esbuild/build.js b/grunt/esbuild/build.js index 262644ca53..9be1152642 100644 --- a/grunt/esbuild/build.js +++ b/grunt/esbuild/build.js @@ -43,15 +43,6 @@ const nodeConfig = { platform: 'node', entryPoints: ['src/platform/nodejs/index.ts'], outfile: 'build/ably-node.js', - /* - * in order to support both named, and default exports in commonjs, esbuild - * will export named exports by name on module.exports and default exports on - * module.exports.default. Since we export everything on the default export - * object anyway, we can just ignore named exports and only export the default. - * Without this footer, you would need to require('ably').default.Realtime to - * access client constructors. - */ - footer: { js: 'module.exports = module.exports.default;' }, }; module.exports = { diff --git a/src/platform/nodejs/index.ts b/src/platform/nodejs/index.ts index d53b83dadd..057d412e66 100644 --- a/src/platform/nodejs/index.ts +++ b/src/platform/nodejs/index.ts @@ -41,7 +41,7 @@ if (Platform.Config.agent) { Platform.Defaults.agent += ' ' + Platform.Config.agent; } -export default { +module.exports = { ErrorInfo, Rest: DefaultRest, Realtime: DefaultRealtime, From bb31bd1aece8e356142cdbc417aab9e2fb488de3 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 20 Mar 2024 10:28:53 -0300 Subject: [PATCH 457/468] Remove some lingering `any` from `stats()` param type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This appears to be the result of me not incorporating e524d68’s changes into ff7cb41, probably a botched merge conflict resolution. --- ably.d.ts | 4 ++-- modular.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 6afba3d0f3..886a02c426 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2686,7 +2686,7 @@ export declare class Rest implements RestClient { body?: any[] | any, headers?: any, ): Promise>; - stats(params?: StatsParams | any): Promise>; + stats(params?: StatsParams): Promise>; time(): Promise; batchPublish(spec: BatchPublishSpec): Promise>; batchPublish( @@ -2741,7 +2741,7 @@ export declare class Realtime implements RealtimeClient { body?: any[] | any, headers?: any, ): Promise>; - stats(params?: StatsParams | any): Promise>; + stats(params?: StatsParams): Promise>; time(): Promise; batchPublish(spec: BatchPublishSpec): Promise>; batchPublish( diff --git a/modular.d.ts b/modular.d.ts index 45bd3ece26..a62de692dc 100644 --- a/modular.d.ts +++ b/modular.d.ts @@ -290,7 +290,7 @@ export declare class BaseRest implements RestClient { body?: any[] | any, headers?: any, ): Promise>; - stats(params?: StatsParams | any): Promise>; + stats(params?: StatsParams): Promise>; time(): Promise; batchPublish(spec: BatchPublishSpec): Promise>; batchPublish( @@ -343,7 +343,7 @@ export declare class BaseRealtime implements RealtimeClient { body?: any[] | any, headers?: any, ): Promise>; - stats(params?: StatsParams | any): Promise>; + stats(params?: StatsParams): Promise>; time(): Promise; batchPublish(spec: BatchPublishSpec): Promise>; batchPublish( From 54487f76b71890332f9f90b20198b846378bde52 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 20 Mar 2024 14:06:21 +0000 Subject: [PATCH 458/468] Use `useLayoutEffect` when calling `.setOptions()` in `ChannelProvider` This is a temporary fix for #1704. This does not fix the underlying problem of ably-js sending two `ATTACH` messages with the same options when we call `.setOptions()` in `ChannelProvider`. What `useLayoutEffect` accomplishes is it runs before children components' `useEffect` hooks, thus it sets options for a channel before attachment process starts, so there will be no need for reattachment and sending a second `ATTACH` message. --- src/platform/react-hooks/src/ChannelProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/react-hooks/src/ChannelProvider.tsx b/src/platform/react-hooks/src/ChannelProvider.tsx index 91465fa59b..9e6a0c0f30 100644 --- a/src/platform/react-hooks/src/ChannelProvider.tsx +++ b/src/platform/react-hooks/src/ChannelProvider.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useLayoutEffect, useMemo } from 'react'; import * as Ably from 'ably'; import { type AblyContextValue, AblyContext } from './AblyContext.js'; import { channelOptionsWithAgent } from './AblyReactHooks.js'; @@ -44,7 +44,7 @@ export const ChannelProvider = ({ }; }, [derived, client, channel, channelName, _channelNameToChannelContext, ablyId, context]); - useEffect(() => { + useLayoutEffect(() => { channel.setOptions(channelOptionsWithAgent(options)); }, [channel, options]); From 7778ea059039be96dc752ddbc010f59a237d6744 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Mar 2024 15:29:55 -0300 Subject: [PATCH 459/468] Add missed file to format:check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (It’s already in `format`). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dd77fe2af..124cf85ac2 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "lint:fix": "eslint --fix .", "prepare": "npm run build", "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md grunt", - "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s grunt", + "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md grunt", "sourcemap": "source-map-explorer build/ably.min.js", "modulereport": "tsc --noEmit --esModuleInterop scripts/moduleReport.ts && esr scripts/moduleReport.ts", "docs": "typedoc" From 1916b4c251d999504012fcb825416dc90dfbc562 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Mar 2024 15:33:05 -0300 Subject: [PATCH 460/468] Format all the Markdown in docs/ --- docs/react-migration-guide.md | 25 +-- docs/react.md | 280 +++++++++++++++++----------------- package.json | 4 +- 3 files changed, 161 insertions(+), 148 deletions(-) diff --git a/docs/react-migration-guide.md b/docs/react-migration-guide.md index 6bc1705c2e..cf648835da 100644 --- a/docs/react-migration-guide.md +++ b/docs/react-migration-guide.md @@ -9,12 +9,14 @@ Since these hooks now return more values we've opted to change them to return ob You can still access the return values using simple destructuring syntax like in the below example: ```jsx -const { channel, ably } = useChannel("your-channel-name", (message) => { /* ... */ }); +const { channel, ably } = useChannel('your-channel-name', (message) => { + /* ... */ +}); -const { presenceData, updateStatus } = usePresence("your-channel-name"); +const { presenceData, updateStatus } = usePresence('your-channel-name'); ``` -### Replacing `configureAbly` with `AblyProvider` +### Replacing `configureAbly` with `AblyProvider` In versions 1 and 2 of our react-hooks, we exported a function called `configureAbly` which was used to register an Ably client instance to global state. This caused a few issues (most notably it made the hooks difficult to use with hot module reloading), so we have replaced the global configuration function with a context provider (`AblyProvider`) @@ -22,29 +24,32 @@ The simplest way to use the context provider is to create your own ably-js clien All child components of the `AblyProvider` will then be able to use the hooks, making use of the provided Ably client instance. For this reason, we recommend putting the `AblyProvider` high up in your component tree, surrounding all components which may need to use Ably hooks. For example, replace this: + ```jsx configureAbly(options); ``` With this: + ```jsx const client = new Ably.Realtime(options); -return - {children} - +return {children}; ``` If you were already using multiple Ably clients in the same react application, you may pass in an optional `id` prop to the provider, which you can then pass to the hooks to specify which Ably client instance the hook should use: + ```jsx const client = new Ably.Realtime(options); -return - {children} - +return ( + + {children} + +); // in a child component: -useChannel({channelName: 'my_channel', id: 'foo'}, (msg) => { +useChannel({ channelName: 'my_channel', id: 'foo' }, (msg) => { console.log(msg); }); ``` diff --git a/docs/react.md b/docs/react.md index a1fde416fe..68995dbbd0 100644 --- a/docs/react.md +++ b/docs/react.md @@ -28,8 +28,7 @@ The hooks provide a simplified syntax for interacting with Ably, and manage the - [usePresence](#usepresence) - [usePresenceListener](#usepresencelistener) - ---- +## ### Compatible React Versions @@ -42,16 +41,16 @@ Start by connecting your app to Ably using the `AblyProvider` component. See the The `AblyProvider` should be high in your component tree, wrapping every component which needs to access Ably. ```jsx -import { AblyProvider } from "ably/react"; -import * as Ably from "ably"; +import { AblyProvider } from 'ably/react'; +import * as Ably from 'ably'; -const client = new Ably.Realtime({ key: "your-ably-api-key", clientId: 'me' }); +const client = new Ably.Realtime({ key: 'your-ably-api-key', clientId: 'me' }); root.render( - -) +
    , +); ``` ### Define Ably channels @@ -67,8 +66,8 @@ Once you've set up `AblyProvider`, define Ably channels you want to use by utili After setting up `ChannelProvider`, you can employ the provided `hooks` in your code. Here's a basic example: ```javascript -const { channel } = useChannel("your-channel-name", (message) => { - console.log(message); +const { channel } = useChannel('your-channel-name', (message) => { + console.log(message); }); ``` @@ -113,8 +112,8 @@ return ( The useChannel hook lets you subscribe to an [Ably Channel](https://ably.com/docs/channels) and receive messages from it. ```javascript -const { channel, ably } = useChannel("your-channel-name", (message) => { - console.log(message); +const { channel, ably } = useChannel('your-channel-name', (message) => { + console.log(message); }); ``` @@ -124,8 +123,8 @@ const { channel, ably } = useChannel("your-channel-name", (message) => { ```javascript const [messages, updateMessages] = useState([]); -const { channel } = useChannel("your-channel-name", (message) => { - updateMessages((prev) => [...prev, message]); +const { channel } = useChannel('your-channel-name', (message) => { + updateMessages((prev) => [...prev, message]); }); // Convert the messages to list items to render in a react component @@ -135,8 +134,8 @@ const messagePreviews = messages.map((msg, index) =>
  • {msg.data.s `useChannel` supports all of the parameter combinations of a regular call to `channel.subscribe`, so you can filter the messages you subscribe to by providing a `message type` to the `useChannel` function: ```javascript -useChannel("your-channel-name", "test-message", (message) => { - console.log(message); // Only logs messages sent using the `test-message` message type +useChannel('your-channel-name', 'test-message', (message) => { + console.log(message); // Only logs messages sent using the `test-message` message type }); ``` @@ -145,8 +144,8 @@ useChannel("your-channel-name", "test-message", (message) => { The `publish` function returned by `useChannel` can be used to send messages to the channel. ```javascript -const { publish } = useChannel("your-channel-name") -publish("test-message", { text: "message text" }); +const { publish } = useChannel('your-channel-name'); +publish('test-message', { text: 'message text' }); ``` #### useChannel `channel` instance @@ -158,13 +157,13 @@ By providing both the channel instance and the Ably SDK instance through our use For instance, you can easily fetch the history of the channel using the following method: ```javascript -const { channel } = useChannel("your-channel-name", (message) => { - console.log(message); +const { channel } = useChannel('your-channel-name', (message) => { + console.log(message); }); const history = channel.history((err, result) => { - var lastMessage = resultPage.items[0]; - console.log('Last message: ' + lastMessage.id + ' - ' + lastMessage.data); + var lastMessage = resultPage.items[0]; + console.log('Last message: ' + lastMessage.id + ' - ' + lastMessage.data); }); ``` @@ -175,19 +174,19 @@ const history = channel.history((err, result) => { The usePresence hook [enters the presence set on a channel](https://ably.com/docs/presence-occupancy/presence) and enables you to send presence updates for current client. To find out more about Presence, see the [Presence documentation](https://ably.com/docs/presence-occupancy/presence). ```javascript -const { updateStatus } = usePresence("your-channel-name"); +const { updateStatus } = usePresence('your-channel-name'); // The `updateStatus` function can be used to update the presence data for the current client -updateStatus("status"); +updateStatus('status'); ``` You can optionally provide a second parameter when you `usePresence` to set an initial `presence data`. ```javascript -const { updateStatus } = usePresence("your-channel-name", "initial state"); +const { updateStatus } = usePresence('your-channel-name', 'initial state'); // The `updateStatus` function can be used to update the presence data for the current client -updateStatus("new status"); +updateStatus('new status'); ``` The new state will be sent to the channel, and all clients subscribed to the channel (including current, if you've subscribed to updates using [`usePresenceListener` hook](#usepresencelistener)) will be notified of the change immediately. @@ -195,34 +194,36 @@ The new state will be sent to the channel, and all clients subscribed to the cha `usePresence` supports objects and numbers, as well as strings: ```javascript -usePresence("your-channel-name", { foo: "bar" }); -usePresence("another-channel-name", 123); -usePresence("third-channel-name", "initial status"); +usePresence('your-channel-name', { foo: 'bar' }); +usePresence('another-channel-name', 123); +usePresence('third-channel-name', 'initial status'); ``` If you're using `TypeScript` there are type hints to make sure that value passed to `updateStatus` is of the same `type` as your initial constraint, or a provided generic type parameter: ```tsx const TypedUsePresenceComponent = () => { - // In this example MyPresenceType will be used for type checking updateStatus function. - // If omitted, the shape of the initial value will be used, and if that's omitted, `any` will be the default. + // In this example MyPresenceType will be used for type checking updateStatus function. + // If omitted, the shape of the initial value will be used, and if that's omitted, `any` will be the default. - const { updateStatus } = usePresence("testChannelName", { foo: "bar" }); + const { updateStatus } = usePresence('testChannelName', { foo: 'bar' }); - return ( - - ); -} + return ( + + ); +}; interface MyPresenceType { - foo: string; + foo: string; } ``` @@ -232,13 +233,17 @@ interface MyPresenceType { The usePresenceListener hook [subscribes you to presence 'enter', 'update' and 'leave' events on a channel](https://ably.com/docs/presence-occupancy/presence?lang=javascript#subscribe) - this will allow you to get notified when a user joins or leaves the channel, or updates its presence data. To find out more about Presence, see the [Presence documentation](https://ably.com/docs/presence-occupancy/presence). -**Please note** that fetching present members is executed as an effect, so it'll load in *after* your component renders for the first time. +**Please note** that fetching present members is executed as an effect, so it'll load in _after_ your component renders for the first time. ```javascript -const { presenceData } = usePresenceListener("your-channel-name"); +const { presenceData } = usePresenceListener('your-channel-name'); // Convert presence data to the list of items to render -const membersData = presenceData.map((msg, index) =>
  • {msg.clientId}: {msg.data}
  • ); +const membersData = presenceData.map((msg, index) => ( +
  • + {msg.clientId}: {msg.data} +
  • +)); ``` The `usePresenceListener` hook returns an array of presence messages - each message is a regular Ably JavaScript SDK `PresenceMessage` instance. @@ -246,8 +251,8 @@ The `usePresenceListener` hook returns an array of presence messages - each mess If you don't want to use the `presenceData` returned from `usePresenceListener`, you can configure a callback: ```javascript -usePresenceListener("your-channel-name", (presenceUpdate) => { - console.log(presenceUpdate); +usePresenceListener('your-channel-name', (presenceUpdate) => { + console.log(presenceUpdate); }); ``` @@ -255,25 +260,25 @@ If you're using `TypeScript` there are type hints to make sure that presence dat ```tsx const TypedUsePresenceListenerComponent = () => { - // In this example MyPresenceType will be used for presenceData type hints. - // If that's omitted, `any` will be the default. - - const { presenceData } = usePresenceListener("testChannelName"); - - const membersData = presenceData.map((presenceMsg, index) => { - return ( -
  • - {/* you will have Intellisense for presenceMsg.data of type MyPresenceType here */} - {presenceMsg.clientId} - {presenceMsg.data.foo} -
  • - ); - }); + // In this example MyPresenceType will be used for presenceData type hints. + // If that's omitted, `any` will be the default. - return
      {membersData}
    ; -} + const { presenceData } = usePresenceListener('testChannelName'); + + const membersData = presenceData.map((presenceMsg, index) => { + return ( +
  • + {/* you will have Intellisense for presenceMsg.data of type MyPresenceType here */} + {presenceMsg.clientId} - {presenceMsg.data.foo} +
  • + ); + }); + + return
      {membersData}
    ; +}; interface MyPresenceType { - foo: string; + foo: string; } ``` @@ -285,10 +290,10 @@ The `useConnectionStateListener` hook lets you attach a listener to be notified ```javascript useConnectionStateListener((stateChange) => { - console.log(stateChange.current) // the new connection state - console.log(stateChange.previous) // the previous connection state - console.log(stateChange.reason) // if applicable, an error indicating the reason for the connection state change -}) + console.log(stateChange.current); // the new connection state + console.log(stateChange.previous); // the previous connection state + console.log(stateChange.reason); // if applicable, an error indicating the reason for the connection state change +}); ``` You can also pass in a filter to only listen to a set of connection states: @@ -304,10 +309,10 @@ The `useChannelStateListener` hook lets you attach a listener to be notified of ```javascript useChannelStateListener((stateChange) => { - console.log(stateChange.current) // the new channel state - console.log(stateChange.previous) // the previous channel state - console.log(stateChange.reason) // if applicable, an error indicating the reason for the channel state change -}) + console.log(stateChange.current); // the new channel state + console.log(stateChange.previous); // the previous channel state + console.log(stateChange.reason); // if applicable, an error indicating the reason for the channel state change +}); ``` You can also pass in a filter to only listen to a set of channel states: @@ -341,18 +346,25 @@ if (connectionError) { } else if (channelError) { // TODO: handle channel errors } else { - return + return ; } ``` Alternatively, you can also pass callbacks to the hooks to be called when the client encounters an error: ```js -useChannel({ - channelName: 'my_channel', - onConnectionError: (err) => { /* handle connection error */ }, - onChannelError: (err) => { /* handle channel error */ }, -}, messageHandler); +useChannel( + { + channelName: 'my_channel', + onConnectionError: (err) => { + /* handle connection error */ + }, + onChannelError: (err) => { + /* handle channel error */ + }, + }, + messageHandler, +); ``` ### Usage with multiple clients @@ -365,8 +377,8 @@ root.render( -
    -) +
    , +); ``` This `ablyId` can then be passed in to each hook to specify which client to use. @@ -376,14 +388,14 @@ const ablyId = 'providerOne'; const client = useAbly(ablyId); -useChannel({ channelName: "your-channel-name", ablyId }, (message) => { - console.log(message); +useChannel({ channelName: 'your-channel-name', ablyId }, (message) => { + console.log(message); }); -usePresence({ channelName: "your-channel-name", ablyId }, "initial state"); +usePresence({ channelName: 'your-channel-name', ablyId }, 'initial state'); -usePresenceListener({ channelName: "your-channel-name", ablyId }, (presenceUpdate) => { - // ... +usePresenceListener({ channelName: 'your-channel-name', ablyId }, (presenceUpdate) => { + // ... }); ``` @@ -396,8 +408,8 @@ To address this, the `skip` parameter allows you to control the mounting behavio ```tsx const [skip, setSkip] = useState(true); -const { channel, publish } = useChannel({ channelName: "your-channel-name", skip }, (message) => { - updateMessages((prev) => [...prev, message]); +const { channel, publish } = useChannel({ channelName: 'your-channel-name', skip }, (message) => { + updateMessages((prev) => [...prev, message]); }); ``` @@ -412,39 +424,39 @@ This parameter is useful in next situations: Consider a scenario where a component uses the `useChannel` hook, but the user's authentication status is determined asynchronously: ```tsx -import React, { useEffect, useState } from "react"; -import { useChannel } from "ably/react"; -import * as Ably from "ably"; +import React, { useEffect, useState } from 'react'; +import { useChannel } from 'ably/react'; +import * as Ably from 'ably'; const ChatComponent = () => { - const [isUserAuthenticated, setIsUserAuthenticated] = useState(false); - const [messages, updateMessages] = useState([]); - - // Simulate asynchronous authentication - useEffect(() => { - async function authenticate() { - const isAuthenticated = await someAuthenticationFunction(); - setIsUserAuthenticated(isAuthenticated); - } - authenticate(); - }, []); + const [isUserAuthenticated, setIsUserAuthenticated] = useState(false); + const [messages, updateMessages] = useState([]); + + // Simulate asynchronous authentication + useEffect(() => { + async function authenticate() { + const isAuthenticated = await someAuthenticationFunction(); + setIsUserAuthenticated(isAuthenticated); + } + authenticate(); + }, []); - // useChannel with skip parameter - useChannel({ channelName: "your-channel-name", skip: !isUserAuthenticated }, (message) => { - updateMessages((prev) => [...prev, message]); - }); + // useChannel with skip parameter + useChannel({ channelName: 'your-channel-name', skip: !isUserAuthenticated }, (message) => { + updateMessages((prev) => [...prev, message]); + }); - if (!isUserAuthenticated) { - return

    Please log in to join the chat.

    ; - } + if (!isUserAuthenticated) { + return

    Please log in to join the chat.

    ; + } - return ( -
    - {messages.map((message, index) => ( -

    {message.data.text}

    - ))} -
    - ); + return ( +
    + {messages.map((message, index) => ( +

    {message.data.text}

    + ))} +
    + ); }; ``` @@ -453,31 +465,27 @@ const ChatComponent = () => { In an application with both free and premium features, you might want to conditionally use channels based on the user's subscription status: ```tsx -import React from "react"; -import { useChannel } from "ably/react"; -import * as Ably from "ably"; +import React from 'react'; +import { useChannel } from 'ably/react'; +import * as Ably from 'ably'; interface PremiumFeatureComponentProps { - isPremiumUser: boolean; + isPremiumUser: boolean; } const PremiumFeatureComponent = ({ isPremiumUser }: PremiumFeatureComponentProps) => { - const [messages, updateMessages] = useState([]); + const [messages, updateMessages] = useState([]); - // Skip attaching to the channel if the user is not a premium subscriber - useChannel({ channelName: "premium-feature-channel", skip: !isPremiumUser }, (message) => { - updateMessages((prev) => [...prev, message]); - }); + // Skip attaching to the channel if the user is not a premium subscriber + useChannel({ channelName: 'premium-feature-channel', skip: !isPremiumUser }, (message) => { + updateMessages((prev) => [...prev, message]); + }); - if (!isPremiumUser) { - return

    This feature is available for premium users only.

    ; - } + if (!isPremiumUser) { + return

    This feature is available for premium users only.

    ; + } - return ( -
    - {/* Render premium feature based on messages */} -
    - ); + return
    {/* Render premium feature based on messages */}
    ; }; ``` @@ -513,9 +521,9 @@ module.exports = { webpack: (config) => { config.externals.push({ 'utf-8-validate': 'commonjs utf-8-validate', - 'bufferutil': 'commonjs bufferutil', - }) - return config + bufferutil: 'commonjs bufferutil', + }); + return config; }, -} +}; ``` diff --git a/package.json b/package.json index 124cf85ac2..9b976a7c04 100644 --- a/package.json +++ b/package.json @@ -143,8 +143,8 @@ "lint": "eslint .", "lint:fix": "eslint --fix .", "prepare": "npm run build", - "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md grunt", - "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md grunt", + "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/**/*.md grunt", + "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modular.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/**/*.md grunt", "sourcemap": "source-map-explorer build/ably.min.js", "modulereport": "tsc --noEmit --esModuleInterop scripts/moduleReport.ts && esr scripts/moduleReport.ts", "docs": "typedoc" From 9ee56819cdb4f59cb21e601cc2d37e2f71e92405 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 20 Mar 2024 08:48:54 -0300 Subject: [PATCH 461/468] Introduce directory structure for migration guides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’ll add the v2 migration guide for the library at docs/migration-guides/v2/lib.md. And then in #1672 I anticipate Andrii adding the v2 migration guide for React Hooks at docs/migration-guides/v2/react-hooks.md. --- .../v1/react-hooks.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{react-migration-guide.md => migration-guides/v1/react-hooks.md} (100%) diff --git a/docs/react-migration-guide.md b/docs/migration-guides/v1/react-hooks.md similarity index 100% rename from docs/react-migration-guide.md rename to docs/migration-guides/v1/react-hooks.md From 5356141f42ae8529f138ada5e64b9a36ef6beec7 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 20 Mar 2024 13:28:59 -0300 Subject: [PATCH 462/468] =?UTF-8?q?Update=20documentation=20for=20`request?= =?UTF-8?q?()`=E2=80=99s=20version=20param?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Taken from sdk-api-reference commit 7cc5a28 (see [1], not merged yet). [1] https://github.com/ably/sdk-api-reference/pull/40 --- ably.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 886a02c426..0ce97953a3 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -1578,7 +1578,7 @@ export declare interface RestClient { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. - * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. + * @param version - The version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. @@ -1669,7 +1669,7 @@ export declare interface RealtimeClient { * * @param method - The request method to use, such as `GET`, `POST`. * @param path - The request path. - * @param version - The major version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. + * @param version - The version of the Ably REST API to use. See the [REST API reference](https://ably.com/docs/api/rest-api#versioning) for information on versioning. * @param params - The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the [REST API reference](https://ably.com/docs/api/rest-api) for the available parameters of each endpoint. * @param body - The JSON body of the request. * @param headers - Additional HTTP headers to include in the request. From b44e6290b734e11bdadfcdf023aa9caf409375f5 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 7 Mar 2024 15:05:39 -0300 Subject: [PATCH 463/468] Add migration guide for v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on e71ed5f...2e12459. Note that 1.2.50, to which this guide refers, doesn't exist yet. We’ll do that in #1672. The description of the stats response changes comes from Simon in [1], and the description of the removal of connection upgrades comes from Owen in [2]. HTML tags used for headings so that I can specify a fixed `id` to use in fragment links, because I’m probably going to keep editing the headings and so if I rely on the generated headings then I’ll almost certainly end up with a broken link. (GitHub doesn’t seem to support the custom heading ID syntax described in [3].) And we don’t have a linter that checks for broken fragment links. Resolves ECO-1416. [1] https://github.com/ably/ably-js/pull/1670#discussion_r1532093309 [2] https://github.com/ably/ably-js/pull/1670#discussion_r1532206072 [3] https://www.markdownguide.org/extended-syntax/#heading-ids --- docs/migration-guides/v2/lib.md | 412 ++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 docs/migration-guides/v2/lib.md diff --git a/docs/migration-guides/v2/lib.md b/docs/migration-guides/v2/lib.md new file mode 100644 index 0000000000..b0ae1dd788 --- /dev/null +++ b/docs/migration-guides/v2/lib.md @@ -0,0 +1,412 @@ +# Migration guide for ably-js v2 + +Here’s how to migrate from ably-js v1 to v2: + +1. [Stop using functionality that v1 deprecated and which v2 removes](#stop-using-v1-deprecated). +2. [Stop using other functionality that v2 removes](#stop-using-v2-removed). +3. [Update to v2 and handle its breaking changes](#update-to-v2-and-handle-breaking-changes). +4. (Optional) [Stop using functionality that v2 deprecates](#stop-using-v2-deprecated). +5. (Optional) [Take advantage of new features that v2 introduces](#use-v2-new-features). + +

    Stop using functionality that v1 deprecated and which v2 removes

    + +Begin by updating to ably-js 1.2.50 or later, to make sure you see the deprecation log messages [described below](#stop-using-v1-deprecated-general). + +Now, you need to stop using the functionality that is deprecated by v1 and which is removed in v2. Here we explain how. + +The changes below are split into: + +- general changes +- changes that only affect TypeScript users + +If you’re not using any of the deprecated functionality described below, then this section does not affect you. + +

    General changes

    + +In ably-js 1.2.50 or later, use of the following APIs will trigger a deprecation warning at runtime. + +**Note about deprecation warnings:** These deprecation warnings take the form of error-level log messages emitted through the library’s logging mechanism (i.e. `ClientOptions.log.handler` or `ClientOptions.logHandler` if you’ve set these properties, or else `console.log()`). To find them in your logs, search for `Ably: Deprecation warning`. + +- The `log` client option has been removed in v2. Equivalent functionality is provided by the `logLevel` and `logHandler` client options. Update your client options code of the form `{ log: { level: logLevel, handler: logHandler } }` to instead be `{ logLevel, logHandler }`. +- Changes to `Crypto.getDefaultParams()`: + - The ability to pass a callback to this method has been removed in v2. This method now directly returns its result, instead of returning it asynchronously. Update your code so that it uses the return value of this method instead of passing a callback. + - The ability to call this method without specifying an encryption key has been removed in v2. Update your code so that it instead passes an object whose `key` property contains an encryption key. That is, replace `Crypto.getDefaultParams()` with `Crypto.getDefaultParams({ key })`, where `key` is an encryption key that you have generated (for example from the `Crypto.generateRandomKey()` method). + - The ability to pass the encryption key as the first argument of this method has been removed in v2. Update your code so that it instead passes an object whose `key` property contains the key. That is, replace `Crypto.getDefaultParams(key)` with `Crypto.getDefaultParams({ key })`. +- The `fallbackHostsUseDefault` client option has been removed in v2. + - If you’re using this client option to force the library to make use of fallback hosts even though you’ve set the `environment` client option, then this is no longer necessary: remove your usage of the `fallbackHostsUseDefault` client option and the library will then automatically choose the correct fallback hosts to use for the specified environment. + - If you’re using this client option to force the library to make use of fallback hosts even though you’re not using the primary Ably environment, then stop using `fallbackHostsUseDefault`, and update your code to either pass the `environment` client option (in which case the library will automatically choose the correct fallback hosts to use for the specified environment), or to pass the `fallbackHosts` client option to specify a custom list of fallback hosts to use (for example, if you’re using a custom CNAME, in which case Ably will have provided you with an explicit list of fallback hosts). +- The ability to use a boolean value for the `recover` client option has been removed in v2. If you wish for the connection to always be recovered, replace `{ recover: true }` with a function that always passes `true` to its callback: `{ recover: function(lastConnectionDetails, cb) { cb(true); } }`. +- The ability to pass an array of channel mode flags as the first argument of `RealtimeChannel.attach()` has been removed in v2. To set channel mode flags, populate the `modes` property of the channel options object that you pass to `Channels.get()` or `RealtimeChannel.setOptions()`. +- The `force` auth option has been removed in v2. If you’re using this option to force `authorize()` to fetch a new token even if the current token has not expired, this is no longer necessary, as `authorize()` now always fetches a new token. Update your code to no longer pass the `force` auth option. Note that, in general, passing an auth options argument to `authorize()` will overwrite the library’s stored auth options, which may not be what you want. In v1, the library contains a special case behavior where passing an auth options object which only contains `{ force: true }` will _not_ overwrite the stored options. This special case behavior has been removed in v2, so if you’re currently passing `authorize()` an auth options object which only contains `{ force: true }`, you should stop passing it an auth options object entirely. +- The `host` client option has been renamed to `restHost`. Update your code to use `restHost`. +- The `wsHost` client option has been renamed to `realtimeHost`. Update your code to use `realtimeHost`. +- The `queueEvents` client option has been renamed to `queueMessages`. Update your code to use `queueMessages`. +- `RealtimePresence`’s `on` method has been renamed to `subscribe`. Update your code to use `subscribe`. +- `RealtimePresence`’s `off` method has been renamed to `unsubscribe`. Update your code to use `unsubscribe`. +- `Auth`’s `authorise` method has been renamed to `authorize`. Update your code to use `authorize`. +- The `headers` client option has been removed in v2. Remove your use of this client option. + +### Only TypeScript users + +- In v2, the `stats()` method on `Rest` and `Realtime` no longer accepts an argument of type `any`. Make sure that any argument you pass to this method implements the `StatsParams` interface. +- In v2, `fromEncoded` and `fromEncodedArray` types, which were already not being used by the library, are no longer exported by the library. Remove your references to these types. + +

    Stop using other functionality that v2 removes

    + +

    Switch from the callbacks-based variant of the library to the promise-based variant

    + +**Note:** This section is only relevant if you’re not already using v1’s promise-based API. + +ably-js v1 offered a choice between styles of asynchronous programming. There was a variant of the library that implemented asynchronous function calls via callbacks, and another that implemented them via promises. Now that promises are widely supported across modern JavaScript engines, ably-js v2 removes the callbacks variant of the library, and only offers promise-based asynchronicity. + +So, if you’re currently using the callbacks variant of the library, then before upgrading to v2 you should switch to using the promises variant. Here, we explain how to do this. + +#### Choose the promises variant of the library instead of the callbacks variant + +First, you should stop choosing the callbacks variant of the library, and instead choose the promises variant. How exactly you should change your code to achieve this depends on how you’re currently choosing the callbacks variant: + +- If you’re implicitly choosing the callbacks variant of the library by writing `require('ably')`, then update your code to `require('ably/promises')`. +- If you’re explicitly choosing the callbacks variant through the subpath of the imported module — that is, if you’re writing `require('ably/callbacks')` — then update your code to `require('ably/promises')`. +- If you’re explicitly choosing the callbacks variant at the moment of instantiating the client — that is, if you’re writing `new Ably.Rest.Callbacks(…)` or `new Ably.Realtime.Callbacks(…)` — then update your code to use the `Promise` property instead, i.e. write `new Ably.Realtime.Promise(…)` or `new Ably.Rest.Promise(…)`. + +#### Update your code to use the promise-based API + +Now, update your code to use ably-js’s promise-based API instead of the callback-based API. The best way to do this is to consult the [documentation for v1’s promise-based API](https://ably.com/docs/sdk/js/v1.2/promises), to find the documentation for the promise-based version of each method that you’re using in your code. If you don’t want to do this, it’s generally sufficient to understand that the promise-based API differs from the callback-based API in a way that’s consistent across all methods. What follows is a description of this difference. + +Given a method which, in the callback-based API, takes a callback of the form `(err, result)` as its final argument, its equivalent in the promise-based API does not take this final argument. Instead, it returns a promise. If the operation succeeds then this promise will be resolved with a value equivalent to the callback’s `result` argument, and if it fails then this promise will be rejected with a value equivalent to the callback’s `err` argument. + +So, you need to update your code to stop passing this callback, and then make use of the returned promise. In general, JavaScript offers a couple of approaches for interacting with promises, and so now we’ll demonstrate how these apply to an example ably-js method call. + +For example, given the following call to the callbacks-based version of `RestChannel.history()`: + +```javascript +channel.history({ direction: 'forwards' }, (err, paginatedResult) => { + if (err) { + // Perform some sort of error handling + return; + } + + // Make use of paginatedResult +}); +``` + +We could do one of the following: + +1. Use JavaScript’s `await` keyword to retrieve the result of the operation, combined with the `catch` keyword for error handling: + + ```javascript + try { + const paginatedResult = await channel.history({ direction: 'forwards' }); + // Make use of paginatedResult + } catch (err) { + // Perform some sort of error handling + } + ``` + +2. Use the promise’s `then` method to retrieve the result of the operation, combined with its `catch` method for error handling: + + ```javascript + channel + .history({ direction: 'forwards' }) + .then((paginatedResult) => { + // Make use of paginatedResult + }) + .catch((err) => { + // Perform some sort of error handling + }); + ``` + +#### A caveat regarding `Crypto.generateRandomKey()` + +**Important:** For historical reasons, the `Crypto.generateRandomKey()` method does not have a promise-based version in v1. That is, even in the promise-based variant of the SDK, it implements asynchronicity via callbacks. So, if you’re using this method, then you’ll need to keep using the callback-based version of this method until upgrading to v2, which replaces the callback-based variant of this method with a promise-based one. For more information see [here](#switch-to-promise-based-generateRandomKey). + +

    Update to v2 and handle its breaking changes

    + +Next, update to ably-js version 2.0.0 or later. + +Now, you need to address the other breaking changes introduced by v2. Here we explain how. + +The changes below are split into: + +- general changes +- changes that only affect TypeScript users + +Some of these changes are only relevant if you’re using specific features of the library. The guidance below makes it clear when this is the case. + +### General changes + +#### Stop explicitly selecting the promise-based variant of the library + +As [mentioned above](#switch-from-callbacks-to-promises), v2 of the library no longer offers a choice between a callbacks-based API and a promises-based API. This means that v1’s mechanism for choosing which variant to use has been removed, so you should stop using this mechanism. + +- If you’re explicitly choosing the promises variant of the library through the subpath of the imported module — that is, if you’re writing `require('ably/promises')` — then update your code to `require('ably')`. +- If you’re explicitly choosing the promises variant at the moment of instantiating the client — that is, if you’re writing `new Ably.Rest.Promise(…)` or `new Ably.Realtime.Promise(…)` — then update your code to remove the use of the `Promise` property, i.e. write `new Ably.Realtime(…)` or `new Ably.Rest(…)`. + +

    Be aware of platforms that are no longer supported

    + +v2 of the library drops support for some platforms that v1 supported. Most notably, it no longer supports Internet Explorer or Node.js versions below 16. For more information, see [this section of the Readme](../../../README.md#supported-platforms). + +#### Be aware of a new endpoint to add to firewall whitelists + +**Note:** This change is most likely to affect you if you’re already explicitly configuring your firewall (or instructing your users to configure their firewall) to allow realtime connections to Ably, as described in [this FAQ on ably.com](https://faqs.ably.com/if-i-need-to-whitelist-ablys-servers-from-a-firewall-which-ports-ips-and/or-domains-should-i-add). + +When attempting to establish a realtime connection using WebSocket, v2 uses the `wss://ws-up.ably-realtime.com` endpoint to check if WebSocket connectivity is available. Update your firewall whitelist to allow connectivity to that endpoint. + +#### Be aware of changes affecting environments where WebSocket connections may be blocked + +In v1 of ably-js, realtime clients with multiple available transports would initially attempt to connect with the transport most likely to succeed (`xhr_polling` in web browsers). Upon the success of this initial connection, they would subsequently attempt to "upgrade" to a preferable transport such as WebSocket. + +This behaviour has been changed in v2. Now, realtime clients will instead first attempt to connect to Ably using WebSocket, and only failover to alternative transports if this WebSocket connection attempt fails. For the vast majority of users this will result in a smoother initial connection sequence, however in environments where WebSocket connections are unavailable and time out instead of failing immediately (such as when using a corporate proxy which strips WebSocket headers) this may result in a slower initial connection. Once a connection is first established, transport preference will be cached in the web browser local storage so subsequent connections will use the best available transport. If you expect WebSocket connection attempts to always fail in your enviornment, you can skip the WebSocket connection step by explicitly providing a list of transports which omits the `web_socket` transport via the `transports` client option. + +

    Switch to using the new promise-based API of Crypto.generateRandomKey()

    + +**Note:** This section is only relevant if you’re using the `Crypto.generateRandomKey()` method. + +If you’re using the `Crypto.generateRandomKey()` method, you’ll need to change how you call this method. In v1, this method required that you pass a callback. In v2, it communicates its result by returning a promise. + +So you need to change code that looks like this: + +```javascript +Ably.Realtime.Crypto.generateRandomKey((err, key) => { … }) +``` + +into code that makes use of the returned promise, for example by using the `await` keyword on it: + +```javascript +const key = await Ably.Realtime.Crypto.generateRandomKey(); +``` + +#### Update your usage of `request()` to pass a `version` argument + +**Note:** This section is only relevant if you’re using the `Rest.request()` or `Realtime.request()` methods; that is, if you’re using the library to manually make a request to the Ably REST API. + +The signature of this method has been changed; it now requires that you pass a `version` argument to specify the version of the REST API to use. For compatibility with v1 of this library, specify a version of `2`. + +As an example, given the current code to [get the service time](https://ably.com/docs/api/rest-api#time): + +```javascript +const time = await rest.request('get', '/time'); +``` + +add an argument to specify the API version: + +```javascript +const time = await rest.request('get', '/time', 2); +``` + +#### Update your usage of the result of the `stats()` method + +**Note:** This section is only relevant if you’re using the `Rest.stats()` or `Realtime.stats()` method; that is, if you’re using the library to retrieve your application’s usage statistics. + +The `Stats` type returned by the `Rest.stats()` and `Realtime.stats()` method has been simplified in v2. Specifically, there is a new property called `entries`, which is an object all of whose properties have numeric values. The `entries` property replaces the following properties: + +- `all` +- `inbound` +- `outbound` +- `persisted` +- `connections` +- `channels` +- `apiRequests` +- `tokenRequests` +- `xchgProducer` +- `xchgConsumer` +- `pushStats` +- `processed` + +You should migrate your code to use the `entries` property instead of these properties. + +The main differences between the v1 `Stats` type and the `entries` property in v2 are: + +- the various stats that count messages in different ways (`all`, `inbound`, `outbound`, `persisted`, `processed`) are now under `messages` instead of all in the top level +- `connections` is no longer broken down into `plain` and `tls` +- instead of top-level `apiRequests` and `tokenRequests`, which doesn't make much sense because token requests are API requests, you now have a top level `apiRequests` that’s broken down currently into `tokenRequests` and `other` with an `all` aggregate, with potential to split other out into more specific types later +- `push` is completely reorganised in a way that makes more sense + +For more detailed information on `entries`, see the `Stats` type’s new `schema` property. It provides you with the URL of a [JSON Schema](https://json-schema.org/) which describes the structure of this `Stats` object. (Alternatively, if you wish to view this schema now, you can find it [here](https://github.com/ably/ably-common/blob/main/json-schemas/src/app-stats.json).) + +As an example, given the following v1 code that uses the `stats()` API: + +```javascript +const stats = await rest.stats(); +const inboundMessageCount = stats[0].inbound.all.messages.count; +``` + +This is the equivalent v2 code: + +```javascript +const stats = await rest.stats(); +const inboundMessageCount = stats[0].entries['messages.inbound.all.messages.count'] ?? 0; +``` + +Notice that a given property may be absent from `entries`. If a property is absent, this is equivalent to its value being 0. + +#### Be aware of changed `whenState()` behaviour + +**Note:** This section is only relevant if you’re using the `RealtimeChannel.whenState()` or `Connection.whenState()` methods. + +The `RealtimeChannel.whenState()` and `Connection.whenState()` methods now return `null` when the connection is already in the given state, instead of attempting to synthesize an artificial state change. + +#### Be aware that the `fromEncoded()` and `fromEncodedArray()` methods are now async + +**Note:** This section is only relevant if you’re using `Message` or `PresenceMessage`’s `fromEncoded` or `fromEncodedArray` methods. + +`Message` and `PresenceMessage`’s `fromEncoded` and `fromEncodedArray` methods now operate asynchronously. That is, instead of returning the decoding result, they return a promise. Update your code to retrieve the result of these promises, for example by using the `await` keyword. + +

    Be aware that symmetric encryption in a browser now requires a secure context

    + +**Note:** This section is only relevant if you’re using the `cipher` client option and running in a browser. + +If you’re making use of the `cipher` client option to enable symmetric encryption on a channel, be aware that when running in a browser this functionality is now implemented using the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). Hence, this functionality is only available when this API is available; namely, when the current environment is a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). Roughly speaking, this means that you can now only use Ably channel encryption inside a web page when serving your page over HTTPS or from `localhost`. + +

    Be aware that the library no longer uses CryptoJS and hence no longer works with WordArray

    + +**Note:** This section is only relevant if you’re using the `cipher` client option and running in a browser. + +When running in a browser, the library previously used the CryptoJS library for implementing symmetric encryption on a channel. As mentioned [here](#secure-context), the library now instead uses the built-in Web Crypto API. + +This means that the library is no longer capable of interacting with CryptoJS’s `WordArray` type. Specifically: + +- `Crypto.generateRandomKey()` now returns an `ArrayBuffer` instead of a `WordArray`. +- `Crypto.getDefaultParams({ key })` no longer accepts a `WordArray` key; pass an `ArrayBuffer` or `Uint8Array` instead. + +#### Stop requesting the `xhr_streaming` and `xhr` transports + +**Note:** This section is only relevant if you’re explicitly writing `"xhr_streaming"` or `"xhr"` as part of your `transports` client option. + +v1 offered the `xhr_streaming` transport, also known as `xhr`, primarily to provide a performant realtime transport for browsers which did not support WebSocket connections. Since this limitation does not apply to any of the [browsers supported by v2](#supported-platforms), the `xhr_streaming` transport has been removed. If your `transports` client option contains `"xhr_streaming"` or `"xhr"`, remove this value. If you still wish to explicitly request a non-WebSocket transport, request `xhr_polling` instead. + +#### Stop requesting the `jsonp` transport + +**Note:** This section is only relevant if you’re explicitly writing `"jsonp"` as part of your `transports` client option. + +v1 offered JSONP as a fallback transport for browsers that did not support cross-origin XHR. Since this limitation does not apply to any of the [browsers supported by v2](#supported-platforms), the JSONP transport has been removed. If your `transports` client option contains `"jsonp"`, remove this value. + +#### Stop using the `noencryption` variant of the library + +**Note:** This section is only relevant if you’re importing `ably/build/ably.noencryption.min.js` or using the `ably.noencryption.min-1.js` CDN build. + +In v1, we provided a separate version of the library that did not support the `cipher` channel option. This was offered for those who did not wish to bloat their app’s bundle size with encryption code. + +Since v2 [no longer uses the CryptoJS library](#no-more-CryptoJS), the `cipher` channel option functionality now has a much smaller impact on your app’s bundle size. So, we no longer offer the `noencryption` variant of the library. + +Furthermore, v2 introduces the [modular variant of the library](#modular-variant), which is specifically aimed at those who are concerned about their app’s bundle size. It provides a general mechanism for choosing which Ably functionality you wish to include in your app’s bundle. So, if you do not wish to incur even the small bundle size overhead that the `cipher` channel option imposes, consider using the modular variant of the library without the `Crypto` module. + +#### Stop using the special Web Worker build of the library + +**Note:** This section is only relevant if you’re using ably-js inside a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). + +In v1, if you wished to use ably-js inside a Web Worker, you had to use a special build of the library, by writing `import Ably from 'ably/build/ably-webworker.min'`. + +In v2, the library can be used inside a Web Worker without needing to use a special build, and this special `ably-webworker` build no longer exists. So, update your code to `import Ably from 'ably'`. + +#### Stop importing the `ably/build/ably-commonjs.js` or `ably/build/ably-commonjs.noencryption.js` files + +**Note:** This section is only relevant if you’re importing `ably/build/ably-commonjs.js` or `ably/build/ably-commonjs.noencryption.js`. + +The `ably/build/ably-commonjs.js` file no longer exists. + +- If you’re currently writing `require('ably/build/ably-commonjs.js')`, write `require('ably')` instead. +- If you’re currently writing `import * as Ably from 'ably/build/ably-commonjs.js'`, write `import * as Ably from 'ably'` instead. + +(The same applies for `ably-commonjs.noencryption.js`.) + +### Only TypeScript users + +#### Stop referring to the `Types` namespace + +v1 exported many of its types inside the `Types` TypeScript namespace. v2 removes this namespace, and the types it contained are now exported at the top level. Remove your references to the `Types` namespace. + +For example, code like this: + +```typescript +let options: Ably.Types.ClientOptions = { key: 'foo' }; +``` + +should now be written + +```typescript +let options: Ably.ClientOptions = { key: 'foo' }; +``` + +In order to avoid a clash with the existing top-level types of the same type, the `Types.Rest` and `Types.Realtime` types have been renamed to `RestClient` and `RealtimeClient`. If you’re referring to these types in your code (which is probably unlikely) then update these references. + +

    Stop referring to the *Base, *Callbacks and *Promise types

    + +The v1 type declarations contained various sets of three related types in order to reflect the different asynchronicity styles offered by the SDK. For example, it had a `RealtimeChannelBase` type which contained functionality common to the callbacks-based and promises-based API of a realtime channel, and then `RealtimeChannelCallbacks` and `RealtimeChannelPromises` types which contained the APIs unique to those asynchronicity styles. + +Now that [the callbacks-based variant of the library has been removed](#switch-from-callbacks-to-promises), we’ve simplified the types, removing the `*Callbacks` types and combining each pair of `*Base` and `*Promise` types into a single type. For example, where previously there existed three types that described a realtime channel, now there is just one, named `RealtimeChannel`. You should update your code to stop referring to the suffixed types. In general, this is a case of removing these suffixes from the type names. + +#### Be aware that some properties might not be populated on messages received from Ably + +The following properties of `Message` are now declared as optional: + +- `clientId` +- `data` +- `encoding` +- `extras` +- `name` + +This update to the type declarations reflects the fact that these properties are not (and never have been, despite what the v1 declarations suggested) guaranteed to be populated on a message received from Ably. + +#### Be aware that the `Message` type now refers to a message that you publish to Ably + +The `Message` type has been changed so that it represents a message that you publish to Ably (see [migration guidance for the changes to the type declaration for the `publish()` method](#publish-type-changes)). Since you are not required to populate the following properties of a message that you publish to Ably, they are now optional: + +- `id` +- `timestamp` + +If you were previously using the `Message` type to refer to a message received from Ably (e.g. from the `RealtimeChannel.subscribe()` or `RealtimeChannel.history()` methods), then switch to using the new `InboundMessage` type, which represents a message received from Ably; it’s a version of `Message` in which these two properties are _not_ optional (matching the v1 `Message` type). + +

    Be aware that publish() is stricter about its arguments

    + +In v1, the type declarations for the publishing methods `Channel.publish()` and `RealtimeChannel.publish()` stated that they accepted an argument of type `any`. This was inaccurate, as in fact there were expectations about the shape of the message or messages that you pass to these methods. Now, the type declarations state you must pass an object that satisfies the `Message` interface, or an array of such objects. + +#### Be aware that enum-like namespaces have changed name + +**Note:** It’s unlikely that you’re affected by this change. + +In v1, there existed a pattern in the type declarations where a single name was used for a namespace and also a type whose possible values were the members of that namespace. For example: + +```typescript +declare namespace ChannelState { + type INITIALIZED = 'initialized'; + type ATTACHING = 'attaching'; + type ATTACHED = 'attached'; + type DETACHING = 'detaching'; + type DETACHED = 'detached'; + type SUSPENDED = 'suspended'; +} + +export type ChannelState = + | ChannelState.FAILED + | ChannelState.INITIALIZED + | ChannelState.SUSPENDED + | ChannelState.ATTACHED + | ChannelState.ATTACHING + | ChannelState.DETACHED + | ChannelState.DETACHING; +``` + +In v2, the namespace has been changed so that its name has a plural inflection. For example, whilst the `ChannelState` _type_ maintains its name, the `ChannelState` _namespace_ is now called `ChannelStates`. Update your code accordingly. + +Also, there was a type in v1 called `ChannelModes` which was just an alias for `Array`. In order to accommodate the naming scheme described above, the `ChannelModes` type no longer has its v1 meaning. + +#### Stop relying on the declared inheritance relationship between REST and realtime types + +In v1, the `Types.RealtimeBase` class is declared as inheriting from the `Types.RestBase` class. [As mentioned above](#stop-referring-to-suffixed-types), these classes no longer exist. Their replacements (the `RealtimeClient` and `RestClient` interfaces) do not declare an inheritance relationship. + +

    Stop using functionality that v2 deprecates

    + +#### Use `Connection.createRecoveryKey()` instead of `Connection.recoveryKey` + +**Note:** This section only applies if you’re using the `Connection.recoveryKey` property. You’re likely only using this if making use of the library’s connection recovery functionality, and have opted out of the library’s recovery key persistance functionality; that is, if you’re populating the `recover` client option with a string (as opposed to a callback). + +The `Connection.recoveryKey` property is deprecated and will be removed in a future version. It has been replaced by the `Connection.createRecoveryKey()` method. The return value of this method is identical to the value of the `Connection.recoveryKey` property. Update your code to use this return value. + +

    Take advantage of new features that v2 introduces

    + +

    Using the modular variant of the library

    + +Aimed at those who are concerned about their app’s bundle size, the modular variant of the library allows you to create a client which has only the functionality that you choose. Unused functionality can then be tree-shaken by your module bundler. + +To get started with the modular variant of the library, see [this section of the Readme](../../../README.md#modular-tree-shakable-variant). From 8ddc3ac8a06235183474e14af8f89d41845ed1ce Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 15 Mar 2024 10:49:17 +0000 Subject: [PATCH 464/468] Add migration guide for react hooks for ably-js v2 Resolves #1684 --- docs/migration-guides/v2/react-hooks.md | 223 ++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 docs/migration-guides/v2/react-hooks.md diff --git a/docs/migration-guides/v2/react-hooks.md b/docs/migration-guides/v2/react-hooks.md new file mode 100644 index 0000000000..c8fe37b115 --- /dev/null +++ b/docs/migration-guides/v2/react-hooks.md @@ -0,0 +1,223 @@ +# React hooks migration guide for ably-js v2 + +Here’s how to migrate React hooks from ably-js v1 to v2: + +1. [Use new `ChannelProvider` component](#use-new-channelprovider-component) +2. [Update usage of the `usePresence` hook, which has been split into two separate hooks](#update-usage-of-the-usepresence-hook-which-has-been-split-into-two-separate-hooks) +3. [Rename optional `id` field to `ablyId`](#rename-optional-id-field-to-ablyid) +4. (Optional) [Use new convenience function `publish` in `useChannel`](#optional-use-new-convenience-function-publish-in-usechannel) + +## Use new `ChannelProvider` component + +In previous versions, you were able to provide channel options as a parameter in the `useChannel`/`usePresence` hooks. This could, in some cases, lead to errors when using hooks with the same channel name but different options, or when attempting to dynamically change options for a channel. + +To address these issues, the new version introduces the `ChannelProvider` component to define the channels you wish to use and their options. The ability to provide channel options using the `options` or `deriveOptions` parameters in `useChannel`/`usePresence` hooks has been removed. + +Replace code that used `options` in the `useChannel` hook: + +```jsx +const { channel } = useChannel( + { channelName: 'your-channel-name', options: { params: { rewind: '1' } } }, + (message) => { + console.log(message); + }, +); +``` + +With this: + +```jsx +// in a parent component: +return ( + + {children} + +); + +// in a child component: +const { channel } = useChannel({ channelName: 'your-channel-name' }, (message) => { + console.log(message); +}); +``` + +Replace code that used `deriveOptions` in `useChannel` hook: + +```jsx +useChannel( + { + channelName: 'your-derived-channel-name', + deriveOptions: { filter: 'headers.role == `"marketing"`' }, + }, + (message) => { + console.log(message); + }, +); +``` + +With this: + +```jsx +// in a parent component: +return ( + + {children} + +); + +// in a child component: +useChannel({ channelName: 'your-derived-channel-name' }, (message) => { + console.log(message); +}); +``` + +Replace code that used `options` in `usePresence` hook: + +```jsx +const { updateStatus } = usePresence( + { channelName: 'presence-channel-name', options: { modes: ['PRESENCE'] } }, + { foo: 'bar' }, +); +``` + +With this: + +```jsx +// in a parent component: +return ( + + {children} + +); + +// in a child component: +const { updateStatus } = usePresence({ channelName: 'presence-channel-name' }, { foo: 'bar' }); +``` + +Additionally, if you were calling `.setOptions()` on a channel instance returned by the `useChannel` hook before, you must remove those calls and instead modify options provided to the `ChannelProvider` component if you want to change channel options during runtime. + +Change this: + +```jsx +const { channel } = useChannel({ channelName: 'your-channel-name' }, (message) => { + console.log(message); +}); +channel.setOptions({ params: { rewind: '1' } }); +``` + +To this: + +```jsx +// in a parent component: +// change channel options during runtime using state +const [options, setOptions] = useState({}); +return ( + + {children} + +); + +// in a child component: +const { channel } = useChannel({ channelName: 'your-channel-name' }, (message) => { + console.log(message); +}); + +// use the useState setter to change the options provided to the `ChannelProvider` component +setOptions({ params: { rewind: '1' } }); +``` + +## Update usage of the `usePresence` hook, which has been split into two separate hooks + +The functionality of the `usePresence` hook has been split into two separate hooks. + +The `usePresence` hook can now only be used to enter the presence with optional initial state and update the presence status for the current client. It no longer returns the `presenceData` value and does not accept the `onPresenceUpdated` callback as its third parameter. + +To listen for presence updates, a new hook called `usePresenceListener` has been introduced. This hook returns the `presenceData` object previously returned by `usePresence` and accepts an `onPresenceMessageReceived` callback as its second parameter, which is called on new presence messages. + +Replace this: + +```jsx +const { presenceData, updateStatus } = usePresence( + { channelName: 'presence-channel-name' }, + { foo: 'bar' }, + (update) => { + console.log(update); + }, +); +``` + +With this: + +```jsx +const { updateStatus } = usePresence({ channelName: 'presence-channel-name' }, { foo: 'bar' }); +const { presenceData } = usePresenceListener({ channelName: 'presence-channel-name' }, (update) => { + console.log(update); +}); +``` + +## Rename optional `id` field to `ablyId` + +All instances of the `id` field, which optionally were used to specify the `AblyProvider` component and the underlying Ably client to use, have been renamed to `ablyId`. + +Replace this: + +```jsx +// in a parent component: +const client = new Ably.Realtime(options); + +return ( + + {children} + +); + +// in a child component: +useChannel({ channelName: 'your-channel-name', id: 'foo' }, (message) => { + console.log(message); +}); +``` + +With this: + +```jsx +// in a parent component: +const client = new Ably.Realtime(options); + +return ( + + + {children} + + +); + +// in a child component: +useChannel({ channelName: 'your-channel-name', ablyId: 'foo' }, (message) => { + console.log(message); +}); +``` + +## (Optional) Use new convenience function `publish` in `useChannel` + +A new convenience function, `publish`, is now being returned by the `useChannel` hook. It performs the same function as calling `channel.publish()`. Additionally, using this dedicated `publish` function allows you to send messages to derived channels (channels with a filter qualifier) without attaching to the channel or using other workarounds. + +It is recommended to use the dedicated `publish` function returned by the `useChannel` hook instead of calling `channel.publish()`. + +Replace this: + +```jsx +const { channel } = useChannel({ channelName: 'your-channel-name' }); + +channel.publish('test-message', { + text: 'message text', +}); +``` + +With this: + +```jsx +const { publish } = useChannel({ channelName: 'your-channel-name' }); + +publish('test-message', { + text: 'message text', +}); +``` From 679e49555d6760d35376214afadfd8bbb980c7f5 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Wed, 20 Mar 2024 21:29:46 +0000 Subject: [PATCH 465/468] Fix incorrect repo version in react hooks migration guide --- docs/migration-guides/v1/react-hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration-guides/v1/react-hooks.md b/docs/migration-guides/v1/react-hooks.md index cf648835da..cf39b9bdf3 100644 --- a/docs/migration-guides/v1/react-hooks.md +++ b/docs/migration-guides/v1/react-hooks.md @@ -1,6 +1,6 @@ # React hooks upgrade / migration guide -## Version 2.x to 3.x +## `@ably-labs/react-hooks` to `ably 1.x` ### Hooks now return object From 7e13b2fb80fc82d881dadd182b3e102d5cdc3cc3 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 1 Mar 2024 05:30:53 +0000 Subject: [PATCH 466/468] chore: update changelog for 2.0.0 release Based on changes between dc761e71b779d0d2ec5de4579ab36933e9c7af5c...c02ec568b51be10f0e4c70805f36a01514b8fcac --- CHANGELOG.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e10ca454da..152a66c2b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,92 @@ This contains only the most important and/or user-facing changes; for a full changelog, see the commit history. +## [2.0.0](https://github.com/ably/ably-js/tree/2.0.0) (2024-03-21) + +The 2.0.0 release introduces a number of new features and QoL improvements, including a new way to remove bloat and reduce the bundle size of your ably-js client, first-class support for Promises, a more idiomatic approach to using ably-js' React Hooks, enhancements to TypeScript typings, and more. + +Below is an overview of the major changes in this release. + +Please refer to the ably-js v2 [lib migration guide](./docs/migration-guides/v2/lib.md) and [React Hooks migration guide](./docs/migration-guides/v2/react-hooks.md) for the full details, including a list of all breaking changes and instructions on how to address them. + +### Bundle Size Reduction + +The default bundle size for the web has been reduced by ~32% compared to v1 - from 234.11 KiB to 159.32 KiB. When calculated with gzip compression, the reduction is ~30%, from 82.54 KiB to 57.9 KiB. + +Additionally, by utilizing the new modular variant of the library (see below) and JavaScript tree shaking, you can create your own minimal useful `Realtime` client and achieve a bundle size reduction of ~60.5% compared to v1 - from 234.11 KiB to 92.38 KiB (or ~66% for gzip: from 82.54 KiB to 28.18 KiB). + +### Modular variant of the library + +An ESM variant of the library is now available for browsers (but not for Node.js) via import from `ably/modular`. This modular variant of the library supports tree shaking, allowing for a reduction in the Ably bundle size within your application and improving the user experience. It can also be used by Web Workers. + +### React Hooks changes + +React Hooks, exported at `ably/react`, now require the new `ChannelProvider` component to define the channels you wish to use and the options for them. This addresses the complexities previously encountered with `useChannel` and `usePresence` hooks when attempting to dynamically change options for a channel and provides a more straightforward approach to set and manage these options. + +The functionality of the `usePresence` hook has been split into two separate hooks: `usePresence`, which is now used only to enter presence, and `usePresenceListener`, which is used to listen for presence updates. These new hooks offer better control over the desired presence behavior in your React components. + +### First-class support for Promises + +The callbacks API has been entirely removed, and the library now supports promises for all its asynchronous operations by default. + +### TypeScript typings + +The Types namespace has been removed. All types it contained are now exported at the top level. + +### Browser and Web Worker bundles + +- The browser bundle now relies on the native Web Crypto API instead of CryptoJS. The `ably/build/ably.noencryption` bundle has been removed, as it is no longer necessary. +- The browser bundle can now be directly used by Web Workers. The `ably/build/ably-webworker` bundle has been removed, as it is no longer necessary. + +### Supported platforms changes + +- Support for Internet Explorer has been dropped. +- Support for Node.js versions lower than 16 has been dropped. The supported Node.js versions are now 16, 18, and 20. +- The minimum supported versions for major browsers are: Chrome 58, Firefox 52, Edge 79, Safari 11, and Opera 45. + +
    +View merged Pull Requests + +### Breaking Changes + +- Add `ChannelProvider` component to React Hooks [\#1620](https://github.com/ably/ably-js/pull/1620), [\#1654](https://github.com/ably/ably-js/pull/1654) +- Add untyped stats API [\#1522](https://github.com/ably/ably-js/pull/1522) +- Add type definitions for key returned by `generateRandomKey` [\#1320](https://github.com/ably/ably-js/pull/1320) +- Add mandatory `version` param to `Rest.request` [\#1231](https://github.com/ably/ably-js/pull/1231) +- Change `id` field to be named `ablyId` in React Hooks [\#1676](https://github.com/ably/ably-js/pull/1676) +- Change `usePresence` hook to two different hooks: for entering presence and subscribing to presence updates [\#1674](https://github.com/ably/ably-js/pull/1674) +- Change naming for enum-like namespaces in type declarations and change meaning for public `ChannelModes` type [\#1601](https://github.com/ably/ably-js/pull/1601) +- Change publishing methods to accept a `Message`-shaped object [\#1515](https://github.com/ably/ably-js/pull/1515) +- Change `Crypto.generateRandomKey` API to use Promises [\#1351](https://github.com/ably/ably-js/pull/1351) +- Change `fromEncoded()` and `fromEncodedArray()` methods on `Message` and `PresenceMessage` to be async [\#1311](https://github.com/ably/ably-js/pull/1311) +- Remove `XHRStreaming` transport support [\#1645](https://github.com/ably/ably-js/pull/1645) +- Remove code that's supporting older platforms [\#1629](https://github.com/ably/ably-js/pull/1629), [\#1633](https://github.com/ably/ably-js/pull/1633), [\#1641](https://github.com/ably/ably-js/pull/1641) +- Remove `recoveryKey` in favour of `createRecoveryKey()` on `Connection` [\#1613](https://github.com/ably/ably-js/pull/1613) +- Remove `any` from `stats()` param type [\#1561](https://github.com/ably/ably-js/pull/1561) +- Remove the dedicated Web Worker bundle `ably/build/ably-webworker` and add support for using `ably` and `ably/modular` in Web Workers [\#1550](https://github.com/ably/ably-js/pull/1550) +- Remove false class exports in type declarations [\#1524](https://github.com/ably/ably-js/pull/1524) +- Remove the `Types` namespace [\#1518](https://github.com/ably/ably-js/pull/1518) +- Remove `noencryption` variant of the library [\#1500](https://github.com/ably/ably-js/pull/1500) +- Remove public callbacks API [\#1358](https://github.com/ably/ably-js/pull/1358) +- Remove CryptoJS library and replace it with the Web Crypto API in web bundle [\#1299](https://github.com/ably/ably-js/pull/1299), [\#1325](https://github.com/ably/ably-js/pull/1325), [\#1333](https://github.com/ably/ably-js/pull/1333) +- Remove `ably-commonjs*.js` files [\#1229](https://github.com/ably/ably-js/pull/1229) +- Remove deprecated APIs [\#1227](https://github.com/ably/ably-js/pull/1227) +- Remove deprecated `fromEncoded*` type declarations [\#1222](https://github.com/ably/ably-js/pull/1222) +- Remove deprecated `ClientOptions` parameters [\#1221](https://github.com/ably/ably-js/pull/1221) +- Remove the `ClientOptions.log` property and replace it with separate `logLevel` and `logHandler` properties [\#1216](https://github.com/ably/ably-js/pull/1216) +- Remove support for JSONP [\#1215](https://github.com/ably/ably-js/pull/1215) +- Fix `whenState` inconsistent behavior in `Connection` and `RealtimeChannel` [\#1640](https://github.com/ably/ably-js/pull/1640) +- Fix the type definition of `Crypto.getDefaultParams` [\#1352](https://github.com/ably/ably-js/pull/1352) + +### Features + +- Add `publish` function to the `useChannel` hook [\#1658](https://github.com/ably/ably-js/pull/1658) +- Add logs to all HTTP activity [\#1581](https://github.com/ably/ably-js/pull/1581) + +
    + +[Full Changelog](https://github.com/ably/ably-js/compare/1.2.50...2.0.0) + ## [1.2.49](https://github.com/ably/ably-js/tree/1.2.49) (2024-02-07) - \[React-Hooks\] `usePresence` unsubscribes all listeners on unmount and run `Presence.leave` even if connection has been terminated [\#1610](https://github.com/ably/ably-js/issues/1610) From b84c64c6bebe13dae718ac8b79101c5b2bfdfb68 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Thu, 21 Mar 2024 16:51:15 +0000 Subject: [PATCH 467/468] chore: bump version for 2.0.0 release --- package-lock.json | 4 ++-- package.json | 2 +- src/platform/react-hooks/src/AblyReactHooks.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d87d019bcf..19d7c78759 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ably", - "version": "1.2.50", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ably", - "version": "1.2.50", + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { "@ably/msgpack-js": "^0.4.0", diff --git a/package.json b/package.json index b214c1cb40..b6430e5827 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ably", "description": "Realtime client library for Ably, the realtime messaging service", - "version": "1.2.50", + "version": "2.0.0", "license": "Apache-2.0", "bugs": { "url": "https://github.com/ably/ably-js/issues", diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 40c83b8b60..eea6e6f3db 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -12,7 +12,7 @@ export type ChannelNameAndOptions = { export type ChannelNameAndAblyId = Pick; export type ChannelParameters = string | ChannelNameAndOptions; -export const version = '1.2.50'; +export const version = '2.0.0'; export function channelOptionsWithAgent(options?: Ably.ChannelOptions) { return { From 2fed96b1b9712d44f21b67b60e495a24e98ea3db Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Fri, 22 Mar 2024 11:52:17 +0000 Subject: [PATCH 468/468] Update date in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8592ab8886..5430363b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ This contains only the most important and/or user-facing changes; for a full changelog, see the commit history. -## [2.0.0](https://github.com/ably/ably-js/tree/2.0.0) (2024-03-21) +## [2.0.0](https://github.com/ably/ably-js/tree/2.0.0) (2024-03-22) The 2.0.0 release introduces a number of new features and QoL improvements, including a new way to remove bloat and reduce the bundle size of your ably-js client, first-class support for Promises, a more idiomatic approach to using ably-js' React Hooks, enhancements to TypeScript typings, and more.