From d61c5e2efb575066258e0de3a3e69df3ccff9db1 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 5 Mar 2024 00:22:08 +0600 Subject: [PATCH 1/9] build(deps): bump json5 from 2.2.1 to 2.2.3 (#245) Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3. - [Release notes](https://github.com/json5/json5/releases) - [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md) - [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3) --- updated-dependencies: - dependency-name: json5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8545760..e821167 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3279,9 +3279,9 @@ json-stable-stringify-without-jsonify@^1.0.1: integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@2.x, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^4.0.0: version "4.0.0" From cbb60628a041e82dedbcaa41fb9a2cf495fdfc98 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Wed, 6 Mar 2024 00:38:55 +0600 Subject: [PATCH 2/9] build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#247) Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2. - [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases) - [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2) --- updated-dependencies: - dependency-name: decode-uri-component dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e821167..8c0ecb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1569,9 +1569,9 @@ decimal.js@^10.2.1: integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz" - integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== decompress-response@^4.2.1: version "4.2.1" From f02a72053c56eaa91fdcbb71389d5289bab69982 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Wed, 6 Mar 2024 01:26:35 +0600 Subject: [PATCH 3/9] [FSSDK-9969] replace uglify plugin with terser (#248) --- package.json | 2 +- scripts/config.js | 4 +- yarn.lock | 118 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 86 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 4043c0e..7656c5b 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^10.0.0", "@rollup/plugin-replace": "^2.3.4", + "@rollup/plugin-terser": "^0.4.4", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@types/hoist-non-react-statics": "^3.3.1", @@ -66,7 +67,6 @@ "react-dom": "^18.2.0", "rollup": "^2.32.1", "rollup-plugin-typescript2": "^0.28.0", - "rollup-plugin-uglify": "^6.0.4", "ts-jest": "^26.4.1", "tslib": "^2.4.0", "typescript": "^4.7.4" diff --git a/scripts/config.js b/scripts/config.js index 56ef507..60cb85f 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -18,7 +18,7 @@ const typescript = require('rollup-plugin-typescript2'); const commonjs = require('@rollup/plugin-commonjs'); const replace = require('@rollup/plugin-replace'); const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const { uglify } = require('rollup-plugin-uglify'); +const terser = require('@rollup/plugin-terser'); const packageDeps = require('../package.json').dependencies || {}; const packagePeers = require('../package.json').peerDependencies || {}; @@ -57,7 +57,7 @@ function getPlugins(env, externals) { plugins.push(typescript()); if (env === 'production') { - plugins.push(uglify()); + plugins.push(terser()); } return plugins; diff --git a/yarn.lock b/yarn.lock index 8c0ecb2..a424761 100644 --- a/yarn.lock +++ b/yarn.lock @@ -575,6 +575,15 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" @@ -599,6 +608,19 @@ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" @@ -617,6 +639,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.14" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz" @@ -669,6 +699,15 @@ "@rollup/pluginutils" "^3.1.0" magic-string "^0.25.7" +"@rollup/plugin-terser@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz#15dffdb3f73f121aa4fbb37e7ca6be9aeea91962" + integrity sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A== + dependencies: + serialize-javascript "^6.0.1" + smob "^1.0.0" + terser "^5.17.4" + "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz" @@ -986,6 +1025,11 @@ acorn@^8.2.4: resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +acorn@^8.8.2: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -1452,6 +1496,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" @@ -3181,14 +3230,6 @@ jest-watcher@^26.6.2: jest-util "^26.6.2" string-length "^4.0.1" -jest-worker@^24.0.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz" - integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== - dependencies: - merge-stream "^2.0.0" - supports-color "^6.1.0" - jest-worker@^26.6.2: version "26.6.2" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz" @@ -3925,6 +3966,13 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" @@ -4115,16 +4163,6 @@ rollup-plugin-typescript2@^0.28.0: resolve "1.17.0" tslib "2.0.1" -rollup-plugin-uglify@^6.0.4: - version "6.0.4" - resolved "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.4.tgz" - integrity sha512-ddgqkH02klveu34TF0JqygPwZnsbhHVI6t8+hGTcYHngPkQb5MIHI0XiztXIN/d6V9j+efwHAqEL7LspSxQXGw== - dependencies: - "@babel/code-frame" "^7.0.0" - jest-worker "^24.0.0" - serialize-javascript "^2.1.2" - uglify-js "^3.4.9" - rollup@^2.32.1: version "2.32.1" resolved "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz" @@ -4149,6 +4187,11 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" @@ -4212,10 +4255,12 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz" - integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== +serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" set-blocking@^2.0.0: version "2.0.0" @@ -4294,6 +4339,11 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +smob@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/smob/-/smob-1.4.1.tgz#66270e7df6a7527664816c5b577a23f17ba6f5b5" + integrity sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ== + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz" @@ -4343,7 +4393,7 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@^0.5.6: +source-map-support@^0.5.6, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -4535,13 +4585,6 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" @@ -4585,6 +4628,16 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" +terser@^5.17.4: + version "5.28.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.28.1.tgz#bf00f7537fd3a798c352c2d67d67d65c915d1b28" + integrity sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" @@ -4758,11 +4811,6 @@ ua-parser-js@^1.0.37: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== -uglify-js@^3.4.9: - version "3.16.3" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.3.tgz" - integrity sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw== - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" From 8e3b29aa8432352c79328fcbcdb0ac0c73730023 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Mon, 11 Mar 2024 19:45:00 +0600 Subject: [PATCH 4/9] [FSSDK-9586] update node versions in ci workflows (#249) --- .github/workflows/react.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/react.yml b/.github/workflows/react.yml index 58f3278..d03559c 100644 --- a/.github/workflows/react.yml +++ b/.github/workflows/react.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [ '16', '18' ] + node: [ '14', '16', '18', '20' ] steps: - name: Checkout branch uses: actions/checkout@v3 diff --git a/package.json b/package.json index 7656c5b..d5c098b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "access": "public" }, "dependencies": { - "@optimizely/optimizely-sdk": "^5.0.1", + "@optimizely/optimizely-sdk": "^5.1.0", "hoist-non-react-statics": "^3.3.0", "prop-types": "^15.6.2", "utility-types": "^2.1.0 || ^3.0.0" From 916f82ba9d4d2afb167bad6203f21c7d1e841d40 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 19 Mar 2024 17:20:13 +0600 Subject: [PATCH 5/9] build(deps): bump word-wrap from 1.2.3 to 1.2.5 (#251) Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index a424761..fb4ffbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -655,10 +655,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@optimizely/optimizely-sdk@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@optimizely/optimizely-sdk/-/optimizely-sdk-5.0.1.tgz#9ac9e098f3840c8b381a7d3df77cfacb2e381e78" - integrity sha512-KFXFIUKwmNDJGCjOqDE8pPnI6fmpW1bPAPUWTtVOxUXvty+fhSVFqfsVY0pvWsY6WOEuMGM30iIKoItcU4Raig== +"@optimizely/optimizely-sdk@^5.1.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@optimizely/optimizely-sdk/-/optimizely-sdk-5.2.0.tgz#6449336182515dd4cca9d6913e24ca8e814e6278" + integrity sha512-JxdhWHU9OiJKrKfyWe9lL+2/e28gBZnPNHQAA+heyWTAtcuxZWdSZ24bIkLwAAuIf26dh89o9LNB2L8aKPmwWw== dependencies: decompress-response "^4.2.1" json-schema "^0.4.0" @@ -5002,9 +5002,9 @@ which@^2.0.1, which@^2.0.2: isexe "^2.0.0" word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== wrap-ansi@^6.2.0: version "6.2.0" From 2e5bdf9436c29f9c31b872fc7d5f134a63fc76a8 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 19 Mar 2024 17:35:26 +0600 Subject: [PATCH 6/9] build(deps): bump semver from 5.7.1 to 5.7.2 (#253) Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index fb4ffbd..503fe74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4239,21 +4239,21 @@ scheduler@^0.23.0: loose-envify "^1.1.0" "semver@2 || 3 || 4 || 5", semver@^5.5.0: - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@7.x, semver@^7.3.2: - version "7.3.7" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== dependencies: lru-cache "^6.0.0" semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== serialize-javascript@^6.0.1: version "6.0.2" From a87fe08c791d06f6b24c83220122a72a9df59f89 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 19 Mar 2024 22:55:32 +0600 Subject: [PATCH 7/9] [FSSDK-9621] bump js sdk to 5.2.0 (#254) this adds persistentCacheProvider option to createInstance config cause react sdk createInstane just uses the js sdk config type --- .gitignore | 3 +++ LICENSE | 2 +- package.json | 2 +- yarn.lock | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 98ffa79..61b66fd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ dist/ build/ .rpt2_cache .env + +# test artifacts +**.tgz diff --git a/LICENSE b/LICENSE index 31d8660..9a89d2d 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2018-2019, Optimizely, Inc. and contributors + © Optimizely 2018 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/package.json b/package.json index d5c098b..8d55dd1 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "access": "public" }, "dependencies": { - "@optimizely/optimizely-sdk": "^5.1.0", + "@optimizely/optimizely-sdk": "^5.2.0", "hoist-non-react-statics": "^3.3.0", "prop-types": "^15.6.2", "utility-types": "^2.1.0 || ^3.0.0" diff --git a/yarn.lock b/yarn.lock index 503fe74..8f314f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -655,7 +655,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@optimizely/optimizely-sdk@^5.1.0": +"@optimizely/optimizely-sdk@^5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@optimizely/optimizely-sdk/-/optimizely-sdk-5.2.0.tgz#6449336182515dd4cca9d6913e24ca8e814e6278" integrity sha512-JxdhWHU9OiJKrKfyWe9lL+2/e28gBZnPNHQAA+heyWTAtcuxZWdSZ24bIkLwAAuIf26dh89o9LNB2L8aKPmwWw== From fae169f9ff0225a11d3e1e3415f03bcb6033cda6 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Tue, 9 Apr 2024 08:31:31 -0400 Subject: [PATCH 8/9] [FSSDK-9984] fix: initialization and setUser errors (#255) * fix: remove successful fetch requirement for onReady * Revert "fix: remove successful fetch requirement for onReady" This reverts commit 566daaff800e349a8e7dfbb8beeef5cca7b930f4. * fix: error with OnReadyResult being undefined * fix: setUser should use VUID if possible * revert: timeout of undefined * docs: update copyright year * revert: Provider.tsx copyright since no code change * build: bump JS SDK version * refactor: `res` should never be `undefined` * docs: add clarifying comment * revert: retrieval & use of current user context * wip: partial solution; needs collab * refactor: setUser logic updated * revert: move setUser back to Provider constructor * style: remove commented code * fix: fetchQualifiedSegments under SSR/sync scenario * ci: VS Code jest settings to run via extension * test: use NotReadyReason enum * test: use NotReadyReason & add missing getUserId in mock user context * fix: add onInitStateChange for default ready result * fix: logic in Promise.all user & client readiness * refactor: isReady() to isReactClientReady() * test: fixes for uses of getUserId() in setUser() * wip: fixing tests * wip: fixed more tests * revert: refactor of isReactClientReady() * docs: Update copyrights * fix: later setUser not getting new usercontext (manual testing) * fix: PR review changes * test: add initial OptimizelyProvider tests * fix: PR review changes * docs: add clarification inline comments * build: bump underlying JS SDK version to 5.3.0 * fix: add missing getProjectConfig() from JS SDK * refactor: omit getProjectConfig instead of implementing it * style: remove unused import --------- Co-authored-by: Mike Chu --- .vscode/launch.json | 25 +++++++++ .vscode/settings.json | 5 +- package.json | 2 +- src/Feature.spec.tsx | 6 +-- src/Provider.spec.tsx | 95 ++++++++++++++++++++++++++++++++ src/Provider.tsx | 11 ++-- src/client.spec.ts | 87 ++++++++++++++++++++++-------- src/client.ts | 123 ++++++++++++++++++++++++++++-------------- src/hooks.ts | 102 ++++++++++++++++++++--------------- yarn.lock | 14 ++--- 10 files changed, 347 insertions(+), 123 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/Provider.spec.tsx diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4193108 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "name": "vscode-jest-tests.v2.react-sdk", + "request": "launch", + "args": [ + "--runInBand", + "--watchAll=false", + "--testNamePattern", + "${jest.testNamePattern}", + "--runTestsByPath", + "${jest.testFile}" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 669180a..e79b4db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "jest.autoRun": { - "onStartup": ["all-tests"] - } + "jest.runMode": "on-demand", + "jest.jestCommandLine": "~/.nvm/nvm-exec yarn test" } diff --git a/package.json b/package.json index 8d55dd1..54de565 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "access": "public" }, "dependencies": { - "@optimizely/optimizely-sdk": "^5.2.0", + "@optimizely/optimizely-sdk": "^5.3.0", "hoist-non-react-statics": "^3.3.0", "prop-types": "^15.6.2", "utility-types": "^2.1.0 || ^3.0.0" diff --git a/src/Feature.spec.tsx b/src/Feature.spec.tsx index a747e80..bc2b824 100644 --- a/src/Feature.spec.tsx +++ b/src/Feature.spec.tsx @@ -1,5 +1,5 @@ /** - * Copyright 2018-2019, 2023 Optimizely + * Copyright 2018-2019, 2023-2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { OptimizelyProvider } from './Provider'; -import { ReactSDKClient, VariableValuesObject } from './client'; +import { NotReadyReason, ReactSDKClient, VariableValuesObject } from './client'; import { OptimizelyFeature } from './Feature'; describe('', () => { @@ -298,7 +298,7 @@ describe('', () => { // while it's waiting for onReady() expect(container.innerHTML).toBe(''); - resolver.resolve({ success: false, reason: 'fail', dataReadyPromise: Promise.resolve() }); + resolver.resolve({ success: false, reason: NotReadyReason.TIMEOUT, dataReadyPromise: Promise.resolve() }); // Simulate config update notification firing after datafile fetched await optimizelyMock.onReady().then(res => res.dataReadyPromise); diff --git a/src/Provider.spec.tsx b/src/Provider.spec.tsx new file mode 100644 index 0000000..98739c2 --- /dev/null +++ b/src/Provider.spec.tsx @@ -0,0 +1,95 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// + +//jest.mock('./client'); + +import React from 'react'; +import { render, act } from '@testing-library/react'; +import { OptimizelyProvider } from './Provider'; +import { DefaultUser, ReactSDKClient, createInstance } from './client'; + +describe('OptimizelyProvider', () => { + let mockReactClient: ReactSDKClient; + const config = { + datafile: {}, + }; + + beforeEach(() => { + mockReactClient = ({ + user: { + id: 'test-id', + attributes: {}, + }, + setUser: jest.fn().mockResolvedValue(undefined), + } as unknown) as ReactSDKClient; + }); + + it('should render successfully with user provided', () => { + act(() => { + render(); + }); + + expect(mockReactClient.setUser).toHaveBeenCalledWith({ + id: 'user1', + attributes: {}, + }); + }); + + it('should render successfully with userId provided', () => { + act(() => { + render(); + }); + + expect(mockReactClient.setUser).toHaveBeenCalledWith({ + id: 'user1', + attributes: {}, + }); + }); + + it('should render successfully without user or userId provided', () => { + act(() => { + render(); + }); + + expect(mockReactClient.setUser).toHaveBeenCalledWith(DefaultUser); + }); + + it('should render successfully with user id & attributes provided', () => { + act(() => { + render( + + ); + }); + + expect(mockReactClient.setUser).toHaveBeenCalledWith({ + id: 'user1', + attributes: { attr1: 'value1' }, + }); + }); + + it('should succeed just userAttributes provided', () => { + act(() => { + render(); + }); + + expect(mockReactClient.setUser).toHaveBeenCalledWith({ + id: DefaultUser.id, + attributes: { attr1: 'value1' }, + }); + }); +}); diff --git a/src/Provider.tsx b/src/Provider.tsx index 17b7fb3..a3612bc 100644 --- a/src/Provider.tsx +++ b/src/Provider.tsx @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,9 +43,7 @@ interface OptimizelyProviderState { export class OptimizelyProvider extends React.Component { constructor(props: OptimizelyProviderProps) { super(props); - } - componentDidMount(): void { this.setUserInOptimizely(); } @@ -78,12 +76,15 @@ export class OptimizelyProvider extends React.Component { import * as optimizely from '@optimizely/optimizely-sdk'; -import { createInstance, OnReadyResult, ReactSDKClient } from './client'; +import { createInstance, DefaultUser, NotReadyReason, OnReadyResult, ReactSDKClient } from './client'; import { logger } from './logger'; interface MockedReactSDKClient extends ReactSDKClient { @@ -37,6 +37,7 @@ interface MockedReactSDKClient extends ReactSDKClient { } describe('ReactSDKClient', () => { + const validVuid = 'vuid_8de3bb278fce47f6b000cadc1ac'; const config: optimizely.Config = { datafile: {}, }; @@ -51,6 +52,8 @@ describe('ReactSDKClient', () => { decideAll: jest.fn(), decideForKeys: jest.fn(), fetchQualifiedSegments: jest.fn(), + getUserId: jest.fn(), + getAttributes: jest.fn(), setForcedDecision: jest.fn(), removeForcedDecision: jest.fn(), removeAllForcedDecisions: jest.fn(), @@ -75,6 +78,7 @@ describe('ReactSDKClient', () => { getFeatureVariableInteger: jest.fn(() => null), getFeatureVariableString: jest.fn(() => null), getOptimizelyConfig: jest.fn(() => null), + getProjectConfig: jest.fn(() => null), onReady: jest.fn(() => Promise.resolve({ success: false })), close: jest.fn(), getVuid: jest.fn(), @@ -144,32 +148,41 @@ describe('ReactSDKClient', () => { }); it('fulfills the returned promise with success: true when a user is set', async () => { - jest.spyOn(instance, 'fetchQualifiedSegments').mockImplementation(async () => true); + jest.spyOn(mockInnerClient, 'onReady').mockResolvedValue({ success: true }); + const instance = createInstance(config); + jest.spyOn(instance, 'fetchQualifiedSegments').mockResolvedValue(true); + await instance.setUser({ id: 'user12345', }); + const result = await instance.onReady(); + expect(result.success).toBe(true); }); it('fulfills the returned promise with success: false when fetchqualifiedsegment is false', async () => { - jest.spyOn(instance, 'fetchQualifiedSegments').mockImplementation(async () => false); + jest.spyOn(mockInnerClient, 'onReady').mockResolvedValue({ success: true }); + const instance = createInstance(config); + jest.spyOn(instance, 'fetchQualifiedSegments').mockResolvedValue(false); await instance.setUser({ id: 'user12345', }); const result = await instance.onReady(); + expect(result.success).toBe(false); }); describe('if Optimizely client is null', () => { beforeEach(() => { - // Mocks dataReadyPromise value instead of _client = null because test initialization of instance causes dataReadyPromise to return { success: true } + // Mocks clientAndUserReadyPromise value instead of _client = null because test initialization of + // instance causes clientAndUserReadyPromise to return { success: true } // @ts-ignore - instance.dataReadyPromise = new Promise((resolve, reject) => { + instance.clientAndUserReadyPromise = new Promise(resolve => { resolve({ success: false, - reason: 'NO_CLIENT', + reason: NotReadyReason.NO_CLIENT, message: 'Optimizely client failed to initialize.', }); }); @@ -185,16 +198,20 @@ describe('ReactSDKClient', () => { it('waits for the inner client onReady to fulfill with success = false before fulfilling the returned promise', async () => { const mockInnerClientOnReady = jest.spyOn(mockInnerClient, 'onReady'); - let resolveInnerClientOnReady: (result: OnReadyResult) => void; + let resolveInnerClientOnReady: (result: OnReadyResult) => void = () => {}; const mockReadyPromise: Promise = new Promise(res => { resolveInnerClientOnReady = res; }); mockInnerClientOnReady.mockReturnValueOnce(mockReadyPromise); + const userId = 'user999'; + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId); + resolveInnerClientOnReady({ success: true }); + await instance.setUser({ - id: 'user999', + id: userId, }); - resolveInnerClientOnReady!({ success: true }); const result = await instance.onReady(); + expect(result.success).toBe(false); }); }); @@ -206,6 +223,7 @@ describe('ReactSDKClient', () => { resolveInnerClientOnReady = res; }); mockInnerClientOnReady.mockReturnValueOnce(mockReadyPromise); + const instance = createInstance(config); jest.spyOn(instance, 'fetchQualifiedSegments').mockImplementation(async () => true); await instance.setUser({ id: 'user999', @@ -217,16 +235,29 @@ describe('ReactSDKClient', () => { }); describe('setUser', () => { + it('can be called with no/default user set', async () => { + const instance = createInstance(config); + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(validVuid); + + await instance.setUser(DefaultUser); + + expect(instance.user.id).toEqual(validVuid); + }); + it('updates the user object with id and attributes', async () => { + const userId = 'xxfueaojfe8&86'; + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId); + const instance = createInstance(config); await instance.setUser({ - id: 'xxfueaojfe8&86', + id: userId, attributes: { foo: 'bar', }, }); + expect(instance.user).toEqual({ - id: 'xxfueaojfe8&86', + id: userId, attributes: { foo: 'bar', }, @@ -234,33 +265,39 @@ describe('ReactSDKClient', () => { }); it('adds and removes update handlers', async () => { + const userId = 'newUser'; + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId); const instance = createInstance(config); const onUserUpdateListener = jest.fn(); const dispose = instance.onUserUpdate(onUserUpdateListener); + await instance.setUser({ - id: 'newUser', + id: userId, }); + expect(onUserUpdateListener).toBeCalledTimes(1); expect(onUserUpdateListener).toBeCalledWith({ - id: 'newUser', + id: userId, attributes: {}, }); + dispose(); await instance.setUser({ id: 'newUser2', }); + expect(onUserUpdateListener).toBeCalledTimes(1); }); - it('does not call fetchqualifiedsegements on setUser if onready is not calleed initially', async () => { + it('implicitly calls fetchqualifiedsegements', async () => { const instance = createInstance(config); - jest.spyOn(instance, 'fetchQualifiedSegments').mockImplementation(async () => true); + jest.spyOn(instance, 'fetchQualifiedSegments').mockResolvedValue(true); await instance.setUser({ id: 'xxfueaojfe8&86', }); - expect(instance.fetchQualifiedSegments).toBeCalledTimes(0); + expect(instance.fetchQualifiedSegments).toBeCalledTimes(1); }); it('calls fetchqualifiedsegements internally on each setuser call after onready', async () => { @@ -286,9 +323,11 @@ describe('ReactSDKClient', () => { describe('pre-set user and user overrides', () => { let instance: ReactSDKClient; beforeEach(async () => { + const userId = 'user1'; + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId); instance = createInstance(config); await instance.setUser({ - id: 'user1', + id: userId, attributes: { foo: 'bar', }, @@ -1067,9 +1106,11 @@ describe('ReactSDKClient', () => { } } ); + const userId = 'user1123'; + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId); const instance = createInstance(config); await instance.setUser({ - id: 'user1123', + id: userId, }); const result = instance.getFeatureVariables('feat1'); expect(result).toEqual({ @@ -1353,9 +1394,11 @@ describe('ReactSDKClient', () => { describe('setForcedDecision', () => { let instance: ReactSDKClient; beforeEach(async () => { + const userId = 'user1'; + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId); instance = createInstance(config); await instance.setUser({ - id: 'user1', + id: userId, attributes: { foo: 'bar', }, @@ -1374,7 +1417,6 @@ describe('ReactSDKClient', () => { variables: {}, variationKey: 'varition1', }); - // @ts-ignore instance._client = null; @@ -1462,6 +1504,8 @@ describe('ReactSDKClient', () => { describe('removeForcedDecision', () => { let instance: ReactSDKClient; beforeEach(async () => { + const userId = 'user1'; + jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId); instance = createInstance(config); await instance.setUser({ id: 'user1', @@ -1640,7 +1684,6 @@ describe('ReactSDKClient', () => { }); it('should return a valid vuid', async () => { - const validVuid = 'vuid_8de3bb278fce47f6b000cadc1ac'; const mockGetVuid = mockInnerClient.getVuid as jest.Mock; mockGetVuid.mockReturnValue(validVuid); diff --git a/src/client.ts b/src/client.ts index 008df57..5c57bc7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -30,7 +30,11 @@ type OnUserUpdateHandler = (userInfo: UserInfo) => void; type OnForcedVariationsUpdateHandler = () => void; -type NotReadyReason = 'TIMEOUT' | 'NO_CLIENT' | 'USER_NOT_READY'; +export enum NotReadyReason { + TIMEOUT = 'TIMEOUT', + NO_CLIENT = 'NO_CLIENT', + USER_NOT_READY = 'USER_NOT_READY', +} type ResolveResult = { success: boolean; @@ -50,7 +54,7 @@ export const DefaultUser: UserInfo = { attributes: {}, }; -export interface ReactSDKClient extends Omit { +export interface ReactSDKClient extends Omit { user: UserInfo; onReady(opts?: { timeout?: number }): Promise; @@ -192,6 +196,12 @@ export const DEFAULT_ON_READY_TIMEOUT = 5_000; class OptimizelyReactSDKClient implements ReactSDKClient { private userContext: optimizely.OptimizelyUserContext | null = null; private onUserUpdateHandlers: OnUserUpdateHandler[] = []; + private userPromiseResolver: (resolveResult: ResolveResult) => void; + private isUserPromiseResolved = false; + // Its usually true from the beginning when user is provided as an object in the `OptimizelyProvider` + // This becomes more significant when a promise is provided instead. + private isUserReady = false; + private onForcedVariationsUpdateHandlers: OnForcedVariationsUpdateHandler[] = []; private forcedDecisionFlagKeys: Set = new Set(); @@ -199,14 +209,14 @@ class OptimizelyReactSDKClient implements ReactSDKClient { private isClientReady = false; // We need to add autoupdate listener to the hooks after the instance became fully ready to avoid redundant updates to hooks - private isReadyPromiseFulfilled = false; + private clientAndUserReadyPromiseFulfilled = false; private isUsingSdkKey = false; private readonly _client: optimizely.Client | null; // promise keeping track of async requests for initializing client instance - private dataReadyPromise: Promise; + private clientAndUserReadyPromise: Promise; public initialConfig: optimizely.Config; public user: UserInfo = { ...DefaultUser }; @@ -217,36 +227,45 @@ class OptimizelyReactSDKClient implements ReactSDKClient { */ constructor(config: optimizely.Config) { this.initialConfig = config; - const configWithClientInfo = { ...config, clientEngine: REACT_SDK_CLIENT_ENGINE, clientVersion: REACT_SDK_CLIENT_VERSION, }; + this.userPromiseResolver = () => {}; + const userReadyPromise = new Promise(resolve => { + this.userPromiseResolver = resolve; + }); + this._client = optimizely.createInstance(configWithClientInfo); this.isClientReady = !!configWithClientInfo.datafile; this.isUsingSdkKey = !!configWithClientInfo.sdkKey; if (this._client) { - this.dataReadyPromise = this._client.onReady().then((clientResult: { success: boolean }) => { - this.isReadyPromiseFulfilled = true; - this.isClientReady = true; + const clientReadyPromise = this._client.onReady(); - return { - success: true, - message: clientResult.success - ? 'Successfully resolved client datafile.' - : 'Client datafile was not not ready.', - }; - }); + this.clientAndUserReadyPromise = Promise.all([userReadyPromise, clientReadyPromise]).then( + ([userResult, clientResult]) => { + this.isClientReady = clientResult.success; + this.isUserReady = userResult.success; + const clientAndUserReady = this.isClientReady && this.isUserReady; + + this.clientAndUserReadyPromiseFulfilled = true; + + return { + success: clientAndUserReady, + message: clientAndUserReady ? 'Client and user are both ready.' : 'Client or user did not become ready.', + }; + } + ); } else { logger.warn('Unable to resolve datafile and user information because Optimizely client failed to initialize.'); - this.dataReadyPromise = new Promise(resolve => { + this.clientAndUserReadyPromise = new Promise(resolve => { resolve({ success: false, - reason: 'NO_CLIENT', + reason: NotReadyReason.NO_CLIENT, message: 'Optimizely client failed to initialize.', }); }); @@ -265,43 +284,36 @@ class OptimizelyReactSDKClient implements ReactSDKClient { } public getIsReadyPromiseFulfilled(): boolean { - return this.isReadyPromiseFulfilled; + return this.clientAndUserReadyPromiseFulfilled; } public getIsUsingSdkKey(): boolean { return this.isUsingSdkKey; } + private get odpExplicitlyOff() { + return this.initialConfig.odpOptions?.disabled; + } + public onReady(config: { timeout?: number } = {}): Promise { let timeoutId: number | undefined; let timeout: number = DEFAULT_ON_READY_TIMEOUT; if (config && config.timeout !== undefined) { timeout = config.timeout; } - const timeoutPromise = new Promise(resolve => { timeoutId = setTimeout(() => { resolve({ success: false, - reason: 'TIMEOUT', - message: 'Failed to initialize onReady before timeout, data was not set before the timeout period', - dataReadyPromise: this.dataReadyPromise, + reason: NotReadyReason.TIMEOUT, + message: 'Failed to initialize before timeout', + dataReadyPromise: this.clientAndUserReadyPromise, }); }, timeout) as any; }); - return Promise.race([this.dataReadyPromise, timeoutPromise]).then(async res => { + return Promise.race([this.clientAndUserReadyPromise, timeoutPromise]).then(async res => { clearTimeout(timeoutId); - if (res.success && !this.initialConfig.odpOptions?.disabled) { - const isSegmentsFetched = await this.fetchQualifiedSegments(); - if (!isSegmentsFetched) { - return { - success: false, - reason: 'USER_NOT_READY', - message: 'Failed to fetch qualified segments', - }; - } - } return res; }); } @@ -326,13 +338,22 @@ class OptimizelyReactSDKClient implements ReactSDKClient { return; } - if (!this.userContext || (this.userContext && !areUsersEqual(userInfo, this.user))) { + if (!this.userContext) { + this.userContext = this._client.createUserContext(userInfo.id || undefined, userInfo.attributes); + return; + } + + const currentUserContextUserInfo: UserInfo = { + id: this.userContext.getUserId(), + attributes: this.userContext.getAttributes(), + }; + if (!areUsersEqual(userInfo, currentUserContextUserInfo)) { this.userContext = this._client.createUserContext(userInfo.id || undefined, userInfo.attributes); } } private makeUserContextInstance(userInfo: UserInfo): optimizely.OptimizelyUserContext | null { - if (!this._client || !this.isReady()) { + if (!this._client) { logger.warn( `Unable to create user context for ${userInfo.id}. Optimizely client failed to initialize or not ready.` ); @@ -343,7 +364,11 @@ class OptimizelyReactSDKClient implements ReactSDKClient { } public async fetchQualifiedSegments(options?: optimizely.OptimizelySegmentOption[]): Promise { - if (!this.userContext || !this.isReady()) { + if (this.odpExplicitlyOff) { + return true; + } + + if (!this.userContext) { return false; } @@ -351,15 +376,33 @@ class OptimizelyReactSDKClient implements ReactSDKClient { } public async setUser(userInfo: UserInfo): Promise { - this.setCurrentUserContext(userInfo); - this.user = { id: userInfo.id || DefaultUser.id, attributes: userInfo.attributes || DefaultUser.attributes, }; - if (this.getIsReadyPromiseFulfilled()) { - await this.fetchQualifiedSegments(); + // if user is anonymous... + if (userInfo.id === DefaultUser.id) { + // wait for the SDK client to be ready before... + await this._client?.onReady(); + // setting the user context + this.setCurrentUserContext(userInfo); + + // (potentially) retrieve the VUID set in JS userContext or noop or to DefaultUser + this.user.id = this.userContext?.getUserId() || DefaultUser.id; + } else { + // synchronous user context setting is required including for server side rendering (SSR) + this.setCurrentUserContext(userInfo); + + // we need to wait for fetch qualified segments success for failure + await this._client?.onReady(); + } + + const fetchQualifedSegmentsSucceed = await this.fetchQualifiedSegments(); + + if (!this.isUserPromiseResolved) { + this.userPromiseResolver({ success: fetchQualifedSegmentsSucceed }); + this.isUserPromiseResolved = true; } this.onUserUpdateHandlers.forEach(handler => handler(this.user)); diff --git a/src/hooks.ts b/src/hooks.ts index f13bd30..038f3c5 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,5 +1,5 @@ /** - * Copyright 2018-2019, 2022-2023, Optimizely + * Copyright 2018-2019, 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import { useCallback, useContext, useEffect, useState, useRef } from 'react'; import { UserAttributes, OptimizelyDecideOption, getLogger } from '@optimizely/optimizely-sdk'; import { setupAutoUpdateListeners } from './autoUpdate'; -import { ReactSDKClient, VariableValuesObject, OnReadyResult } from './client'; +import { ReactSDKClient, VariableValuesObject, OnReadyResult, NotReadyReason } from './client'; import { notifier } from './notifier'; import { OptimizelyContext } from './Context'; import { areAttributesEqual, OptimizelyDecision, createFailedDecision } from './utils'; @@ -66,7 +66,7 @@ interface UseExperiment { (experimentKey: string, options?: HookOptions, overrides?: HookOverrides): [ ExperimentDecisionValues['variation'], ClientReady, - DidTimeout, + DidTimeout ]; } @@ -75,7 +75,7 @@ interface UseFeature { FeatureDecisionValues['isEnabled'], FeatureDecisionValues['variables'], ClientReady, - DidTimeout, + DidTimeout ]; } @@ -83,7 +83,7 @@ interface UseDecision { (featureKey: string, options?: DecideHooksOptions, overrides?: HookOverrides): [ OptimizelyDecision, ClientReady, - DidTimeout, + DidTimeout ]; } @@ -127,7 +127,7 @@ function subscribeToInitialization( .onReady({ timeout }) .then((res: OnReadyResult) => { if (res.success) { - hooksLogger.info('Client became ready'); + hooksLogger.info('Client immediately ready'); onInitStateChange({ clientReady: true, didTimeout: false, @@ -137,13 +137,13 @@ function subscribeToInitialization( switch (res.reason) { // Optimizely client failed to initialize. - case 'NO_CLIENT': + case NotReadyReason.NO_CLIENT: hooksLogger.warn(`Client not ready, reason="${res.message}"`); onInitStateChange({ clientReady: false, didTimeout: false, }); - res.dataReadyPromise!.then(() => { + res.dataReadyPromise?.then(() => { hooksLogger.info('Client became ready.'); onInitStateChange({ clientReady: true, @@ -151,21 +151,47 @@ function subscribeToInitialization( }); }); break; - // Assume timeout for all other cases. - // TODO: Other reasons may fall into this case - need to update later to specify 'TIMEOUT' case and general fallback case. - default: - hooksLogger.info(`Client did not become ready before timeout of ${timeout}ms, reason="${res.message}"`); + case NotReadyReason.USER_NOT_READY: + hooksLogger.warn(`User was not ready, reason="${res.message}"`); + onInitStateChange({ + clientReady: false, + didTimeout: false, + }); + res.dataReadyPromise?.then(() => { + hooksLogger.info('User became ready later.'); + onInitStateChange({ + clientReady: true, + didTimeout: false, + }); + }); + break; + case NotReadyReason.TIMEOUT: + hooksLogger.info(`Client did not become ready before timeout of ${timeout} ms, reason="${res.message}"`); onInitStateChange({ clientReady: false, didTimeout: true, }); - res.dataReadyPromise!.then(() => { + res.dataReadyPromise?.then(() => { hooksLogger.info('Client became ready after timeout already elapsed'); onInitStateChange({ clientReady: true, didTimeout: true, }); }); + break; + default: + hooksLogger.warn(`Other reason client not ready, reason="${res.message}"`); + onInitStateChange({ + clientReady: false, + didTimeout: true, // assume timeout + }); + res.dataReadyPromise?.then(() => { + hooksLogger.info('Client became ready later'); + onInitStateChange({ + clientReady: true, + didTimeout: true, // assume timeout + }); + }); } }) .catch(() => { @@ -192,7 +218,9 @@ export const useExperiment: UseExperiment = (experimentKey, options = {}, overri const { optimizely, isServerSide, timeout } = useContext(OptimizelyContext); if (!optimizely) { - hooksLogger.error(`Unable to use experiment ${experimentKey}. optimizely prop must be supplied via a parent `); + hooksLogger.error( + `Unable to use experiment ${experimentKey}. optimizely prop must be supplied via a parent ` + ); return [null, false, false]; } @@ -259,7 +287,7 @@ export const useExperiment: UseExperiment = (experimentKey, options = {}, overri })); }); } - return (): void => { }; + return (): void => {}; }, [optimizely.getIsReadyPromiseFulfilled(), options.autoUpdate, optimizely, experimentKey, getCurrentDecision]); useEffect( @@ -287,13 +315,10 @@ export const useFeature: UseFeature = (featureKey, options = {}, overrides = {}) const { optimizely, isServerSide, timeout } = useContext(OptimizelyContext); if (!optimizely) { - hooksLogger.error(`Unable to properly use feature ${featureKey}. optimizely prop must be supplied via a parent `); - return [ - false, - {}, - false, - false, - ]; + hooksLogger.error( + `Unable to properly use feature ${featureKey}. optimizely prop must be supplied via a parent ` + ); + return [false, {}, false, false]; } const overrideAttrs = useCompareAttrsMemoize(overrides.overrideAttributes); @@ -359,15 +384,10 @@ export const useFeature: UseFeature = (featureKey, options = {}, overrides = {}) })); }); } - return (): void => { }; + return (): void => {}; }, [optimizely.getIsReadyPromiseFulfilled(), options.autoUpdate, optimizely, featureKey, getCurrentDecision]); - return [ - state.isEnabled, - state.variables, - state.clientReady, - state.didTimeout, - ]; + return [state.isEnabled, state.variables, state.clientReady, state.didTimeout]; }; /** @@ -381,7 +401,9 @@ export const useDecision: UseDecision = (flagKey, options = {}, overrides = {}) const { optimizely, isServerSide, timeout } = useContext(OptimizelyContext); if (!optimizely) { - hooksLogger.error(`Unable to use decision ${flagKey}. optimizely prop must be supplied via a parent `); + hooksLogger.error( + `Unable to use decision ${flagKey}. optimizely prop must be supplied via a parent ` + ); return [ createFailedDecision(flagKey, 'Optimizely SDK not configured properly yet.', { id: null, @@ -389,7 +411,7 @@ export const useDecision: UseDecision = (flagKey, options = {}, overrides = {}) }), false, false, - ] + ]; } const overrideAttrs = useCompareAttrsMemoize(overrides.overrideAttributes); @@ -403,11 +425,11 @@ export const useDecision: UseDecision = (flagKey, options = {}, overrides = {}) const decisionState = isClientReady ? getCurrentDecision() : { - decision: createFailedDecision(flagKey, 'Optimizely SDK not configured properly yet.', { - id: overrides.overrideUserId || null, - attributes: overrideAttrs, - }), - }; + decision: createFailedDecision(flagKey, 'Optimizely SDK not configured properly yet.', { + id: overrides.overrideUserId || null, + attributes: overrideAttrs, + }), + }; return { ...decisionState, clientReady: isClientReady, @@ -473,12 +495,8 @@ export const useDecision: UseDecision = (flagKey, options = {}, overrides = {}) })); }); } - return (): void => { }; + return (): void => {}; }, [optimizely.getIsReadyPromiseFulfilled(), options.autoUpdate, optimizely, flagKey, getCurrentDecision]); - return [ - state.decision, - state.clientReady, - state.didTimeout, - ]; + return [state.decision, state.clientReady, state.didTimeout]; }; diff --git a/yarn.lock b/yarn.lock index 8f314f7..43f39ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -655,10 +655,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@optimizely/optimizely-sdk@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@optimizely/optimizely-sdk/-/optimizely-sdk-5.2.0.tgz#6449336182515dd4cca9d6913e24ca8e814e6278" - integrity sha512-JxdhWHU9OiJKrKfyWe9lL+2/e28gBZnPNHQAA+heyWTAtcuxZWdSZ24bIkLwAAuIf26dh89o9LNB2L8aKPmwWw== +"@optimizely/optimizely-sdk@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@optimizely/optimizely-sdk/-/optimizely-sdk-5.3.0.tgz#dd1ca9d19287b31675d2f95c24234e074ade9a8b" + integrity sha512-PzfjcApCvcHGir8XWSG3IBaOJXvPADjqpzXypEWTfArrONA3FlmqdnwDAlxF4b557fo/UZI6ZCyj3AWrG8cprg== dependencies: decompress-response "^4.2.1" json-schema "^0.4.0" @@ -1624,7 +1624,7 @@ decode-uri-component@^0.2.0: decompress-response@^4.2.1: version "4.2.1" - resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== dependencies: mimic-response "^2.0.0" @@ -3311,7 +3311,7 @@ json-schema-traverse@^0.4.1: json-schema@^0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: @@ -3511,7 +3511,7 @@ mimic-fn@^2.1.0: mimic-response@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== min-indent@^1.0.0: From 3389cd6e45179a152eb30f781f2f4f04a8978509 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:32:44 -0400 Subject: [PATCH 9/9] [FSSDK-9984] chore: prepare for release v3.1.0 (#257) * build: increment the semantic version * docs: update changelog.md * docs: PR review adjustment to CHANGELOG --------- Co-authored-by: Mike Chu --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- src/client.spec.ts | 2 +- src/client.ts | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 322d2b1..b3b0ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [3.1.0] - April 9, 2024 + +### Bug Fixes +- Error initializing client. The core client or user promise(s) rejected. + ([#255](https://github.com/optimizely/react-sdk/pull/255)) +- Unable to determine if feature "{your-feature-key}" is enabled because User ID is not set([#255](https://github.com/optimizely/react-sdk/pull/255)) + +### Changed +- Bumped Optimizely JS SDK version in use ([#255](https://github.com/optimizely/react-sdk/pull/255)) +- Resolve dependabot dependency vulnerabilities ([#245](https://github.com/optimizely/react-sdk/pull/245), [#247](https://github.com/optimizely/react-sdk/pull/247), [#248](https://github.com/optimizely/react-sdk/pull/248), [#251](https://github.com/optimizely/react-sdk/pull/251), [#253](https://github.com/optimizely/react-sdk/pull/253)) +- Add node versions during testing ([#249](https://github.com/optimizely/react-sdk/pull/249)) + ## [3.0.1] - February 27, 2024 ### Changed diff --git a/package.json b/package.json index 54de565..fa5e7e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@optimizely/react-sdk", - "version": "3.0.1", + "version": "3.1.0", "description": "React SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", "homepage": "https://github.com/optimizely/react-sdk", "repository": "https://github.com/optimizely/react-sdk", diff --git a/src/client.spec.ts b/src/client.spec.ts index 1b7b65b..177770b 100644 --- a/src/client.spec.ts +++ b/src/client.spec.ts @@ -127,7 +127,7 @@ describe('ReactSDKClient', () => { expect(createInstanceSpy).toBeCalledWith({ ...config, clientEngine: 'react-sdk', - clientVersion: '3.0.1', + clientVersion: '3.1.0', }); }); diff --git a/src/client.ts b/src/client.ts index 5c57bc7..405493d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -47,7 +47,7 @@ export interface OnReadyResult extends ResolveResult { } const REACT_SDK_CLIENT_ENGINE = 'react-sdk'; -const REACT_SDK_CLIENT_VERSION = '3.0.1'; +const REACT_SDK_CLIENT_VERSION = '3.1.0'; export const DefaultUser: UserInfo = { id: null,