@splitsoftware/splitio-commons 1.0.1-rc.6 → 1.2.1-rc.0
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 +13 -3
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/cjs/logger/messages/info.js +3 -3
- package/cjs/sdkClient/client.js +2 -1
- package/cjs/sdkClient/clientAttributesDecoration.js +108 -0
- package/cjs/sdkClient/clientCS.js +10 -7
- package/cjs/sdkClient/sdkClientMethodCS.js +2 -2
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +2 -2
- package/cjs/sdkFactory/index.js +2 -2
- package/cjs/services/splitHttpClient.js +1 -1
- package/cjs/storages/inMemory/AttributesCacheInMemory.js +70 -0
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +6 -3
- package/cjs/sync/streaming/AuthClient/index.js +1 -2
- package/cjs/sync/streaming/pushManager.js +26 -22
- package/cjs/sync/syncManagerOnline.js +12 -7
- package/cjs/utils/inputValidation/attribute.js +20 -0
- package/cjs/utils/inputValidation/attributes.js +13 -1
- package/cjs/utils/murmur3/legacy.js +44 -0
- package/cjs/utils/promise/timeout.js +1 -1
- package/cjs/utils/settingsValidation/index.js +1 -1
- package/esm/logger/messages/info.js +3 -3
- package/esm/sdkClient/client.js +2 -1
- package/esm/sdkClient/clientAttributesDecoration.js +104 -0
- package/esm/sdkClient/clientCS.js +10 -7
- package/esm/sdkClient/sdkClientMethodCS.js +2 -2
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +2 -2
- package/esm/sdkFactory/index.js +2 -2
- package/esm/services/splitHttpClient.js +1 -1
- package/esm/storages/inMemory/AttributesCacheInMemory.js +67 -0
- package/esm/sync/polling/updaters/splitChangesUpdater.js +6 -3
- package/esm/sync/streaming/AuthClient/index.js +1 -2
- package/esm/sync/streaming/pushManager.js +26 -22
- package/esm/sync/syncManagerOnline.js +12 -7
- package/esm/utils/inputValidation/attribute.js +16 -0
- package/esm/utils/inputValidation/attributes.js +11 -0
- package/esm/utils/murmur3/legacy.js +39 -0
- package/esm/utils/promise/timeout.js +1 -1
- package/esm/utils/settingsValidation/index.js +1 -1
- package/package.json +2 -2
- package/src/logger/messages/info.ts +3 -3
- package/src/sdkClient/client.ts +2 -1
- package/src/sdkClient/clientAttributesDecoration.ts +122 -0
- package/src/sdkClient/clientCS.ts +14 -7
- package/src/sdkClient/sdkClientMethodCS.ts +2 -0
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +2 -0
- package/src/sdkFactory/index.ts +2 -2
- package/src/services/splitHttpClient.ts +1 -1
- package/src/storages/inMemory/AttributesCacheInMemory.ts +73 -0
- package/src/sync/polling/updaters/splitChangesUpdater.ts +5 -2
- package/src/sync/streaming/AuthClient/index.ts +1 -2
- package/src/sync/streaming/pushManager.ts +26 -26
- package/src/sync/syncManagerOnline.ts +10 -6
- package/src/types.ts +43 -0
- package/src/utils/inputValidation/attribute.ts +21 -0
- package/src/utils/inputValidation/attributes.ts +14 -0
- package/src/utils/murmur3/legacy.ts +48 -0
- package/src/utils/promise/timeout.ts +1 -1
- package/src/utils/settingsValidation/index.ts +1 -1
- package/types/sdkClient/clientAttributesDecoration.d.ts +51 -0
- package/types/sdkClient/clientCS.d.ts +2 -1
- package/types/storages/inMemory/AttributesCacheInMemory.d.ts +43 -0
- package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +51 -0
- package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +0 -0
- package/types/storages/pluggable/TelemetryCachePluggable.d.ts +2 -0
- package/types/sync/submitters/telemetrySyncTask.d.ts +17 -0
- package/types/trackers/telemetryRecorder.d.ts +0 -0
- package/types/types.d.ts +40 -0
- package/types/utils/EventEmitter.d.ts +4 -0
- package/types/utils/inputValidation/attribute.d.ts +2 -0
- package/types/utils/inputValidation/attributes.d.ts +1 -0
- package/types/utils/murmur3/legacy.d.ts +2 -0
|
@@ -20,10 +20,10 @@ export var codesInfo = codesWarn.concat([
|
|
|
20
20
|
[c.SUBMITTERS_PUSH_FULL_EVENTS_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full events queue and reseting timer.'],
|
|
21
21
|
[c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s %s.'],
|
|
22
22
|
[c.STREAMING_REFRESH_TOKEN, c.LOG_PREFIX_SYNC_STREAMING + 'Refreshing streaming token in %s seconds, and connecting streaming in %s seconds.'],
|
|
23
|
-
[c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect in %s seconds.'],
|
|
24
|
-
[c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + '
|
|
23
|
+
[c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect streaming in %s seconds.'],
|
|
24
|
+
[c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Connecting streaming.'],
|
|
25
25
|
[c.STREAMING_DISABLED, c.LOG_PREFIX_SYNC_STREAMING + 'Streaming is disabled for given Api key. Switching to polling mode.'],
|
|
26
|
-
[c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting
|
|
26
|
+
[c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting streaming.'],
|
|
27
27
|
[c.SYNC_START_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming not available. Starting polling.'],
|
|
28
28
|
[c.SYNC_CONTINUE_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming couldn\'t connect. Continue polling.'],
|
|
29
29
|
[c.SYNC_STOP_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming (re)connected. Syncing and stopping polling.'],
|
package/esm/sdkClient/client.js
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { AttributesCacheInMemory } from '../storages/inMemory/AttributesCacheInMemory';
|
|
2
|
+
import { validateAttributesDeep } from '../utils/inputValidation/attributes';
|
|
3
|
+
import { objectAssign } from '../utils/lang/objectAssign';
|
|
4
|
+
/**
|
|
5
|
+
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
|
|
6
|
+
*/
|
|
7
|
+
export function clientAttributesDecoration(log, client) {
|
|
8
|
+
var attributeStorage = new AttributesCacheInMemory();
|
|
9
|
+
// Keep a reference to the original methods
|
|
10
|
+
var clientGetTreatment = client.getTreatment;
|
|
11
|
+
var clientGetTreatmentWithConfig = client.getTreatmentWithConfig;
|
|
12
|
+
var clientGetTreatments = client.getTreatments;
|
|
13
|
+
var clientGetTreatmentsWithConfig = client.getTreatmentsWithConfig;
|
|
14
|
+
var clientTrack = client.track;
|
|
15
|
+
function getTreatment(maybeKey, maybeSplit, maybeAttributes) {
|
|
16
|
+
return clientGetTreatment(maybeKey, maybeSplit, combineAttributes(maybeAttributes));
|
|
17
|
+
}
|
|
18
|
+
function getTreatmentWithConfig(maybeKey, maybeSplit, maybeAttributes) {
|
|
19
|
+
return clientGetTreatmentWithConfig(maybeKey, maybeSplit, combineAttributes(maybeAttributes));
|
|
20
|
+
}
|
|
21
|
+
function getTreatments(maybeKey, maybeSplits, maybeAttributes) {
|
|
22
|
+
return clientGetTreatments(maybeKey, maybeSplits, combineAttributes(maybeAttributes));
|
|
23
|
+
}
|
|
24
|
+
function getTreatmentsWithConfig(maybeKey, maybeSplits, maybeAttributes) {
|
|
25
|
+
return clientGetTreatmentsWithConfig(maybeKey, maybeSplits, combineAttributes(maybeAttributes));
|
|
26
|
+
}
|
|
27
|
+
function track(maybeKey, maybeTT, maybeEvent, maybeEventValue, maybeProperties) {
|
|
28
|
+
return clientTrack(maybeKey, maybeTT, maybeEvent, maybeEventValue, maybeProperties);
|
|
29
|
+
}
|
|
30
|
+
function combineAttributes(maybeAttributes) {
|
|
31
|
+
var storedAttributes = attributeStorage.getAll();
|
|
32
|
+
if (Object.keys(storedAttributes).length > 0) {
|
|
33
|
+
return objectAssign({}, storedAttributes, maybeAttributes);
|
|
34
|
+
}
|
|
35
|
+
return maybeAttributes;
|
|
36
|
+
}
|
|
37
|
+
return objectAssign(client, {
|
|
38
|
+
getTreatment: getTreatment,
|
|
39
|
+
getTreatmentWithConfig: getTreatmentWithConfig,
|
|
40
|
+
getTreatments: getTreatments,
|
|
41
|
+
getTreatmentsWithConfig: getTreatmentsWithConfig,
|
|
42
|
+
track: track,
|
|
43
|
+
/**
|
|
44
|
+
* Add an attribute to client's in memory attributes storage
|
|
45
|
+
*
|
|
46
|
+
* @param {string} attributeName Attrinute name
|
|
47
|
+
* @param {string, number, boolean, list} attributeValue Attribute value
|
|
48
|
+
* @returns {boolean} true if the attribute was stored and false otherways
|
|
49
|
+
*/
|
|
50
|
+
setAttribute: function (attributeName, attributeValue) {
|
|
51
|
+
var attribute = {};
|
|
52
|
+
attribute[attributeName] = attributeValue;
|
|
53
|
+
if (!validateAttributesDeep(log, attribute, 'setAttribute'))
|
|
54
|
+
return false;
|
|
55
|
+
log.debug("stored " + attributeValue + " for attribute " + attributeName);
|
|
56
|
+
return attributeStorage.setAttribute(attributeName, attributeValue);
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* Returns the attribute with the given key
|
|
60
|
+
*
|
|
61
|
+
* @param {string} attributeName Attribute name
|
|
62
|
+
* @returns {Object} Attribute with the given key
|
|
63
|
+
*/
|
|
64
|
+
getAttribute: function (attributeName) {
|
|
65
|
+
log.debug("retrieved attribute " + attributeName);
|
|
66
|
+
return attributeStorage.getAttribute(attributeName + '');
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* Add to client's in memory attributes storage the attributes in 'attributes'
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} attributes Object with attributes to store
|
|
72
|
+
* @returns true if attributes were stored an false otherways
|
|
73
|
+
*/
|
|
74
|
+
setAttributes: function (attributes) {
|
|
75
|
+
if (!validateAttributesDeep(log, attributes, 'setAttributes'))
|
|
76
|
+
return false;
|
|
77
|
+
return attributeStorage.setAttributes(attributes);
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Return all the attributes stored in client's in memory attributes storage
|
|
81
|
+
*
|
|
82
|
+
* @returns {Object} returns all the stored attributes
|
|
83
|
+
*/
|
|
84
|
+
getAttributes: function () {
|
|
85
|
+
return attributeStorage.getAll();
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Removes from client's in memory attributes storage the attribute with the given key
|
|
89
|
+
*
|
|
90
|
+
* @param {string} attributeName
|
|
91
|
+
* @returns {boolean} true if attribute was removed and false otherways
|
|
92
|
+
*/
|
|
93
|
+
removeAttribute: function (attributeName) {
|
|
94
|
+
log.debug("removed attribute " + attributeName);
|
|
95
|
+
return attributeStorage.removeAttribute(attributeName + '');
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* Remove all the stored attributes in the client's in memory attribute storage
|
|
99
|
+
*/
|
|
100
|
+
clearAttributes: function () {
|
|
101
|
+
return attributeStorage.clear();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
|
+
import { clientAttributesDecoration } from './clientAttributesDecoration';
|
|
2
3
|
/**
|
|
3
4
|
* Decorator that binds a key and (optionally) a traffic type to client methods
|
|
4
5
|
*
|
|
@@ -6,14 +7,16 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
6
7
|
* @param key validated split key
|
|
7
8
|
* @param trafficType validated traffic type
|
|
8
9
|
*/
|
|
9
|
-
export function clientCSDecorator(client, key, trafficType) {
|
|
10
|
-
|
|
10
|
+
export function clientCSDecorator(log, client, key, trafficType) {
|
|
11
|
+
var clientCS = clientAttributesDecoration(log, client);
|
|
12
|
+
return objectAssign(clientCS, {
|
|
11
13
|
// In the client-side API, we bind a key to the client `getTreatment*` methods
|
|
12
|
-
getTreatment:
|
|
13
|
-
getTreatmentWithConfig:
|
|
14
|
-
getTreatments:
|
|
15
|
-
getTreatmentsWithConfig:
|
|
14
|
+
getTreatment: clientCS.getTreatment.bind(clientCS, key),
|
|
15
|
+
getTreatmentWithConfig: clientCS.getTreatmentWithConfig.bind(clientCS, key),
|
|
16
|
+
getTreatments: clientCS.getTreatments.bind(clientCS, key),
|
|
17
|
+
getTreatmentsWithConfig: clientCS.getTreatmentsWithConfig.bind(clientCS, key),
|
|
16
18
|
// Key is bound to the `track` method. Same thing happens with trafficType but only if provided
|
|
17
|
-
track: trafficType ?
|
|
19
|
+
track: trafficType ? clientCS.track.bind(clientCS, key, trafficType) : clientCS.track.bind(clientCS, key),
|
|
20
|
+
isBrowserClient: true
|
|
18
21
|
});
|
|
19
22
|
}
|
|
@@ -20,7 +20,7 @@ export function sdkClientMethodCSFactory(params) {
|
|
|
20
20
|
// `false` value is used as binded key of the default client, but trafficType is ignored
|
|
21
21
|
// @TODO handle as a non-recoverable error
|
|
22
22
|
var validKey = validateKey(log, key, method);
|
|
23
|
-
var mainClientInstance = clientCSDecorator(sdkClientFactory(params), // @ts-ignore
|
|
23
|
+
var mainClientInstance = clientCSDecorator(log, sdkClientFactory(params), // @ts-ignore
|
|
24
24
|
validKey);
|
|
25
25
|
var parsedDefaultKey = keyParser(key);
|
|
26
26
|
var defaultInstanceId = buildInstanceId(parsedDefaultKey);
|
|
@@ -55,7 +55,7 @@ export function sdkClientMethodCSFactory(params) {
|
|
|
55
55
|
var sharedSyncManager = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
|
|
56
56
|
// As shared clients reuse all the storage information, we don't need to check here if we
|
|
57
57
|
// will use offline or online mode. We should stick with the original decision.
|
|
58
|
-
clientInstances[instanceId] = clientCSDecorator(sdkClientFactory(objectAssign({}, params, {
|
|
58
|
+
clientInstances[instanceId] = clientCSDecorator(log, sdkClientFactory(objectAssign({}, params, {
|
|
59
59
|
sdkReadinessManager: sharedSdkReadiness_1,
|
|
60
60
|
storage: sharedStorage || storage,
|
|
61
61
|
syncManager: sharedSyncManager,
|
|
@@ -26,7 +26,7 @@ export function sdkClientMethodCSFactory(params) {
|
|
|
26
26
|
if (trafficType !== undefined) {
|
|
27
27
|
validTrafficType = validateTrafficType(log, trafficType, method);
|
|
28
28
|
}
|
|
29
|
-
var mainClientInstance = clientCSDecorator(sdkClientFactory(params), // @ts-ignore
|
|
29
|
+
var mainClientInstance = clientCSDecorator(log, sdkClientFactory(params), // @ts-ignore
|
|
30
30
|
validKey, validTrafficType);
|
|
31
31
|
var parsedDefaultKey = keyParser(key);
|
|
32
32
|
var defaultInstanceId = buildInstanceId(parsedDefaultKey, trafficType);
|
|
@@ -68,7 +68,7 @@ export function sdkClientMethodCSFactory(params) {
|
|
|
68
68
|
var sharedSyncManager = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
|
|
69
69
|
// As shared clients reuse all the storage information, we don't need to check here if we
|
|
70
70
|
// will use offline or online mode. We should stick with the original decision.
|
|
71
|
-
clientInstances[instanceId] = clientCSDecorator(sdkClientFactory(objectAssign({}, params, {
|
|
71
|
+
clientInstances[instanceId] = clientCSDecorator(log, sdkClientFactory(objectAssign({}, params, {
|
|
72
72
|
sdkReadinessManager: sharedSdkReadiness_1,
|
|
73
73
|
storage: sharedStorage || storage,
|
|
74
74
|
syncManager: sharedSyncManager,
|
package/esm/sdkFactory/index.js
CHANGED
|
@@ -29,8 +29,8 @@ export function sdkFactory(params) {
|
|
|
29
29
|
splitFiltersValidation: settings.sync.__splitFiltersValidation,
|
|
30
30
|
// ATM, only used by PluggableStorage
|
|
31
31
|
mode: settings.mode,
|
|
32
|
-
// Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined
|
|
33
|
-
// or only
|
|
32
|
+
// Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined,
|
|
33
|
+
// or partial consumer mode, where it only has submitters, and therefore it doesn't emit readiness events.
|
|
34
34
|
onReadyCb: function (error) {
|
|
35
35
|
if (error)
|
|
36
36
|
return; // Don't emit SDK_READY if storage failed to connect. Error message is logged by wrapperAdapter
|
|
@@ -43,7 +43,7 @@ export function splitHttpClientFactory(settings, getFetch, getOptions) {
|
|
|
43
43
|
return response;
|
|
44
44
|
})
|
|
45
45
|
.catch(function (error) {
|
|
46
|
-
var resp = error.response;
|
|
46
|
+
var resp = error && error.response;
|
|
47
47
|
var msg = '';
|
|
48
48
|
if (resp) { // An HTTP error
|
|
49
49
|
switch (resp.status) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { objectAssign } from '../../utils/lang/objectAssign';
|
|
2
|
+
var AttributesCacheInMemory = /** @class */ (function () {
|
|
3
|
+
function AttributesCacheInMemory() {
|
|
4
|
+
this.attributesCache = {};
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Create or update the value for the given attribute
|
|
8
|
+
*
|
|
9
|
+
* @param {string} attributeName attribute name
|
|
10
|
+
* @param {Object} attributeValue attribute value
|
|
11
|
+
* @returns {boolean} the attribute was stored
|
|
12
|
+
*/
|
|
13
|
+
AttributesCacheInMemory.prototype.setAttribute = function (attributeName, attributeValue) {
|
|
14
|
+
this.attributesCache[attributeName] = attributeValue;
|
|
15
|
+
return true;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Retrieves the value of a given attribute
|
|
19
|
+
*
|
|
20
|
+
* @param {string} attributeName attribute name
|
|
21
|
+
* @returns {Object?} stored attribute value
|
|
22
|
+
*/
|
|
23
|
+
AttributesCacheInMemory.prototype.getAttribute = function (attributeName) {
|
|
24
|
+
return this.attributesCache[attributeName];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Create or update all the given attributes
|
|
28
|
+
*
|
|
29
|
+
* @param {[string, Object]} attributes attributes to create or update
|
|
30
|
+
* @returns {boolean} attributes were stored
|
|
31
|
+
*/
|
|
32
|
+
AttributesCacheInMemory.prototype.setAttributes = function (attributes) {
|
|
33
|
+
this.attributesCache = objectAssign(this.attributesCache, attributes);
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Retrieve the full attributes map
|
|
38
|
+
*
|
|
39
|
+
* @returns {Map<string, Object>} stored attributes
|
|
40
|
+
*/
|
|
41
|
+
AttributesCacheInMemory.prototype.getAll = function () {
|
|
42
|
+
return this.attributesCache;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Removes a given attribute from the map
|
|
46
|
+
*
|
|
47
|
+
* @param {string} attributeName attribute to remove
|
|
48
|
+
* @returns {boolean} attribute removed
|
|
49
|
+
*/
|
|
50
|
+
AttributesCacheInMemory.prototype.removeAttribute = function (attributeName) {
|
|
51
|
+
if (Object.keys(this.attributesCache).indexOf(attributeName) >= 0) {
|
|
52
|
+
delete this.attributesCache[attributeName];
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Clears all attributes stored in the SDK
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
AttributesCacheInMemory.prototype.clear = function () {
|
|
62
|
+
this.attributesCache = {};
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
65
|
+
return AttributesCacheInMemory;
|
|
66
|
+
}());
|
|
67
|
+
export { AttributesCacheInMemory };
|
|
@@ -133,10 +133,13 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
|
|
|
133
133
|
}
|
|
134
134
|
return false;
|
|
135
135
|
});
|
|
136
|
-
// After triggering the requests, if we have cached splits information let's notify that.
|
|
136
|
+
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
137
|
+
// Wrapping in a promise since checkCache can be async.
|
|
137
138
|
if (splitsEventEmitter && startingUp) {
|
|
138
|
-
Promise.resolve(splits.checkCache()).then(function (
|
|
139
|
-
|
|
139
|
+
Promise.resolve(splits.checkCache()).then(function (isCacheReady) {
|
|
140
|
+
if (isCacheReady)
|
|
141
|
+
splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
|
|
142
|
+
});
|
|
140
143
|
}
|
|
141
144
|
return fetcherPromise;
|
|
142
145
|
}
|
|
@@ -13,8 +13,7 @@ export function authenticateFactory(fetchAuth) {
|
|
|
13
13
|
* @param {string[] | undefined} userKeys set of user Keys to track MY_SEGMENTS_CHANGES. It is undefined for server-side API.
|
|
14
14
|
*/
|
|
15
15
|
return function authenticate(userKeys) {
|
|
16
|
-
|
|
17
|
-
return authPromise
|
|
16
|
+
return fetchAuth(userKeys)
|
|
18
17
|
.then(function (resp) { return resp.json(); })
|
|
19
18
|
.then(function (json) {
|
|
20
19
|
if (json.token) { // empty token when `"pushEnabled": false`
|
|
@@ -22,7 +22,7 @@ import { hash64 } from '../../utils/murmur3/murmur3_64';
|
|
|
22
22
|
export function pushManagerFactory(pollingManager, storage, readiness, fetchAuth, platform, settings) {
|
|
23
23
|
// `userKey` is the matching key of main client in client-side SDK.
|
|
24
24
|
// It can be used to check if running on client-side or server-side SDK.
|
|
25
|
-
var userKey = settings.core.key ? getMatching(settings.core.key) : undefined;
|
|
25
|
+
var userKey = settings.core.key ? getMatching(settings.core.key) : undefined;
|
|
26
26
|
var log = settings.log;
|
|
27
27
|
var sseClient;
|
|
28
28
|
try {
|
|
@@ -39,7 +39,8 @@ export function pushManagerFactory(pollingManager, storage, readiness, fetchAuth
|
|
|
39
39
|
var sseHandler = SSEHandlerFactory(log, pushEmitter);
|
|
40
40
|
sseClient.setEventHandler(sseHandler);
|
|
41
41
|
// init workers
|
|
42
|
-
|
|
42
|
+
// MySegmentsUpdateWorker (client-side) are initiated in `add` method
|
|
43
|
+
var segmentsUpdateWorker = userKey ? undefined : new SegmentsUpdateWorker(storage.segments, pollingManager.segmentsSyncTask);
|
|
43
44
|
// For server-side we pass the segmentsSyncTask, used by SplitsUpdateWorker to fetch new segments
|
|
44
45
|
var splitsUpdateWorker = new SplitsUpdateWorker(storage.splits, pollingManager.splitsSyncTask, readiness.splits, userKey ? undefined : pollingManager.segmentsSyncTask);
|
|
45
46
|
// [Only for client-side] map of hashes to user keys, to dispatch MY_SEGMENTS_UPDATE events to the corresponding MySegmentsUpdateWorker
|
|
@@ -47,11 +48,6 @@ export function pushManagerFactory(pollingManager, storage, readiness, fetchAuth
|
|
|
47
48
|
// [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
|
|
48
49
|
// Hash64 is used to process MY_SEGMENTS_UPDATE_V2 events and dispatch actions to the corresponding MySegmentsUpdateWorker.
|
|
49
50
|
var clients = {};
|
|
50
|
-
if (userKey) {
|
|
51
|
-
var hash = hashUserKey(userKey);
|
|
52
|
-
userKeyHashes[hash] = userKey;
|
|
53
|
-
clients[userKey] = { hash64: hash64(userKey), worker: segmentsUpdateWorker };
|
|
54
|
-
}
|
|
55
51
|
// [Only for client-side] variable to flag that a new client was added. It is needed to reconnect streaming.
|
|
56
52
|
var connectForNewClient = false;
|
|
57
53
|
// flag that indicates if `stop/disconnectPush` was called, either by the SyncManager, when the client is destroyed, or due to a PUSH_NONRETRYABLE_ERROR error.
|
|
@@ -87,7 +83,8 @@ export function pushManagerFactory(pollingManager, storage, readiness, fetchAuth
|
|
|
87
83
|
// Guard condition in case `stop/disconnectPush` has been called (e.g., calling SDK destroy, or app signal close/background)
|
|
88
84
|
if (disconnected)
|
|
89
85
|
return;
|
|
90
|
-
|
|
86
|
+
// @TODO distinguish log for 'Connecting' (1st time) and 'Re-connecting'
|
|
87
|
+
log.info(STREAMING_CONNECTING);
|
|
91
88
|
disconnected = false;
|
|
92
89
|
var userKeys = userKey ? Object.keys(clients) : undefined;
|
|
93
90
|
authenticate(userKeys).then(function (authData) {
|
|
@@ -257,33 +254,40 @@ export function pushManagerFactory(pollingManager, storage, readiness, fetchAuth
|
|
|
257
254
|
return objectAssign(
|
|
258
255
|
// Expose Event Emitter functionality and Event constants
|
|
259
256
|
Object.create(pushEmitter), {
|
|
260
|
-
//
|
|
261
|
-
stop:
|
|
257
|
+
// Stop/pause push mode
|
|
258
|
+
stop: function () {
|
|
259
|
+
disconnectPush(); // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling.
|
|
260
|
+
if (userKey)
|
|
261
|
+
this.remove(userKey); // Necessary to properly resume streaming in client-side (e.g., RN SDK transition to foreground).
|
|
262
|
+
},
|
|
263
|
+
// Start/resume push mode
|
|
262
264
|
start: function () {
|
|
263
265
|
// Guard condition to avoid calling `connectPush` again if the `start` method is called multiple times or if push has been disabled.
|
|
264
266
|
if (disabled || disconnected === false)
|
|
265
267
|
return;
|
|
266
268
|
disconnected = false;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
+
if (userKey)
|
|
270
|
+
this.add(userKey, pollingManager.segmentsSyncTask); // client-side
|
|
271
|
+
else
|
|
272
|
+
setTimeout(connectPush); // server-side runs in next cycle as in client-side, for consistency with client-side
|
|
269
273
|
},
|
|
270
274
|
// [Only for client-side]
|
|
271
275
|
add: function (userKey, mySegmentsSyncTask) {
|
|
272
|
-
clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) };
|
|
273
276
|
var hash = hashUserKey(userKey);
|
|
274
277
|
if (!userKeyHashes[hash]) {
|
|
275
278
|
userKeyHashes[hash] = userKey;
|
|
279
|
+
clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) };
|
|
276
280
|
connectForNewClient = true; // we must reconnect on start, to listen the channel for the new user key
|
|
281
|
+
// Reconnects in case of a new client.
|
|
282
|
+
// Run in next event-loop cycle to save authentication calls
|
|
283
|
+
// in case multiple clients are created in the current cycle.
|
|
284
|
+
setTimeout(function checkForReconnect() {
|
|
285
|
+
if (connectForNewClient) {
|
|
286
|
+
connectForNewClient = false;
|
|
287
|
+
connectPush();
|
|
288
|
+
}
|
|
289
|
+
}, 0);
|
|
277
290
|
}
|
|
278
|
-
// Reconnects in case of a new client.
|
|
279
|
-
// Run in next event-loop cycle to save authentication calls
|
|
280
|
-
// in case the user is creating several clients in the current cycle.
|
|
281
|
-
setTimeout(function checkForReconnect() {
|
|
282
|
-
if (connectForNewClient) {
|
|
283
|
-
connectForNewClient = false;
|
|
284
|
-
connectPush();
|
|
285
|
-
}
|
|
286
|
-
}, 0);
|
|
287
291
|
},
|
|
288
292
|
// [Only for client-side]
|
|
289
293
|
remove: function (userKey) {
|
|
@@ -27,12 +27,12 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
27
27
|
var submitter = submitterManagerFactory(settings, storage, splitApi);
|
|
28
28
|
/** Sync Manager logic */
|
|
29
29
|
function startPolling() {
|
|
30
|
-
if (
|
|
31
|
-
log.info(
|
|
32
|
-
pollingManager.start();
|
|
30
|
+
if (pollingManager.isRunning()) {
|
|
31
|
+
log.info(SYNC_CONTINUE_POLLING);
|
|
33
32
|
}
|
|
34
33
|
else {
|
|
35
|
-
log.info(
|
|
34
|
+
log.info(SYNC_START_POLLING);
|
|
35
|
+
pollingManager.start();
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
function stopPollingAndSyncAll() {
|
|
@@ -55,6 +55,9 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
55
55
|
* Method used to start the syncManager for the first time, or resume it after being stopped.
|
|
56
56
|
*/
|
|
57
57
|
start: function () {
|
|
58
|
+
if (running)
|
|
59
|
+
return;
|
|
60
|
+
running = true;
|
|
58
61
|
// start syncing splits and segments
|
|
59
62
|
if (pollingManager) {
|
|
60
63
|
if (pushManager) {
|
|
@@ -70,13 +73,16 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
// start periodic data recording (events, impressions, telemetry).
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
if (submitter)
|
|
77
|
+
submitter.start();
|
|
75
78
|
},
|
|
76
79
|
/**
|
|
77
80
|
* Method used to stop/pause the syncManager.
|
|
78
81
|
*/
|
|
79
82
|
stop: function () {
|
|
83
|
+
if (!running)
|
|
84
|
+
return;
|
|
85
|
+
running = false;
|
|
80
86
|
// stop syncing
|
|
81
87
|
if (pushManager)
|
|
82
88
|
pushManager.stop();
|
|
@@ -85,7 +91,6 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
85
91
|
// stop periodic data recording (events, impressions, telemetry).
|
|
86
92
|
if (submitter)
|
|
87
93
|
submitter.stop();
|
|
88
|
-
running = false;
|
|
89
94
|
},
|
|
90
95
|
isRunning: function () {
|
|
91
96
|
return running;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isString, isFiniteNumber, isBoolean } from '../../utils/lang';
|
|
2
|
+
export function validateAttribute(log, attributeKey, attributeValue, method) {
|
|
3
|
+
if (!isString(attributeKey) || attributeKey.length === 0) {
|
|
4
|
+
log.warn(method + ": you passed an invalid attribute name, attribute name must be a non-empty string.");
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
var isStringVal = isString(attributeValue);
|
|
8
|
+
var isFiniteVal = isFiniteNumber(attributeValue);
|
|
9
|
+
var isBoolVal = isBoolean(attributeValue);
|
|
10
|
+
var isArrayVal = Array.isArray(attributeValue);
|
|
11
|
+
if (!(isStringVal || isFiniteVal || isBoolVal || isArrayVal)) { // If it's not of valid type.
|
|
12
|
+
log.warn(method + ": you passed an invalid attribute value for " + attributeKey + ". Acceptable types are: string, number, boolean and array of strings.");
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isObject } from '../lang';
|
|
2
|
+
import { validateAttribute } from './attribute';
|
|
2
3
|
import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants';
|
|
3
4
|
export function validateAttributes(log, maybeAttrs, method) {
|
|
4
5
|
// Attributes are optional
|
|
@@ -7,3 +8,13 @@ export function validateAttributes(log, maybeAttrs, method) {
|
|
|
7
8
|
log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']);
|
|
8
9
|
return false;
|
|
9
10
|
}
|
|
11
|
+
export function validateAttributesDeep(log, maybeAttributes, method) {
|
|
12
|
+
if (!validateAttributes(log, maybeAttributes, method))
|
|
13
|
+
return false;
|
|
14
|
+
var result = true;
|
|
15
|
+
Object.keys(maybeAttributes).forEach(function (attributeKey) {
|
|
16
|
+
if (!validateAttribute(log, attributeKey, maybeAttributes[attributeKey], method))
|
|
17
|
+
result = false;
|
|
18
|
+
});
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Deprecated hashing function, used for split bucketing. Replaced by murmur3
|
|
2
|
+
//
|
|
3
|
+
// JAVA reference implementation for the hashing function.
|
|
4
|
+
//
|
|
5
|
+
// int h = 0;
|
|
6
|
+
// for (int i = 0; i < key.length(); i++) {
|
|
7
|
+
// h = 31 * h + key.charAt(i);
|
|
8
|
+
// }
|
|
9
|
+
// return h ^ seed; // XOR the hash and seed
|
|
10
|
+
//
|
|
11
|
+
function ToInteger(x) {
|
|
12
|
+
x = Number(x);
|
|
13
|
+
return x < 0 ? Math.ceil(x) : Math.floor(x);
|
|
14
|
+
}
|
|
15
|
+
function modulo(a, b) {
|
|
16
|
+
return a - Math.floor(a / b) * b;
|
|
17
|
+
}
|
|
18
|
+
function ToUint32(x) {
|
|
19
|
+
return modulo(ToInteger(x), Math.pow(2, 32));
|
|
20
|
+
}
|
|
21
|
+
function ToInt32(x) {
|
|
22
|
+
var uint32 = ToUint32(x);
|
|
23
|
+
if (uint32 >= Math.pow(2, 31)) {
|
|
24
|
+
return uint32 - Math.pow(2, 32);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return uint32;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function hash(str, seed) {
|
|
31
|
+
var h = 0;
|
|
32
|
+
for (var i = 0; i < str.length; i++) {
|
|
33
|
+
h = ToInt32(ToInt32(31 * h) + str.charCodeAt(i));
|
|
34
|
+
}
|
|
35
|
+
return ToInt32(h ^ seed);
|
|
36
|
+
}
|
|
37
|
+
export function bucket(str, seed) {
|
|
38
|
+
return Math.abs(hash(str, seed) % 100) + 1;
|
|
39
|
+
}
|
|
@@ -3,7 +3,7 @@ export function timeout(ms, promise) {
|
|
|
3
3
|
return promise;
|
|
4
4
|
return new Promise(function (resolve, reject) {
|
|
5
5
|
var tid = setTimeout(function () {
|
|
6
|
-
reject(new Error("Operation timed out because it exceeded the configured time limit of " + ms + "ms."));
|
|
6
|
+
reject(new Error("Operation timed out because it exceeded the configured time limit of " + ms + " ms."));
|
|
7
7
|
}, ms);
|
|
8
8
|
promise.then(function (res) {
|
|
9
9
|
clearTimeout(tid);
|
|
@@ -100,7 +100,7 @@ export function settingsValidation(config, validationParams) {
|
|
|
100
100
|
// Startup periods
|
|
101
101
|
startup.requestTimeoutBeforeReady = fromSecondsToMillis(startup.requestTimeoutBeforeReady);
|
|
102
102
|
startup.readyTimeout = fromSecondsToMillis(startup.readyTimeout);
|
|
103
|
-
startup.eventsFirstPushWindow = fromSecondsToMillis(
|
|
103
|
+
startup.eventsFirstPushWindow = fromSecondsToMillis(startup.eventsFirstPushWindow);
|
|
104
104
|
// ensure a valid SDK mode
|
|
105
105
|
// @ts-ignore, modify readonly prop
|
|
106
106
|
withDefaults.mode = mode(withDefaults.core.authorizationKey, withDefaults.mode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splitsoftware/splitio-commons",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1-rc.0",
|
|
4
4
|
"description": "Split Javascript SDK common components",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "esm/index.js",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"jest-localstorage-mock": "^2.4.3",
|
|
65
65
|
"js-yaml": "^3.14.0",
|
|
66
66
|
"lodash": "^4.17.21",
|
|
67
|
-
"node-fetch": "^2.6.
|
|
67
|
+
"node-fetch": "^2.6.7",
|
|
68
68
|
"redis-server": "1.2.2",
|
|
69
69
|
"rimraf": "^3.0.2",
|
|
70
70
|
"ts-jest": "^27.0.5",
|
|
@@ -23,10 +23,10 @@ export const codesInfo: [number, string][] = codesWarn.concat([
|
|
|
23
23
|
[c.SUBMITTERS_PUSH_FULL_EVENTS_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full events queue and reseting timer.'],
|
|
24
24
|
[c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s %s.'],
|
|
25
25
|
[c.STREAMING_REFRESH_TOKEN, c.LOG_PREFIX_SYNC_STREAMING + 'Refreshing streaming token in %s seconds, and connecting streaming in %s seconds.'],
|
|
26
|
-
[c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect in %s seconds.'],
|
|
27
|
-
[c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + '
|
|
26
|
+
[c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect streaming in %s seconds.'],
|
|
27
|
+
[c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Connecting streaming.'],
|
|
28
28
|
[c.STREAMING_DISABLED, c.LOG_PREFIX_SYNC_STREAMING + 'Streaming is disabled for given Api key. Switching to polling mode.'],
|
|
29
|
-
[c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting
|
|
29
|
+
[c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting streaming.'],
|
|
30
30
|
[c.SYNC_START_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming not available. Starting polling.'],
|
|
31
31
|
[c.SYNC_CONTINUE_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming couldn\'t connect. Continue polling.'],
|
|
32
32
|
[c.SYNC_STOP_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming (re)connected. Syncing and stopping polling.'],
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -123,6 +123,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
|
|
|
123
123
|
getTreatmentWithConfig,
|
|
124
124
|
getTreatments,
|
|
125
125
|
getTreatmentsWithConfig,
|
|
126
|
-
track
|
|
126
|
+
track,
|
|
127
|
+
isBrowserClient: false
|
|
127
128
|
} as SplitIO.IClient | SplitIO.IAsyncClient;
|
|
128
129
|
}
|