Skip to content

Commit 1afa2f8

Browse files
Merge branch 'SDKS-9171_sdk_ready_from_cache' into release_v2.1.0
2 parents a2a1eff + d56ea52 commit 1afa2f8

File tree

24 files changed

+346
-146
lines changed

24 files changed

+346
-146
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
2.1.0 (January XX, 2025)
22
- Added `impressionsDisabled` property to SDK Manager's `SplitView` type.
3+
- Added two new configuration options for the SDK storage in browsers when using storage type `LOCALSTORAGE`:
4+
- `storage.expirationDays` to specify the validity period of the rollout cache.
5+
- `storage.clearOnInit` to clear the rollout cache on SDK initialization.
36
- Updated implementation of the impressions tracker and strategies to support feature flags with impressions tracking disabled.
7+
- Updated SDK_READY_FROM_CACHE event when using the `LOCALSTORAGE` storage type to be emitted alongside the SDK_READY event if it has not already been emitted.
48

59
2.0.3 (January 9, 2025)
610
- Bugfixing - Properly handle rejected promises when using targeting rules with segment matchers in consumer modes (e.g., Redis and Pluggable storages).

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-commons",
3-
"version": "2.0.3-rc.0",
3+
"version": "2.1.0-rc.1",
44
"description": "Split JavaScript SDK common components",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/readiness/__tests__/readinessManager.spec.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { EventEmitter } from '../../utils/MinEvents';
33
import { IReadinessManager } from '../types';
44
import { SDK_READY, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_READY_FROM_CACHE, SDK_SPLITS_CACHE_LOADED, SDK_READY_TIMED_OUT } from '../constants';
55
import { ISettings } from '../../types';
6+
import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
67

78
const settings = {
89
startup: {
910
readyTimeout: 0,
11+
},
12+
storage: {
13+
type: STORAGE_LOCALSTORAGE
1014
}
1115
} as unknown as ISettings;
1216

