@splitsoftware/splitio-commons 2.4.2-rc.3 → 2.5.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +3 -11
- package/cjs/evaluator/convertions/index.js +9 -1
- package/cjs/evaluator/matchersTransform/index.js +2 -3
- package/cjs/sdkClient/sdkClientMethodCS.js +5 -1
- package/cjs/sdkFactory/index.js +8 -2
- package/cjs/storages/getRolloutPlan.js +69 -0
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +21 -17
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/cjs/storages/inLocalStorage/index.js +13 -31
- package/cjs/storages/inLocalStorage/validateCache.js +25 -30
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/cjs/storages/setRolloutPlan.js +66 -0
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +0 -2
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +0 -2
- package/cjs/sync/syncManagerOnline.js +24 -28
- package/cjs/utils/env/isLocalStorageAvailable.js +5 -28
- package/cjs/utils/inputValidation/index.js +1 -3
- package/cjs/utils/settingsValidation/index.js +4 -0
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/evaluator/convertions/index.js +7 -0
- package/esm/evaluator/matchersTransform/index.js +3 -4
- package/esm/sdkClient/sdkClientMethodCS.js +5 -1
- package/esm/sdkFactory/index.js +8 -2
- package/esm/storages/getRolloutPlan.js +65 -0
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +21 -17
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/esm/storages/inLocalStorage/index.js +14 -32
- package/esm/storages/inLocalStorage/validateCache.js +25 -30
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/esm/storages/setRolloutPlan.js +61 -0
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +0 -2
- package/esm/sync/polling/updaters/splitChangesUpdater.js +0 -2
- package/esm/sync/syncManagerOnline.js +24 -28
- package/esm/utils/env/isLocalStorageAvailable.js +3 -24
- package/esm/utils/inputValidation/index.js +0 -1
- package/esm/utils/settingsValidation/index.js +4 -0
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/evaluator/convertions/index.ts +10 -0
- package/src/evaluator/matchersTransform/index.ts +3 -4
- package/src/sdkClient/sdkClientMethodCS.ts +7 -1
- package/src/sdkFactory/index.ts +12 -4
- package/src/storages/getRolloutPlan.ts +72 -0
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +17 -18
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +22 -19
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +37 -34
- package/src/storages/inLocalStorage/index.ts +16 -37
- package/src/storages/inLocalStorage/validateCache.ts +25 -31
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -0
- package/src/storages/setRolloutPlan.ts +71 -0
- package/src/storages/types.ts +25 -23
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +2 -1
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +0 -2
- package/src/sync/polling/updaters/splitChangesUpdater.ts +1 -3
- package/src/sync/syncManagerOnline.ts +22 -27
- package/src/types.ts +2 -35
- package/src/utils/env/isLocalStorageAvailable.ts +3 -24
- package/src/utils/inputValidation/index.ts +0 -1
- package/src/utils/settingsValidation/index.ts +4 -0
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +36 -44
- package/cjs/storages/dataLoader.js +0 -50
- package/cjs/storages/inLocalStorage/storageAdapter.js +0 -54
- package/cjs/utils/inputValidation/preloadedData.js +0 -59
- package/esm/storages/dataLoader.js +0 -46
- package/esm/storages/inLocalStorage/storageAdapter.js +0 -50
- package/esm/utils/inputValidation/preloadedData.js +0 -55
- package/src/storages/dataLoader.ts +0 -55
- package/src/storages/inLocalStorage/storageAdapter.ts +0 -62
- package/src/utils/inputValidation/preloadedData.ts +0 -57
|
@@ -89,41 +89,36 @@ export function syncManagerOnlineFactory(
|
|
|
89
89
|
start() {
|
|
90
90
|
running = true;
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!running) return;
|
|
97
|
-
|
|
98
|
-
if (startFirstTime) {
|
|
99
|
-
// Emits SDK_READY_FROM_CACHE
|
|
100
|
-
if (isCacheLoaded) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
92
|
+
if (startFirstTime) {
|
|
93
|
+
const isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
94
|
+
if (isCacheLoaded) Promise.resolve().then(() => { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
95
|
+
}
|
|
101
96
|
|
|
102
|
-
|
|
97
|
+
// start syncing splits and segments
|
|
98
|
+
if (pollingManager) {
|
|
103
99
|
|
|
104
|
-
//
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (syncEnabled) {
|
|
109
|
-
if (pushManager) {
|
|
110
|
-
// Doesn't call `syncAll` when the syncManager is resuming
|
|
111
|
-
if (startFirstTime) {
|
|
112
|
-
pollingManager.syncAll();
|
|
113
|
-
}
|
|
114
|
-
pushManager.start();
|
|
115
|
-
} else {
|
|
116
|
-
pollingManager.start();
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
100
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
101
|
+
if (syncEnabled) {
|
|
102
|
+
if (pushManager) {
|
|
103
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
119
104
|
if (startFirstTime) {
|
|
120
105
|
pollingManager.syncAll();
|
|
121
106
|
}
|
|
107
|
+
pushManager.start();
|
|
108
|
+
} else {
|
|
109
|
+
pollingManager.start();
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (startFirstTime) {
|
|
113
|
+
pollingManager.syncAll();
|
|
122
114
|
}
|
|
123
115
|
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// start periodic data recording (events, impressions, telemetry).
|
|
119
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
});
|
|
121
|
+
startFirstTime = false;
|
|
127
122
|
},
|
|
128
123
|
|
|
129
124
|
/**
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import SplitIO from '../types/splitio';
|
|
2
2
|
import { ISplitFiltersValidation } from './dtos/types';
|
|
3
3
|
import { ILogger } from './logger/types';
|
|
4
|
+
import { RolloutPlan } from './storages/types';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* SplitIO.ISettings interface extended with private properties for internal use
|
|
@@ -10,6 +11,7 @@ export interface ISettings extends SplitIO.ISettings {
|
|
|
10
11
|
__splitFiltersValidation: ISplitFiltersValidation;
|
|
11
12
|
};
|
|
12
13
|
readonly log: ILogger;
|
|
14
|
+
readonly initialRolloutPlan?: RolloutPlan;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -42,38 +44,3 @@ export interface IBasicClient extends SplitIO.IBasicClient {
|
|
|
42
44
|
isClientSide?: boolean;
|
|
43
45
|
key?: SplitIO.SplitKey;
|
|
44
46
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Defines the format of rollout plan data to preload the factory storage (cache).
|
|
47
|
-
*/
|
|
48
|
-
export interface PreloadedData {
|
|
49
|
-
/**
|
|
50
|
-
* Timestamp of the last moment the data was synchronized with Split servers.
|
|
51
|
-
* If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
|
|
52
|
-
*/
|
|
53
|
-
// @TODO configurable expiration time policy?
|
|
54
|
-
lastUpdated: number;
|
|
55
|
-
/**
|
|
56
|
-
* Change number of the preloaded data.
|
|
57
|
-
* If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
|
|
58
|
-
*/
|
|
59
|
-
since: number;
|
|
60
|
-
/**
|
|
61
|
-
* Map of feature flags to their stringified definitions.
|
|
62
|
-
*/
|
|
63
|
-
splitsData: {
|
|
64
|
-
[splitName: string]: string;
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Optional map of user keys to their list of segments.
|
|
68
|
-
*/
|
|
69
|
-
mySegmentsData?: {
|
|
70
|
-
[key: string]: string[];
|
|
71
|
-
};
|
|
72
|
-
/**
|
|
73
|
-
* Optional map of segments to their stringified definitions.
|
|
74
|
-
* This property is ignored if `mySegmentsData` was provided.
|
|
75
|
-
*/
|
|
76
|
-
segmentsData?: {
|
|
77
|
-
[segmentName: string]: string;
|
|
78
|
-
};
|
|
79
|
-
}
|
|
@@ -1,32 +1,11 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
1
2
|
export function isLocalStorageAvailable(): boolean {
|
|
2
|
-
try {
|
|
3
|
-
// eslint-disable-next-line no-undef
|
|
4
|
-
return isValidStorageWrapper(localStorage);
|
|
5
|
-
} catch (e) {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function isValidStorageWrapper(wrapper: any): boolean {
|
|
11
3
|
var mod = '__SPLITSOFTWARE__';
|
|
12
4
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
wrapper.removeItem(mod);
|
|
5
|
+
localStorage.setItem(mod, mod);
|
|
6
|
+
localStorage.removeItem(mod);
|
|
16
7
|
return true;
|
|
17
8
|
} catch (e) {
|
|
18
9
|
return false;
|
|
19
10
|
}
|
|
20
11
|
}
|
|
21
|
-
|
|
22
|
-
export function isWebStorage(wrapper: any): boolean {
|
|
23
|
-
if (typeof wrapper.length === 'number') {
|
|
24
|
-
try {
|
|
25
|
-
wrapper.key(0);
|
|
26
|
-
return true;
|
|
27
|
-
} catch (e) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
@@ -10,5 +10,4 @@ export { validateTrafficType } from './trafficType';
|
|
|
10
10
|
export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
|
|
11
11
|
export { validateSplitExistence } from './splitExistence';
|
|
12
12
|
export { validateTrafficTypeExistence } from './trafficTypeExistence';
|
|
13
|
-
export { validatePreloadedData } from './preloadedData';
|
|
14
13
|
export { validateEvaluationOptions } from './eventProperties';
|
|
@@ -7,6 +7,7 @@ import { ISettingsValidationParams } from './types';
|
|
|
7
7
|
import { ISettings } from '../../types';
|
|
8
8
|
import { validateKey } from '../inputValidation/key';
|
|
9
9
|
import { ERROR_MIN_CONFIG_PARAM, LOG_PREFIX_CLIENT_INSTANTIATION } from '../../logger/constants';
|
|
10
|
+
import { validateRolloutPlan } from '../../storages/setRolloutPlan';
|
|
10
11
|
|
|
11
12
|
// Exported for telemetry
|
|
12
13
|
export const base = {
|
|
@@ -152,6 +153,9 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
152
153
|
// @ts-ignore, modify readonly prop
|
|
153
154
|
if (storage) withDefaults.storage = storage(withDefaults);
|
|
154
155
|
|
|
156
|
+
// @ts-ignore, modify readonly prop
|
|
157
|
+
if (withDefaults.initialRolloutPlan) withDefaults.initialRolloutPlan = validateRolloutPlan(log, withDefaults);
|
|
158
|
+
|
|
155
159
|
// Validate key and TT (for client-side)
|
|
156
160
|
const maybeKey = withDefaults.core.key;
|
|
157
161
|
if (validationParams.acceptKey) {
|
|
@@ -8,7 +8,7 @@ import { IStorageFactoryParams, IStorageSync } from '../../../storages/types';
|
|
|
8
8
|
|
|
9
9
|
export function __InLocalStorageMockFactory(params: IStorageFactoryParams): IStorageSync {
|
|
10
10
|
const result = InMemoryStorageCSFactory(params);
|
|
11
|
-
result.validateCache = () =>
|
|
11
|
+
result.validateCache = () => true; // to emit SDK_READY_FROM_CACHE
|
|
12
12
|
return result;
|
|
13
13
|
}
|
|
14
14
|
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
package/types/splitio.d.ts
CHANGED
|
@@ -350,6 +350,11 @@ interface IClientSideSyncSharedSettings extends IClientSideSharedSettings, ISync
|
|
|
350
350
|
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#localhost-mode}
|
|
351
351
|
*/
|
|
352
352
|
features?: SplitIO.MockedFeaturesMap;
|
|
353
|
+
/**
|
|
354
|
+
* Rollout plan object (i.e., feature flags and segment definitions) to initialize the SDK storage with. If provided and valid, the SDK will be ready from cache immediately.
|
|
355
|
+
* This object is derived from calling the Node.js SDK’s `getRolloutPlan` method.
|
|
356
|
+
*/
|
|
357
|
+
initialRolloutPlan?: SplitIO.RolloutPlan;
|
|
353
358
|
/**
|
|
354
359
|
* SDK Startup settings.
|
|
355
360
|
*/
|
|
@@ -458,36 +463,6 @@ interface IClientSideSyncSharedSettings extends IClientSideSharedSettings, ISync
|
|
|
458
463
|
*/
|
|
459
464
|
declare namespace SplitIO {
|
|
460
465
|
|
|
461
|
-
interface SyncStorageWrapper {
|
|
462
|
-
/**
|
|
463
|
-
* Returns the value associated with the given key, or null if the key does not exist.
|
|
464
|
-
*/
|
|
465
|
-
getItem(key: string): string | null;
|
|
466
|
-
/**
|
|
467
|
-
* Sets the value for the given key, creating a new key/value pair if key does not exist.
|
|
468
|
-
*/
|
|
469
|
-
setItem(key: string, value: string): void;
|
|
470
|
-
/**
|
|
471
|
-
* Removes the key/value pair for the given key, if the key exists.
|
|
472
|
-
*/
|
|
473
|
-
removeItem(key: string): void;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
interface AsyncStorageWrapper {
|
|
477
|
-
/**
|
|
478
|
-
* Returns a promise that resolves to the value associated with the given key, or null if the key does not exist.
|
|
479
|
-
*/
|
|
480
|
-
getItem(key: string): Promise<string | null>;
|
|
481
|
-
/**
|
|
482
|
-
* Returns a promise that resolves when the value of the pair identified by key is set to value, creating a new key/value pair if key does not exist.
|
|
483
|
-
*/
|
|
484
|
-
setItem(key: string, value: string): Promise<void>;
|
|
485
|
-
/**
|
|
486
|
-
* Returns a promise that resolves when the key/value pair for the given key is removed, if the key exists.
|
|
487
|
-
*/
|
|
488
|
-
removeItem(key: string): Promise<void>;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
466
|
/**
|
|
492
467
|
* EventEmitter interface based on a subset of the Node.js EventEmitter methods.
|
|
493
468
|
*/
|
|
@@ -585,6 +560,7 @@ declare namespace SplitIO {
|
|
|
585
560
|
eventsFirstPushWindow: number;
|
|
586
561
|
};
|
|
587
562
|
readonly storage: StorageSyncFactory | StorageAsyncFactory | StorageOptions;
|
|
563
|
+
readonly initialRolloutPlan?: SplitIO.RolloutPlan;
|
|
588
564
|
readonly urls: {
|
|
589
565
|
events: string;
|
|
590
566
|
sdk: string;
|
|
@@ -1002,12 +978,6 @@ declare namespace SplitIO {
|
|
|
1002
978
|
* @defaultValue `false`
|
|
1003
979
|
*/
|
|
1004
980
|
clearOnInit?: boolean;
|
|
1005
|
-
/**
|
|
1006
|
-
* Optional storage wrapper to persist rollout plan related data. If not provided, the SDK will use the default localStorage Web API.
|
|
1007
|
-
*
|
|
1008
|
-
* @defaultValue `window.localStorage`
|
|
1009
|
-
*/
|
|
1010
|
-
wrapper?: SyncStorageWrapper | AsyncStorageWrapper;
|
|
1011
981
|
}
|
|
1012
982
|
/**
|
|
1013
983
|
* Storage for asynchronous (consumer) SDK.
|
|
@@ -1056,7 +1026,28 @@ declare namespace SplitIO {
|
|
|
1056
1026
|
type: NodeSyncStorage | NodeAsyncStorage | BrowserStorage;
|
|
1057
1027
|
prefix?: string;
|
|
1058
1028
|
options?: Object;
|
|
1059
|
-
}
|
|
1029
|
+
};
|
|
1030
|
+
/**
|
|
1031
|
+
* A JSON-serializable plain object that defines the format of rollout plan data to preload the SDK cache with feature flags and segments.
|
|
1032
|
+
*/
|
|
1033
|
+
type RolloutPlan = Object;
|
|
1034
|
+
/**
|
|
1035
|
+
* Options for the `factory.getRolloutPlan` method.
|
|
1036
|
+
*/
|
|
1037
|
+
type RolloutPlanOptions = {
|
|
1038
|
+
/**
|
|
1039
|
+
* Optional list of keys to generate the rollout plan snapshot with the memberships of the given keys.
|
|
1040
|
+
*
|
|
1041
|
+
* @defaultValue `undefined`
|
|
1042
|
+
*/
|
|
1043
|
+
keys?: SplitKey[];
|
|
1044
|
+
/**
|
|
1045
|
+
* Optional flag to expose segments data in the rollout plan snapshot.
|
|
1046
|
+
*
|
|
1047
|
+
* @defaultValue `false`
|
|
1048
|
+
*/
|
|
1049
|
+
exposeSegments?: boolean;
|
|
1050
|
+
};
|
|
1060
1051
|
/**
|
|
1061
1052
|
* Impression listener interface. This is the interface that needs to be implemented
|
|
1062
1053
|
* by the element you provide to the SDK as impression listener.
|
|
@@ -1079,7 +1070,7 @@ declare namespace SplitIO {
|
|
|
1079
1070
|
type IntegrationFactory = {
|
|
1080
1071
|
readonly type: string;
|
|
1081
1072
|
(params: any): (Integration | void);
|
|
1082
|
-
}
|
|
1073
|
+
};
|
|
1083
1074
|
/**
|
|
1084
1075
|
* A pair of user key and it's trafficType, required for tracking valid Split events.
|
|
1085
1076
|
*/
|
|
@@ -1348,12 +1339,6 @@ declare namespace SplitIO {
|
|
|
1348
1339
|
* @defaultValue `false`
|
|
1349
1340
|
*/
|
|
1350
1341
|
clearOnInit?: boolean;
|
|
1351
|
-
/**
|
|
1352
|
-
* Optional storage wrapper to persist rollout plan related data. If not provided, the SDK will use the default localStorage Web API.
|
|
1353
|
-
*
|
|
1354
|
-
* @defaultValue `window.localStorage`
|
|
1355
|
-
*/
|
|
1356
|
-
wrapper?: SyncStorageWrapper | AsyncStorageWrapper;
|
|
1357
1342
|
};
|
|
1358
1343
|
}
|
|
1359
1344
|
/**
|
|
@@ -1606,6 +1591,13 @@ declare namespace SplitIO {
|
|
|
1606
1591
|
* @returns The manager instance.
|
|
1607
1592
|
*/
|
|
1608
1593
|
manager(): IManager;
|
|
1594
|
+
/**
|
|
1595
|
+
* Returns the current snapshot of the SDK rollout plan in cache.
|
|
1596
|
+
*
|
|
1597
|
+
* @param keys - Optional list of keys to generate the rollout plan snapshot with the memberships of the given keys, rather than the complete segments data.
|
|
1598
|
+
* @returns The current snapshot of the SDK rollout plan.
|
|
1599
|
+
*/
|
|
1600
|
+
getRolloutPlan(options?: RolloutPlanOptions): RolloutPlan;
|
|
1609
1601
|
}
|
|
1610
1602
|
/**
|
|
1611
1603
|
* This represents the interface for the SDK instance for server-side with asynchronous storage.
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dataLoaderFactory = void 0;
|
|
4
|
-
// This value might be eventually set via a config parameter
|
|
5
|
-
var DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
|
|
6
|
-
/**
|
|
7
|
-
* Factory of client-side storage loader
|
|
8
|
-
*
|
|
9
|
-
* @param preloadedData - validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
10
|
-
* and extended with a `mySegmentsData` property.
|
|
11
|
-
* @returns function to preload the storage
|
|
12
|
-
*/
|
|
13
|
-
function dataLoaderFactory(preloadedData) {
|
|
14
|
-
/**
|
|
15
|
-
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
16
|
-
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
17
|
-
*
|
|
18
|
-
* @param storage - object containing `splits` and `segments` cache (client-side variant)
|
|
19
|
-
* @param userId - user key string of the provided MySegmentsCache
|
|
20
|
-
*/
|
|
21
|
-
// @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
22
|
-
// @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
23
|
-
return function loadData(storage, userId) {
|
|
24
|
-
// Do not load data if current preloadedData is empty
|
|
25
|
-
if (Object.keys(preloadedData).length === 0)
|
|
26
|
-
return;
|
|
27
|
-
var _a = preloadedData.lastUpdated, lastUpdated = _a === void 0 ? -1 : _a, _b = preloadedData.segmentsData, segmentsData = _b === void 0 ? {} : _b, _c = preloadedData.since, since = _c === void 0 ? -1 : _c, _d = preloadedData.splitsData, splitsData = _d === void 0 ? {} : _d;
|
|
28
|
-
var storedSince = storage.splits.getChangeNumber();
|
|
29
|
-
var expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
30
|
-
// Do not load data if current localStorage data is more recent,
|
|
31
|
-
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
32
|
-
if (storedSince > since || lastUpdated < expirationTimestamp)
|
|
33
|
-
return;
|
|
34
|
-
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
35
|
-
storage.splits.clear();
|
|
36
|
-
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
37
|
-
storage.splits.update(Object.keys(splitsData).map(function (splitName) { return JSON.parse(splitsData[splitName]); }), [], since);
|
|
38
|
-
// add mySegments data
|
|
39
|
-
var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
|
|
40
|
-
if (!mySegmentsData) {
|
|
41
|
-
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
|
|
42
|
-
mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
|
|
43
|
-
var userIds = JSON.parse(segmentsData[segmentName]).added;
|
|
44
|
-
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
storage.segments.resetSegments({ k: mySegmentsData.map(function (s) { return ({ n: s }); }) });
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
exports.dataLoaderFactory = dataLoaderFactory;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.storageAdapter = void 0;
|
|
4
|
-
var constants_1 = require("./constants");
|
|
5
|
-
function storageAdapter(log, prefix, wrapper) {
|
|
6
|
-
var keys = [];
|
|
7
|
-
var cache = {};
|
|
8
|
-
var loadPromise;
|
|
9
|
-
var savePromise = Promise.resolve();
|
|
10
|
-
return {
|
|
11
|
-
load: function () {
|
|
12
|
-
return loadPromise || (loadPromise = Promise.resolve().then(function () {
|
|
13
|
-
return wrapper.getItem(prefix);
|
|
14
|
-
}).then(function (storedCache) {
|
|
15
|
-
cache = JSON.parse(storedCache || '{}');
|
|
16
|
-
keys = Object.keys(cache);
|
|
17
|
-
}).catch(function (e) {
|
|
18
|
-
log.error(constants_1.LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
|
|
19
|
-
}));
|
|
20
|
-
},
|
|
21
|
-
save: function () {
|
|
22
|
-
return savePromise = savePromise.then(function () {
|
|
23
|
-
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
|
|
24
|
-
}).catch(function (e) {
|
|
25
|
-
log.error(constants_1.LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
whenSaved: function () {
|
|
29
|
-
return savePromise;
|
|
30
|
-
},
|
|
31
|
-
get length() {
|
|
32
|
-
return keys.length;
|
|
33
|
-
},
|
|
34
|
-
getItem: function (key) {
|
|
35
|
-
return cache[key] || null;
|
|
36
|
-
},
|
|
37
|
-
key: function (index) {
|
|
38
|
-
return keys[index] || null;
|
|
39
|
-
},
|
|
40
|
-
removeItem: function (key) {
|
|
41
|
-
var index = keys.indexOf(key);
|
|
42
|
-
if (index === -1)
|
|
43
|
-
return;
|
|
44
|
-
keys.splice(index, 1);
|
|
45
|
-
delete cache[key];
|
|
46
|
-
},
|
|
47
|
-
setItem: function (key, value) {
|
|
48
|
-
if (keys.indexOf(key) === -1)
|
|
49
|
-
keys.push(key);
|
|
50
|
-
cache[key] = value;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
exports.storageAdapter = storageAdapter;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validatePreloadedData = void 0;
|
|
4
|
-
var lang_1 = require("../lang");
|
|
5
|
-
var split_1 = require("./split");
|
|
6
|
-
function validateTimestampData(log, maybeTimestamp, method, item) {
|
|
7
|
-
if ((0, lang_1.isFiniteNumber)(maybeTimestamp) && maybeTimestamp > -1)
|
|
8
|
-
return true;
|
|
9
|
-
log.error(method + ": preloadedData." + item + " must be a positive number.");
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
function validateSplitsData(log, maybeSplitsData, method) {
|
|
13
|
-
if ((0, lang_1.isObject)(maybeSplitsData)) {
|
|
14
|
-
var splitNames = Object.keys(maybeSplitsData);
|
|
15
|
-
if (splitNames.length === 0)
|
|
16
|
-
log.warn(method + ": preloadedData.splitsData doesn't contain feature flag definitions.");
|
|
17
|
-
// @TODO in the future, consider handling the possibility of having parsed definitions of splits
|
|
18
|
-
if (splitNames.every(function (splitName) { return (0, split_1.validateSplit)(log, splitName, method) && (0, lang_1.isString)(maybeSplitsData[splitName]); }))
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
log.error(method + ": preloadedData.splitsData must be a map of feature flag names to their stringified definitions.");
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
function validateMySegmentsData(log, maybeMySegmentsData, method) {
|
|
25
|
-
if ((0, lang_1.isObject)(maybeMySegmentsData)) {
|
|
26
|
-
var userKeys = Object.keys(maybeMySegmentsData);
|
|
27
|
-
if (userKeys.every(function (userKey) {
|
|
28
|
-
var segmentNames = maybeMySegmentsData[userKey];
|
|
29
|
-
// an empty list is valid
|
|
30
|
-
return Array.isArray(segmentNames) && segmentNames.every(function (segmentName) { return (0, lang_1.isString)(segmentName); });
|
|
31
|
-
}))
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
log.error(method + ": preloadedData.mySegmentsData must be a map of user keys to their list of segment names.");
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
function validateSegmentsData(log, maybeSegmentsData, method) {
|
|
38
|
-
if ((0, lang_1.isObject)(maybeSegmentsData)) {
|
|
39
|
-
var segmentNames = Object.keys(maybeSegmentsData);
|
|
40
|
-
if (segmentNames.every(function (segmentName) { return (0, lang_1.isString)(maybeSegmentsData[segmentName]); }))
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
log.error(method + ": preloadedData.segmentsData must be a map of segment names to their stringified definitions.");
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
function validatePreloadedData(log, maybePreloadedData, method) {
|
|
47
|
-
if (!(0, lang_1.isObject)(maybePreloadedData)) {
|
|
48
|
-
log.error(method + ": preloadedData must be an object.");
|
|
49
|
-
}
|
|
50
|
-
else if (validateTimestampData(log, maybePreloadedData.lastUpdated, method, 'lastUpdated') &&
|
|
51
|
-
validateTimestampData(log, maybePreloadedData.since, method, 'since') &&
|
|
52
|
-
validateSplitsData(log, maybePreloadedData.splitsData, method) &&
|
|
53
|
-
(!maybePreloadedData.mySegmentsData || validateMySegmentsData(log, maybePreloadedData.mySegmentsData, method)) &&
|
|
54
|
-
(!maybePreloadedData.segmentsData || validateSegmentsData(log, maybePreloadedData.segmentsData, method))) {
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
exports.validatePreloadedData = validatePreloadedData;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// This value might be eventually set via a config parameter
|
|
2
|
-
var DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
|
|
3
|
-
/**
|
|
4
|
-
* Factory of client-side storage loader
|
|
5
|
-
*
|
|
6
|
-
* @param preloadedData - validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
7
|
-
* and extended with a `mySegmentsData` property.
|
|
8
|
-
* @returns function to preload the storage
|
|
9
|
-
*/
|
|
10
|
-
export function dataLoaderFactory(preloadedData) {
|
|
11
|
-
/**
|
|
12
|
-
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
13
|
-
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
14
|
-
*
|
|
15
|
-
* @param storage - object containing `splits` and `segments` cache (client-side variant)
|
|
16
|
-
* @param userId - user key string of the provided MySegmentsCache
|
|
17
|
-
*/
|
|
18
|
-
// @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
19
|
-
// @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
20
|
-
return function loadData(storage, userId) {
|
|
21
|
-
// Do not load data if current preloadedData is empty
|
|
22
|
-
if (Object.keys(preloadedData).length === 0)
|
|
23
|
-
return;
|
|
24
|
-
var _a = preloadedData.lastUpdated, lastUpdated = _a === void 0 ? -1 : _a, _b = preloadedData.segmentsData, segmentsData = _b === void 0 ? {} : _b, _c = preloadedData.since, since = _c === void 0 ? -1 : _c, _d = preloadedData.splitsData, splitsData = _d === void 0 ? {} : _d;
|
|
25
|
-
var storedSince = storage.splits.getChangeNumber();
|
|
26
|
-
var expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
27
|
-
// Do not load data if current localStorage data is more recent,
|
|
28
|
-
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
29
|
-
if (storedSince > since || lastUpdated < expirationTimestamp)
|
|
30
|
-
return;
|
|
31
|
-
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
32
|
-
storage.splits.clear();
|
|
33
|
-
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
34
|
-
storage.splits.update(Object.keys(splitsData).map(function (splitName) { return JSON.parse(splitsData[splitName]); }), [], since);
|
|
35
|
-
// add mySegments data
|
|
36
|
-
var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
|
|
37
|
-
if (!mySegmentsData) {
|
|
38
|
-
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
|
|
39
|
-
mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
|
|
40
|
-
var userIds = JSON.parse(segmentsData[segmentName]).added;
|
|
41
|
-
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
storage.segments.resetSegments({ k: mySegmentsData.map(function (s) { return ({ n: s }); }) });
|
|
45
|
-
};
|
|
46
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { LOG_PREFIX } from './constants';
|
|
2
|
-
export function storageAdapter(log, prefix, wrapper) {
|
|
3
|
-
var keys = [];
|
|
4
|
-
var cache = {};
|
|
5
|
-
var loadPromise;
|
|
6
|
-
var savePromise = Promise.resolve();
|
|
7
|
-
return {
|
|
8
|
-
load: function () {
|
|
9
|
-
return loadPromise || (loadPromise = Promise.resolve().then(function () {
|
|
10
|
-
return wrapper.getItem(prefix);
|
|
11
|
-
}).then(function (storedCache) {
|
|
12
|
-
cache = JSON.parse(storedCache || '{}');
|
|
13
|
-
keys = Object.keys(cache);
|
|
14
|
-
}).catch(function (e) {
|
|
15
|
-
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
|
|
16
|
-
}));
|
|
17
|
-
},
|
|
18
|
-
save: function () {
|
|
19
|
-
return savePromise = savePromise.then(function () {
|
|
20
|
-
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
|
|
21
|
-
}).catch(function (e) {
|
|
22
|
-
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
|
|
23
|
-
});
|
|
24
|
-
},
|
|
25
|
-
whenSaved: function () {
|
|
26
|
-
return savePromise;
|
|
27
|
-
},
|
|
28
|
-
get length() {
|
|
29
|
-
return keys.length;
|
|
30
|
-
},
|
|
31
|
-
getItem: function (key) {
|
|
32
|
-
return cache[key] || null;
|
|
33
|
-
},
|
|
34
|
-
key: function (index) {
|
|
35
|
-
return keys[index] || null;
|
|
36
|
-
},
|
|
37
|
-
removeItem: function (key) {
|
|
38
|
-
var index = keys.indexOf(key);
|
|
39
|
-
if (index === -1)
|
|
40
|
-
return;
|
|
41
|
-
keys.splice(index, 1);
|
|
42
|
-
delete cache[key];
|
|
43
|
-
},
|
|
44
|
-
setItem: function (key, value) {
|
|
45
|
-
if (keys.indexOf(key) === -1)
|
|
46
|
-
keys.push(key);
|
|
47
|
-
cache[key] = value;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { isObject, isString, isFiniteNumber } from '../lang';
|
|
2
|
-
import { validateSplit } from './split';
|
|
3
|
-
function validateTimestampData(log, maybeTimestamp, method, item) {
|
|
4
|
-
if (isFiniteNumber(maybeTimestamp) && maybeTimestamp > -1)
|
|
5
|
-
return true;
|
|
6
|
-
log.error(method + ": preloadedData." + item + " must be a positive number.");
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
function validateSplitsData(log, maybeSplitsData, method) {
|
|
10
|
-
if (isObject(maybeSplitsData)) {
|
|
11
|
-
var splitNames = Object.keys(maybeSplitsData);
|
|
12
|
-
if (splitNames.length === 0)
|
|
13
|
-
log.warn(method + ": preloadedData.splitsData doesn't contain feature flag definitions.");
|
|
14
|
-
// @TODO in the future, consider handling the possibility of having parsed definitions of splits
|
|
15
|
-
if (splitNames.every(function (splitName) { return validateSplit(log, splitName, method) && isString(maybeSplitsData[splitName]); }))
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
log.error(method + ": preloadedData.splitsData must be a map of feature flag names to their stringified definitions.");
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
function validateMySegmentsData(log, maybeMySegmentsData, method) {
|
|
22
|
-
if (isObject(maybeMySegmentsData)) {
|
|
23
|
-
var userKeys = Object.keys(maybeMySegmentsData);
|
|
24
|
-
if (userKeys.every(function (userKey) {
|
|
25
|
-
var segmentNames = maybeMySegmentsData[userKey];
|
|
26
|
-
// an empty list is valid
|
|
27
|
-
return Array.isArray(segmentNames) && segmentNames.every(function (segmentName) { return isString(segmentName); });
|
|
28
|
-
}))
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
log.error(method + ": preloadedData.mySegmentsData must be a map of user keys to their list of segment names.");
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
function validateSegmentsData(log, maybeSegmentsData, method) {
|
|
35
|
-
if (isObject(maybeSegmentsData)) {
|
|
36
|
-
var segmentNames = Object.keys(maybeSegmentsData);
|
|
37
|
-
if (segmentNames.every(function (segmentName) { return isString(maybeSegmentsData[segmentName]); }))
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
log.error(method + ": preloadedData.segmentsData must be a map of segment names to their stringified definitions.");
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
export function validatePreloadedData(log, maybePreloadedData, method) {
|
|
44
|
-
if (!isObject(maybePreloadedData)) {
|
|
45
|
-
log.error(method + ": preloadedData must be an object.");
|
|
46
|
-
}
|
|
47
|
-
else if (validateTimestampData(log, maybePreloadedData.lastUpdated, method, 'lastUpdated') &&
|
|
48
|
-
validateTimestampData(log, maybePreloadedData.since, method, 'since') &&
|
|
49
|
-
validateSplitsData(log, maybePreloadedData.splitsData, method) &&
|
|
50
|
-
(!maybePreloadedData.mySegmentsData || validateMySegmentsData(log, maybePreloadedData.mySegmentsData, method)) &&
|
|
51
|
-
(!maybePreloadedData.segmentsData || validateSegmentsData(log, maybePreloadedData.segmentsData, method))) {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
}
|