From 51ff9cb1ef1be12cc60616b5b47c1978b49ac1d4 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 16 Sep 2025 18:19:12 -0300 Subject: [PATCH 1/4] Upgrade JS-commons --- CHANGES.txt | 5 + package-lock.json | 339 +++++++++----- package.json | 4 +- .../push-synchronization-retries.spec.js | 4 +- .../ready-from-cache-async-wrapper.spec.js | 412 ++++++++++++++++++ .../browserSuites/ready-from-cache.spec.js | 98 ++++- src/__tests__/logger/browser.spec.js | 56 +-- src/__tests__/online/browser.spec.js | 2 + src/settings/defaults.ts | 2 +- ts-tests/index.ts | 13 +- types/index.d.ts | 4 +- 11 files changed, 789 insertions(+), 150 deletions(-) create mode 100644 src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js diff --git a/CHANGES.txt b/CHANGES.txt index 4f51650..f7de0c1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ +1.4.0 (September 19, 2025) + - Added the `wrapper` option to the SDK's `InLocalStorage` module, to allow passing a custom storage for persisting the SDK rollout plan. The default is `window.localStorage`. + - Added the `initialRolloutPlan` configuration option for the SDK in standalone mode, to allow preloading the SDK storage with a snapshot of the rollout plan. + - Updated @splitsoftware/splitio-commons package to version 2.6.0. + 1.3.1 (June 24, 2025) - Updated @splitsoftware/splitio-commons package to version 2.4.1, which improves the Proxy fallback to flag spec version 1.2 by handling the case when the Proxy does not return an end-of-stream marker in 400 status code responses. diff --git a/package-lock.json b/package-lock.json index f18e626..b6d3787 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.3.1", + "version": "1.3.2-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-browserjs", - "version": "1.3.1", + "version": "1.3.2-rc.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.4.1", + "@splitsoftware/splitio-commons": "2.5.1-rc.0", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, @@ -1396,9 +1396,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.4.1.tgz", - "integrity": "sha512-VcbWpPykfx19LTJ0yeZbV0u3PUIt8MuiZ2a8zqkNf9KnDnhau/XxS/ctoO5jYrg4Nk2rCi0fpt1TkTstqzbaYA==", + "version": "2.5.1-rc.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.5.1-rc.0.tgz", + "integrity": "sha512-AsVEWC8o3CYKSZ4zxAQI756YkOlgWu1jVFKAjxFViZ4snv9V5nDIr0ObiJkVXDg+FGg3W264tSx9bO3RTvbd1Q==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -2608,6 +2608,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3278,6 +3292,21 @@ "ignored": "bin/ignored" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3446,13 +3475,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3466,15 +3493,30 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4176,13 +4218,16 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -4284,16 +4329,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4311,6 +4362,20 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -4459,12 +4524,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4534,10 +4600,11 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4546,12 +4613,13 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -4561,10 +4629,11 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -5993,14 +6062,17 @@ } }, "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -6354,6 +6426,16 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8656,15 +8738,13 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/tmpl": { @@ -10413,9 +10493,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.4.1.tgz", - "integrity": "sha512-VcbWpPykfx19LTJ0yeZbV0u3PUIt8MuiZ2a8zqkNf9KnDnhau/XxS/ctoO5jYrg4Nk2rCi0fpt1TkTstqzbaYA==", + "version": "2.5.1-rc.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.5.1-rc.0.tgz", + "integrity": "sha512-AsVEWC8o3CYKSZ4zxAQI756YkOlgWu1jVFKAjxFViZ4snv9V5nDIr0ObiJkVXDg+FGg3W264tSx9bO3RTvbd1Q==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" @@ -11302,6 +11382,16 @@ "set-function-length": "^1.2.1" } }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -11811,6 +11901,17 @@ "minimatch": "^3.0.4" } }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -11946,13 +12047,10 @@ } }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true }, "es-errors": { "version": "1.3.0", @@ -11960,15 +12058,25 @@ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, "es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "requires": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" } }, "es-shim-unscopables": { @@ -12508,13 +12616,15 @@ } }, "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, @@ -12585,16 +12695,21 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, "get-package-type": { @@ -12603,6 +12718,16 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -12710,13 +12835,10 @@ } }, "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true }, "graceful-fs": { "version": "4.2.11", @@ -12764,24 +12886,24 @@ "dev": true }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true }, "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "requires": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" } }, "hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "requires": { "function-bind": "^1.1.2" @@ -13843,14 +13965,16 @@ }, "dependencies": { "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" } }, "tr46": { @@ -14138,6 +14262,12 @@ "tmpl": "1.0.5" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -15849,13 +15979,10 @@ "dev": true }, "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true }, "tmpl": { "version": "1.0.5", diff --git a/package.json b/package.json index 8d25d6a..a45b0f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.3.1", + "version": "1.3.2-rc.0", "description": "Split SDK for JavaScript on Browser", "main": "cjs/index.js", "module": "esm/index.js", @@ -59,7 +59,7 @@ "bugs": "https://github.com/splitio/javascript-browser-client/issues", "homepage": "https://github.com/splitio/javascript-browser-client#readme", "dependencies": { - "@splitsoftware/splitio-commons": "2.4.1", + "@splitsoftware/splitio-commons": "2.5.1-rc.0", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, diff --git a/src/__tests__/browserSuites/push-synchronization-retries.spec.js b/src/__tests__/browserSuites/push-synchronization-retries.spec.js index b9c36bd..3065275 100644 --- a/src/__tests__/browserSuites/push-synchronization-retries.spec.js +++ b/src/__tests__/browserSuites/push-synchronization-retries.spec.js @@ -135,7 +135,7 @@ export function testSynchronizationRetries(fetchMock, assert) { }); // initial auth - fetchMock.getOnce(url(settings, `/v2/auth?s=1.3&users=${encodeURIComponent(userKey)}&users=${encodeURIComponent(otherUserKeySync)}`), function (url, opts) { + fetchMock.getOnce(url(settings, `/v2/auth?s=1.3&users=${encodeURIComponent(otherUserKeySync)}&users=${encodeURIComponent(userKey)}`), function (url, opts) { if (!opts.headers['Authorization']) assert.fail('`/v2/auth` request must include `Authorization` header'); assert.pass('auth success'); return { status: 200, body: authPushEnabledNicolas }; @@ -144,7 +144,7 @@ export function testSynchronizationRetries(fetchMock, assert) { // initial split and memberships sync fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), { status: 200, body: membershipsNicolasMock1 }); - fetchMock.get({ url: url(settings, '/memberships/marcio%40split.io'), repeat: 3 }, { status: 200, body: membershipsMarcio }); + fetchMock.get({ url: url(settings, '/memberships/marcio%40split.io'), repeat: 4 }, { status: 200, body: membershipsMarcio }); // split and segment sync after SSE opened fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { diff --git a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js new file mode 100644 index 0000000..aa4d8fd --- /dev/null +++ b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js @@ -0,0 +1,412 @@ +import sinon from 'sinon'; +import { nearlyEqual } from '../testUtils'; +import { SplitFactory, InLocalStorage } from '../../'; + +import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; +import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; +import membershipsNicolas from '../mocks/memberships.nicolas@split.io.json'; + +import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS, alwaysOnSplitInverted, splitDeclarations, baseConfig, expectedHashNullFilter } from './ready-from-cache.spec'; + +const customWrapper = (() => { + let cache = {}; + return { + getItem(key) { + return Promise.resolve(cache[key] || null); + }, + setItem(key, value) { + cache[key] = value; + return Promise.resolve(); + }, + removeItem(key) { + delete cache[key]; + return Promise.resolve(); + }, + + // For testing purposes: + clear() { + cache = {}; + }, + getCache() { + return cache; + } + }; +})(); + +export default function (fetchMock, assert) { + + assert.test(t => { // Testing when we start from scratch + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapperEmpty', + events: 'https://events.baseurl/readyFromCacheWrapperEmpty' + }; + customWrapper.clear(); + t.plan(4); + + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } }); + + const splitio = SplitFactory({ + ...baseConfig, + core: { + ...baseConfig.core, + authorizationKey: '', + }, + storage: InLocalStorage({ + prefix: 'readyFromCache_1', + wrapper: customWrapper + }), + urls: testUrls + }); + const client = splitio.client(); + const client2 = splitio.client('nicolas2@split.io'); + const client3 = splitio.client('nicolas3@split.io'); + + client.once(client.Event.SDK_READY_TIMED_OUT, () => { + t.fail('It should not timeout in this scenario.'); + t.end(); + }); + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + + client.on(client.Event.SDK_READY, () => { + t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + }); + client2.on(client.Event.SDK_READY, () => { + t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + }); + client3.on(client.Event.SDK_READY, () => { + t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + }); + + }); + + assert.test(t => { // Testing when we start with cached data and not expired (lastUpdate timestamp higher than default (10) expirationDays ago) + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapperWithData3', + events: 'https://events.baseurl/readyFromCacheWrapperWithData3' + }; + customWrapper.clear(); + t.plan(12 * 2 + 5); + + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=25&rbSince=-1', () => { + t.equal(JSON.parse(customWrapper.getCache()['readyFromCache_3.SPLITIO'])['readyFromCache_3.SPLITIO.split.always_on'], alwaysOnSplitInverted, 'feature flags must not be cleaned from cache'); + return new Promise(res => { setTimeout(() => res({ status: 200, body: { ff: { ...splitChangesMock1.ff, s: 25 } }, headers: {} }), 200); }); // 400ms is how long it'll take to reply with Splits, no SDK_READY should be emitted before that. + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: membershipsNicolas, headers: {} }), 400); }); // First client gets segments before splits. No segment cache loading (yet) + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), 700); }); // Second client gets segments after 700ms + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), 1000); }); // Third client memberships will come after 1s + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas4%40split.io', { 'ms': {} }); + fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', 200); + fetchMock.postOnce(testUrls.events + '/testImpressions/count', 200); + + customWrapper.getCache()['some_user_item'] = 'user_item'; + customWrapper.getCache()['readyFromCache_3.SPLITIO'] = JSON.stringify({ + 'readyFromCache_3.SPLITIO.splits.till': '25', + 'readyFromCache_3.SPLITIO.splits.lastUpdated': Date.now().toString(), + 'readyFromCache_3.SPLITIO.split.always_on': alwaysOnSplitInverted, + 'readyFromCache_3.SPLITIO.hash': expectedHashNullFilter + }); + + const startTime = Date.now(); + const splitio = SplitFactory({ + ...baseConfig, + storage: InLocalStorage({ + prefix: 'readyFromCache_3', + wrapper: customWrapper + }), + startup: { + readyTimeout: 0.85 + }, + urls: testUrls, + }); + const client = splitio.client(); + const client2 = splitio.client('nicolas2@split.io'); + const client3 = splitio.client('nicolas3@split.io'); + + t.equal(client.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + + client.on(client.Event.SDK_READY_TIMED_OUT, () => { + t.fail('It should not timeout in this scenario.'); + t.end(); + }); + + client.on(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); + t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); + + const client4 = splitio.client('nicolas4@split.io'); + t.equal(client4.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control'); + + client4.on(client4.Event.SDK_READY_FROM_CACHE, () => { + t.fail('It should not emit SDK_READY_FROM_CACHE if already done.'); + }); + }); + client2.on(client2.Event.SDK_READY_FROM_CACHE, () => { + t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); + t.equal(client2.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); + }); + client3.on(client3.Event.SDK_READY_FROM_CACHE, () => { + t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); + t.equal(client3.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); + }); + + client.on(client.Event.SDK_READY, () => { + t.true(Date.now() - startTime >= 400, 'It should emit SDK_READY too but after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client.ready().then(() => { + t.true(Date.now() - startTime >= 400, 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.on(client2.Event.SDK_READY, () => { + t.true(Date.now() - startTime >= 700, 'It should emit SDK_READY too but after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.ready().then(() => { + t.true(Date.now() - startTime >= 700, 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY, () => { + client3.ready().then(() => { + t.true(Date.now() - startTime >= 1000, 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + + // Last cb: destroy clients and check that storage wrapper has the expected items + Promise.all([client3.destroy(), client2.destroy(), client.destroy()]).then(() => { + t.equal(customWrapper.getCache()['some_user_item'], 'user_item', 'user items at storage wrapper must not be changed'); + t.equal(JSON.parse(customWrapper.getCache()['readyFromCache_3.SPLITIO'])['readyFromCache_3.SPLITIO.splits.till'], '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits'); + t.true(nearlyEqual(parseInt(JSON.parse(customWrapper.getCache()['readyFromCache_3.SPLITIO'])['readyFromCache_3.SPLITIO.splits.lastUpdated']), Date.now() - 800 /* 800 ms between last Split and memberships fetch */), 'lastUpdated must correspond to the timestamp of the last successfully fetched Splits'); + }); + }); + t.true(Date.now() - startTime >= 1000, 'It should emit SDK_READY too but after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { + client3.ready().catch(() => { + t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with memberships data from cache.'); + }); + t.true(Date.now() - startTime >= 850, 'It should emit SDK_READY_TIMED_OUT before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with memberships data from cache.'); + }); + }); + + assert.test(t => { // Testing when we start with cached data but expired (lastUpdate timestamp lower than custom (1) expirationDays ago) + const CLIENT_READY_MS = 400, CLIENT2_READY_MS = 700, CLIENT3_READY_MS = 1000; + + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapperWithData4', + events: 'https://events.baseurl/readyFromCacheWrapperWithData4' + }; + customWrapper.clear(); + + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', () => { + const cache = JSON.parse(customWrapper.getCache()['readyFromCache_4.SPLITIO']); + t.equal(cache['readyFromCache_4.SPLITIO.hash'], expectedHashNullFilter, 'storage hash must not be changed'); + t.true(nearlyEqual(parseInt(cache['readyFromCache_4.SPLITIO.lastClear'], 10), Date.now()), 'storage lastClear timestamp must be updated'); + t.equal(Object.keys(cache).length, 2, 'feature flags cache data must be cleaned from storage wrapper'); + return { status: 200, body: splitChangesMock1 }; + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: membershipsNicolas, headers: {} }), CLIENT_READY_MS); }); // First client gets segments before splits. No segment cache loading (yet) + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), CLIENT2_READY_MS); }); // Second client gets segments after 700ms + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), CLIENT3_READY_MS); }); // Third client memberships will come after 1s + }); + fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', 200); + fetchMock.postOnce(testUrls.events + '/testImpressions/count', 200); + + customWrapper.getCache()['readyFromCache_4.SPLITIO'] = JSON.stringify({ + 'readyFromCache_4.SPLITIO.splits.till': '25', + 'readyFromCache_4.SPLITIO.splits.lastUpdated': Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS / 10 - 1, // -1 to ensure having an expired lastUpdated item + 'readyFromCache_4.SPLITIO.split.always_on': alwaysOnSplitInverted, + 'readyFromCache_4.SPLITIO.hash': expectedHashNullFilter + }); + + const startTime = Date.now(); + const splitio = SplitFactory({ + ...baseConfig, + storage: InLocalStorage({ + prefix: 'readyFromCache_4', + expirationDays: 1, + wrapper: customWrapper + }), + startup: { + readyTimeout: 0.85 + }, + urls: testUrls, + }); + const client = splitio.client(); + const client2 = splitio.client('nicolas2@split.io'); + const client3 = splitio.client('nicolas3@split.io'); + + t.equal(client.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + + client.once(client.Event.SDK_READY_TIMED_OUT, () => { + t.fail('It should not timeout in this scenario.'); + t.end(); + }); + + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + client2.once(client2.Event.SDK_READY_FROM_CACHE, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + client3.once(client3.Event.SDK_READY_FROM_CACHE, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT3_READY_MS), 'It should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + + client.on(client.Event.SDK_READY, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client.ready().then(() => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.on(client2.Event.SDK_READY, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.ready().then(() => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY, () => { + client3.ready().then(() => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT3_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + + // Last cb: destroy clients and check that storage wrapper has the expected items + Promise.all([client3.destroy(), client2.destroy(), client.destroy()]).then(() => { + const cache = JSON.parse(customWrapper.getCache()['readyFromCache_4.SPLITIO']); + t.equal(cache['readyFromCache_4.SPLITIO.splits.till'], '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits'); + t.true(nearlyEqual(parseInt(cache['readyFromCache_4.SPLITIO.splits.lastUpdated']), Date.now() - 1000 /* 1000 ms between last Split and memberships fetch */), 'lastUpdated must correspond to the timestamp of the last successfully fetched Splits'); + + t.end(); + }); + }); + t.true(Date.now() - startTime >= 1000, 'It should emit SDK_READY after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { + client3.ready().catch(() => { + t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should not evaluate treatments with memberships data from cache.'); + }); + t.true(Date.now() - startTime >= 850, 'It should emit SDK_READY_TIMED_OUT before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate treatments with memberships data from cache.'); + }); + }); + + assert.test(async t => { // Testing clearOnInit config true + sinon.spy(console, 'log'); + + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapper_10', + events: 'https://events.baseurl/readyFromCacheWrapper_10' + }; + const clearOnInitConfig = { + ...baseConfig, + storage: InLocalStorage({ + prefix: 'readyFromCache_10', + clearOnInit: true, + wrapper: customWrapper + }), + urls: testUrls, + debug: true + }; + + // Start with cached data but without lastClear item (JS SDK below 11.1.0) -> cache cleanup + customWrapper.clear(); + customWrapper.getCache()['readyFromCache_10.SPLITIO'] = JSON.stringify({ + 'readyFromCache_10.SPLITIO.splits.till': '25', + 'readyFromCache_10.SPLITIO.split.p1__split': JSON.stringify(splitDeclarations.p1__split), + 'readyFromCache_10.SPLITIO.split.p2__split': JSON.stringify(splitDeclarations.p2__split), + 'readyFromCache_10.SPLITIO.hash': expectedHashNullFilter + }); + + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=100', { status: 200, body: splitChangesMock2 }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: { ms: {} } }); + + let splitio = SplitFactory(clearOnInitConfig); + let client = splitio.client(); + let manager = splitio.manager(); + + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + }); + + await client.ready(); + + t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); + + await splitio.destroy(); + const cache = JSON.parse(customWrapper.getCache()['readyFromCache_10.SPLITIO']); + t.equal(cache['readyFromCache_10.SPLITIO.splits.till'], '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits'); + t.equal(cache['readyFromCache_10.SPLITIO.hash'], expectedHashNullFilter, 'Storage hash must not be changed'); + t.true(nearlyEqual(parseInt(cache['readyFromCache_10.SPLITIO.lastClear']), Date.now()), 'lastClear timestamp must be set'); + + // Start again with cached data and lastClear item within the last 24 hours -> no cache cleanup + console.log.resetHistory(); + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); + + splitio = SplitFactory(clearOnInitConfig); + client = splitio.client(); + manager = splitio.manager(); + + await new Promise(res => client.once(client.Event.SDK_READY_FROM_CACHE, res)); + + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); + t.false(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + + await splitio.destroy(); + + // Start again with cached data and lastClear item older than 24 hours -> cache cleanup + console.log.resetHistory(); + customWrapper.getCache()['readyFromCache_10.SPLITIO'] = JSON.stringify({ + ...JSON.parse(customWrapper.getCache()['readyFromCache_10.SPLITIO']), + 'readyFromCache_10.SPLITIO.lastClear': Date.now() - 25 * 60 * 60 * 1000 // 25 hours ago + }); + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); + + splitio = SplitFactory(clearOnInitConfig); + client = splitio.client(); + manager = splitio.manager(); + + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + }); + + await new Promise(res => client.once(client.Event.SDK_READY, res)); + + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); + t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + + await splitio.destroy(); + + console.log.restore(); + t.end(); + }); + +} diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index cc123e6..6a08d05 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -7,9 +7,9 @@ import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import membershipsNicolas from '../mocks/memberships.nicolas@split.io.json'; -const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days +export const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days -const alwaysOnSplitInverted = JSON.stringify({ +export const alwaysOnSplitInverted = JSON.stringify({ 'environment': null, 'trafficTypeId': null, 'trafficTypeName': null, @@ -19,6 +19,25 @@ const alwaysOnSplitInverted = JSON.stringify({ 'killed': false, 'defaultTreatment': 'off', 'conditions': [ + { + 'matcherGroup': { + 'combiner': 'AND', + 'matchers': [ + { + 'matcherType': 'IN_SEGMENT', + 'userDefinedSegmentMatcherData': { + 'segmentName': 'employees' + }, + } + ] + }, + 'partitions': [ + { + 'treatment': 'on', + 'size': 100 + } + ] + }, { 'matcherGroup': { 'combiner': 'AND', @@ -47,7 +66,7 @@ const alwaysOnSplitInverted = JSON.stringify({ ] }); -const splitDeclarations = { +export const splitDeclarations = { p1__split: { 'name': 'p1__split', 'status': 'ACTIVE', @@ -65,7 +84,7 @@ const splitDeclarations = { }, }; -const baseConfig = { +export const baseConfig = { core: { authorizationKey: '', key: 'nicolas@split.io' @@ -83,8 +102,8 @@ const baseConfig = { streamingEnabled: false }; -const expectedHashNullFilter = '193e6f3f'; // for SDK key '', filter query null, and flags spec version '1.3' -const expectedHashWithFilter = '2ce5cc38'; // for SDK key '', filter query '&names=p1__split,p2__split', and flags spec version '1.3' +export const expectedHashNullFilter = '193e6f3f'; // for SDK key '', filter query null, and flags spec version '1.3' +export const expectedHashWithFilter = '2ce5cc38'; // for SDK key '', filter query '&names=p1__split,p2__split', and flags spec version '1.3' export default function (fetchMock, assert) { @@ -467,6 +486,68 @@ export default function (fetchMock, assert) { }); }); + assert.test(t => { // Testing when we start with initial rollout plan data and sync storage type (is ready from cache immediately) + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWithInitialRolloutPlan', + events: 'https://events.baseurl/readyFromCacheWithInitialRolloutPlan' + }; + + t.plan(5); + + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=25&rbSince=-1', { status: 200, body: { ff: { ...splitChangesMock1.ff, s: 25 } } }); + fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas }); + fetchMock.getOnce(testUrls.sdk + '/memberships/emi%40split.io', { status: 200, body: { 'ms': {} } }); + + fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', 200); + fetchMock.postOnce(testUrls.events + '/testImpressions/count', 200); + + const splitio = SplitFactory({ + ...baseConfig, + // InLocalStorage is supported too + urls: testUrls, + initialRolloutPlan: { + splitChanges: { + ff: { + t: 25, + d: [JSON.parse(alwaysOnSplitInverted)] + } + }, + memberships: { + 'emi@split.io': { ms: { k: [{ n: 'employees' }] } } + } + } + }); + + const client = splitio.client(); + const client2 = splitio.client('emi@split.io'); + + t.equal(client.__getStatus().isReadyFromCache, true, 'Client is ready from cache'); + + t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache. Key without memberships'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with data from cache. Key with memberships'); + + client.on(client.Event.SDK_READY_TIMED_OUT, () => { + t.fail('It should not timeout in this scenario.'); + t.end(); + }); + + client.on(client.Event.SDK_READY_FROM_CACHE, () => { + t.fail('SDK is ready from cache immediately. SDK_READY_FROM_CACHE not emitted.'); + t.end(); + }); + + client.on(client.Event.SDK_READY, () => { + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.on(client2.Event.SDK_READY, () => { + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + + splitio.destroy().then(() => { + t.end(); + }); + }); + }); + /** Fetch specific splits **/ assert.test(t => { // Testing when we start with cached data but without storage hash (JS SDK <=v10.24.0 and Browser SDK <=v0.12.0), and a valid split filter config @@ -804,13 +885,14 @@ export default function (fetchMock, assert) { let client = splitio.client(); let manager = splitio.manager(); - t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); - client.once(client.Event.SDK_READY_FROM_CACHE, () => { t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await client.ready(); + + t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); await splitio.destroy(); diff --git a/src/__tests__/logger/browser.spec.js b/src/__tests__/logger/browser.spec.js index 14549a8..4443fef 100644 --- a/src/__tests__/logger/browser.spec.js +++ b/src/__tests__/logger/browser.spec.js @@ -21,47 +21,47 @@ tape('## E2E Logger Tests ##', assert => { const logSpy = sinon.spy(console, 'log'); - assert.test('debug settings: false', (t) => { + assert.test('debug settings: false', async (t) => { const factory = SplitFactory({ ...minConfig, debug: false }); - factory.client().destroy().then(() => { - t.false(logSpy.calledWithMatch('splitio => '), 'shouldn\'t log split messages'); + await factory.client().destroy(); + t.false(logSpy.calledWithMatch('splitio => '), 'shouldn\'t log split messages'); - logSpy.resetHistory(); - t.end(); - }); + logSpy.resetHistory(); + t.end(); }); - assert.test('debug settings: Logger object', (t) => { + assert.test('debug settings: Logger object', async (t) => { const factory = SplitFactory({ ...minConfig, debug: ErrorLogger() }); - factory.client().destroy().then(() => { - t.false(logSpy.calledWithMatch('[WARN]'), 'shouldn\'t log messages with level WARN'); - t.true(logSpy.calledWithMatch('[ERROR]'), 'should log messages with level ERROR'); - - logSpy.resetHistory(); - localStorage.clear(); - t.end(); - }); + await Promise.resolve(); + + await factory.client().destroy(); + t.false(logSpy.calledWithMatch('[WARN]'), 'shouldn\'t log messages with level WARN'); + t.true(logSpy.calledWithMatch('[ERROR]'), 'should log messages with level ERROR'); + + logSpy.resetHistory(); + localStorage.clear(); + t.end(); }); - assert.test('debug settings: log level', (t) => { + assert.test('debug settings: log level', async (t) => { const factory = SplitFactory({ ...minConfig, debug: 'INFO' }); - factory.client().destroy().then(() => { - t.false(logSpy.calledWithMatch('[DEBUG]'), 'shouldn\'t log messages with level DEBUG'); - t.true(logSpy.calledWithMatch('[INFO]'), 'should log messages with level INFO'); + await Promise.resolve(); + await factory.client().destroy(); + t.false(logSpy.calledWithMatch('[DEBUG]'), 'shouldn\'t log messages with level DEBUG'); + t.true(logSpy.calledWithMatch('[INFO]'), 'should log messages with level INFO'); - logSpy.resetHistory(); - t.end(); - }); + logSpy.resetHistory(); + t.end(); }); - assert.test('debug settings: localStorage.splitio_debug = "enable"', (t) => { + assert.test('debug settings: localStorage.splitio_debug = "enable"', async (t) => { const factory = SplitFactory(minConfig); - factory.client().destroy().then(() => { - t.true(logSpy.calledWithMatch('[' + initialLogLevel + ']'), 'should log messages with level' + initialLogLevel); + await Promise.resolve(); + await factory.client().destroy(); + t.true(logSpy.calledWithMatch('[' + initialLogLevel + ']'), 'should log messages with level' + initialLogLevel); - logSpy.resetHistory(); - t.end(); - }); + logSpy.resetHistory(); + t.end(); }); assert.test('Logger API', (t) => { diff --git a/src/__tests__/online/browser.spec.js b/src/__tests__/online/browser.spec.js index ef0841c..a844787 100644 --- a/src/__tests__/online/browser.spec.js +++ b/src/__tests__/online/browser.spec.js @@ -8,6 +8,7 @@ import telemetrySuite from '../browserSuites/telemetry.spec'; import impressionsListenerSuite from '../browserSuites/impressions-listener.spec'; import readinessSuite from '../browserSuites/readiness.spec'; import readyFromCache from '../browserSuites/ready-from-cache.spec'; +import readyFromCacheAsyncWrapper from '../browserSuites/ready-from-cache-async-wrapper.spec'; import { withoutBindingTT /*, bindingTT */ } from '../browserSuites/events.spec'; import sharedInstantiationSuite from '../browserSuites/shared-instantiation.spec'; import managerSuite from '../browserSuites/manager.spec'; @@ -126,6 +127,7 @@ tape('## E2E CI Tests ##', function (assert) { assert.test('E2E / Use Beacon API DEBUG (or Fetch if not available) to send remaining impressions and events when browser page is unload or hidden', useBeaconDebugApiSuite.bind(null, fetchMock)); /* Validate ready from cache behavior (might be merged into another suite if we end up having simple behavior around it as expected) */ assert.test('E2E / Readiness from cache', readyFromCache.bind(null, fetchMock)); + assert.test('E2E / Readiness from cache with custom async wrapper', readyFromCacheAsyncWrapper.bind(null, fetchMock)); /* Validate readiness with ready promises */ assert.test('E2E / Ready promise', readyPromiseSuite.bind(null, fetchMock)); /* Validate fetching specific splits */ diff --git a/src/settings/defaults.ts b/src/settings/defaults.ts index f181403..de874c3 100644 --- a/src/settings/defaults.ts +++ b/src/settings/defaults.ts @@ -2,7 +2,7 @@ import type SplitIO from '@splitsoftware/splitio-commons/types/splitio'; import { LogLevels, isLogLevelString } from '@splitsoftware/splitio-commons/src/logger/index'; import { CONSENT_GRANTED } from '@splitsoftware/splitio-commons/src/utils/constants'; -const packageVersion = '1.3.1'; +const packageVersion = '1.3.2-rc.0'; /** * In browser, the default debug level, can be set via the `localStorage.splitio_debug` item. diff --git a/ts-tests/index.ts b/ts-tests/index.ts index 7601ec4..643fc26 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -120,8 +120,15 @@ let syncStorageFactory: SplitIO.StorageSyncFactory = InLocalStorage(); let localStorageOptions: SplitIO.InLocalStorageOptions = { prefix: 'PREFIX', expirationDays: 1, - clearOnInit: true + clearOnInit: true, + wrapper: window.localStorage }; +let customStorage: SplitIO.StorageWrapper = { + getItem(key: string) { return Promise.resolve('value'); }, + setItem(key: string, value: string) { return Promise.resolve(); }, + removeItem(key: string) { return Promise.resolve(); }, +}; +localStorageOptions.wrapper = customStorage; syncStorageFactory = InLocalStorage(localStorageOptions); // mockedFeaturesPath = 'path/to/file'; @@ -662,6 +669,10 @@ fullBrowserSettings.debug = InfoLoggerFull(); fullBrowserSettings.debug = WarnLoggerFull(); fullBrowserSettings.debug = ErrorLoggerFull(); +// Preload rollout plan +let rolloutPlanSnapshot: SplitIO.RolloutPlan = {}; +fullBrowserSettings.initialRolloutPlan = rolloutPlanSnapshot; + // let fullNodeSettings: SplitIO.INodeSettings = { // core: { // authorizationKey: 'asd', diff --git a/types/index.d.ts b/types/index.d.ts index c33e845..a082927 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -17,9 +17,9 @@ declare module JsSdk { export function SplitFactory(settings: SplitIO.IClientSideAsyncSettings): SplitIO.IBrowserAsyncSDK; /** - * Persistent storage based on the LocalStorage Web API for browsers. + * Persistent storage. By default, it uses the browser's LocalStorage API. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#storage} + * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#configuring-localstorage-cache-for-the-sdk} */ export function InLocalStorage(options?: SplitIO.InLocalStorageOptions): SplitIO.StorageSyncFactory; From daa23f0c583be1d4e78caaacf38eacb02ce8581a Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 16 Sep 2025 18:19:56 -0300 Subject: [PATCH 2/4] rc --- .github/workflows/ci-cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index eade2ac..3c6f17f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -46,7 +46,7 @@ jobs: run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build - name: Store assets - if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main') }} + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/inlocalstorage' || github.ref == 'refs/heads/main') }} uses: actions/upload-artifact@v4 with: name: assets @@ -57,7 +57,7 @@ jobs: name: Upload assets runs-on: ubuntu-latest needs: build - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/inlocalstorage' }} strategy: matrix: environment: From 51f86a24362cd55a73848f45f08da1b592628874 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 18 Sep 2025 14:10:19 -0300 Subject: [PATCH 3/4] stable version --- CHANGES.txt | 2 +- README.md | 36 ++++++++++++++++++------------------ package-lock.json | 18 +++++++++--------- package.json | 4 ++-- src/settings/defaults.ts | 2 +- types/full/index.d.ts | 16 ++++++++-------- types/index.d.ts | 16 ++++++++-------- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f7de0c1..e8fb97f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -1.4.0 (September 19, 2025) +1.4.0 (September 18, 2025) - Added the `wrapper` option to the SDK's `InLocalStorage` module, to allow passing a custom storage for persisting the SDK rollout plan. The default is `window.localStorage`. - Added the `initialRolloutPlan` configuration option for the SDK in standalone mode, to allow preloading the SDK storage with a snapshot of the rollout plan. - Updated @splitsoftware/splitio-commons package to version 2.6.0. diff --git a/README.md b/README.md index 9f4babf..9b9d618 100644 --- a/README.md +++ b/README.md @@ -61,24 +61,24 @@ To learn more about Split, contact hello@split.io, or get started with feature f Split has built and maintains SDKs for: -* .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK) -* Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK) -* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/6495326064397-Angular-utilities) -* Elixir thin-client [Github](https://github.com/splitio/elixir-thin-client) [Docs](https://help.split.io/hc/en-us/articles/26988707417869-Elixir-Thin-Client-SDK) -* Flutter [Github](https://github.com/splitio/flutter-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/8096158017165-Flutter-plugin) -* GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK) -* iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK) -* Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK) -* JavaScript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK) -* JavaScript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK) -* Node.js [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) -* PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK) -* PHP thin-client [Github](https://github.com/splitio/php-thin-client) [Docs](https://help.split.io/hc/en-us/articles/18305128673933-PHP-Thin-Client-SDK) -* Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) -* React [Github](https://github.com/splitio/react-client) [Docs](https://help.split.io/hc/en-us/articles/360038825091-React-SDK) -* React Native [Github](https://github.com/splitio/react-native-client) [Docs](https://help.split.io/hc/en-us/articles/4406066357901-React-Native-SDK) -* Redux [Github](https://github.com/splitio/redux-client) [Docs](https://help.split.io/hc/en-us/articles/360038851551-Redux-SDK) -* Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://help.split.io/hc/en-us/articles/360020673251-Ruby-SDK) +* .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/net-sdk/) +* Android [Github](https://github.com/splitio/android-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/android-sdk/) +* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/angular-utilities/) +* Elixir thin-client [Github](https://github.com/splitio/elixir-thin-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/elixir-thin-client-sdk/) +* Flutter [Github](https://github.com/splitio/flutter-sdk-plugin) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/flutter-plugin/) +* GO [Github](https://github.com/splitio/go-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/go-sdk/) +* iOS [Github](https://github.com/splitio/ios-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/ios-sdk/) +* Java [Github](https://github.com/splitio/java-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/java-sdk/) +* JavaScript [Github](https://github.com/splitio/javascript-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/) +* JavaScript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/) +* Node.js [Github](https://github.com/splitio/javascript-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/) +* PHP [Github](https://github.com/splitio/php-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/php-sdk/) +* PHP thin-client [Github](https://github.com/splitio/php-thin-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/php-thin-client-sdk/) +* Python [Github](https://github.com/splitio/python-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/python-sdk/) +* React [Github](https://github.com/splitio/react-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/react-sdk/) +* React Native [Github](https://github.com/splitio/react-native-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/react-native-sdk/) +* Redux [Github](https://github.com/splitio/redux-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/redux-sdk/) +* Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/ruby-sdk/) For a comprehensive list of open source projects visit our [Github page](https://github.com/splitio?utf8=%E2%9C%93&query=%20only%3Apublic%20). diff --git a/package-lock.json b/package-lock.json index b6d3787..457f921 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.3.2-rc.0", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-browserjs", - "version": "1.3.2-rc.0", + "version": "1.4.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.5.1-rc.0", + "@splitsoftware/splitio-commons": "2.6.0", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, @@ -1396,9 +1396,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.5.1-rc.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.5.1-rc.0.tgz", - "integrity": "sha512-AsVEWC8o3CYKSZ4zxAQI756YkOlgWu1jVFKAjxFViZ4snv9V5nDIr0ObiJkVXDg+FGg3W264tSx9bO3RTvbd1Q==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.6.0.tgz", + "integrity": "sha512-0xODXLciIvHSuMlb8eukIB2epb3ZyGOsrwS0cMuTdxEvCqr7Nuc9pWDdJtRuN1UwL/jIjBnpDYAc8s6mpqLX2g==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -10493,9 +10493,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.5.1-rc.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.5.1-rc.0.tgz", - "integrity": "sha512-AsVEWC8o3CYKSZ4zxAQI756YkOlgWu1jVFKAjxFViZ4snv9V5nDIr0ObiJkVXDg+FGg3W264tSx9bO3RTvbd1Q==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.6.0.tgz", + "integrity": "sha512-0xODXLciIvHSuMlb8eukIB2epb3ZyGOsrwS0cMuTdxEvCqr7Nuc9pWDdJtRuN1UwL/jIjBnpDYAc8s6mpqLX2g==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index a45b0f6..cfbb8dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-browserjs", - "version": "1.3.2-rc.0", + "version": "1.4.0", "description": "Split SDK for JavaScript on Browser", "main": "cjs/index.js", "module": "esm/index.js", @@ -59,7 +59,7 @@ "bugs": "https://github.com/splitio/javascript-browser-client/issues", "homepage": "https://github.com/splitio/javascript-browser-client#readme", "dependencies": { - "@splitsoftware/splitio-commons": "2.5.1-rc.0", + "@splitsoftware/splitio-commons": "2.6.0", "tslib": "^2.3.1", "unfetch": "^4.2.0" }, diff --git a/src/settings/defaults.ts b/src/settings/defaults.ts index de874c3..5f930ad 100644 --- a/src/settings/defaults.ts +++ b/src/settings/defaults.ts @@ -2,7 +2,7 @@ import type SplitIO from '@splitsoftware/splitio-commons/types/splitio'; import { LogLevels, isLogLevelString } from '@splitsoftware/splitio-commons/src/logger/index'; import { CONSENT_GRANTED } from '@splitsoftware/splitio-commons/src/utils/constants'; -const packageVersion = '1.3.2-rc.0'; +const packageVersion = '1.4.0'; /** * In browser, the default debug level, can be set via the `localStorage.splitio_debug` item. diff --git a/types/full/index.d.ts b/types/full/index.d.ts index e765b7f..b464fbd 100644 --- a/types/full/index.d.ts +++ b/types/full/index.d.ts @@ -10,10 +10,10 @@ declare module JsSdk { /** * Full version of the Split.io SDK factory function. * - * Unlike the default version, it includes a `fetch` polyfill to support old browsers @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#language-support}. + * Unlike the default version, it includes a `fetch` polyfill to support old browsers @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#language-support}. * * The settings parameter should be an object that complies with the SplitIO.IClientSideSettings or SplitIO.IClientSideAsyncSettings interfaces. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.IClientSideSettings): SplitIO.IBrowserSDK; export function SplitFactory(settings: SplitIO.IClientSideAsyncSettings): SplitIO.IBrowserAsyncSDK; @@ -21,42 +21,42 @@ declare module JsSdk { /** * Persistent storage based on the LocalStorage Web API for browsers. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#storage} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#storage} */ export function InLocalStorage(options?: SplitIO.InLocalStorageOptions): SplitIO.StorageSyncFactory; /** * Pluggable storage to use the SDK in consumer mode. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#sharing-state-with-a-pluggable-storage} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#sharing-state-with-a-pluggable-storage} */ export function PluggableStorage(options: SplitIO.PluggableStorageOptions): SplitIO.StorageAsyncFactory; /** * Creates a logger instance that enables descriptive log messages with DEBUG log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function DebugLogger(): SplitIO.ILogger; /** * Creates a logger instance that enables descriptive log messages with INFO log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function InfoLogger(): SplitIO.ILogger; /** * Creates a logger instance that enables descriptive log messages with WARN log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function WarnLogger(): SplitIO.ILogger; /** * Creates a logger instance that enables descriptive log messages with ERROR log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function ErrorLogger(): SplitIO.ILogger; } diff --git a/types/index.d.ts b/types/index.d.ts index a082927..1a44e1a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -11,50 +11,50 @@ declare module JsSdk { * Split.io SDK factory function. * * The settings parameter should be an object that complies with the SplitIO.IClientSideSettings or SplitIO.IClientSideAsyncSettings interfaces. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.IClientSideSettings): SplitIO.IBrowserSDK; export function SplitFactory(settings: SplitIO.IClientSideAsyncSettings): SplitIO.IBrowserAsyncSDK; /** - * Persistent storage. By default, it uses the browser's LocalStorage API. + * Persistent storage. By default, it uses the browser's LocalStorage API if available. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#configuring-localstorage-cache-for-the-sdk} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#configuring-persistent-cache-for-the-sdk} */ export function InLocalStorage(options?: SplitIO.InLocalStorageOptions): SplitIO.StorageSyncFactory; /** * Pluggable storage to use the SDK in consumer mode. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#sharing-state-with-a-pluggable-storage} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#sharing-state-with-a-pluggable-storage} */ export function PluggableStorage(options: SplitIO.PluggableStorageOptions): SplitIO.StorageAsyncFactory; /** * Creates a logger instance that enables descriptive log messages with DEBUG log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function DebugLogger(): SplitIO.ILogger; /** * Creates a logger instance that enables descriptive log messages with INFO log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function InfoLogger(): SplitIO.ILogger; /** * Creates a logger instance that enables descriptive log messages with WARN log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function WarnLogger(): SplitIO.ILogger; /** * Creates a logger instance that enables descriptive log messages with ERROR log level when passed in the factory settings. * - * @see {@link https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK#logging} + * @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/#logging} */ export function ErrorLogger(): SplitIO.ILogger; } From 3ab96b53d06206e13e20a44e379e6694af928e83 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 18 Sep 2025 14:34:21 -0300 Subject: [PATCH 4/4] rollback ci-cd --- .github/workflows/ci-cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3c6f17f..eade2ac 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -46,7 +46,7 @@ jobs: run: BUILD_BRANCH=$(echo "${GITHUB_REF#refs/heads/}") npm run build - name: Store assets - if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/inlocalstorage' || github.ref == 'refs/heads/main') }} + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/development' || github.ref == 'refs/heads/main') }} uses: actions/upload-artifact@v4 with: name: assets @@ -57,7 +57,7 @@ jobs: name: Upload assets runs-on: ubuntu-latest needs: build - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/inlocalstorage' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/development' }} strategy: matrix: environment: