diff --git a/CHANGES.txt b/CHANGES.txt index 84cebae03..e568f5b31 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +11.10.0 (January 28, 2026) + - Updated @splitsoftware/splitio-commons package to version 2.11.0, which: + - Added functionality to provide metadata alongside SDK update and READY events. Read more in our docs. + 11.9.1 (December 18, 2025) - Bugfix - Updated @splitsoftware/splitio-commons package to version 2.10.1 to handle `null` prerequisites properly diff --git a/package-lock.json b/package-lock.json index a185a39bb..4d97a38d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio", - "version": "11.9.1", + "version": "11.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.9.1", + "version": "11.10.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.10.1", + "@splitsoftware/splitio-commons": "2.11.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -352,9 +352,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.10.1.tgz", - "integrity": "sha512-yYmWC1stuH2DvxEXP9xZFOOvw3SkRDjE5UwZ68y011Oev9ILXSN7E2Yd6w8ag85cwbF5GjtaDgT7bOURhyvKww==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.11.0.tgz", + "integrity": "sha512-/cY9V2CHG2EnOAJp3vVWcs+ZqJ3zqEKHdKX115cK6zHKRMNDXODuPQSX7CIkuCLr6C0kQMQuBnXwcaf5C+cO1A==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -443,7 +443,8 @@ "node_modules/@types/node": { "version": "18.11.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", - "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" + "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==", + "peer": true }, "node_modules/@types/node-fetch": { "version": "2.6.10", @@ -682,6 +683,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -730,6 +732,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1271,6 +1274,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001538", "electron-to-chromium": "^1.4.526", @@ -2442,6 +2446,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3645,6 +3650,7 @@ "version": "4.28.5", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz", "integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==", + "peer": true, "dependencies": { "cluster-key-slot": "^1.1.0", "debug": "^4.3.1", @@ -6837,10 +6843,11 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -6980,6 +6987,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7229,6 +7237,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -7275,6 +7284,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", @@ -7831,9 +7841,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.10.1.tgz", - "integrity": "sha512-yYmWC1stuH2DvxEXP9xZFOOvw3SkRDjE5UwZ68y011Oev9ILXSN7E2Yd6w8ag85cwbF5GjtaDgT7bOURhyvKww==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.11.0.tgz", + "integrity": "sha512-/cY9V2CHG2EnOAJp3vVWcs+ZqJ3zqEKHdKX115cK6zHKRMNDXODuPQSX7CIkuCLr6C0kQMQuBnXwcaf5C+cO1A==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" @@ -7913,7 +7923,8 @@ "@types/node": { "version": "18.11.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", - "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" + "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==", + "peer": true }, "@types/node-fetch": { "version": "2.6.10", @@ -8135,7 +8146,8 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true + "dev": true, + "peer": true }, "acorn-import-attributes": { "version": "1.9.5", @@ -8168,6 +8180,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8612,6 +8625,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz", "integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001538", "electron-to-chromium": "^1.4.526", @@ -9513,6 +9527,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -10413,6 +10428,7 @@ "version": "4.28.5", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz", "integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==", + "peer": true, "requires": { "cluster-key-slot": "^1.1.0", "debug": "^4.3.1", @@ -12818,9 +12834,9 @@ } }, "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", @@ -12918,7 +12934,8 @@ "version": "4.4.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", - "dev": true + "dev": true, + "peer": true }, "ua-parser-js": { "version": "0.7.33", @@ -13090,6 +13107,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "requires": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -13139,6 +13157,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/package.json b/package.json index e5dec9956..c78b1b739 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.9.1", + "version": "11.10.0", "description": "Split SDK", "files": [ "README.md", @@ -38,7 +38,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.10.1", + "@splitsoftware/splitio-commons": "2.11.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", diff --git a/src/__tests__/browserSuites/push-corner-cases.spec.js b/src/__tests__/browserSuites/push-corner-cases.spec.js index 37edd1587..d953d8129 100644 --- a/src/__tests__/browserSuites/push-corner-cases.spec.js +++ b/src/__tests__/browserSuites/push-corner-cases.spec.js @@ -97,7 +97,7 @@ export function testSplitKillOnReadyFromCache(fetchMock, assert) { }); client.on(client.Event.SDK_READY, () => { const lapse = Date.now() - start; - assert.true(nearlyEqual(lapse, MILLIS_SPLIT_CHANGES_RESPONSE), 'SDK_READY once split changes arrives'); + assert.true(nearlyEqual(lapse, MILLIS_SPLIT_CHANGES_RESPONSE, 200), 'SDK_READY once split changes arrives'); client.destroy().then(() => { assert.end(); }); }); diff --git a/src/__tests__/browserSuites/push-synchronization.spec.js b/src/__tests__/browserSuites/push-synchronization.spec.js index d9db7ff79..b0a91567a 100644 --- a/src/__tests__/browserSuites/push-synchronization.spec.js +++ b/src/__tests__/browserSuites/push-synchronization.spec.js @@ -91,7 +91,7 @@ const EXPECTED_DELAY_AND_BACKOFF = 241 + 100; export function testSynchronization(fetchMock, assert) { // Force the backoff base of UpdateWorkers to reduce test time Backoff.__TEST__BASE_MILLIS = 100; - assert.plan(34); + assert.plan(39); // +3 for FLAGS_UPDATE metadata, +2 for SEGMENTS_UPDATE metadata fetchMock.reset(); let start, splitio, client, otherClient, keylistAddClient, keylistRemoveClient, bitmapTrueClient, sharedClients = []; @@ -108,7 +108,10 @@ export function testSynchronization(fetchMock, assert) { setTimeout(() => { assert.equal(client.getTreatment('whitelist'), 'not_allowed', 'evaluation of initial Split'); - client.once(client.Event.SDK_UPDATE, () => { + client.once(client.Event.SDK_UPDATE, (metadata) => { + assert.equal(metadata.type, 'FLAGS_UPDATE', 'SDK_UPDATE for SPLIT_UPDATE should have type FLAGS_UPDATE'); + assert.true(Array.isArray(metadata.names), 'metadata.names should be an array'); + assert.true(metadata.names.includes('whitelist'), 'metadata.names should include the updated whitelist split'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_FIRST_SPLIT_UPDATE_EVENT), 'SDK_UPDATE due to SPLIT_UPDATE event'); assert.equal(client.getTreatment('whitelist'), 'allowed', 'evaluation of updated Split'); @@ -202,7 +205,9 @@ export function testSynchronization(fetchMock, assert) { const timestampUnboundEvent = Date.now(); - client.once(client.Event.SDK_UPDATE, () => { + client.once(client.Event.SDK_UPDATE, (metadata) => { + assert.equal(metadata.type, 'SEGMENTS_UPDATE', 'SDK_UPDATE for MEMBERSHIPS_LS_UPDATE should have type SEGMENTS_UPDATE'); + assert.true(Array.isArray(metadata.names), 'metadata.names should be an array'); assert.true(nearlyEqual(Date.now() - timestampUnboundEvent, EXPECTED_DELAY_AND_BACKOFF), 'SDK_UPDATE after fetching memberships with a delay'); assert.equal(client.getTreatment('in_large_segment'), 'yes', 'evaluation after myLargeSegment fetch'); }); diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index bacbf2cbb..5a90c0b48 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -179,7 +179,7 @@ export default function (fetchMock, assert) { events: 'https://events.baseurl/readyFromCacheWithData' }; localStorage.clear(); - t.plan(12 * 2 + 3); + t.plan(12 * 2 + 3 + 2); // +2 for SDK_READY and SDK_READY_FROM_CACHE metadata.initialCacheLoad asserts fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=25&rbSince=-1', () => { 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. @@ -225,7 +225,8 @@ export default function (fetchMock, assert) { t.end(); }); - client.on(client.Event.SDK_READY_FROM_CACHE, () => { + client.on(client.Event.SDK_READY_FROM_CACHE, (metadata) => { + t.false(metadata.initialCacheLoad, 'SDK_READY_FROM_CACHE from cache should have initialCacheLoad false'); 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'); }); @@ -238,7 +239,8 @@ export default function (fetchMock, assert) { 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, () => { + client.on(client.Event.SDK_READY, (metadata) => { + t.false(metadata.initialCacheLoad, 'SDK_READY when from cache first should have initialCacheLoad false'); 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.'); }); @@ -371,8 +373,8 @@ export default function (fetchMock, assert) { 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.whenReadyFromCache().then((isReady) => { - t.false(isReady, 'It should be ready from cache before ready (syncing with the cloud).'); + client.whenReadyFromCache().then((metadata) => { + t.false(metadata.initialCacheLoad, 'It should be ready from cache before ready (syncing with the cloud).'); t.true(Date.now() - startTime < 50, 'It should resolve ready from cache promise almost immediately.'); }); client.whenReady().then(() => { @@ -383,8 +385,8 @@ export default function (fetchMock, assert) { 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.whenReadyFromCache().then((isReady) => { - t.false(isReady, 'It should be ready from cache before ready (syncing with the cloud).'); + client2.whenReadyFromCache().then((metadata) => { + t.false(metadata.initialCacheLoad, 'It should be ready from cache before ready (syncing with the cloud).'); }); client2.whenReady().then(() => { t.true(Date.now() - startTime >= 700, 'It should resolve ready promise after syncing with the cloud.'); @@ -474,7 +476,9 @@ export default function (fetchMock, assert) { t.end(); }); - client.once(client.Event.SDK_READY_FROM_CACHE, () => { + client.once(client.Event.SDK_READY_FROM_CACHE, (metadata) => { + t.true(metadata.initialCacheLoad, 'SDK_READY_FROM_CACHE on fresh install should have initialCacheLoad true'); + t.equal(metadata.lastUpdateTimestamp, undefined, 'lastUpdateTimestamp should be undefined when initialCacheLoad is true'); 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, () => { @@ -484,12 +488,13 @@ export default function (fetchMock, assert) { 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, () => { + client.on(client.Event.SDK_READY, (metadata) => { + t.true(metadata.initialCacheLoad, 'SDK_READY on fresh install should have initialCacheLoad true'); 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.whenReadyFromCache().then((isReady) => { - t.true(isReady, 'It should be ready from cache and ready.'); + client.whenReadyFromCache().then((metadata) => { + t.true(metadata.initialCacheLoad, 'It should be ready from cache and ready.'); t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should resolve ready from cache promise after syncing with the cloud.'); }); client.whenReady().then(() => { diff --git a/src/__tests__/browserSuites/ready-promise.spec.js b/src/__tests__/browserSuites/ready-promise.spec.js index f632a177c..abd2dcd0d 100644 --- a/src/__tests__/browserSuites/ready-promise.spec.js +++ b/src/__tests__/browserSuites/ready-promise.spec.js @@ -521,7 +521,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { consoleSpy.log.resetHistory(); setTimeout(() => { - client.whenReadyFromCache().then((isReady) => t.true(isReady, 'SDK IS READY (& READY FROM CACHE) - Should resolve')).catch(() => t.fail('SDK TIMED OUT - Should not reject')); + client.whenReadyFromCache().then((metadata) => t.true(metadata.initialCacheLoad, 'SDK IS READY (& READY FROM CACHE) - Should resolve')).catch(() => t.fail('SDK TIMED OUT - Should not reject')); assertGetTreatmentWhenReady(t, client); diff --git a/src/__tests__/nodeSuites/push-synchronization.spec.js b/src/__tests__/nodeSuites/push-synchronization.spec.js index a97ad5937..fee427fe5 100644 --- a/src/__tests__/nodeSuites/push-synchronization.spec.js +++ b/src/__tests__/nodeSuites/push-synchronization.spec.js @@ -92,7 +92,7 @@ const MILLIS_DESTROY = 1700; * 1.6 secs: RB_SEGMENT_UPDATE IFFU event with ZLib compression */ export function testSynchronization(fetchMock, assert) { - assert.plan(53); + assert.plan(56); // +3 for SDK_UPDATE metadata (type, names array, names includes whitelist) fetchMock.reset(); __setEventSource(EventSourceMock); @@ -114,7 +114,10 @@ export function testSynchronization(fetchMock, assert) { }, MILLIS_SSE_OPEN); // open SSE connection after 0.1 seconds setTimeout(() => { assert.equal(client.getTreatment(key, 'whitelist'), 'not_allowed', 'evaluation of initial Split'); - client.once(client.Event.SDK_UPDATE, () => { + client.once(client.Event.SDK_UPDATE, (metadata) => { + assert.equal(metadata.type, 'FLAGS_UPDATE', 'SDK_UPDATE for SPLIT_UPDATE should have type FLAGS_UPDATE'); + assert.true(Array.isArray(metadata.names), 'metadata.names should be an array'); + assert.true(metadata.names.includes('whitelist'), 'metadata.names should include the updated whitelist split'); const lapse = Date.now() - start; assert.true(nearlyEqual(lapse, MILLIS_FIRST_SPLIT_UPDATE_EVENT), 'SDK_UPDATE due to SPLIT_UPDATE event'); assert.equal(client.getTreatment(key, 'whitelist'), 'allowed', 'evaluation of updated Split'); diff --git a/src/__tests__/nodeSuites/ready-promise.spec.js b/src/__tests__/nodeSuites/ready-promise.spec.js index c62c060fe..bdcfd2737 100644 --- a/src/__tests__/nodeSuites/ready-promise.spec.js +++ b/src/__tests__/nodeSuites/ready-promise.spec.js @@ -494,7 +494,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { consoleSpy.log.resetHistory(); setTimeout(() => { - client.whenReadyFromCache().then((isReady) => t.true(isReady, 'SDK IS READY (& READY FROM CACHE) - Should resolve')).catch(() => t.fail('SDK TIMED OUT - Should not reject')); + client.whenReadyFromCache().then((metadata) => t.true(metadata.initialCacheLoad, 'SDK IS READY (& READY FROM CACHE) - Should resolve')).catch(() => t.fail('SDK TIMED OUT - Should not reject')); assertGetTreatmentWhenReady(t, client, key); diff --git a/src/__tests__/offline/browser.spec.js b/src/__tests__/offline/browser.spec.js index 8edd7d50c..8b5436c2d 100644 --- a/src/__tests__/offline/browser.spec.js +++ b/src/__tests__/offline/browser.spec.js @@ -105,7 +105,9 @@ tape('Browser offline mode', function (assert) { assert.equal(client.getTreatment('testing_split_with_config'), 'off'); readyCount++; }); - client.on(client.Event.SDK_UPDATE, () => { + client.on(client.Event.SDK_UPDATE, (metadata) => { + assert.equal(metadata.type, 'FLAGS_UPDATE', 'SDK_UPDATE for localhost features update should have type FLAGS_UPDATE'); + assert.true(Array.isArray(metadata.names), 'metadata.names should be an array'); assert.deepEqual(manager.names().sort(), ['testing_split', 'testing_split_2', 'testing_split_3', 'testing_split_with_config']); assert.equal(client.getTreatment('testing_split_with_config'), 'nope'); updateCount++; diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index cbc6b4aa3..167ab9a33 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.9.1'; +export const packageVersion = '11.10.0'; diff --git a/ts-tests/index.ts b/ts-tests/index.ts index e807fc913..fa2f02e0c 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -258,12 +258,12 @@ let nodeEventEmitter: NodeJS.EventEmitter = client; // Ready, destroy and flush let promise: Promise = client.ready(); -promise = client.whenReady(); promise = client.destroy(); promise = SDK.destroy(); // @TODO not public yet // promise = client.flush(); -let promiseWhenReadyFromCache: Promise = client.whenReadyFromCache(); +let promiseWithMetadata: Promise = client.whenReady(); +promiseWithMetadata = client.whenReadyFromCache(); // Get readiness status let status: SplitIO.ReadinessStatus = client.getStatus(); @@ -358,6 +358,62 @@ tracked = browserClient.track('myTrafficType', 'myEventType', 10); // Properties parameter is optional on all signatures. tracked = client.track(splitKey, 'myTrafficType', 'myEventType', 10, { prop1: 1, prop2: '2', prop3: false, prop4: null }); tracked = browserClient.track('myTrafficType', 'myEventType', undefined, { prop1: 1, prop2: '2', prop3: false, prop4: null }); +/*** Tests for SDK Update Metadata ***/ + +// Using addListener with typed metadata +client.addListener(client.Event.SDK_UPDATE, (metadata: SplitIO.SdkUpdateMetadata) => { + const type: SplitIO.SdkUpdateMetadataType = metadata.type; + const names: string[] = metadata.names; +}); +client.addListener(client.Event.SDK_READY, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +client.addListener(client.Event.SDK_READY_FROM_CACHE, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +client.addListener(client.Event.SDK_UPDATE, () => { }); +client.addListener(client.Event.SDK_READY, () => { }); +client.addListener(client.Event.SDK_READY_FROM_CACHE, () => { }); + +// Using once with typed metadata +client.once(client.Event.SDK_UPDATE, (metadata: SplitIO.SdkUpdateMetadata) => { + const type: SplitIO.SdkUpdateMetadataType = metadata.type; + const names: string[] = metadata.names; +}); +client.once(client.Event.SDK_READY, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +client.once(client.Event.SDK_READY_FROM_CACHE, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +client.once(client.Event.SDK_UPDATE, () => { }); +client.once(client.Event.SDK_READY, () => { }); +client.once(client.Event.SDK_READY_FROM_CACHE, () => { }); + +// SDK_READY event listener with metadata +client.on(client.Event.SDK_READY, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); + +// SDK_READY_FROM_CACHE event listener with metadata +client.on(client.Event.SDK_READY_FROM_CACHE, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); + +// SDK_UPDATE event listener with metadata +client.on(client.Event.SDK_UPDATE, (metadata: SplitIO.SdkUpdateMetadata) => { + const type: SplitIO.SdkUpdateMetadataType = metadata.type; + const names: string[] = metadata.names; +}); +client.on(client.Event.SDK_UPDATE, () => { }); +client.on(client.Event.SDK_READY, () => { }); +client.on(client.Event.SDK_READY_FROM_CACHE, () => { }); /*** Repeating tests for Async Client ***/ @@ -378,12 +434,12 @@ nodeEventEmitter = asyncClient; // Ready, destroy and flush (same as for sync client, just for interface checking) promise = asyncClient.ready(); -promise = asyncClient.whenReady(); promise = asyncClient.destroy(); promise = AsyncSDK.destroy(); // @TODO not public yet // promise = asyncClient.flush(); -promiseWhenReadyFromCache = asyncClient.whenReadyFromCache(); +promiseWithMetadata = asyncClient.whenReady(); +promiseWithMetadata = asyncClient.whenReadyFromCache(); // Get readiness status status = asyncClient.getStatus(); @@ -443,6 +499,63 @@ trackPromise = asyncClient.track(splitKey, 'myTrafficType', 'myEventType', 10); // Properties parameter is optional trackPromise = asyncClient.track(splitKey, 'myTrafficType', 'myEventType', 10, { prop1: 1, prop2: '2', prop3: true, prop4: null }); +/*** Tests for SDK Update Metadata ***/ + +// Using addListener with typed metadata +asyncClient.addListener(asyncClient.Event.SDK_UPDATE, (metadata: SplitIO.SdkUpdateMetadata) => { + const type: SplitIO.SdkUpdateMetadataType = metadata.type; + const names: string[] = metadata.names; +}); +asyncClient.addListener(asyncClient.Event.SDK_READY, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +asyncClient.addListener(asyncClient.Event.SDK_READY_FROM_CACHE, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +asyncClient.addListener(asyncClient.Event.SDK_UPDATE, () => { }); +asyncClient.addListener(asyncClient.Event.SDK_READY, () => { }); +asyncClient.addListener(asyncClient.Event.SDK_READY_FROM_CACHE, () => { }); + +// Using once with typed metadata +asyncClient.once(asyncClient.Event.SDK_UPDATE, (metadata: SplitIO.SdkUpdateMetadata) => { + const type: SplitIO.SdkUpdateMetadataType = metadata.type; + const names: string[] = metadata.names; +}); +asyncClient.once(asyncClient.Event.SDK_READY, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +asyncClient.once(asyncClient.Event.SDK_READY_FROM_CACHE, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); +asyncClient.once(asyncClient.Event.SDK_UPDATE, () => { }); +asyncClient.once(asyncClient.Event.SDK_READY, () => { }); +asyncClient.once(asyncClient.Event.SDK_READY_FROM_CACHE, () => { }); + +// SDK_READY event listener with metadata +asyncClient.on(asyncClient.Event.SDK_READY, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); + +// SDK_READY_FROM_CACHE event listener with metadata +asyncClient.on(asyncClient.Event.SDK_READY_FROM_CACHE, (metadata: SplitIO.SdkReadyMetadata) => { + const fromCache: boolean = metadata.initialCacheLoad; + const timestamp: number = metadata.lastUpdateTimestamp; +}); + +// SDK_UPDATE event listener with metadata +asyncClient.on(asyncClient.Event.SDK_UPDATE, (metadata: SplitIO.SdkUpdateMetadata) => { + const type: SplitIO.SdkUpdateMetadataType = metadata.type; + const names: string[] = metadata.names; +}); +asyncClient.on(asyncClient.Event.SDK_UPDATE, () => { }); +asyncClient.on(asyncClient.Event.SDK_READY, () => { }); +asyncClient.on(asyncClient.Event.SDK_READY_FROM_CACHE, () => { }); + /**** Tests for IManager interface ****/ splitNames = manager.names();