diff --git a/CHANGES.txt b/CHANGES.txt index 1cfb08c8..4a335cf8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.0.1 (November 25, 2024) + - Bugfixing - Fixed an issue with the SDK_UPDATE event on server-side, where it was not being emitted if there was an empty segment and the SDK received a feature flag update notification. + 2.0.0 (November 1, 2024) - Added support for targeting rules based on large segments. - Added `factory.destroy()` method, which invokes the `destroy` method on all SDK clients created by the factory. diff --git a/package-lock.json b/package-lock.json index 40fecca7..65555d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.0.0", + "version": "2.0.1", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -2531,9 +2531,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -10011,9 +10011,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", diff --git a/package.json b/package.json index 608c3982..fdf8c86d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.0.0", + "version": "2.0.1", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/sdkClient/sdkClient.ts b/src/sdkClient/sdkClient.ts index fbc4aeb9..cda5ba2e 100644 --- a/src/sdkClient/sdkClient.ts +++ b/src/sdkClient/sdkClient.ts @@ -56,22 +56,18 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo // Mark the SDK as destroyed immediately sdkReadinessManager.readinessManager.destroy(); - // For main client, release the SDK Key and record stat before flushing data + // For main client, cleanup the SDK Key, listeners and scheduled jobs, and record stat before flushing data if (!isSharedClient) { releaseApiKey(settings.core.authorizationKey); telemetryTracker.sessionLength(); + signalListener && signalListener.stop(); + uniqueKeysTracker && uniqueKeysTracker.stop(); } // Stop background jobs syncManager && syncManager.stop(); return __flush().then(() => { - // For main client, cleanup event listeners and scheduled jobs - if (!isSharedClient) { - signalListener && signalListener.stop(); - uniqueKeysTracker && uniqueKeysTracker.stop(); - } - // Cleanup storage return storage.destroy(); }); diff --git a/src/storages/AbstractMySegmentsCacheSync.ts b/src/storages/AbstractMySegmentsCacheSync.ts index a03fc416..7d3dc304 100644 --- a/src/storages/AbstractMySegmentsCacheSync.ts +++ b/src/storages/AbstractMySegmentsCacheSync.ts @@ -42,7 +42,7 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync // @TODO for client-side it should be the number of clients, but it requires a refactor of MySegments caches to simplify the code. abstract getKeysCount(): number - abstract getChangeNumber(name: string): number + abstract getChangeNumber(): number /** * For server-side synchronizer: the method is not used. diff --git a/src/storages/inMemory/SegmentsCacheInMemory.ts b/src/storages/inMemory/SegmentsCacheInMemory.ts index 87ca71ce..d25b89d2 100644 --- a/src/storages/inMemory/SegmentsCacheInMemory.ts +++ b/src/storages/inMemory/SegmentsCacheInMemory.ts @@ -65,7 +65,7 @@ export class SegmentsCacheInMemory implements ISegmentsCacheSync { getChangeNumber(name: string) { const value = this.segmentChangeNumber[name]; - return isIntegerNumber(value) ? value : -1; + return isIntegerNumber(value) ? value : undefined; } // No-op. Not used in server-side diff --git a/src/storages/inRedis/SegmentsCacheInRedis.ts b/src/storages/inRedis/SegmentsCacheInRedis.ts index 42ed3b10..d645495f 100644 --- a/src/storages/inRedis/SegmentsCacheInRedis.ts +++ b/src/storages/inRedis/SegmentsCacheInRedis.ts @@ -44,10 +44,10 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync { return this.redis.get(this.keys.buildSegmentTillKey(name)).then((value: string | null) => { const i = parseInt(value as string, 10); - return isNaNNumber(i) ? -1 : i; + return isNaNNumber(i) ? undefined : i; }).catch((e) => { this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e); - return -1; + return undefined; }); } diff --git a/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts index 62799bab..f31d2c91 100644 --- a/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts @@ -17,7 +17,7 @@ describe('SEGMENTS CACHE IN REDIS', () => { expect(await cache.getChangeNumber('mocked-segment') === 1).toBe(true); - expect(await cache.getChangeNumber('inexistent-segment')).toBe(-1); // -1 if the segment doesn't exist + expect(await cache.getChangeNumber('inexistent-segment')).toBe(undefined); // -1 if the segment doesn't exist await cache.update('mocked-segment', ['d', 'e'], [], 2); diff --git a/src/storages/pluggable/SegmentsCachePluggable.ts b/src/storages/pluggable/SegmentsCachePluggable.ts index 033b1a49..145953d2 100644 --- a/src/storages/pluggable/SegmentsCachePluggable.ts +++ b/src/storages/pluggable/SegmentsCachePluggable.ts @@ -55,10 +55,10 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync { return this.wrapper.get(this.keys.buildSegmentTillKey(name)).then((value: string | null) => { const i = parseInt(value as string, 10); - return isNaNNumber(i) ? -1 : i; + return isNaNNumber(i) ? undefined : i; }).catch((e) => { this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from segments storage. Error: ' + e); - return -1; + return undefined; }); } diff --git a/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts b/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts index eedb8f11..459f59eb 100644 --- a/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts +++ b/src/storages/pluggable/__tests__/SegmentsCachePluggable.spec.ts @@ -20,7 +20,7 @@ describe('SEGMENTS CACHE PLUGGABLE', () => { expect(await cache.getChangeNumber('mocked-segment') === 1).toBe(true); - expect(await cache.getChangeNumber('inexistent-segment')).toBe(-1); // -1 if the segment doesn't exist + expect(await cache.getChangeNumber('inexistent-segment')).toBe(undefined); // -1 if the segment doesn't exist await cache.update('mocked-segment', ['d', 'e'], [], 2); diff --git a/src/storages/types.ts b/src/storages/types.ts index 83f388b1..5d7b40b6 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -237,7 +237,7 @@ export interface ISegmentsCacheBase { isInSegment(name: string, key?: string): MaybeThenable // different signature on Server and Client-Side registerSegments(names: string[]): MaybeThenable // only for Server-Side getRegisteredSegments(): MaybeThenable // only for Server-Side - getChangeNumber(name: string): MaybeThenable // only for Server-Side + getChangeNumber(name: string): MaybeThenable // only for Server-Side update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): MaybeThenable // only for Server-Side clear(): MaybeThenable } @@ -248,7 +248,7 @@ export interface ISegmentsCacheSync extends ISegmentsCacheBase { registerSegments(names: string[]): boolean getRegisteredSegments(): string[] getKeysCount(): number // only used for telemetry - getChangeNumber(name?: string): number + getChangeNumber(name?: string): number | undefined update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): boolean // only for Server-Side resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean // only for Sync Client-Side clear(): void @@ -258,7 +258,7 @@ export interface ISegmentsCacheAsync extends ISegmentsCacheBase { isInSegment(name: string, key: string): Promise registerSegments(names: string[]): Promise getRegisteredSegments(): Promise - getChangeNumber(name: string): Promise + getChangeNumber(name: string): Promise update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): Promise clear(): Promise } diff --git a/src/sync/polling/updaters/segmentChangesUpdater.ts b/src/sync/polling/updaters/segmentChangesUpdater.ts index 5f9db114..c1009077 100644 --- a/src/sync/polling/updaters/segmentChangesUpdater.ts +++ b/src/sync/polling/updaters/segmentChangesUpdater.ts @@ -33,9 +33,9 @@ export function segmentChangesUpdaterFactory( return sincePromise.then(since => { // if fetchOnlyNew flag, avoid processing already fetched segments - return fetchOnlyNew && since !== -1 ? + return fetchOnlyNew && since !== undefined ? false : - segmentChangesFetcher(since, segmentName, noCache, till).then((changes) => { + segmentChangesFetcher(since || -1, segmentName, noCache, till).then((changes) => { return Promise.all(changes.map(x => { log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`); return segments.update(segmentName, x.added, x.removed, x.till); diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index bf8803a2..e8153987 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -19,7 +19,7 @@ function checkAllSegmentsExist(segments: ISegmentsCacheBase): Promise { let registeredSegments = Promise.resolve(segments.getRegisteredSegments()); return registeredSegments.then(segmentNames => { return Promise.all(segmentNames.map(segmentName => segments.getChangeNumber(segmentName))) - .then(changeNumbers => changeNumbers.every(changeNumber => changeNumber !== -1)); + .then(changeNumbers => changeNumbers.every(changeNumber => changeNumber !== undefined)); }); } diff --git a/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts b/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts index bafdd37d..52cccb22 100644 --- a/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +++ b/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts @@ -3,10 +3,11 @@ import { Backoff } from '../../../utils/Backoff'; import { IUpdateWorker } from './types'; import { ITelemetryTracker } from '../../../trackers/types'; import { MEMBERSHIPS } from '../../../utils/constants'; -import { ISegmentsCacheSync, IStorageSync } from '../../../storages/types'; +import { IStorageSync } from '../../../storages/types'; import { ILogger } from '../../../logger/types'; import { FETCH_BACKOFF_MAX_RETRIES } from './constants'; import { MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../constants'; +import { AbstractMySegmentsCacheSync } from '../../../storages/AbstractMySegmentsCacheSync'; /** * MySegmentsUpdateWorker factory @@ -16,7 +17,7 @@ export function MySegmentsUpdateWorker(log: ILogger, storage: Pick segmentsCache.getChangeNumber(segment)) { + if (maxChangeNumber > (segmentsCache.getChangeNumber(segment) || -1)) { handleNewEvent = false; // fetch segments revalidating data if cached @@ -32,7 +32,7 @@ export function SegmentsUpdateWorker(log: ILogger, segmentsSyncTask: ISegmentsSy } else { const attempts = backoff.attempts + 1; - if (maxChangeNumber <= segmentsCache.getChangeNumber(segment)) { + if (maxChangeNumber <= (segmentsCache.getChangeNumber(segment) || -1)) { log.debug(`Refresh completed${cdnBypass ? ' bypassing the CDN' : ''} in ${attempts} attempts.`); isHandlingEvent = false; return; @@ -60,7 +60,7 @@ export function SegmentsUpdateWorker(log: ILogger, segmentsSyncTask: ISegmentsSy return { put(changeNumber: number) { - const currentChangeNumber = segmentsCache.getChangeNumber(segment); + const currentChangeNumber = segmentsCache.getChangeNumber(segment) || -1; if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return; diff --git a/src/sync/syncTask.ts b/src/sync/syncTask.ts index 5f22f6c0..7820c060 100644 --- a/src/sync/syncTask.ts +++ b/src/sync/syncTask.ts @@ -68,8 +68,8 @@ export function syncTaskFactory(log: ILogger, }, stop() { - running = false; - if (timeoutID) { + if (running) { + running = false; log.debug(SYNC_TASK_STOP, [taskName]); clearTimeout(timeoutID); timeoutID = undefined;