From 547b3d0befc4e5726226eef91561d63b10b60aa3 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 14 Dec 2023 11:59:20 -0300 Subject: [PATCH 01/15] Bump @splitsoftware/splitio --- package-lock.json | 595 ++++++++++++++++++++++++++-------------------- package.json | 4 +- 2 files changed, 339 insertions(+), 260 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6b0d63..ad923cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio-redux", - "version": "1.9.0", + "version": "1.9.1-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-redux", - "version": "1.9.0", + "version": "1.9.1-rc.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio": "10.23.0" + "@splitsoftware/splitio": "10.24.1" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", @@ -51,18 +51,19 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", - "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", + "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -126,28 +127,20 @@ } }, "node_modules/@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.23.0", + "@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/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", @@ -175,39 +168,35 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "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, - "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "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/types": "^7.15.4" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "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.15.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -305,21 +294,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "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.15.4" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "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" @@ -349,13 +347,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -363,9 +361,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", - "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -549,32 +547,33 @@ } }, "node_modules/@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@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.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -583,12 +582,13 @@ } }, "node_modules/@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1396,10 +1396,58 @@ "node": ">=8" } }, + "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.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@mdn/browser-compat-data": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.3.4.tgz", - "integrity": "sha512-zzSR4lk5i9YSSQtQwZpSYK84xrSCPtrtU6F6WkYcchOYwkOiJXezpiaS4GnKFmafFcxCXNI2wPrsCoj2OO0VrA==", + "version": "5.3.28", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.3.28.tgz", + "integrity": "sha512-vC+UDAsQti7Cv2oBahPfgnTXT7n0XZk8e7UFucNMmkauszdiiEsNFI0elmMMrh2u+IaMOvAAHo3DDzMx7y80Cw==", "dev": true }, "node_modules/@nodelib/fs.scandir": { @@ -1456,11 +1504,11 @@ } }, "node_modules/@splitsoftware/splitio": { - "version": "10.23.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-10.23.0.tgz", - "integrity": "sha512-b9mn2B8U1DfpDETsaWH4T1jhkn8XWwlAVsHwhgIRhCgBs0B9wm4SsXx+OWHZ5bl5uvEwtFFIAtCU58j/irnqpw==", + "version": "10.24.1", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-10.24.1.tgz", + "integrity": "sha512-WzVZrP2IAqzNBywNXgmLxiS60qumkcnu6u1lUPlNgdVek82TzWeqyqW+htKmDMJ/ifsJPWrgT1VLMZJvOnBsVA==", "dependencies": { - "@splitsoftware/splitio-commons": "1.9.0", + "@splitsoftware/splitio-commons": "1.12.1", "@types/google.analytics": "0.0.40", "@types/ioredis": "^4.28.0", "bloom-filters": "^3.0.0", @@ -1478,9 +1526,9 @@ } }, "node_modules/@splitsoftware/splitio-commons": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.9.0.tgz", - "integrity": "sha512-2QoWvGOk/LB+q2TglqGD0w/hcUKG4DZwBSt5NtmT1ODGiLyCf2wbcfG/eBR9QlUnLisJ62dj6vOQsVUB2kiHOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.12.1.tgz", + "integrity": "sha512-EkCcqlYvVafazs9c5i+pmhf6rIyj3A70dqQ4U3BKE646t7tf6mxGzqZz1sAl540xNyYI7CA/iIqisEWvDtJc0A==", "dependencies": { "tslib": "^2.3.1" }, @@ -1494,9 +1542,9 @@ } }, "node_modules/@splitsoftware/splitio-commons/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@testing-library/dom": { "version": "9.0.1", @@ -1703,12 +1751,6 @@ "node": ">= 6" } }, - "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/@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", @@ -2716,9 +2758,9 @@ "dev": true }, "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==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -2728,13 +2770,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -2835,9 +2881,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001516", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz", - "integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==", + "version": "1.0.30001559", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", + "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", "dev": true, "funding": [ { @@ -3240,9 +3286,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.330", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.330.tgz", - "integrity": "sha512-PqyefhybrVdjAJ45HaPLtuVaehiSw7C3ya0aad+rvmV53IVyXmYRk3pwIOb2TxTDTnmgQdn46NjMMaysx79/6Q==", + "version": "1.4.574", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz", + "integrity": "sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg==", "dev": true }, "node_modules/emittery": { @@ -3533,19 +3579,18 @@ } }, "node_modules/eslint-plugin-compat": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.4.tgz", - "integrity": "sha512-RxySWBmzfIROLFKgeJBJue2BU/6vM2KJWXWAUq+oW4QtrsZXRxbjgxmO1OfF3sHcRuuIenTS/wgo3GyUWZF24w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.2.0.tgz", + "integrity": "sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w==", "dev": true, "dependencies": { - "@mdn/browser-compat-data": "^5.2.47", - "@tsconfig/node14": "^1.0.3", + "@mdn/browser-compat-data": "^5.3.13", "ast-metadata-inferer": "^0.8.0", - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001473", + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001524", "find-up": "^5.0.0", - "lodash.memoize": "4.1.2", - "semver": "7.3.8" + "lodash.memoize": "^4.1.2", + "semver": "^7.5.4" }, "engines": { "node": ">=14.x" @@ -3616,9 +3661,9 @@ } }, "node_modules/eslint-plugin-compat/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -7893,9 +7938,9 @@ } }, "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==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/normalize-package-data": { @@ -9564,9 +9609,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "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": [ { @@ -9576,6 +9621,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { @@ -9583,7 +9632,7 @@ "picocolors": "^1.0.0" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -9963,18 +10012,19 @@ }, "dependencies": { "@adobe/css-tools": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", - "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", + "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", "dev": true }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -10021,22 +10071,15 @@ } }, "@babel/generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", - "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.15.4", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" } }, "@babel/helper-compilation-targets": { @@ -10059,33 +10102,29 @@ } } }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } + "@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-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "@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/types": "^7.15.4" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "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.15.4" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -10159,18 +10198,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "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.15.4" + "@babel/types": "^7.22.5" } }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true + }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "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": { @@ -10191,20 +10236,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", - "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -10334,40 +10379,42 @@ } }, "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@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.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -10968,10 +11015,49 @@ } } }, + "@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.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "@mdn/browser-compat-data": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.3.4.tgz", - "integrity": "sha512-zzSR4lk5i9YSSQtQwZpSYK84xrSCPtrtU6F6WkYcchOYwkOiJXezpiaS4GnKFmafFcxCXNI2wPrsCoj2OO0VrA==", + "version": "5.3.28", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.3.28.tgz", + "integrity": "sha512-vC+UDAsQti7Cv2oBahPfgnTXT7n0XZk8e7UFucNMmkauszdiiEsNFI0elmMMrh2u+IaMOvAAHo3DDzMx7y80Cw==", "dev": true }, "@nodelib/fs.scandir": { @@ -11019,11 +11105,11 @@ } }, "@splitsoftware/splitio": { - "version": "10.23.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-10.23.0.tgz", - "integrity": "sha512-b9mn2B8U1DfpDETsaWH4T1jhkn8XWwlAVsHwhgIRhCgBs0B9wm4SsXx+OWHZ5bl5uvEwtFFIAtCU58j/irnqpw==", + "version": "10.24.1", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-10.24.1.tgz", + "integrity": "sha512-WzVZrP2IAqzNBywNXgmLxiS60qumkcnu6u1lUPlNgdVek82TzWeqyqW+htKmDMJ/ifsJPWrgT1VLMZJvOnBsVA==", "requires": { - "@splitsoftware/splitio-commons": "1.9.0", + "@splitsoftware/splitio-commons": "1.12.1", "@types/google.analytics": "0.0.40", "@types/ioredis": "^4.28.0", "bloom-filters": "^3.0.0", @@ -11035,17 +11121,17 @@ } }, "@splitsoftware/splitio-commons": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.9.0.tgz", - "integrity": "sha512-2QoWvGOk/LB+q2TglqGD0w/hcUKG4DZwBSt5NtmT1ODGiLyCf2wbcfG/eBR9QlUnLisJ62dj6vOQsVUB2kiHOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.12.1.tgz", + "integrity": "sha512-EkCcqlYvVafazs9c5i+pmhf6rIyj3A70dqQ4U3BKE646t7tf6mxGzqZz1sAl540xNyYI7CA/iIqisEWvDtJc0A==", "requires": { "tslib": "^2.3.1" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, @@ -11201,12 +11287,6 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "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 - }, "@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", @@ -11986,15 +12066,15 @@ "dev": true }, "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" } }, "bs-logger": { @@ -12070,9 +12150,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001516", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz", - "integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==", + "version": "1.0.30001559", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", + "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", "dev": true }, "chalk": { @@ -12392,9 +12472,9 @@ } }, "electron-to-chromium": { - "version": "1.4.330", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.330.tgz", - "integrity": "sha512-PqyefhybrVdjAJ45HaPLtuVaehiSw7C3ya0aad+rvmV53IVyXmYRk3pwIOb2TxTDTnmgQdn46NjMMaysx79/6Q==", + "version": "1.4.574", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz", + "integrity": "sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg==", "dev": true }, "emittery": { @@ -12862,19 +12942,18 @@ } }, "eslint-plugin-compat": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.4.tgz", - "integrity": "sha512-RxySWBmzfIROLFKgeJBJue2BU/6vM2KJWXWAUq+oW4QtrsZXRxbjgxmO1OfF3sHcRuuIenTS/wgo3GyUWZF24w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.2.0.tgz", + "integrity": "sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w==", "dev": true, "requires": { - "@mdn/browser-compat-data": "^5.2.47", - "@tsconfig/node14": "^1.0.3", + "@mdn/browser-compat-data": "^5.3.13", "ast-metadata-inferer": "^0.8.0", - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001473", + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001524", "find-up": "^5.0.0", - "lodash.memoize": "4.1.2", - "semver": "7.3.8" + "lodash.memoize": "^4.1.2", + "semver": "^7.5.4" }, "dependencies": { "find-up": { @@ -12915,9 +12994,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -15846,9 +15925,9 @@ "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==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "normalize-package-data": { @@ -17093,9 +17172,9 @@ "dev": true }, "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "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", diff --git a/package.json b/package.json index f22f007..b105cd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-redux", - "version": "1.9.0", + "version": "1.9.1-rc.0", "description": "A library to easily use Split JS SDK with Redux and React Redux", "main": "lib/index.js", "module": "es/index.js", @@ -59,7 +59,7 @@ }, "homepage": "https://github.com/splitio/redux-client#readme", "dependencies": { - "@splitsoftware/splitio": "10.23.0" + "@splitsoftware/splitio": "10.24.1" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", From bf0a89eaed3fba2d33357d0954296945755007ff Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 14 Dec 2023 11:59:49 -0300 Subject: [PATCH 02/15] Dependency vulnerability fixes --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ad923cf..0ad91ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,9 +51,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "node_modules/@babel/code-frame": { @@ -10012,9 +10012,9 @@ }, "dependencies": { "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "@babel/code-frame": { From b06ac39efcdde9f22e066e0dad00d9186757ee24 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 14 Dec 2023 12:03:10 -0300 Subject: [PATCH 03/15] Update IGetTreatmentsParams type, to include optional flagSets property --- src/types.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/types.ts b/src/types.ts index 9b38b8f..499354b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -95,7 +95,7 @@ export interface IInitSplitSdkParams { /** * Type of the param object passed to `getTreatments` action creator. */ -export interface IGetTreatmentsParams { +export type IGetTreatmentsParams = { /** * user key used to evaluate. It is mandatory for node but optional for browser. If not provided in browser, @@ -103,11 +103,6 @@ export interface IGetTreatmentsParams { */ key?: SplitIO.SplitKey; - /** - * feature flag name or array of feature flag names to evaluate. - */ - splitNames: string[] | string; - /** * optional map of attributes passed to the actual `client.getTreatment*` methods. * @see {@link https://help.split.io/hc/en-us/articles/360038851551-Redux-SDK#attribute-syntax} @@ -129,7 +124,21 @@ export interface IGetTreatmentsParams { * @default false */ evalOnReadyFromCache?: boolean; -} +} & ({ + + /** + * Feature flag name or array of feature flag names to evaluate. Either this or the `flagSets` property must be provided. If both are provided, the `flagSets` option is ignored. + */ + splitNames: string[] | string; + flagSets?: undefined; +} | { + + /** + * Feature flag set or array of feature flag sets to evaluate. Either this or the `splitNames` property must be provided. If both are provided, the `flagSets` option is ignored. + */ + flagSets: string[] | string; + splitNames?: undefined; +}) /** * Type of the param object passed to `destroySplitSdk` action creator. From bb8e653c947b3f3628af61c9d816e0374414e515 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 14 Dec 2023 17:21:34 -0300 Subject: [PATCH 04/15] Validate getTreatments input, in the same way than React SDK --- src/asyncActions.ts | 16 +++++----- src/constants.ts | 10 +++--- src/utils.ts | 77 +++++++++++++++++++++++++++++++++++++++++++++ types/utils.d.ts | 13 ++++++++ 4 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/asyncActions.ts b/src/asyncActions.ts index fa60d6c..94f9f7c 100644 --- a/src/asyncActions.ts +++ b/src/asyncActions.ts @@ -4,7 +4,7 @@ import { Dispatch, Action } from 'redux'; import { IInitSplitSdkParams, IGetTreatmentsParams, IDestroySplitSdkParams, ISplitFactoryBuilder } from './types'; import { splitReady, splitReadyWithEvaluations, splitReadyFromCache, splitReadyFromCacheWithEvaluations, splitTimedout, splitUpdate, splitUpdateWithEvaluations, splitDestroy, addTreatments } from './actions'; import { VERSION, ERROR_GETT_NO_INITSPLITSDK, ERROR_DESTROY_NO_INITSPLITSDK, getControlTreatmentsWithConfig } from './constants'; -import { matching, getStatus } from './utils'; +import { matching, getStatus, validateGetTreatmentsParams } from './utils'; /** * Internal object SplitSdk. This object should not be accessed or @@ -108,10 +108,8 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi return () => { }; } - // Convert string feature flag name to a one item array. - if (typeof params.splitNames === 'string') { - params.splitNames = [params.splitNames]; - } + params = validateGetTreatmentsParams(params); + const splitNames = params.splitNames as string[]; if (!splitSdk.isDetached) { // Split SDK running in Browser @@ -119,11 +117,11 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi // Register or unregister the current `getTreatments` action from being re-executed on SDK_UPDATE. if (params.evalOnUpdate) { - params.splitNames.forEach((featureFlagName) => { + splitNames.forEach((featureFlagName) => { client.evalOnUpdate[featureFlagName] = { ...params, splitNames: [featureFlagName] }; }); } else { - params.splitNames.forEach((featureFlagName) => { + splitNames.forEach((featureFlagName) => { delete client.evalOnUpdate[featureFlagName]; }); } @@ -147,14 +145,14 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi } else { // Otherwise, it adds control treatments to the store, without calling the SDK (no impressions sent) // @TODO remove eventually to minimize state changes - return addTreatments(params.key || (splitSdk.config as SplitIO.IBrowserSettings).core.key, getControlTreatmentsWithConfig(params.splitNames)); + return addTreatments(params.key || (splitSdk.config as SplitIO.IBrowserSettings).core.key, getControlTreatmentsWithConfig(splitNames)); } } else { // Split SDK running in Node // Evaluate Split and return redux action. const client = splitSdk.factory.client(); - const treatments = client.getTreatmentsWithConfig(params.key, params.splitNames, params.attributes); + const treatments = client.getTreatmentsWithConfig(params.key, splitNames, params.attributes); return addTreatments(params.key, treatments); } diff --git a/src/constants.ts b/src/constants.ts index f9ba036..c353fd1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -42,12 +42,12 @@ export const SPLIT_DESTROY = 'SPLIT_DESTROY'; export const ADD_TREATMENTS = 'ADD_TREATMENTS'; // Warning and error messages -export const ERROR_GETT_NO_INITSPLITSDK = '[Error] To use "getTreatments" the SDK must be first initialized with a "initSplitSdk" action'; +export const ERROR_GETT_NO_INITSPLITSDK = '[ERROR] To use "getTreatments" the SDK must be first initialized with a "initSplitSdk" action'; -export const ERROR_DESTROY_NO_INITSPLITSDK = '[Error] To use "destroySplitSdk" the SDK must be first initialized with a "initSplitSdk" action'; +export const ERROR_DESTROY_NO_INITSPLITSDK = '[ERROR] To use "destroySplitSdk" the SDK must be first initialized with a "initSplitSdk" action'; -export const ERROR_TRACK_NO_INITSPLITSDK = '[Error] To use "track" the SDK must be first initialized with an "initSplitSdk" action'; +export const ERROR_TRACK_NO_INITSPLITSDK = '[ERROR] To use "track" the SDK must be first initialized with an "initSplitSdk" action'; -export const ERROR_MANAGER_NO_INITSPLITSDK = '[Error] To use the manager, the SDK must be first initialized with an "initSplitSdk" action'; +export const ERROR_MANAGER_NO_INITSPLITSDK = '[ERROR] To use the manager, the SDK must be first initialized with an "initSplitSdk" action'; -export const ERROR_SELECTOR_NO_SPLITSTATE = '[Error] When using selectors, "splitState" value must be a proper splitio piece of state'; +export const ERROR_SELECTOR_NO_SPLITSTATE = '[ERROR] When using selectors, "splitState" value must be a proper splitio piece of state'; diff --git a/src/utils.ts b/src/utils.ts index dff2aab..1409026 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +import { IGetTreatmentsParams } from './types'; + /** * Validates if a value is an object. */ @@ -29,3 +31,78 @@ export function getStatus(client: SplitIO.IClient): IClientStatus { // @ts-expect-error, function exists but it is not part of JS SDK type definitions return client.__getStatus(); } + +/** + * Validates and sanitizes the parameters passed to the "getTreatments" action creator. + * + * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. + */ +export function validateGetTreatmentsParams(params: IGetTreatmentsParams) { + let { splitNames } = params; + if (typeof splitNames === 'string') splitNames = [splitNames]; + if (!validateFeatureFlags(splitNames)) splitNames = []; + + return { + ...params, + splitNames: splitNames, + }; +} + +// The following input validation utils are based on the ones in the React SDK. They might be replaced by utils from the JS SDK in the future. + +function validateFeatureFlags(maybeFeatureFlags: unknown, listName = 'feature flag names'): false | string[] { + if (Array.isArray(maybeFeatureFlags) && maybeFeatureFlags.length > 0) { + const validatedArray: string[] = []; + // Remove invalid values + maybeFeatureFlags.forEach((maybeFeatureFlag) => { + const featureFlagName = validateFeatureFlag(maybeFeatureFlag); + if (featureFlagName) validatedArray.push(featureFlagName); + }); + + // Strip off duplicated values if we have valid feature flag names then return + if (validatedArray.length) return uniq(validatedArray); + } + + console.log(`[ERROR] ${listName} must be a non-empty array.`); + return false; +} + +const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/; + +function validateFeatureFlag(maybeFeatureFlag: unknown, item = 'feature flag name'): false | string { + if (maybeFeatureFlag == undefined) { + console.log(`[ERROR] you passed a null or undefined ${item}, ${item} must be a non-empty string.`); + } else if (!isString(maybeFeatureFlag)) { + console.log(`[ERROR] you passed an invalid ${item}, ${item} must be a non-empty string.`); + } else { + if (TRIMMABLE_SPACES_REGEX.test(maybeFeatureFlag)) { + console.log(`[WARN] ${item} "${maybeFeatureFlag}" has extra whitespace, trimming.`); + maybeFeatureFlag = maybeFeatureFlag.trim(); + } + + if ((maybeFeatureFlag as string).length > 0) { + return maybeFeatureFlag as string; + } else { + console.log(`[ERROR] you passed an empty ${item}, ${item} must be a non-empty string.`); + } + } + + return false; +} + +/** + * Removes duplicate items on an array of strings. + */ +function uniq(arr: string[]): string[] { + const seen: Record = {}; + return arr.filter((item) => { + return Object.prototype.hasOwnProperty.call(seen, item) ? false : seen[item] = true; + }); +} + +/** + * Checks if a given value is a string. + */ +function isString(val: unknown): val is string { + return typeof val === 'string' || val instanceof String; +} diff --git a/types/utils.d.ts b/types/utils.d.ts index 8608b7b..241e116 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -1,3 +1,4 @@ +import { IGetTreatmentsParams } from './types'; /** * Validates if a value is an object. */ @@ -17,3 +18,15 @@ export interface IClientStatus { isDestroyed: boolean; } export declare function getStatus(client: SplitIO.IClient): IClientStatus; +/** + * Validates and sanitizes the parameters passed to the "getTreatments" action creator. + * + * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. + */ +export declare function validateGetTreatmentsParams(params: IGetTreatmentsParams): { + splitNames: string[]; + key?: import("@splitsoftware/splitio/types/splitio").SplitKey; + attributes?: import("@splitsoftware/splitio/types/splitio").Attributes; + evalOnUpdate?: boolean; + evalOnReadyFromCache?: boolean; +}; From 939269c23ec9b6b808ca0989761871c78debf6b4 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 14 Dec 2023 18:21:35 -0300 Subject: [PATCH 05/15] Add unit tests and clean code --- CHANGES.txt | 3 +++ src/__tests__/utils.test.ts | 26 +++++++++++++++++++++++++- src/utils.ts | 16 ++++++++++------ types/constants.d.ts | 10 +++++----- types/utils.d.ts | 3 +-- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fde5f24..45caa03 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +1.10.0 (December 19, 2023) + - Updated `getTreatments` actions creator to validate the provided params object, in order to log a descriptive error when an invalid object is provided rather than throwing a cryptic error. + 1.9.0 (July 18, 2023) - Updated some transitive dependencies for vulnerability fixes. - Updated @splitsoftware/splitio package to version 10.23.0 that includes: diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 8b8edd6..d93741e 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -1,5 +1,5 @@ /** Test targets */ -import { matching } from '../utils'; +import { matching, validateGetTreatmentsParams } from '../utils'; describe('matching', () => { @@ -31,3 +31,27 @@ describe('matching', () => { }); }); + +describe('validateGetTreatmentsParams', () => { + + it('should return a sanitized copy of the provided params object', () => { + expect(validateGetTreatmentsParams({ splitNames: ['some split', null] })).toEqual({ splitNames: ['some split'] }); + }); + + it('should return a sanitized copy of the provided params object', () => { + expect(validateGetTreatmentsParams({ splitNames: 'some split' })).toEqual({ splitNames: ['some split'] }); + }); + + it('should return an object with an empty splitNames array if the provided param is an empty object', () => { + expect(validateGetTreatmentsParams({})).toEqual({ splitNames: [] }); + }); + + it('should return an object with an empty splitNames array if the provided param is not an object', () => { // @ts-expect-error testing invalid input + expect(validateGetTreatmentsParams()).toEqual({ splitNames: [] }); + }); + + it('should return an object with an empty splitNames array if the provided param is not an object', () => { + expect(validateGetTreatmentsParams('invalid')).toEqual({ splitNames: [] }); + }); + +}); diff --git a/src/utils.ts b/src/utils.ts index 1409026..839cb8c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -37,14 +37,18 @@ export function getStatus(client: SplitIO.IClient): IClientStatus { * * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. */ -export function validateGetTreatmentsParams(params: IGetTreatmentsParams) { - let { splitNames } = params; - if (typeof splitNames === 'string') splitNames = [splitNames]; - if (!validateFeatureFlags(splitNames)) splitNames = []; +export function validateGetTreatmentsParams(params: unknown) { + if (!isObject(params)) { + console.log('[ERROR] "getTreatments" must be called with a param object.'); + params = {}; + } + + let { splitNames } = params as IGetTreatmentsParams; + splitNames = validateFeatureFlags(typeof splitNames === 'string' ? [splitNames] : splitNames) || []; return { - ...params, - splitNames: splitNames, + ...params as IGetTreatmentsParams, + splitNames, }; } diff --git a/types/constants.d.ts b/types/constants.d.ts index 25d8ff4..81a0edd 100644 --- a/types/constants.d.ts +++ b/types/constants.d.ts @@ -14,8 +14,8 @@ export declare const SPLIT_UPDATE_WITH_EVALUATIONS = "SPLIT_UPDATE_WITH_EVALUATI export declare const SPLIT_TIMEDOUT = "SPLIT_TIMEDOUT"; export declare const SPLIT_DESTROY = "SPLIT_DESTROY"; export declare const ADD_TREATMENTS = "ADD_TREATMENTS"; -export declare const ERROR_GETT_NO_INITSPLITSDK = "[Error] To use \"getTreatments\" the SDK must be first initialized with a \"initSplitSdk\" action"; -export declare const ERROR_DESTROY_NO_INITSPLITSDK = "[Error] To use \"destroySplitSdk\" the SDK must be first initialized with a \"initSplitSdk\" action"; -export declare const ERROR_TRACK_NO_INITSPLITSDK = "[Error] To use \"track\" the SDK must be first initialized with an \"initSplitSdk\" action"; -export declare const ERROR_MANAGER_NO_INITSPLITSDK = "[Error] To use the manager, the SDK must be first initialized with an \"initSplitSdk\" action"; -export declare const ERROR_SELECTOR_NO_SPLITSTATE = "[Error] When using selectors, \"splitState\" value must be a proper splitio piece of state"; +export declare const ERROR_GETT_NO_INITSPLITSDK = "[ERROR] To use \"getTreatments\" the SDK must be first initialized with a \"initSplitSdk\" action"; +export declare const ERROR_DESTROY_NO_INITSPLITSDK = "[ERROR] To use \"destroySplitSdk\" the SDK must be first initialized with a \"initSplitSdk\" action"; +export declare const ERROR_TRACK_NO_INITSPLITSDK = "[ERROR] To use \"track\" the SDK must be first initialized with an \"initSplitSdk\" action"; +export declare const ERROR_MANAGER_NO_INITSPLITSDK = "[ERROR] To use the manager, the SDK must be first initialized with an \"initSplitSdk\" action"; +export declare const ERROR_SELECTOR_NO_SPLITSTATE = "[ERROR] When using selectors, \"splitState\" value must be a proper splitio piece of state"; diff --git a/types/utils.d.ts b/types/utils.d.ts index 241e116..bb1a97e 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -1,4 +1,3 @@ -import { IGetTreatmentsParams } from './types'; /** * Validates if a value is an object. */ @@ -23,7 +22,7 @@ export declare function getStatus(client: SplitIO.IClient): IClientStatus; * * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. */ -export declare function validateGetTreatmentsParams(params: IGetTreatmentsParams): { +export declare function validateGetTreatmentsParams(params: unknown): { splitNames: string[]; key?: import("@splitsoftware/splitio/types/splitio").SplitKey; attributes?: import("@splitsoftware/splitio/types/splitio").Attributes; From 6b82d804ec39ad17897504ba54daca65bc171b99 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 14 Dec 2023 18:32:30 -0300 Subject: [PATCH 06/15] Update a function return type --- src/utils.ts | 2 +- types/utils.d.ts | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 839cb8c..fe3ec21 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -37,7 +37,7 @@ export function getStatus(client: SplitIO.IClient): IClientStatus { * * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. */ -export function validateGetTreatmentsParams(params: unknown) { +export function validateGetTreatmentsParams(params: unknown): IGetTreatmentsParams { if (!isObject(params)) { console.log('[ERROR] "getTreatments" must be called with a param object.'); params = {}; diff --git a/types/utils.d.ts b/types/utils.d.ts index bb1a97e..ce0d039 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -1,3 +1,4 @@ +import { IGetTreatmentsParams } from './types'; /** * Validates if a value is an object. */ @@ -22,10 +23,4 @@ export declare function getStatus(client: SplitIO.IClient): IClientStatus; * * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. */ -export declare function validateGetTreatmentsParams(params: unknown): { - splitNames: string[]; - key?: import("@splitsoftware/splitio/types/splitio").SplitKey; - attributes?: import("@splitsoftware/splitio/types/splitio").Attributes; - evalOnUpdate?: boolean; - evalOnReadyFromCache?: boolean; -}; +export declare function validateGetTreatmentsParams(params: unknown): IGetTreatmentsParams; From 8db6602e9728590090ebd478c712448226eed91d Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 15 Dec 2023 11:42:30 -0300 Subject: [PATCH 07/15] Update validateGetTreatmentsParams function, and bump JS SDK --- src/__tests__/utils.test.ts | 43 ++++++++++++++++++++++++++++--------- src/constants.ts | 2 ++ src/utils.ts | 21 ++++++++++++------ types/constants.d.ts | 1 + types/types.d.ts | 20 +++++++++++------ types/utils.d.ts | 3 +-- 6 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index d93741e..0db9d31 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -1,4 +1,5 @@ /** Test targets */ +import { WARN_FEATUREFLAGS_AND_FLAGSETS } from '../constants'; import { matching, validateGetTreatmentsParams } from '../utils'; describe('matching', () => { @@ -35,23 +36,45 @@ describe('matching', () => { describe('validateGetTreatmentsParams', () => { it('should return a sanitized copy of the provided params object', () => { - expect(validateGetTreatmentsParams({ splitNames: ['some split', null] })).toEqual({ splitNames: ['some split'] }); - }); + // String values are converted to arrays + expect(validateGetTreatmentsParams({ splitNames: 'some split' })).toStrictEqual({ splitNames: ['some split'], flagSets: undefined }); + expect(validateGetTreatmentsParams({ flagSets: 'flag set' })).toStrictEqual({ splitNames: undefined, flagSets: ['flag set'] }); - it('should return a sanitized copy of the provided params object', () => { - expect(validateGetTreatmentsParams({ splitNames: 'some split' })).toEqual({ splitNames: ['some split'] }); + // Feature flag names are sanitized because they are used by getControlTreatmentsWithConfig function while the SDK is not ready + expect(validateGetTreatmentsParams({ splitNames: ['some split', null] })).toStrictEqual({ splitNames: ['some split'], flagSets: undefined }); + // Flag set names are not sanitized, because they are not used by Redux SDK directly + expect(validateGetTreatmentsParams({ flagSets: ['flag set', null] })).toStrictEqual({ splitNames: undefined, flagSets: ['flag set', null] }); }); - it('should return an object with an empty splitNames array if the provided param is an empty object', () => { - expect(validateGetTreatmentsParams({})).toEqual({ splitNames: [] }); + it('should ignore flagSets if both splitNames and flagSets are provided', () => { + const consoleSpy = jest.spyOn(console, 'log'); + + expect(validateGetTreatmentsParams({ splitNames: ['some split', null], flagSets: ['flag set', null] })).toStrictEqual({ splitNames: ['some split'], flagSets: undefined }); + expect(validateGetTreatmentsParams({ splitNames: true, flagSets: ['flag set', null] })).toStrictEqual({ splitNames: [], flagSets: undefined }); + + expect(consoleSpy.mock.calls).toEqual([ + [WARN_FEATUREFLAGS_AND_FLAGSETS], + ['[ERROR] you passed a null or undefined feature flag name, feature flag name must be a non-empty string.'], + [WARN_FEATUREFLAGS_AND_FLAGSETS], + ['[ERROR] feature flag names must be a non-empty array.'] + ]); + consoleSpy.mockRestore(); }); - it('should return an object with an empty splitNames array if the provided param is not an object', () => { // @ts-expect-error testing invalid input - expect(validateGetTreatmentsParams()).toEqual({ splitNames: [] }); + it('should return a valid object if splitNames and flagSets values are invalid', () => { + // Invalid values for splitNames and flagSets are converted to empty arrays + expect(validateGetTreatmentsParams({ splitNames: {} })).toStrictEqual({ splitNames: [], flagSets: undefined }); + expect(validateGetTreatmentsParams({ flagSets: {} })).toStrictEqual({ splitNames: undefined, flagSets: [] }); + expect(validateGetTreatmentsParams({ splitNames: true })).toStrictEqual({ splitNames: [], flagSets: undefined }); + expect(validateGetTreatmentsParams({ flagSets: true })).toStrictEqual({ splitNames: undefined, flagSets: [] }); + expect(validateGetTreatmentsParams({ splitNames: null, flagSets: null })).toStrictEqual({ splitNames: undefined, flagSets: [] }); + expect(validateGetTreatmentsParams({})).toStrictEqual({ splitNames: undefined, flagSets: [] }); }); - it('should return an object with an empty splitNames array if the provided param is not an object', () => { - expect(validateGetTreatmentsParams('invalid')).toEqual({ splitNames: [] }); + it('should return a valid object if the provided param is not an object', () => { // @ts-expect-error testing invalid input + expect(validateGetTreatmentsParams()).toStrictEqual({ splitNames: undefined, flagSets: [] }); + expect(validateGetTreatmentsParams([])).toStrictEqual({ splitNames: undefined, flagSets: [] }); + expect(validateGetTreatmentsParams('invalid')).toStrictEqual({ splitNames: undefined, flagSets: [] }); }); }); diff --git a/src/constants.ts b/src/constants.ts index c353fd1..43ba9c2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -51,3 +51,5 @@ export const ERROR_TRACK_NO_INITSPLITSDK = '[ERROR] To use "track" the SDK must export const ERROR_MANAGER_NO_INITSPLITSDK = '[ERROR] To use the manager, the SDK must be first initialized with an "initSplitSdk" action'; export const ERROR_SELECTOR_NO_SPLITSTATE = '[ERROR] When using selectors, "splitState" value must be a proper splitio piece of state'; + +export const WARN_FEATUREFLAGS_AND_FLAGSETS = '[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored.'; diff --git a/src/utils.ts b/src/utils.ts index fe3ec21..8ffb424 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import { WARN_FEATUREFLAGS_AND_FLAGSETS } from './constants'; import { IGetTreatmentsParams } from './types'; /** @@ -34,22 +35,28 @@ export function getStatus(client: SplitIO.IClient): IClientStatus { /** * Validates and sanitizes the parameters passed to the "getTreatments" action creator. - * - * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. + * The returned object is a copy of the passed one, with the "splitNames" and "flagSets" properties converted to an array of strings. */ -export function validateGetTreatmentsParams(params: unknown): IGetTreatmentsParams { +export function validateGetTreatmentsParams(params: unknown) { if (!isObject(params)) { console.log('[ERROR] "getTreatments" must be called with a param object.'); params = {}; } - let { splitNames } = params as IGetTreatmentsParams; - splitNames = validateFeatureFlags(typeof splitNames === 'string' ? [splitNames] : splitNames) || []; + const { splitNames, flagSets } = params as IGetTreatmentsParams; + if (splitNames && flagSets) console.log(WARN_FEATUREFLAGS_AND_FLAGSETS); return { ...params as IGetTreatmentsParams, - splitNames, - }; + splitNames: splitNames ? + // Feature flag names are sanitized because they are passed to the getControlTreatmentsWithConfig function. + validateFeatureFlags(typeof splitNames === 'string' ? [splitNames] : splitNames) || [] : + undefined, + flagSets: splitNames ? + undefined : + // Flag set names are not sanitized, because they are not used by Redux SDK directly. We just make sure it is an array. + typeof flagSets === 'string' ? [flagSets] : Array.isArray(flagSets) ? flagSets : [], + } as IGetTreatmentsParams; } // The following input validation utils are based on the ones in the React SDK. They might be replaced by utils from the JS SDK in the future. diff --git a/types/constants.d.ts b/types/constants.d.ts index 81a0edd..b4fbe3a 100644 --- a/types/constants.d.ts +++ b/types/constants.d.ts @@ -19,3 +19,4 @@ export declare const ERROR_DESTROY_NO_INITSPLITSDK = "[ERROR] To use \"destroySp export declare const ERROR_TRACK_NO_INITSPLITSDK = "[ERROR] To use \"track\" the SDK must be first initialized with an \"initSplitSdk\" action"; export declare const ERROR_MANAGER_NO_INITSPLITSDK = "[ERROR] To use the manager, the SDK must be first initialized with an \"initSplitSdk\" action"; export declare const ERROR_SELECTOR_NO_SPLITSTATE = "[ERROR] When using selectors, \"splitState\" value must be a proper splitio piece of state"; +export declare const WARN_FEATUREFLAGS_AND_FLAGSETS = "[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored."; diff --git a/types/types.d.ts b/types/types.d.ts index fdfdb2f..56b5b79 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -79,16 +79,12 @@ export interface IInitSplitSdkParams { /** * Type of the param object passed to `getTreatments` action creator. */ -export interface IGetTreatmentsParams { +export declare type IGetTreatmentsParams = { /** * user key used to evaluate. It is mandatory for node but optional for browser. If not provided in browser, * it defaults to the key defined in the SDK config, i.e., the config object passed to `initSplitSdk`. */ key?: SplitIO.SplitKey; - /** - * feature flag name or array of feature flag names to evaluate. - */ - splitNames: string[] | string; /** * optional map of attributes passed to the actual `client.getTreatment*` methods. * @see {@link https://help.split.io/hc/en-us/articles/360038851551-Redux-SDK#attribute-syntax} @@ -108,7 +104,19 @@ export interface IGetTreatmentsParams { * @default false */ evalOnReadyFromCache?: boolean; -} +} & ({ + /** + * Feature flag name or array of feature flag names to evaluate. Either this or the `flagSets` property must be provided. If both are provided, the `flagSets` option is ignored. + */ + splitNames: string[] | string; + flagSets?: undefined; +} | { + /** + * Feature flag set or array of feature flag sets to evaluate. Either this or the `splitNames` property must be provided. If both are provided, the `flagSets` option is ignored. + */ + flagSets: string[] | string; + splitNames?: undefined; +}); /** * Type of the param object passed to `destroySplitSdk` action creator. */ diff --git a/types/utils.d.ts b/types/utils.d.ts index ce0d039..03974bf 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -20,7 +20,6 @@ export interface IClientStatus { export declare function getStatus(client: SplitIO.IClient): IClientStatus; /** * Validates and sanitizes the parameters passed to the "getTreatments" action creator. - * - * @returns {IGetTreatmentsParams} The returned object is a copy of the passed one, with the "splitNames" property converted to an array of strings. + * The returned object is a copy of the passed one, with the "splitNames" and "flagSets" properties converted to an array of strings. */ export declare function validateGetTreatmentsParams(params: unknown): IGetTreatmentsParams; From f0349f218d927d38f8a7009ff0a5ed22c78b86cb Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 15 Dec 2023 11:48:33 -0300 Subject: [PATCH 08/15] Update Split views mocks. Update CHANGES entry --- CHANGES.txt | 4 +++- src/__tests__/helpers.browser.test.ts | 4 ++++ src/__tests__/helpers.node.test.ts | 4 ++++ src/asyncActions.ts | 4 ++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 45caa03..aa221cc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,7 @@ 1.10.0 (December 19, 2023) - - Updated `getTreatments` actions creator to validate the provided params object, in order to log a descriptive error when an invalid object is provided rather than throwing a cryptic error. + - Added `defaultTreatment` property to the `SplitView` object returned by the `getSplit` and `getSplits` helper functions (Related to issue https://github.com/splitio/javascript-commons/issues/225). + - Updated `getTreatments` action creator to validate the provided params object, in order to log a descriptive error when an invalid object is provided rather than throwing a cryptic error. + - Updated @splitsoftware/splitio package to version 10.24.1 that includes flag sets support, vulnerability fixes and other improvements. 1.9.0 (July 18, 2023) - Updated some transitive dependencies for vulnerability fixes. diff --git a/src/__tests__/helpers.browser.test.ts b/src/__tests__/helpers.browser.test.ts index 5c1267d..64e4f30 100644 --- a/src/__tests__/helpers.browser.test.ts +++ b/src/__tests__/helpers.browser.test.ts @@ -29,6 +29,8 @@ const featureFlagViews: SplitIO.SplitViews = [ treatments: ['on', 'off'], changeNumber: 0, configs: { on: null, off: null }, + sets: [], + defaultTreatment: 'off', }, { name: 'split_2', trafficType: 'user', @@ -36,6 +38,8 @@ const featureFlagViews: SplitIO.SplitViews = [ treatments: ['on', 'off'], changeNumber: 0, configs: { on: null, off: null }, + sets: [], + defaultTreatment: 'off', }, ]; diff --git a/src/__tests__/helpers.node.test.ts b/src/__tests__/helpers.node.test.ts index e5807c8..cd1db86 100644 --- a/src/__tests__/helpers.node.test.ts +++ b/src/__tests__/helpers.node.test.ts @@ -29,6 +29,8 @@ const featureFlagViews: SplitIO.SplitViews = [ treatments: ['on', 'off'], changeNumber: 0, configs: { on: null, off: null }, + sets: [], + defaultTreatment: 'off', }, { name: 'split_2', trafficType: 'user', @@ -36,6 +38,8 @@ const featureFlagViews: SplitIO.SplitViews = [ treatments: ['on', 'off'], changeNumber: 0, configs: { on: null, off: null }, + sets: [], + defaultTreatment: 'off', }, ]; diff --git a/src/asyncActions.ts b/src/asyncActions.ts index 94f9f7c..c859769 100644 --- a/src/asyncActions.ts +++ b/src/asyncActions.ts @@ -87,7 +87,7 @@ export function initSplitSdk(params: IInitSplitSdkParams): (dispatch: Dispatch { @@ -118,7 +118,7 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi // Register or unregister the current `getTreatments` action from being re-executed on SDK_UPDATE. if (params.evalOnUpdate) { splitNames.forEach((featureFlagName) => { - client.evalOnUpdate[featureFlagName] = { ...params, splitNames: [featureFlagName] }; + client.evalOnUpdate[featureFlagName] = { ...params, splitNames: [featureFlagName] } as IGetTreatmentsParams; }); } else { splitNames.forEach((featureFlagName) => { From 2ec9665f88fc83a38956ed736ad9f8874c42b663 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 15 Dec 2023 13:59:34 -0300 Subject: [PATCH 09/15] Polishing --- src/__tests__/actions.browser.test.ts | 20 ++++++++++++- src/__tests__/utils.test.ts | 34 +++++++++------------ src/asyncActions.ts | 4 ++- src/constants.ts | 8 +++-- src/utils.ts | 43 ++++++++++++++++----------- types/constants.d.ts | 7 +++-- types/utils.d.ts | 2 +- 7 files changed, 72 insertions(+), 46 deletions(-) diff --git a/src/__tests__/actions.browser.test.ts b/src/__tests__/actions.browser.test.ts index 432edd8..a7375a8 100644 --- a/src/__tests__/actions.browser.test.ts +++ b/src/__tests__/actions.browser.test.ts @@ -13,7 +13,7 @@ import { sdkBrowserConfig } from './utils/sdkConfigs'; import { SPLIT_READY, SPLIT_READY_WITH_EVALUATIONS, SPLIT_READY_FROM_CACHE, SPLIT_READY_FROM_CACHE_WITH_EVALUATIONS, SPLIT_UPDATE, SPLIT_UPDATE_WITH_EVALUATIONS, SPLIT_TIMEDOUT, SPLIT_DESTROY, ADD_TREATMENTS, - ERROR_GETT_NO_INITSPLITSDK, ERROR_DESTROY_NO_INITSPLITSDK, getControlTreatmentsWithConfig, + ERROR_GETT_NO_INITSPLITSDK, ERROR_DESTROY_NO_INITSPLITSDK, getControlTreatmentsWithConfig, ERROR_GETT_NO_PARAM_OBJECT, } from '../constants'; /** Test targets */ @@ -160,6 +160,24 @@ describe('getTreatments', () => { expect(store.getActions().length).toBe(0); }); + it('logs error and dispatches a no-op async action if the provided param is invalid', async () => { + // Init SDK and set ready + const store = mockStore(STATE_INITIAL); + const actionResult = store.dispatch(initSplitSdk({ config: sdkBrowserConfig })); + (splitSdk.factory as any).client().__emitter__.emit(Event.SDK_READY); + await actionResult; + + const consoleLogSpy = jest.spyOn(console, 'log'); + store.clearActions(); + + // @ts-expect-error testing invalid input + store.dispatch(getTreatments()); + + expect(store.getActions().length).toBe(0); + expect(consoleLogSpy).toBeCalledWith(ERROR_GETT_NO_PARAM_OBJECT); + consoleLogSpy.mockRestore(); + }); + it('dispatches an ADD_TREATMENTS action if Split SDK is ready', (done) => { // Init SDK and set ready diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 0db9d31..1748cde 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -49,32 +49,26 @@ describe('validateGetTreatmentsParams', () => { it('should ignore flagSets if both splitNames and flagSets are provided', () => { const consoleSpy = jest.spyOn(console, 'log'); - expect(validateGetTreatmentsParams({ splitNames: ['some split', null], flagSets: ['flag set', null] })).toStrictEqual({ splitNames: ['some split'], flagSets: undefined }); - expect(validateGetTreatmentsParams({ splitNames: true, flagSets: ['flag set', null] })).toStrictEqual({ splitNames: [], flagSets: undefined }); - - expect(consoleSpy.mock.calls).toEqual([ - [WARN_FEATUREFLAGS_AND_FLAGSETS], - ['[ERROR] you passed a null or undefined feature flag name, feature flag name must be a non-empty string.'], - [WARN_FEATUREFLAGS_AND_FLAGSETS], - ['[ERROR] feature flag names must be a non-empty array.'] - ]); + expect(validateGetTreatmentsParams({ splitNames: ['some split'], flagSets: ['flag set', null] })).toStrictEqual({ splitNames: ['some split'], flagSets: undefined }); + + expect(consoleSpy.mock.calls).toEqual([[WARN_FEATUREFLAGS_AND_FLAGSETS]]); consoleSpy.mockRestore(); }); - it('should return a valid object if splitNames and flagSets values are invalid', () => { + it('should return false if splitNames and flagSets values are invalid', () => { // Invalid values for splitNames and flagSets are converted to empty arrays - expect(validateGetTreatmentsParams({ splitNames: {} })).toStrictEqual({ splitNames: [], flagSets: undefined }); - expect(validateGetTreatmentsParams({ flagSets: {} })).toStrictEqual({ splitNames: undefined, flagSets: [] }); - expect(validateGetTreatmentsParams({ splitNames: true })).toStrictEqual({ splitNames: [], flagSets: undefined }); - expect(validateGetTreatmentsParams({ flagSets: true })).toStrictEqual({ splitNames: undefined, flagSets: [] }); - expect(validateGetTreatmentsParams({ splitNames: null, flagSets: null })).toStrictEqual({ splitNames: undefined, flagSets: [] }); - expect(validateGetTreatmentsParams({})).toStrictEqual({ splitNames: undefined, flagSets: [] }); + expect(validateGetTreatmentsParams({ splitNames: {} })).toStrictEqual(false); + expect(validateGetTreatmentsParams({ flagSets: {} })).toStrictEqual(false); + expect(validateGetTreatmentsParams({ splitNames: true })).toStrictEqual(false); + expect(validateGetTreatmentsParams({ flagSets: true })).toStrictEqual(false); + expect(validateGetTreatmentsParams({ splitNames: null, flagSets: null })).toStrictEqual(false); + expect(validateGetTreatmentsParams({})).toStrictEqual(false); }); - it('should return a valid object if the provided param is not an object', () => { // @ts-expect-error testing invalid input - expect(validateGetTreatmentsParams()).toStrictEqual({ splitNames: undefined, flagSets: [] }); - expect(validateGetTreatmentsParams([])).toStrictEqual({ splitNames: undefined, flagSets: [] }); - expect(validateGetTreatmentsParams('invalid')).toStrictEqual({ splitNames: undefined, flagSets: [] }); + it('should return false if the provided param is not an object', () => { // @ts-expect-error testing invalid input + expect(validateGetTreatmentsParams()).toStrictEqual(false); + expect(validateGetTreatmentsParams([])).toStrictEqual(false); + expect(validateGetTreatmentsParams('invalid')).toStrictEqual(false); }); }); diff --git a/src/asyncActions.ts b/src/asyncActions.ts index c859769..92fd3af 100644 --- a/src/asyncActions.ts +++ b/src/asyncActions.ts @@ -108,7 +108,9 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi return () => { }; } - params = validateGetTreatmentsParams(params); + params = validateGetTreatmentsParams(params) as IGetTreatmentsParams; + if (!params) return () => { }; + const splitNames = params.splitNames as string[]; if (!splitSdk.isDetached) { // Split SDK running in Browser diff --git a/src/constants.ts b/src/constants.ts index 43ba9c2..71fcde5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -42,9 +42,9 @@ export const SPLIT_DESTROY = 'SPLIT_DESTROY'; export const ADD_TREATMENTS = 'ADD_TREATMENTS'; // Warning and error messages -export const ERROR_GETT_NO_INITSPLITSDK = '[ERROR] To use "getTreatments" the SDK must be first initialized with a "initSplitSdk" action'; +export const ERROR_GETT_NO_INITSPLITSDK = '[ERROR] To use "getTreatments" the SDK must be first initialized with an "initSplitSdk" action'; -export const ERROR_DESTROY_NO_INITSPLITSDK = '[ERROR] To use "destroySplitSdk" the SDK must be first initialized with a "initSplitSdk" action'; +export const ERROR_DESTROY_NO_INITSPLITSDK = '[ERROR] To use "destroySplitSdk" the SDK must be first initialized with an "initSplitSdk" action'; export const ERROR_TRACK_NO_INITSPLITSDK = '[ERROR] To use "track" the SDK must be first initialized with an "initSplitSdk" action'; @@ -52,4 +52,6 @@ export const ERROR_MANAGER_NO_INITSPLITSDK = '[ERROR] To use the manager, the SD export const ERROR_SELECTOR_NO_SPLITSTATE = '[ERROR] When using selectors, "splitState" value must be a proper splitio piece of state'; -export const WARN_FEATUREFLAGS_AND_FLAGSETS = '[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored.'; +export const ERROR_GETT_NO_PARAM_OBJECT = '[ERROR] "getTreatments" must be called with a param object containing either splitNames or flagSets properties'; + +export const WARN_FEATUREFLAGS_AND_FLAGSETS = '[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored'; diff --git a/src/utils.ts b/src/utils.ts index 8ffb424..62bb68c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { WARN_FEATUREFLAGS_AND_FLAGSETS } from './constants'; +import { ERROR_GETT_NO_PARAM_OBJECT, WARN_FEATUREFLAGS_AND_FLAGSETS } from './constants'; import { IGetTreatmentsParams } from './types'; /** @@ -37,26 +37,35 @@ export function getStatus(client: SplitIO.IClient): IClientStatus { * Validates and sanitizes the parameters passed to the "getTreatments" action creator. * The returned object is a copy of the passed one, with the "splitNames" and "flagSets" properties converted to an array of strings. */ -export function validateGetTreatmentsParams(params: unknown) { - if (!isObject(params)) { - console.log('[ERROR] "getTreatments" must be called with a param object.'); - params = {}; +export function validateGetTreatmentsParams(params: any): IGetTreatmentsParams | false { + if (!isObject(params) || (!params.splitNames && !params.flagSets)) { + console.log(ERROR_GETT_NO_PARAM_OBJECT); + return false; } - const { splitNames, flagSets } = params as IGetTreatmentsParams; - if (splitNames && flagSets) console.log(WARN_FEATUREFLAGS_AND_FLAGSETS); + let { splitNames, flagSets } = params; + + if (splitNames) { + // Feature flag names are sanitized because they are passed to the getControlTreatmentsWithConfig function. + splitNames = validateFeatureFlags(typeof splitNames === 'string' ? [splitNames] : splitNames); + if (!splitNames) return false; + + // Ignore flagSets if splitNames are provided + if (flagSets) { + console.log(WARN_FEATUREFLAGS_AND_FLAGSETS); + flagSets = undefined; + } + } else { + // Flag set names are not sanitized, because they are not used by Redux SDK directly. We just make sure it is an array. + flagSets = typeof flagSets === 'string' ? [flagSets] : flagSets; + if (!Array.isArray(flagSets)) return false; + } return { - ...params as IGetTreatmentsParams, - splitNames: splitNames ? - // Feature flag names are sanitized because they are passed to the getControlTreatmentsWithConfig function. - validateFeatureFlags(typeof splitNames === 'string' ? [splitNames] : splitNames) || [] : - undefined, - flagSets: splitNames ? - undefined : - // Flag set names are not sanitized, because they are not used by Redux SDK directly. We just make sure it is an array. - typeof flagSets === 'string' ? [flagSets] : Array.isArray(flagSets) ? flagSets : [], - } as IGetTreatmentsParams; + ...params, + splitNames, + flagSets, + }; } // The following input validation utils are based on the ones in the React SDK. They might be replaced by utils from the JS SDK in the future. diff --git a/types/constants.d.ts b/types/constants.d.ts index b4fbe3a..6583aaf 100644 --- a/types/constants.d.ts +++ b/types/constants.d.ts @@ -14,9 +14,10 @@ export declare const SPLIT_UPDATE_WITH_EVALUATIONS = "SPLIT_UPDATE_WITH_EVALUATI export declare const SPLIT_TIMEDOUT = "SPLIT_TIMEDOUT"; export declare const SPLIT_DESTROY = "SPLIT_DESTROY"; export declare const ADD_TREATMENTS = "ADD_TREATMENTS"; -export declare const ERROR_GETT_NO_INITSPLITSDK = "[ERROR] To use \"getTreatments\" the SDK must be first initialized with a \"initSplitSdk\" action"; -export declare const ERROR_DESTROY_NO_INITSPLITSDK = "[ERROR] To use \"destroySplitSdk\" the SDK must be first initialized with a \"initSplitSdk\" action"; +export declare const ERROR_GETT_NO_INITSPLITSDK = "[ERROR] To use \"getTreatments\" the SDK must be first initialized with an \"initSplitSdk\" action"; +export declare const ERROR_DESTROY_NO_INITSPLITSDK = "[ERROR] To use \"destroySplitSdk\" the SDK must be first initialized with an \"initSplitSdk\" action"; export declare const ERROR_TRACK_NO_INITSPLITSDK = "[ERROR] To use \"track\" the SDK must be first initialized with an \"initSplitSdk\" action"; export declare const ERROR_MANAGER_NO_INITSPLITSDK = "[ERROR] To use the manager, the SDK must be first initialized with an \"initSplitSdk\" action"; export declare const ERROR_SELECTOR_NO_SPLITSTATE = "[ERROR] When using selectors, \"splitState\" value must be a proper splitio piece of state"; -export declare const WARN_FEATUREFLAGS_AND_FLAGSETS = "[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored."; +export declare const ERROR_GETT_NO_PARAM_OBJECT = "[ERROR] \"getTreatments\" must be called with a param object containing either splitNames or flagSets properties"; +export declare const WARN_FEATUREFLAGS_AND_FLAGSETS = "[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored"; diff --git a/types/utils.d.ts b/types/utils.d.ts index 03974bf..f37d930 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -22,4 +22,4 @@ export declare function getStatus(client: SplitIO.IClient): IClientStatus; * Validates and sanitizes the parameters passed to the "getTreatments" action creator. * The returned object is a copy of the passed one, with the "splitNames" and "flagSets" properties converted to an array of strings. */ -export declare function validateGetTreatmentsParams(params: unknown): IGetTreatmentsParams; +export declare function validateGetTreatmentsParams(params: any): IGetTreatmentsParams | false; From 38aac02013ed4ce6f4bc9bfeca2083536b077be8 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 15 Dec 2023 16:20:52 -0300 Subject: [PATCH 10/15] Update getTreatments action creator to support the flagSets property --- CHANGES.txt | 6 ++- src/__tests__/actions.browser.test.ts | 58 ++++++++++++++-------- src/__tests__/actions.node.test.ts | 21 +++++--- src/__tests__/utils/mockBrowserSplitSdk.ts | 7 +++ src/__tests__/utils/mockNodeSplitSdk.ts | 7 +++ src/asyncActions.ts | 30 +++++++---- types/asyncActions.d.ts | 4 +- 7 files changed, 93 insertions(+), 40 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index aa221cc..e5b1601 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,8 @@ 1.10.0 (December 19, 2023) + - Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation): + - Added a new optional `flagSets` property to the param object of the `getTreatments` action creator, to support evaluating flags in given flag set/s. Either `splitNames` or `flagSets` must be provided to the function. If both are provided, `splitNames` will be used. + - Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload. + - Added `sets` property to the `SplitView` object returned by the `getSplit` and `getSplits` helper functions to expose flag sets on flag views. - Added `defaultTreatment` property to the `SplitView` object returned by the `getSplit` and `getSplits` helper functions (Related to issue https://github.com/splitio/javascript-commons/issues/225). - Updated `getTreatments` action creator to validate the provided params object, in order to log a descriptive error when an invalid object is provided rather than throwing a cryptic error. - Updated @splitsoftware/splitio package to version 10.24.1 that includes flag sets support, vulnerability fixes and other improvements. @@ -73,7 +77,7 @@ - Updated Split's SDK dependency to fix vulnerabilities. 1.3.0 (December 9, 2020) - - Added a new parameter to `getTreatments` actions creator: `evalOnReadyFromCache` to evaluate feature flags when the SDK_READY_FROM_CACHE event is emitted. Learn more in our Redux SDK documentation. + - Added a new parameter to `getTreatments` action creator: `evalOnReadyFromCache` to evaluate feature flags when the SDK_READY_FROM_CACHE event is emitted. Learn more in our Redux SDK documentation. - Updated how feature flag evaluations are handled on SDK_READY, SDK_READY_FROM_CACHE and SDK_UPDATE events, to dispatch a single action with evaluations that results in all treatments updates in the state at once, instead of having multiple actions that might lead to multiple store notifications. - Updated some NPM dependencies for vulnerability fixes. diff --git a/src/__tests__/actions.browser.test.ts b/src/__tests__/actions.browser.test.ts index a7375a8..1c114cb 100644 --- a/src/__tests__/actions.browser.test.ts +++ b/src/__tests__/actions.browser.test.ts @@ -187,13 +187,20 @@ describe('getTreatments', () => { actionResult.then(() => { store.dispatch(getTreatments({ splitNames: 'split1' })); + store.dispatch(getTreatments({ flagSets: 'set1' })); - const action = store.getActions()[1]; - expect(action.type).toBe(ADD_TREATMENTS); - expect(action.payload.key).toBe(sdkBrowserConfig.core.key); + const actions = [store.getActions()[1], store.getActions()[2]]; + actions.forEach(action => { + expect(action.type).toBe(ADD_TREATMENTS); + expect(action.payload.key).toBe(sdkBrowserConfig.core.key); + }); // getting the evaluation result and validating it matches the results from SDK - expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveLastReturnedWith(action.payload.treatments); + expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveBeenLastCalledWith(['split1'], undefined); + expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveLastReturnedWith(actions[0].payload.treatments); + expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toHaveBeenLastCalledWith(['set1'], undefined); + expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toHaveLastReturnedWith(actions[1].payload.treatments); + expect(getClient(splitSdk).evalOnUpdate).toEqual({}); expect(getClient(splitSdk).evalOnReady.length).toEqual(0); @@ -256,10 +263,16 @@ describe('getTreatments', () => { // The first ADD_TREATMENTS actions is dispatched again, but this time is registered for 'evalOnUpdate' store.dispatch(getTreatments({ splitNames: 'split1', evalOnUpdate: true })); + // Dispatch another ADD_TREATMENTS action with flag sets + store.dispatch(getTreatments({ flagSets: 'set1', evalOnUpdate: true })); // Validate action and registered callback expect(splitSdk.factory.client().getTreatmentsWithConfig).toBeCalledTimes(5); - expect(Object.values(getClient(splitSdk).evalOnUpdate).length).toBe(1); + expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toBeCalledTimes(1); + expect(getClient(splitSdk).evalOnUpdate).toEqual({ + 'flag::split1': { evalOnUpdate: true, flagSets: undefined, splitNames: ['split1'] }, + 'set::set1': { evalOnUpdate: true, flagSets: ['set1'], splitNames: undefined } + }); done(); }); @@ -271,23 +284,25 @@ describe('getTreatments', () => { const store = mockStore(STATE_INITIAL); const actionResult = store.dispatch(initSplitSdk({ config: sdkBrowserConfig, onReadyFromCache: onReadyFromCacheCb })); store.dispatch(getTreatments({ splitNames: 'split2' })); // `evalOnUpdate` and `evalOnReadyFromCache` params are false by default + store.dispatch(getTreatments({ flagSets: 'set2' })); - // If SDK is not operational, an ADD_TREATMENTS action is dispatched with control treatments - // without calling SDK client, but the item is added to 'evalOnReady' list. - expect(store.getActions().length).toBe(1); - expect(getClient(splitSdk).evalOnReady.length).toEqual(1); - expect(getClient(splitSdk).evalOnUpdate).toEqual({}); - let action = store.getActions()[0]; - expect(action.type).toBe(ADD_TREATMENTS); - expect(action.payload.key).toBe(sdkBrowserConfig.core.key); - expect(action.payload.treatments).toEqual(getControlTreatmentsWithConfig(['split2'])); + // If SDK is not operational, ADD_TREATMENTS actions are dispatched, with control treatments for provided feature flag names, and no treatments for provided flag sets. + + expect(store.getActions()).toEqual([ + { type: ADD_TREATMENTS, payload: { key: sdkBrowserConfig.core.key, treatments: getControlTreatmentsWithConfig(['split2']) } }, + { type: ADD_TREATMENTS, payload: { key: sdkBrowserConfig.core.key, treatments: {} } }, + ]); + // SDK client is not called, but items are added to 'evalOnReady' list. expect(splitSdk.factory.client().getTreatmentsWithConfig).toBeCalledTimes(0); + expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toBeCalledTimes(0); + expect(getClient(splitSdk).evalOnReady.length).toEqual(2); + expect(getClient(splitSdk).evalOnUpdate).toEqual({}); // When the SDK is ready from cache, the SPLIT_READY_FROM_CACHE_WITH_EVALUATIONS action is not dispatched if the `getTreatments` action was dispatched with `evalOnReadyFromCache` false (splitSdk.factory as any).client().__emitter__.emit(Event.SDK_READY_FROM_CACHE); function onReadyFromCacheCb() { - expect(store.getActions().length).toBe(2); - action = store.getActions()[1]; + expect(store.getActions().length).toBe(3); + const action = store.getActions()[2]; expect(action.type).toBe(SPLIT_READY_FROM_CACHE); } @@ -295,16 +310,19 @@ describe('getTreatments', () => { actionResult.then(() => { // The SPLIT_READY_WITH_EVALUATIONS action is dispatched if the SDK is ready and there are pending evaluations. - action = store.getActions()[2]; + const action = store.getActions()[3]; expect(action.type).toBe(SPLIT_READY_WITH_EVALUATIONS); expect(action.payload.key).toBe(sdkBrowserConfig.core.key); // getting the evaluation result and validating it matches the results from SDK const treatments = action.payload.treatments; - expect(splitSdk.factory.client().getTreatmentsWithConfig).lastCalledWith(['split2'], undefined); - expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveLastReturnedWith(treatments); + expect(splitSdk.factory.client().getTreatmentsWithConfig).toBeCalledWith(['split2'], undefined); + expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toBeCalledWith(['set2'], undefined); + expect(treatments).toEqual({ + ...(splitSdk.factory.client().getTreatmentsWithConfig as jest.Mock).mock.results[0].value, + ...(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets as jest.Mock).mock.results[0].value, + }) - expect(splitSdk.factory.client().getTreatmentsWithConfig).toBeCalledTimes(1); // control assertion - getTreatmentsWithConfig calls expect(getClient(splitSdk).evalOnUpdate).toEqual({}); // control assertion - cbs scheduled for update // The ADD_TREATMENTS actions is dispatched again, but this time is registered for 'evalOnUpdate' diff --git a/src/__tests__/actions.node.test.ts b/src/__tests__/actions.node.test.ts index 7e7bb3d..3d99a73 100644 --- a/src/__tests__/actions.node.test.ts +++ b/src/__tests__/actions.node.test.ts @@ -167,22 +167,27 @@ describe('getTreatments', () => { // Invoke with a feature flag name string and no attributes store.dispatch(getTreatments({ key: splitKey, splitNames: 'split1' })); + store.dispatch(getTreatments({ key: splitKey, flagSets: ['set1'] })); - let action = store.getActions()[1]; // action 0 is SPLIT_READY - expect(action.type).toBe(ADD_TREATMENTS); - expect(action.payload.key).toBe(splitKey); + const actions = [store.getActions()[1], store.getActions()[2]]; // action 0 is SPLIT_READY + actions.forEach(action => { + expect(action.type).toBe(ADD_TREATMENTS); + expect(action.payload.key).toBe(splitKey); + }); expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveBeenLastCalledWith(splitKey, ['split1'], undefined); - expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveLastReturnedWith(action.payload.treatments); + expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveLastReturnedWith(actions[0].payload.treatments); + expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toHaveBeenLastCalledWith(splitKey, ['set1'], undefined); + expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toHaveLastReturnedWith(actions[1].payload.treatments); // Invoke with a list of feature flag names and a attributes object const featureFlagNames = ['split1', 'split2']; const attributes = { att1: 'att1' }; - store.dispatch(getTreatments({ key: splitKey, splitNames: featureFlagNames, attributes })); + store.dispatch(getTreatments({ key: 'other_user', splitNames: featureFlagNames, attributes })); - action = store.getActions()[2]; + const action = store.getActions()[3]; expect(action.type).toBe(ADD_TREATMENTS); - expect(action.payload.key).toBe(splitKey); - expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveBeenLastCalledWith(splitKey, featureFlagNames, attributes); + expect(action.payload.key).toBe('other_user'); + expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveBeenLastCalledWith('other_user', featureFlagNames, attributes); expect(splitSdk.factory.client().getTreatmentsWithConfig).toHaveLastReturnedWith(action.payload.treatments); } diff --git a/src/__tests__/utils/mockBrowserSplitSdk.ts b/src/__tests__/utils/mockBrowserSplitSdk.ts index 872e3bf..f6c696e 100644 --- a/src/__tests__/utils/mockBrowserSplitSdk.ts +++ b/src/__tests__/utils/mockBrowserSplitSdk.ts @@ -60,6 +60,12 @@ export function mockSdk() { return acc; }, {}); }); + const getTreatmentsWithConfigByFlagSets: jest.Mock = jest.fn((flagSets) => { + return flagSets.reduce((acc: SplitIO.TreatmentsWithConfig, flagSet: string) => { + acc[flagSet + '_feature_flag'] = { treatment: 'fakeTreatment', config: null }; + return acc; + }, {}); + }); const setAttributes: jest.Mock = jest.fn(() => { return true; }); @@ -89,6 +95,7 @@ export function mockSdk() { return Object.assign(Object.create(__emitter__), { getTreatmentsWithConfig, + getTreatmentsWithConfigByFlagSets, track, ready, destroy, diff --git a/src/__tests__/utils/mockNodeSplitSdk.ts b/src/__tests__/utils/mockNodeSplitSdk.ts index a346f20..a5eee41 100644 --- a/src/__tests__/utils/mockNodeSplitSdk.ts +++ b/src/__tests__/utils/mockNodeSplitSdk.ts @@ -27,6 +27,12 @@ function mockClient() { return acc; }, {}); }); + const getTreatmentsWithConfigByFlagSets: jest.Mock = jest.fn((key, flagSets) => { + return flagSets.reduce((acc: SplitIO.TreatmentsWithConfig, flagSet: string) => { + acc[flagSet + '_feature_flag'] = { treatment: 'fakeTreatment', config: null }; + return acc; + }, {}); + }); const ready: jest.Mock = jest.fn(() => { return promiseWrapper(new Promise((res, rej) => { __isReady__ ? res() : __emitter__.on(Event.SDK_READY, res); @@ -47,6 +53,7 @@ function mockClient() { return Object.assign(Object.create(__emitter__), { getTreatmentsWithConfig, + getTreatmentsWithConfigByFlagSets, track, ready, destroy, diff --git a/src/asyncActions.ts b/src/asyncActions.ts index 92fd3af..7f63093 100644 --- a/src/asyncActions.ts +++ b/src/asyncActions.ts @@ -91,7 +91,9 @@ export function initSplitSdk(params: IInitSplitSdkParams): (dispatch: Dispatch { - return { ...acc, ...client.getTreatmentsWithConfig((params.splitNames as string[]), params.attributes) }; + return params.splitNames ? + { ...acc, ...client.getTreatmentsWithConfig(params.splitNames as string[], params.attributes) } : + { ...acc, ...client.getTreatmentsWithConfigByFlagSets(params.flagSets as string[], params.attributes) }; }, {}); } @@ -112,6 +114,7 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi if (!params) return () => { }; const splitNames = params.splitNames as string[]; + const flagSets = params.flagSets as string[]; if (!splitSdk.isDetached) { // Split SDK running in Browser @@ -119,12 +122,18 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi // Register or unregister the current `getTreatments` action from being re-executed on SDK_UPDATE. if (params.evalOnUpdate) { - splitNames.forEach((featureFlagName) => { - client.evalOnUpdate[featureFlagName] = { ...params, splitNames: [featureFlagName] } as IGetTreatmentsParams; + splitNames && splitNames.forEach((featureFlagName) => { + client.evalOnUpdate[`flag::${featureFlagName}`] = { ...params, splitNames: [featureFlagName] } as IGetTreatmentsParams; + }); + flagSets && flagSets.forEach((flagSetName) => { + client.evalOnUpdate[`set::${flagSetName}`] = { ...params, flagSets: [flagSetName] } as IGetTreatmentsParams; }); } else { - splitNames.forEach((featureFlagName) => { - delete client.evalOnUpdate[featureFlagName]; + splitNames && splitNames.forEach((featureFlagName) => { + delete client.evalOnUpdate[`flag::${featureFlagName}`]; + }); + flagSets && flagSets.forEach((flagSetName) => { + delete client.evalOnUpdate[`set::${flagSetName}`]; }); } @@ -146,15 +155,18 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi return addTreatments(params.key || (splitSdk.config as SplitIO.IBrowserSettings).core.key, treatments); } else { // Otherwise, it adds control treatments to the store, without calling the SDK (no impressions sent) + // With flag sets, an empty object is passed since we don't know their feature flag names // @TODO remove eventually to minimize state changes - return addTreatments(params.key || (splitSdk.config as SplitIO.IBrowserSettings).core.key, getControlTreatmentsWithConfig(splitNames)); + return addTreatments(params.key || (splitSdk.config as SplitIO.IBrowserSettings).core.key, splitNames ? getControlTreatmentsWithConfig(splitNames) : {}); } } else { // Split SDK running in Node // Evaluate Split and return redux action. const client = splitSdk.factory.client(); - const treatments = client.getTreatmentsWithConfig(params.key, splitNames, params.attributes); + const treatments = splitNames ? + client.getTreatmentsWithConfig(params.key, splitNames, params.attributes) : + client.getTreatmentsWithConfigByFlagSets(params.key, flagSets, params.attributes); return addTreatments(params.key, treatments); } @@ -167,9 +179,9 @@ interface IClientNotDetached extends SplitIO.IClient { _trackingStatus?: boolean; /** * stored evaluations to execute on SDK update. It is an object because we might - * want to change the evaluation parameters (i.e. attributes) per each feature flag name. + * want to change the evaluation parameters (i.e. attributes) per each feature flag name or flag set. */ - evalOnUpdate: { [featureFlagName: string]: IGetTreatmentsParams }; + evalOnUpdate: { [name: string]: IGetTreatmentsParams }; /** * stored evaluations to execute when the SDK is ready. It is an array, so if multiple evaluations * are set with the same feature flag name, the result (i.e. treatment) of the last one is the stored one. diff --git a/types/asyncActions.d.ts b/types/asyncActions.d.ts index 2697703..1498a97 100644 --- a/types/asyncActions.d.ts +++ b/types/asyncActions.d.ts @@ -35,10 +35,10 @@ interface IClientNotDetached extends SplitIO.IClient { _trackingStatus?: boolean; /** * stored evaluations to execute on SDK update. It is an object because we might - * want to change the evaluation parameters (i.e. attributes) per each feature flag name. + * want to change the evaluation parameters (i.e. attributes) per each feature flag name or flag set. */ evalOnUpdate: { - [featureFlagName: string]: IGetTreatmentsParams; + [name: string]: IGetTreatmentsParams; }; /** * stored evaluations to execute when the SDK is ready. It is an array, so if multiple evaluations From 1745bc177c018981efcc9ce6e7d435f137dbacf6 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 18 Dec 2023 11:29:09 -0300 Subject: [PATCH 11/15] Polishing --- src/__tests__/index.test.ts | 46 +++++++++++++++++++++++++++++++++++++ src/utils.ts | 6 ++--- 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/index.test.ts diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts new file mode 100644 index 0000000..69e68cd --- /dev/null +++ b/src/__tests__/index.test.ts @@ -0,0 +1,46 @@ +import { + splitReducer as exportedSplitReducer, + initSplitSdk as exportedInitSplitSdk, + getTreatments as exportedGetTreatments, + destroySplitSdk as exportedDestroySplitSdk, + splitSdk as exportedSplitSdk, + track as exportedTrack, + getSplitNames as exportedGetSplitNames, + getSplit as exportedGetSplit, + getSplits as exportedGetSplits, + selectTreatmentValue as exportedSelectTreatmentValue, + selectTreatmentWithConfig as exportedSelectTreatmentWithConfig, + connectSplit as exportedConnectSplit, + connectToggler as exportedConnectToggler, + mapTreatmentToProps as exportedMapTreatmentToProps, + mapIsFeatureOnToProps as exportedMapIsFeatureOnToProps, + // Checks that types are exported. Otherwise, the test would fail with a TS error. + ISplitState, +} from '../index'; + +import { splitReducer } from '../reducer'; +import { initSplitSdk, getTreatments, destroySplitSdk, splitSdk } from '../asyncActions'; +import { track, getSplitNames, getSplit, getSplits } from '../helpers'; +import { selectTreatmentValue, selectTreatmentWithConfig } from '../selectors'; +import { connectSplit } from '../react-redux/connectSplit'; +import { connectToggler, mapTreatmentToProps, mapIsFeatureOnToProps } from '../react-redux/connectToggler'; + +it('index should export modules', () => { + + expect(exportedSplitReducer).toBe(splitReducer); + expect(exportedInitSplitSdk).toBe(initSplitSdk); + expect(exportedGetTreatments).toBe(getTreatments); + expect(exportedDestroySplitSdk).toBe(destroySplitSdk); + expect(exportedSplitSdk).toBe(splitSdk); + expect(exportedTrack).toBe(track); + expect(exportedGetSplitNames).toBe(getSplitNames); + expect(exportedGetSplit).toBe(getSplit); + expect(exportedGetSplits).toBe(getSplits); + expect(exportedSelectTreatmentValue).toBe(selectTreatmentValue); + expect(exportedSelectTreatmentWithConfig).toBe(selectTreatmentWithConfig); + expect(exportedConnectSplit).toBe(connectSplit); + expect(exportedConnectToggler).toBe(connectToggler); + expect(exportedMapTreatmentToProps).toBe(mapTreatmentToProps); + expect(exportedMapIsFeatureOnToProps).toBe(mapIsFeatureOnToProps); + +}); diff --git a/src/utils.ts b/src/utils.ts index 62bb68c..0a90e0f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -51,10 +51,8 @@ export function validateGetTreatmentsParams(params: any): IGetTreatmentsParams | if (!splitNames) return false; // Ignore flagSets if splitNames are provided - if (flagSets) { - console.log(WARN_FEATUREFLAGS_AND_FLAGSETS); - flagSets = undefined; - } + if (flagSets) console.log(WARN_FEATUREFLAGS_AND_FLAGSETS); + flagSets = undefined; } else { // Flag set names are not sanitized, because they are not used by Redux SDK directly. We just make sure it is an array. flagSets = typeof flagSets === 'string' ? [flagSets] : flagSets; From d1a01a619e0f6c8b043e8a33f1383c208949fb81 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 18 Dec 2023 12:21:53 -0300 Subject: [PATCH 12/15] Log error if invalid split names or flag sets are provided --- src/__tests__/index.test.ts | 2 +- src/constants.ts | 2 +- src/utils.ts | 10 ++++++++-- types/constants.d.ts | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 69e68cd..a6ff13b 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -14,7 +14,7 @@ import { connectToggler as exportedConnectToggler, mapTreatmentToProps as exportedMapTreatmentToProps, mapIsFeatureOnToProps as exportedMapIsFeatureOnToProps, - // Checks that types are exported. Otherwise, the test would fail with a TS error. + /* eslint-disable @typescript-eslint/no-unused-vars */ // Checks that types are exported. Otherwise, the test would fail with a TS error. ISplitState, } from '../index'; diff --git a/src/constants.ts b/src/constants.ts index 71fcde5..c62d723 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -52,6 +52,6 @@ export const ERROR_MANAGER_NO_INITSPLITSDK = '[ERROR] To use the manager, the SD export const ERROR_SELECTOR_NO_SPLITSTATE = '[ERROR] When using selectors, "splitState" value must be a proper splitio piece of state'; -export const ERROR_GETT_NO_PARAM_OBJECT = '[ERROR] "getTreatments" must be called with a param object containing either splitNames or flagSets properties'; +export const ERROR_GETT_NO_PARAM_OBJECT = '[ERROR] "getTreatments" must be called with a param object containing a valid splitNames or flagSets properties'; export const WARN_FEATUREFLAGS_AND_FLAGSETS = '[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored'; diff --git a/src/utils.ts b/src/utils.ts index 0a90e0f..0a9b1f7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -48,7 +48,10 @@ export function validateGetTreatmentsParams(params: any): IGetTreatmentsParams | if (splitNames) { // Feature flag names are sanitized because they are passed to the getControlTreatmentsWithConfig function. splitNames = validateFeatureFlags(typeof splitNames === 'string' ? [splitNames] : splitNames); - if (!splitNames) return false; + if (!splitNames) { + console.log(ERROR_GETT_NO_PARAM_OBJECT); + return false; + } // Ignore flagSets if splitNames are provided if (flagSets) console.log(WARN_FEATUREFLAGS_AND_FLAGSETS); @@ -56,7 +59,10 @@ export function validateGetTreatmentsParams(params: any): IGetTreatmentsParams | } else { // Flag set names are not sanitized, because they are not used by Redux SDK directly. We just make sure it is an array. flagSets = typeof flagSets === 'string' ? [flagSets] : flagSets; - if (!Array.isArray(flagSets)) return false; + if (!Array.isArray(flagSets)) { + console.log(ERROR_GETT_NO_PARAM_OBJECT); + return false; + } } return { diff --git a/types/constants.d.ts b/types/constants.d.ts index 6583aaf..9817bac 100644 --- a/types/constants.d.ts +++ b/types/constants.d.ts @@ -19,5 +19,5 @@ export declare const ERROR_DESTROY_NO_INITSPLITSDK = "[ERROR] To use \"destroySp export declare const ERROR_TRACK_NO_INITSPLITSDK = "[ERROR] To use \"track\" the SDK must be first initialized with an \"initSplitSdk\" action"; export declare const ERROR_MANAGER_NO_INITSPLITSDK = "[ERROR] To use the manager, the SDK must be first initialized with an \"initSplitSdk\" action"; export declare const ERROR_SELECTOR_NO_SPLITSTATE = "[ERROR] When using selectors, \"splitState\" value must be a proper splitio piece of state"; -export declare const ERROR_GETT_NO_PARAM_OBJECT = "[ERROR] \"getTreatments\" must be called with a param object containing either splitNames or flagSets properties"; +export declare const ERROR_GETT_NO_PARAM_OBJECT = "[ERROR] \"getTreatments\" must be called with a param object containing a valid splitNames or flagSets properties"; export declare const WARN_FEATUREFLAGS_AND_FLAGSETS = "[WARN] Both splitNames and flagSets properties were provided. flagSets will be ignored"; From b5775582a7a76a1a49a8a98144d16d9b8f3296e2 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 18 Dec 2023 12:24:02 -0300 Subject: [PATCH 13/15] Prepare rc --- CHANGES.txt | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e5b1601..365c02b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -1.10.0 (December 19, 2023) +1.10.0 (December 18, 2023) - Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation): - Added a new optional `flagSets` property to the param object of the `getTreatments` action creator, to support evaluating flags in given flag set/s. Either `splitNames` or `flagSets` must be provided to the function. If both are provided, `splitNames` will be used. - Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload. diff --git a/package-lock.json b/package-lock.json index 0ad91ee..6a41572 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-redux", - "version": "1.9.1-rc.0", + "version": "1.9.1-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-redux", - "version": "1.9.1-rc.0", + "version": "1.9.1-rc.1", "license": "Apache-2.0", "dependencies": { "@splitsoftware/splitio": "10.24.1" diff --git a/package.json b/package.json index b105cd9..2087b1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-redux", - "version": "1.9.1-rc.0", + "version": "1.9.1-rc.1", "description": "A library to easily use Split JS SDK with Redux and React Redux", "main": "lib/index.js", "module": "es/index.js", From 89d908d4b2da75fc17433a6a0a25ced85ec50962 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 18 Dec 2023 12:35:29 -0300 Subject: [PATCH 14/15] Prepare stable version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a41572..8fb3d00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-redux", - "version": "1.9.1-rc.1", + "version": "1.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-redux", - "version": "1.9.1-rc.1", + "version": "1.10.0", "license": "Apache-2.0", "dependencies": { "@splitsoftware/splitio": "10.24.1" diff --git a/package.json b/package.json index 2087b1b..a27c6a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-redux", - "version": "1.9.1-rc.1", + "version": "1.10.0", "description": "A library to easily use Split JS SDK with Redux and React Redux", "main": "lib/index.js", "module": "es/index.js", From bf76689d33c2c711d9b2656f9585c33903787ec6 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 18 Dec 2023 14:19:51 -0300 Subject: [PATCH 15/15] Code style refactor --- src/asyncActions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/asyncActions.ts b/src/asyncActions.ts index 7f63093..7a3b828 100644 --- a/src/asyncActions.ts +++ b/src/asyncActions.ts @@ -91,9 +91,13 @@ export function initSplitSdk(params: IInitSplitSdkParams): (dispatch: Dispatch { - return params.splitNames ? - { ...acc, ...client.getTreatmentsWithConfig(params.splitNames as string[], params.attributes) } : - { ...acc, ...client.getTreatmentsWithConfigByFlagSets(params.flagSets as string[], params.attributes) }; + return { + ...acc, + ...(params.splitNames ? + client.getTreatmentsWithConfig(params.splitNames as string[], params.attributes) : + client.getTreatmentsWithConfigByFlagSets(params.flagSets as string[], params.attributes) + ) + }; }, {}); }