@@ -67,7 +71,14 @@ test('READINESS MANAGER / Ready event should be fired once', () => {
6771
const readinessManager = readinessManagerFactory(EventEmitter, settings);
6872
let counter = 0;
6973

74+
readinessManager.gate.on(SDK_READY_FROM_CACHE, () => {
75+
expect(readinessManager.isReadyFromCache()).toBe(true);
76+
expect(readinessManager.isReady()).toBe(true);
77+
counter++;
78+
});
79+
7080
readinessManager.gate.on(SDK_READY, () => {
81+
expect(readinessManager.isReadyFromCache()).toBe(true);
7182
expect(readinessManager.isReady()).toBe(true);
7283
counter++;
7384
});
@@ -79,7 +90,7 @@ test('READINESS MANAGER / Ready event should be fired once', () => {
7990
readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
8091
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
8192

82-
expect(counter).toBe(1); // should be called once
93+
expect(counter).toBe(2); // should be called once
8394
});
8495

8596
test('READINESS MANAGER / Ready from cache event should be fired once', (done) => {
@@ -88,6 +99,7 @@ test('READINESS MANAGER / Ready from cache event should be fired once', (done) =
8899

89100
readinessManager.gate.on(SDK_READY_FROM_CACHE, () => {
90101
expect(readinessManager.isReadyFromCache()).toBe(true);
102+
expect(readinessManager.isReady()).toBe(false);
91103
counter++;
92104
});
93105

src/readiness/readinessManager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ISettings } from '../types';
33
import SplitIO from '../../types/splitio';
44
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants';
55
import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, ISplitsEventEmitter } from './types';
6+
import { STORAGE_LOCALSTORAGE } from '../utils/constants';
67

78
function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): ISplitsEventEmitter {
89
const splitsEventEmitter = objectAssign(new EventEmitter(), {
@@ -114,6 +115,10 @@ export function readinessManagerFactory(
114115
isReady = true;
115116
try {
116117
syncLastUpdate();
118+
if (!isReadyFromCache && settings.storage?.type === STORAGE_LOCALSTORAGE) {
119+
isReadyFromCache = true;
120+
gate.emit(SDK_READY_FROM_CACHE);
121+
}
117122
gate.emit(SDK_READY);
118123
} catch (e) {
119124
// throws user callback exceptions in next tick

src/storages/AbstractSplitsCacheAsync.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,6 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
2727
return Promise.resolve(true);
2828
}
2929

30-
/**
31-
* Check if the splits information is already stored in cache.
32-
* Noop, just keeping the interface. This is used by client-side implementations only.
33-
*/
34-
checkCache(): Promise<boolean> {
35-
return Promise.resolve(false);
36-
}
37-
3830
/**
3931
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
4032
* Used for SPLIT_KILL push notifications.

src/storages/AbstractSplitsCacheSync.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
4747

4848
abstract clear(): void
4949

50-
/**
51-
* Check if the splits information is already stored in cache. This data can be preloaded.
52-
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
53-
*/
54-
checkCache(): boolean {
55-
return false;
56-
}
57-
5850
/**
5951
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
6052
* Used for SPLIT_KILL push notifications.

src/storages/KeyBuilderCS.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
4343
buildTillKey() {
4444
return `${this.prefix}.${this.matchingKey}.segments.till`;
4545
}
46+
47+
buildLastClear() {
48+
return `${this.prefix}.lastClear`;
49+
}
4650
}
4751

4852
export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string): MySegmentsKeyBuilder {

src/storages/dataLoader.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { PreloadedData } from '../types';
2-
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
32
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
43

4+
// This value might be eventually set via a config parameter
5+
const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
6+
57
/**
68
* Factory of client-side storage loader
79
*

src/storages/inLocalStorage/SplitsCacheInLocal.ts

Lines changed: 1 addition & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { KeyBuilderCS } from '../KeyBuilderCS';
55
import { ILogger } from '../../logger/types';
66
import { LOG_PREFIX } from './constants';
77
import { ISettings } from '../../types';
8-
import { getStorageHash } from '../KeyBuilder';
98
import { setToArray } from '../../utils/lang/sets';
109

1110
/**
@@ -15,21 +14,14 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
1514

1615
private readonly keys: KeyBuilderCS;
1716
private readonly log: ILogger;
18-
private readonly storageHash: string;
1917
private readonly flagSetsFilter: string[];
2018
private hasSync?: boolean;
21-
private updateNewFilter?: boolean;
2219

23-
constructor(settings: ISettings, keys: KeyBuilderCS, expirationTimestamp?: number) {
20+
constructor(settings: ISettings, keys: KeyBuilderCS) {
2421
super();
2522
this.keys = keys;
2623
this.log = settings.log;
27-
this.storageHash = getStorageHash(settings);
2824
this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
29-
30-
this._checkExpiration(expirationTimestamp);
31-
32-
this._checkFilterQuery();
3325
}
3426

3527
private _decrementCount(key: string) {
@@ -79,8 +71,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
7971
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
8072
*/
8173
clear() {
82-
this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
83-
8474
// collect item keys
8575
const len = localStorage.length;
8676
const accum = [];
@@ -138,19 +128,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
138128
}
139129

140130
setChangeNumber(changeNumber: number): boolean {
141-
142-
// when using a new split query, we must update it at the store
143-
if (this.updateNewFilter) {
144-
this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
145-
const storageHashKey = this.keys.buildHashKey();
146-
try {
147-
localStorage.setItem(storageHashKey, this.storageHash);
148-
} catch (e) {
149-
this.log.error(LOG_PREFIX + e);
150-
}
151-
this.updateNewFilter = false;
152-
}
153-
154131
try {
155132
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
156133
// update "last updated" timestamp with current time
@@ -212,48 +189,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
212189
}
213190
}
214191

215-
/**
216-
* Check if the splits information is already stored in browser LocalStorage.
217-
* In this function we could add more code to check if the data is valid.
218-
* @override
219-
*/
220-
checkCache(): boolean {
221-
return this.getChangeNumber() > -1;
222-
}
223-
224-
/**
225-
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
226-
*
227-
* @param expirationTimestamp - if the value is not a number, data will not be cleaned
228-
*/
229-
private _checkExpiration(expirationTimestamp?: number) {
230-
let value: string | number | null = localStorage.getItem(this.keys.buildLastUpdatedKey());
231-
if (value !== null) {
232-
value = parseInt(value, 10);
233-
if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp) this.clear();
234-
}
235-
}
236-
237-
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
238-
private _checkFilterQuery() {
239-
const storageHashKey = this.keys.buildHashKey();
240-
const storageHash = localStorage.getItem(storageHashKey);
241-
242-
if (storageHash !== this.storageHash) {
243-
try {
244-
// mark cache to update the new query filter on first successful splits fetch
245-
this.updateNewFilter = true;
246-
247-
// if there is cache, clear it
248-
if (this.checkCache()) this.clear();
249-
250-
} catch (e) {
251-
this.log.error(LOG_PREFIX + e);
252-
}
253-
}
254-
// if the filter didn't change, nothing is done
255-
}
256-
257192
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
258193
return flagSets.map(flagSet => {
259194
const flagSetKey = this.keys.buildFlagSetKey(flagSet);

0 commit comments

Comments
 (0)