diff --git a/CHANGES.txt b/CHANGES.txt index 2f76f56..1e64f26 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,9 @@ +2.0.1 (December 4, 2024) + - Updated @splitsoftware/splitio package to version 11.0.3 that includes some improvements and bugfixes. + - Updated internal handling of the `updateOnSdkTimedout` param to remove the wrong log "[ERROR] A listener was added for SDK_READY_TIMED_OUT on the SDK, which has already fired and won't be emitted again". + - Updated implementation of `SplitFactoryProvider` component to support React Strict Mode (Related to https://github.com/splitio/react-client/issues/221). + - Bugfixing - Fixed an issue with the `updateOn***` object parameters of the `useSplitClient` and `useSplitTreatments` hooks, and their components and HOCs alternatives, which were not defaulting to `true` when a non-boolean value was provided. + 2.0.0 (November 1, 2024) - Added support for targeting rules based on large segments. - Added support for passing factory instances to the `factory` prop of the `SplitFactoryProvider` component from other SDK packages that extends the `SplitIO.IBrowserSDK` interface, such as `@splitsoftware/splitio-react-native`, `@splitsoftware/splitio-browserjs` and `@splitsoftware/browser-suite` packages. diff --git a/package-lock.json b/package-lock.json index 957d359..a0caaca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio-react", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-react", - "version": "2.0.0", + "version": "2.0.1", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio": "11.0.0", + "@splitsoftware/splitio": "11.0.3", "memoize-one": "^5.1.1", "shallowequal": "^1.1.0", "tslib": "^2.3.1" @@ -1589,12 +1589,12 @@ } }, "node_modules/@splitsoftware/splitio": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-11.0.0.tgz", - "integrity": "sha512-9ZEhbpsgjvg41FQaahaG+JouzTQ91mX3xgzSClPMPL55kryjXAnQk9jawapnlkI1cKqE4VDvrznkmfr2/4qHRA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-11.0.3.tgz", + "integrity": "sha512-UtoixGfICCj52FfaVdI186Czw0qCvvEyCw/OtJVTsgM4Zq0k2mY8yKzQ7tSB/HJtzbUVnuPoxkwEcj0479stmg==", "dependencies": { - "@splitsoftware/splitio-commons": "2.0.0", - "bloom-filters": "^3.0.0", + "@splitsoftware/splitio-commons": "2.0.2", + "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", "node-fetch": "^2.7.0", @@ -1606,9 +1606,9 @@ } }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.0.0.tgz", - "integrity": "sha512-Sz4+vFacl29xw3451z9IUgB4zBFKUWZdCnmOB0DDXA803YKPqjXphdAwN6nV+1vsX9pXV/OS6UaNC4oUICa6PA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.0.2.tgz", + "integrity": "sha512-r2m3kwWnSuROT+7zTzhWBrM0DMRBGJNQcTyvXw8zLPPmBs/PnmAnxCy7uRpfMHOGbP9Q3Iju0bU/H5dG8svyiw==", "dependencies": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" @@ -1998,6 +1998,11 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "node_modules/@types/seedrandom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.8.tgz", + "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==" + }, "node_modules/@types/semver": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", @@ -2953,15 +2958,14 @@ } }, "node_modules/bloom-filters": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bloom-filters/-/bloom-filters-3.0.0.tgz", - "integrity": "sha512-DBDgLkYokKS5NA5y8P9fuTavKQCkleAP39yqpW/5Nab/vwzHv+wOPRM/yDAStghARDleyRI4orW91uuxj48LKQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bloom-filters/-/bloom-filters-3.0.4.tgz", + "integrity": "sha512-BdnPWo2OpYhlvuP2fRzJBdioMCkm7Zp0HCf8NJgF5Mbyqy7VQ/CnTiVWMMyq4EZCBHwj0Kq6098gW2/3RsZsrA==", "dependencies": { + "@types/seedrandom": "^3.0.8", "base64-arraybuffer": "^1.0.2", "is-buffer": "^2.0.5", - "lodash": "^4.17.15", - "lodash.eq": "^4.0.0", - "lodash.indexof": "^4.0.5", + "lodash": "^4.17.21", "long": "^5.2.0", "reflect-metadata": "^0.1.13", "seedrandom": "^3.0.5", @@ -3308,9 +3312,9 @@ } }, "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "dependencies": { "nice-try": "^1.0.4", @@ -4326,9 +4330,9 @@ "dev": true }, "node_modules/eslint/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -6009,9 +6013,9 @@ } }, "node_modules/jest-changed-files/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -7217,9 +7221,9 @@ "dev": true }, "node_modules/jest-runtime/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -8144,21 +8148,11 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, - "node_modules/lodash.eq": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lodash.eq/-/lodash.eq-4.0.0.tgz", - "integrity": "sha512-vbrJpXL6kQNG6TkInxX12DZRfuYVllSxhwYqjYB78g2zF3UI15nFO/0AgmZnZRnaQ38sZtjCiVjGr2rnKt4v0g==" - }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" }, - "node_modules/lodash.indexof": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/lodash.indexof/-/lodash.indexof-4.0.5.tgz", - "integrity": "sha512-t9wLWMQsawdVmf6/IcAgVGqAJkNzYVcn4BHYZKTPW//l7N5Oq7Bq138BaVk19agcsPZePcidSgTTw4NqS1nUAw==" - }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -8177,9 +8171,9 @@ "dev": true }, "node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -9113,9 +9107,9 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" }, "node_modules/regenerator-runtime": { "version": "0.13.11", @@ -10519,9 +10513,9 @@ } }, "node_modules/webpack-cli/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -12150,12 +12144,12 @@ } }, "@splitsoftware/splitio": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-11.0.0.tgz", - "integrity": "sha512-9ZEhbpsgjvg41FQaahaG+JouzTQ91mX3xgzSClPMPL55kryjXAnQk9jawapnlkI1cKqE4VDvrznkmfr2/4qHRA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio/-/splitio-11.0.3.tgz", + "integrity": "sha512-UtoixGfICCj52FfaVdI186Czw0qCvvEyCw/OtJVTsgM4Zq0k2mY8yKzQ7tSB/HJtzbUVnuPoxkwEcj0479stmg==", "requires": { - "@splitsoftware/splitio-commons": "2.0.0", - "bloom-filters": "^3.0.0", + "@splitsoftware/splitio-commons": "2.0.2", + "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", "node-fetch": "^2.7.0", @@ -12164,9 +12158,9 @@ } }, "@splitsoftware/splitio-commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.0.0.tgz", - "integrity": "sha512-Sz4+vFacl29xw3451z9IUgB4zBFKUWZdCnmOB0DDXA803YKPqjXphdAwN6nV+1vsX9pXV/OS6UaNC4oUICa6PA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.0.2.tgz", + "integrity": "sha512-r2m3kwWnSuROT+7zTzhWBrM0DMRBGJNQcTyvXw8zLPPmBs/PnmAnxCy7uRpfMHOGbP9Q3Iju0bU/H5dG8svyiw==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" @@ -12495,6 +12489,11 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "@types/seedrandom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.8.tgz", + "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==" + }, "@types/semver": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", @@ -13215,15 +13214,14 @@ "dev": true }, "bloom-filters": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bloom-filters/-/bloom-filters-3.0.0.tgz", - "integrity": "sha512-DBDgLkYokKS5NA5y8P9fuTavKQCkleAP39yqpW/5Nab/vwzHv+wOPRM/yDAStghARDleyRI4orW91uuxj48LKQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bloom-filters/-/bloom-filters-3.0.4.tgz", + "integrity": "sha512-BdnPWo2OpYhlvuP2fRzJBdioMCkm7Zp0HCf8NJgF5Mbyqy7VQ/CnTiVWMMyq4EZCBHwj0Kq6098gW2/3RsZsrA==", "requires": { + "@types/seedrandom": "^3.0.8", "base64-arraybuffer": "^1.0.2", "is-buffer": "^2.0.5", - "lodash": "^4.17.15", - "lodash.eq": "^4.0.0", - "lodash.indexof": "^4.0.5", + "lodash": "^4.17.21", "long": "^5.2.0", "reflect-metadata": "^0.1.13", "seedrandom": "^3.0.5", @@ -13489,9 +13487,9 @@ } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -14030,9 +14028,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -15560,9 +15558,9 @@ }, "dependencies": { "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -16470,9 +16468,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -17086,21 +17084,11 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, - "lodash.eq": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lodash.eq/-/lodash.eq-4.0.0.tgz", - "integrity": "sha512-vbrJpXL6kQNG6TkInxX12DZRfuYVllSxhwYqjYB78g2zF3UI15nFO/0AgmZnZRnaQ38sZtjCiVjGr2rnKt4v0g==" - }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" }, - "lodash.indexof": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/lodash.indexof/-/lodash.indexof-4.0.5.tgz", - "integrity": "sha512-t9wLWMQsawdVmf6/IcAgVGqAJkNzYVcn4BHYZKTPW//l7N5Oq7Bq138BaVk19agcsPZePcidSgTTw4NqS1nUAw==" - }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -17119,9 +17107,9 @@ "dev": true }, "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "loose-envify": { "version": "1.4.0", @@ -17841,9 +17829,9 @@ } }, "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" }, "regenerator-runtime": { "version": "0.13.11", @@ -18885,9 +18873,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", diff --git a/package.json b/package.json index 9e8b856..3eb460e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-react", - "version": "2.0.0", + "version": "2.0.1", "description": "A React library to easily integrate and use Split JS SDK", "main": "cjs/index.js", "module": "esm/index.js", @@ -63,7 +63,7 @@ }, "homepage": "https://github.com/splitio/react-client#readme", "dependencies": { - "@splitsoftware/splitio": "11.0.0", + "@splitsoftware/splitio": "11.0.3", "memoize-one": "^5.1.1", "shallowequal": "^1.1.0", "tslib": "^2.3.1" diff --git a/src/SplitFactoryProvider.tsx b/src/SplitFactoryProvider.tsx index 02e36b0..38b2502 100644 --- a/src/SplitFactoryProvider.tsx +++ b/src/SplitFactoryProvider.tsx @@ -1,9 +1,21 @@ import React from 'react'; import { ISplitFactoryProviderProps } from './types'; -import { WARN_SF_CONFIG_AND_FACTORY } from './constants'; -import { getSplitFactory, destroySplitFactory, getSplitClient, getStatus, initAttributes } from './utils'; +import { VERSION, WARN_SF_CONFIG_AND_FACTORY } from './constants'; +import { getSplitClient, getStatus, initAttributes } from './utils'; import { SplitContext } from './SplitContext'; +import { SplitFactory } from '@splitsoftware/splitio/client'; + +/** + * Implementation rationale: + * - Follows React rules: pure components & hooks, with side effects managed in `useEffect`. + * - The `factory` and `client` properties in the context are available from the initial render, rather than being set lazily in a `useEffect`, so that: + * - Hooks retrieve the correct values from the start; for example, `useTrack` accesses the client's `track` method rather than a no-op function (related to https://github.com/splitio/react-client/issues/198). + * - Hooks can support Suspense and Server components where `useEffect` is not called (related to https://github.com/splitio/react-client/issues/192). + * - Re-renders are avoided for child components that do not depend on the factory being ready (e.g., tracking events, updating attributes, or managing consent). + * - `SplitFactoryProvider` updates the context only when props change (`config` or `factory`) but not the state (e.g., client status), preventing unnecessary updates to child components and allowing them to control when to update independently. + * - For these reasons, and to reduce component tree depth, `SplitFactoryProvider` no longer wraps the child component in a `SplitClient` component and thus does not accept a child as a function or `updateOn` props anymore. + */ /** * The SplitFactoryProvider is the top level component that provides the Split SDK factory to all child components via the Split Context. @@ -17,13 +29,22 @@ import { SplitContext } from './SplitContext'; export function SplitFactoryProvider(props: ISplitFactoryProviderProps) { const { config, factory: propFactory, attributes } = props; - const factory = React.useMemo(() => { - const factory = propFactory || (config ? getSplitFactory(config) : undefined); - initAttributes(factory && factory.client(), attributes); - return factory; - }, [config, propFactory, attributes]); + const factory = React.useMemo void }>(() => { + return propFactory ? + propFactory : + config ? + // @ts-expect-error. 2nd param is not part of type definitions. Used to overwrite the SDK version and enable lazy init + SplitFactory(config, (modules) => { + modules.settings.version = VERSION; + modules.lazyInit = true; + }) : + undefined; + }, [config, propFactory]); + const client = factory ? getSplitClient(factory) : undefined; + initAttributes(client, attributes); + // Effect to initialize and destroy the factory when config is provided React.useEffect(() => { if (propFactory) { @@ -31,15 +52,14 @@ export function SplitFactoryProvider(props: ISplitFactoryProviderProps) { return; } - if (config) { - const factory = getSplitFactory(config); + if (factory) { factory.init && factory.init(); return () => { - destroySplitFactory(factory); + factory.destroy(); } } - }, [config, propFactory]); + }, [config, propFactory, factory]); return ( diff --git a/src/__tests__/SplitClient.test.tsx b/src/__tests__/SplitClient.test.tsx index 70d37e1..4943ffd 100644 --- a/src/__tests__/SplitClient.test.tsx +++ b/src/__tests__/SplitClient.test.tsx @@ -15,7 +15,7 @@ import { SplitFactoryProvider } from '../SplitFactoryProvider'; import { SplitClient } from '../SplitClient'; import { SplitContext } from '../SplitContext'; import { INITIAL_STATUS, testAttributesBinding, TestComponentProps } from './testUtils/utils'; -import { IClientWithContext } from '../utils'; +import { getStatus } from '../utils'; import { EXCEPTION_NO_SFP } from '../constants'; describe('SplitClient', () => { @@ -56,7 +56,7 @@ describe('SplitClient', () => { client: outerFactory.client(), isReady: true, isReadyFromCache: true, - lastUpdate: (outerFactory.client() as IClientWithContext).__getStatus().lastUpdate + lastUpdate: getStatus(outerFactory.client()).lastUpdate }); return null; diff --git a/src/__tests__/SplitFactoryProvider.test.tsx b/src/__tests__/SplitFactoryProvider.test.tsx index bb6c2f5..5a6ff96 100644 --- a/src/__tests__/SplitFactoryProvider.test.tsx +++ b/src/__tests__/SplitFactoryProvider.test.tsx @@ -13,14 +13,30 @@ const logSpy = jest.spyOn(console, 'log'); /** Test target */ import { SplitFactoryProvider } from '../SplitFactoryProvider'; import { SplitContext, useSplitContext } from '../SplitContext'; -import { __factories, IClientWithContext } from '../utils'; +import { getStatus } from '../utils'; import { WARN_SF_CONFIG_AND_FACTORY } from '../constants'; import { INITIAL_STATUS } from './testUtils/utils'; import { useSplitClient } from '../useSplitClient'; describe('SplitFactoryProvider', () => { - test('passes no-ready props to the child if initialized with a config.', () => { + test('passes no-ready properties, no factory and no client to the context if initialized without a config and factory props.', () => { + render( + + {React.createElement(() => { + const context = useSplitContext(); + expect(context).toEqual({ + ...INITIAL_STATUS, + factory: undefined, + client: undefined, + }); + return null; + })} + + ); + }); + + test('passes no-ready properties to the context if initialized with a config.', () => { render( {React.createElement(() => { @@ -36,7 +52,7 @@ describe('SplitFactoryProvider', () => { ); }); - test('passes ready props to the child if initialized with a ready factory.', async () => { + test('passes ready properties to the context if initialized with a ready factory.', async () => { const outerFactory = SplitFactory(sdkBrowser); (outerFactory as any).client().__emitter__.emit(Event.SDK_READY_FROM_CACHE); (outerFactory as any).client().__emitter__.emit(Event.SDK_READY); @@ -54,7 +70,7 @@ describe('SplitFactoryProvider', () => { client: outerFactory.client(), isReady: true, isReadyFromCache: true, - lastUpdate: (outerFactory.client() as IClientWithContext).__getStatus().lastUpdate + lastUpdate: getStatus(outerFactory.client()).lastUpdate }); return null; })} @@ -183,8 +199,7 @@ describe('SplitFactoryProvider', () => { wrapper.unmount(); - // Created factories are removed from `factories` cache and `destroy` method is called - expect(__factories.size).toBe(0); + // factory `destroy` methods are called expect(createdFactories.size).toBe(2); expect(factoryDestroySpies.length).toBe(2); factoryDestroySpies.forEach(spy => expect(spy).toBeCalledTimes(1)); @@ -197,8 +212,6 @@ describe('SplitFactoryProvider', () => { {React.createElement(() => { const { factory } = useSplitClient(); - // if factory is provided as a prop, `factories` cache is not modified - expect(__factories.size).toBe(0); destroySpy = jest.spyOn(factory!, 'destroy'); return null; })} diff --git a/src/__tests__/SplitTreatments.test.tsx b/src/__tests__/SplitTreatments.test.tsx index b94b477..ce33a96 100644 --- a/src/__tests__/SplitTreatments.test.tsx +++ b/src/__tests__/SplitTreatments.test.tsx @@ -8,7 +8,7 @@ jest.mock('@splitsoftware/splitio/client', () => { }); import { SplitFactory } from '@splitsoftware/splitio/client'; import { sdkBrowser } from './testUtils/sdkConfigs'; -import { getStatus, IClientWithContext } from '../utils'; +import { getStatus } from '../utils'; import { newSplitFactoryLocalhostInstance } from './testUtils/utils'; import { CONTROL_WITH_CONFIG, EXCEPTION_NO_SFP } from '../constants'; @@ -73,7 +73,7 @@ describe('SplitTreatments', () => { expect(clientMock.getTreatmentsWithConfig.mock.calls.length).toBe(1); expect(treatments).toBe(clientMock.getTreatmentsWithConfig.mock.results[0].value); expect(featureFlagNames).toBe(clientMock.getTreatmentsWithConfig.mock.calls[0][0]); - expect([isReady2, isReadyFromCache, hasTimedout, isTimedout, isDestroyed, lastUpdate]).toStrictEqual([true, false, false, false, false, (outerFactory.client() as IClientWithContext).__getStatus().lastUpdate]); + expect([isReady2, isReadyFromCache, hasTimedout, isTimedout, isDestroyed, lastUpdate]).toStrictEqual([true, false, false, false, false, getStatus(outerFactory.client()).lastUpdate]); return null; }} diff --git a/src/__tests__/useSplitClient.test.tsx b/src/__tests__/useSplitClient.test.tsx index a1e6790..bbd9d88 100644 --- a/src/__tests__/useSplitClient.test.tsx +++ b/src/__tests__/useSplitClient.test.tsx @@ -103,7 +103,7 @@ describe('useSplitClient', () => { return null; })} {React.createElement(() => { - const { client, isReady, isReadyFromCache, hasTimedout } = useSplitClient({ splitKey: 'user_2', updateOnSdkUpdate: true }); + const { client, isReady, isReadyFromCache, hasTimedout } = useSplitClient({ splitKey: 'user_2', updateOnSdkUpdate: undefined /* default is true */ }); expect(client).toBe(user2Client); countUseSplitClientUser2++; @@ -218,7 +218,7 @@ describe('useSplitClient', () => { act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // do not trigger re-render because updateOnSdkUpdate is false expect(rendersCount).toBe(2); - wrapper.rerender(); // trigger re-render + wrapper.rerender(); // trigger re-render expect(rendersCount).toBe(3); act(() => mainClient.__emitter__.emit(Event.SDK_UPDATE)); // trigger re-render because updateOnSdkUpdate is true now diff --git a/src/__tests__/withSplitTreatments.test.tsx b/src/__tests__/withSplitTreatments.test.tsx index b4f5d2e..7ed6901 100644 --- a/src/__tests__/withSplitTreatments.test.tsx +++ b/src/__tests__/withSplitTreatments.test.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; /** Mocks */ -import { mockSdk } from './testUtils/mockSplitFactory'; +import { mockSdk, getLastInstance, Event } from './testUtils/mockSplitFactory'; jest.mock('@splitsoftware/splitio/client', () => { return { SplitFactory: mockSdk() }; }); +import { SplitFactory } from '@splitsoftware/splitio/client'; import { sdkBrowser } from './testUtils/sdkConfigs'; /** Test target */ @@ -14,12 +15,13 @@ import { withSplitClient } from '../withSplitClient'; import { withSplitTreatments } from '../withSplitTreatments'; import { getControlTreatmentsWithConfig } from '../utils'; +const featureFlagNames = ['split1', 'split2']; + describe('withSplitTreatments', () => { it(`passes Split props and outer props to the child. In this test, the value of "props.treatments" is obtained by the function "getControlTreatmentsWithConfig", and not "client.getTreatmentsWithConfig" since the client is not ready.`, () => { - const featureFlagNames = ['split1', 'split2']; const Component = withSplitFactory(sdkBrowser)<{ outerProp1: string, outerProp2: number }>( ({ outerProp1, outerProp2, factory }) => { @@ -51,4 +53,28 @@ describe('withSplitTreatments', () => { render(); }); + it('disabling "updateOnSdkTimedout" requires passing `false` in all HOCs since the default value is `true`.', () => { + + let renderCount = 0; + + const Component = withSplitFactory(sdkBrowser)( + withSplitClient(sdkBrowser.core.key)( + withSplitTreatments(featureFlagNames)( + (props) => { + renderCount++; + expect(props.hasTimedout).toBe(false); + + return null; + }, undefined, false + ), undefined, false + ), undefined, false + ); + + render(); + + act(() => getLastInstance(SplitFactory).client().__emitter__.emit(Event.SDK_READY_TIMED_OUT)); + + expect(renderCount).toBe(1); + }); + }); diff --git a/src/useSplitClient.ts b/src/useSplitClient.ts index 74e26d6..f2808a3 100644 --- a/src/useSplitClient.ts +++ b/src/useSplitClient.ts @@ -1,6 +1,6 @@ import React from 'react'; import { useSplitContext } from './SplitContext'; -import { getSplitClient, initAttributes, IClientWithContext, getStatus } from './utils'; +import { getSplitClient, initAttributes, getStatus } from './utils'; import { ISplitContextValues, IUseSplitClientOptions } from './types'; export const DEFAULT_UPDATE_OPTIONS = { @@ -33,7 +33,7 @@ export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextV // @TODO Move `getSplitClient` side effects // @TODO Once `SplitClient` is removed, which updates the context, simplify next line as `const client = factory ? getSplitClient(factory, splitKey) : undefined;` - const client = factory && splitKey ? getSplitClient(factory, splitKey) : contextClient as IClientWithContext; + const client = factory && splitKey ? getSplitClient(factory, splitKey) : contextClient; initAttributes(client, attributes); @@ -44,25 +44,29 @@ export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextV React.useEffect(() => { if (!client) return; - const update = () => setLastUpdate(client.__getStatus().lastUpdate); + const update = () => setLastUpdate(getStatus(client).lastUpdate); // Clients are created on the hook's call, so the status may have changed const statusOnEffect = getStatus(client); // Subscribe to SDK events - if (updateOnSdkReady) { + if (updateOnSdkReady !== false) { if (!statusOnEffect.isReady) client.once(client.Event.SDK_READY, update); else if (!status.isReady) update(); } - if (updateOnSdkReadyFromCache) { + if (updateOnSdkReadyFromCache !== false) { if (!statusOnEffect.isReadyFromCache) client.once(client.Event.SDK_READY_FROM_CACHE, update); else if (!status.isReadyFromCache) update(); } - if (updateOnSdkTimedout) { - if (!statusOnEffect.hasTimedout) client.once(client.Event.SDK_READY_TIMED_OUT, update); - else if (!status.hasTimedout) update(); + if (updateOnSdkTimedout !== false) { + if (!statusOnEffect.hasTimedout) { + // Required to avoid error log for event already emitted + if (!statusOnEffect.isReady) client.once(client.Event.SDK_READY_TIMED_OUT, update); + } else { + if (!status.hasTimedout) update(); + } } - if (updateOnSdkUpdate) client.on(client.Event.SDK_UPDATE, update); + if (updateOnSdkUpdate !== false) client.on(client.Event.SDK_UPDATE, update); return () => { // Unsubscribe from events diff --git a/src/useTrack.ts b/src/useTrack.ts index 2af449b..a71058f 100644 --- a/src/useTrack.ts +++ b/src/useTrack.ts @@ -13,5 +13,7 @@ const noOpFalse = () => false; export function useTrack(splitKey?: SplitIO.SplitKey): SplitIO.IBrowserClient['track'] { // All update options are false to avoid re-renders. The track method doesn't need the client to be operational. const { client } = useSplitClient({ splitKey, updateOnSdkReady: false, updateOnSdkReadyFromCache: false, updateOnSdkTimedout: false, updateOnSdkUpdate: false }); + + // Retrieve the client `track` rather than a bound version of it, as there is no need to bind the function, and can be used as a reactive dependency that only changes if the underlying client changes. return client ? client.track : noOpFalse; } diff --git a/src/utils.ts b/src/utils.ts index f7a8f33..26b5f90 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,6 @@ import memoizeOne from 'memoize-one'; import shallowEqual from 'shallowequal'; -import { SplitFactory } from '@splitsoftware/splitio/client'; -import { CONTROL_WITH_CONFIG, VERSION, WARN_NAMES_AND_FLAGSETS } from './constants'; +import { CONTROL_WITH_CONFIG, WARN_NAMES_AND_FLAGSETS } from './constants'; import { ISplitStatus } from './types'; // Utils used to access singleton instances of Split factories and clients, and to gracefully shutdown all clients together. @@ -9,7 +8,7 @@ import { ISplitStatus } from './types'; /** * ClientWithContext interface. */ -export interface IClientWithContext extends SplitIO.IBrowserClient { +interface IClientWithContext extends SplitIO.IBrowserClient { __getStatus(): { isReady: boolean; isReadyFromCache: boolean; @@ -26,24 +25,6 @@ export interface IFactoryWithLazyInit extends SplitIO.IBrowserSDK { init(): void; } -// exported for testing purposes -export const __factories: Map = new Map(); - -// idempotent operation -export function getSplitFactory(config: SplitIO.IBrowserSettings) { - if (!__factories.has(config)) { - // SplitFactory is not an idempotent operation - // @ts-expect-error. 2nd param is not part of type definitions. Used to overwrite the SDK version - const newFactory = SplitFactory(config, (modules) => { - modules.settings.version = VERSION; - modules.lazyInit = true; - }) as IFactoryWithLazyInit; - newFactory.config = config; - __factories.set(config, newFactory); - } - return __factories.get(config) as IFactoryWithLazyInit; -} - // idempotent operation export function getSplitClient(factory: SplitIO.IBrowserSDK, key?: SplitIO.SplitKey): IClientWithContext { // factory.client is an idempotent operation @@ -56,11 +37,6 @@ export function getSplitClient(factory: SplitIO.IBrowserSDK, key?: SplitIO.Split return client; } -export function destroySplitFactory(factory: IFactoryWithLazyInit): Promise | undefined { - __factories.delete(factory.config); - return factory.destroy(); -} - // Util used to get client status. // It might be removed in the future, if the JS SDK extends its public API with a `getStatus` method export function getStatus(client?: SplitIO.IBrowserClient): ISplitStatus { @@ -79,6 +55,7 @@ export function getStatus(client?: SplitIO.IBrowserClient): ISplitStatus { /** * Manage client attributes binding */ +// @TODO should reset attributes rather than set/merge them, to keep SFP and hooks pure. export function initAttributes(client?: SplitIO.IBrowserClient, attributes?: SplitIO.Attributes) { if (client && attributes) client.setAttributes(attributes); } diff --git a/src/withSplitClient.tsx b/src/withSplitClient.tsx index 3528b98..3dec7b9 100644 --- a/src/withSplitClient.tsx +++ b/src/withSplitClient.tsx @@ -16,10 +16,10 @@ export function withSplitClient(splitKey: SplitIO.SplitKey, attributes?: SplitIO return function withSplitClientHoc( WrappedComponent: React.ComponentType, - updateOnSdkUpdate = false, - updateOnSdkTimedout = false, - updateOnSdkReady = true, - updateOnSdkReadyFromCache = true, + updateOnSdkUpdate?: boolean, + updateOnSdkTimedout?: boolean, + updateOnSdkReady?: boolean, + updateOnSdkReadyFromCache?: boolean, ) { return function wrapper(props: OuterProps) { diff --git a/src/withSplitFactory.tsx b/src/withSplitFactory.tsx index a3b32be..e587223 100644 --- a/src/withSplitFactory.tsx +++ b/src/withSplitFactory.tsx @@ -18,10 +18,10 @@ export function withSplitFactory(config?: SplitIO.IBrowserSettings, factory?: Sp return function withSplitFactoryHoc( WrappedComponent: React.ComponentType, - updateOnSdkUpdate = false, - updateOnSdkTimedout = false, - updateOnSdkReady = true, - updateOnSdkReadyFromCache = true, + updateOnSdkUpdate?: boolean, + updateOnSdkTimedout?: boolean, + updateOnSdkReady?: boolean, + updateOnSdkReadyFromCache?: boolean, ) { return function wrapper(props: OuterProps) { diff --git a/src/withSplitTreatments.tsx b/src/withSplitTreatments.tsx index be452b8..0c4ceda 100644 --- a/src/withSplitTreatments.tsx +++ b/src/withSplitTreatments.tsx @@ -16,12 +16,20 @@ export function withSplitTreatments(names: string[], attributes?: SplitIO.Attrib return function withSplitTreatmentsHoc( WrappedComponent: React.ComponentType, + updateOnSdkUpdate?: boolean, + updateOnSdkTimedout?: boolean, + updateOnSdkReady?: boolean, + updateOnSdkReadyFromCache?: boolean, ) { return function wrapper(props: OuterProps) { return ( {(splitProps) => { return (