@splitsoftware/splitio 10.16.1 → 10.17.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.
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ exports.__esModule = true;
6
+ exports.default = void 0;
7
+
8
+ var _objectAssign = _interopRequireDefault(require("object-assign"));
9
+
10
+ var AttributesCacheInMemory = /*#__PURE__*/function () {
11
+ function AttributesCacheInMemory() {
12
+ this.attributesCache = {};
13
+ }
14
+ /**
15
+ * Create or update the value for the given attribute
16
+ *
17
+ * @param {string} attributeName attribute name
18
+ * @param {Object} attributeValue attribute value
19
+ * @returns {boolean} the attribute was stored
20
+ */
21
+
22
+
23
+ var _proto = AttributesCacheInMemory.prototype;
24
+
25
+ _proto.setAttribute = function setAttribute(attributeName, attributeValue) {
26
+ this.attributesCache[attributeName] = attributeValue;
27
+ return true;
28
+ }
29
+ /**
30
+ * Retrieves the value of a given attribute
31
+ *
32
+ * @param {string} attributeName attribute name
33
+ * @returns {Object?} stored attribute value
34
+ */
35
+ ;
36
+
37
+ _proto.getAttribute = function getAttribute(attributeName) {
38
+ return this.attributesCache[attributeName];
39
+ }
40
+ /**
41
+ * Create or update all the given attributes
42
+ *
43
+ * @param {[string, Object]} attributes attributes to create or update
44
+ * @returns {boolean} attributes were stored
45
+ */
46
+ ;
47
+
48
+ _proto.setAttributes = function setAttributes(attributes) {
49
+ this.attributesCache = (0, _objectAssign.default)(this.attributesCache, attributes);
50
+ return true;
51
+ }
52
+ /**
53
+ * Retrieve the full attributes map
54
+ *
55
+ * @returns {Map<string, Object>} stored attributes
56
+ */
57
+ ;
58
+
59
+ _proto.getAll = function getAll() {
60
+ return this.attributesCache;
61
+ }
62
+ /**
63
+ * Removes a given attribute from the map
64
+ *
65
+ * @param {string} attributeName attribute to remove
66
+ * @returns {boolean} attribute removed
67
+ */
68
+ ;
69
+
70
+ _proto.removeAttribute = function removeAttribute(attributeName) {
71
+ if (Object.keys(this.attributesCache).indexOf(attributeName) >= 0) {
72
+ delete this.attributesCache[attributeName];
73
+ return true;
74
+ }
75
+
76
+ return false;
77
+ }
78
+ /**
79
+ * Clears all attributes stored in the SDK
80
+ *
81
+ */
82
+ ;
83
+
84
+ _proto.clear = function clear() {
85
+ this.attributesCache = {};
86
+ return true;
87
+ };
88
+
89
+ return AttributesCacheInMemory;
90
+ }();
91
+
92
+ var _default = AttributesCacheInMemory;
93
+ exports.default = _default;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ exports.__esModule = true;
6
+ exports.validateAttribute = validateAttribute;
7
+
8
+ var _lang = require("../lang");
9
+
10
+ var _logger = _interopRequireDefault(require("../logger"));
11
+
12
+ var log = (0, _logger.default)('');
13
+
14
+ function validateAttribute(attributeKey, attributeValue, method) {
15
+ if (!(0, _lang.isString)(attributeKey) || attributeKey.length === 0) {
16
+ log.warn(method + ": you passed an invalid attribute name, attribute name must be a non-empty string.");
17
+ return false;
18
+ }
19
+
20
+ var isStringVal = (0, _lang.isString)(attributeValue);
21
+ var isFiniteVal = (0, _lang.numberIsFinite)(attributeValue);
22
+ var isBoolVal = (0, _lang.isBoolean)(attributeValue);
23
+ var isArrayVal = Array.isArray(attributeValue);
24
+
25
+ if (!(isStringVal || isFiniteVal || isBoolVal || isArrayVal)) {
26
+ // If it's not of valid type.
27
+ log.warn(method + ": you passed an invalid attribute value for " + attributeKey + ". Acceptable types are: string, number, boolean and array of strings.");
28
+ return false;
29
+ }
30
+
31
+ return true;
32
+ }
@@ -4,11 +4,14 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
 
5
5
  exports.__esModule = true;
6
6
  exports.validateAttributes = validateAttributes;
7
+ exports.validateAttributesDeep = validateAttributesDeep;
7
8
 
8
9
  var _lang = require("../lang");
9
10
 
10
11
  var _logger = _interopRequireDefault(require("../logger"));
11
12
 
13
+ var _attribute = require("./attribute");
14
+
12
15
  var log = (0, _logger.default)('');
13
16
 
14
17
  function validateAttributes(maybeAttrs, method) {
@@ -17,4 +20,13 @@ function validateAttributes(maybeAttrs, method) {
17
20
  return maybeAttrs;
18
21
  log.error(method + ": attributes must be a plain object.");
19
22
  return false;
23
+ }
24
+
25
+ function validateAttributesDeep(maybeAttributes, method) {
26
+ if (!validateAttributes(maybeAttributes, method)) return false;
27
+ var result = true;
28
+ Object.keys(maybeAttributes).forEach(function (attributeKey) {
29
+ if (!(0, _attribute.validateAttribute)(attributeKey, maybeAttributes[attributeKey], method)) result = false;
30
+ });
31
+ return result;
20
32
  }
@@ -44,7 +44,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44
44
  See the License for the specific language governing permissions and
45
45
  limitations under the License.
46
46
  **/
47
- var version = '10.16.1';
47
+ var version = '10.17.1';
48
48
  var eventsEndpointMatcher = /^\/(testImpressions|metrics|events)/;
49
49
  var authEndpointMatcher = /^\/v2\/auth/;
50
50
  var streamingEndpointMatcher = /^\/(sse|event-stream)/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio",
3
- "version": "10.16.1",
3
+ "version": "10.17.1",
4
4
  "description": "Split SDK",
5
5
  "files": [
6
6
  "README.md",
@@ -0,0 +1,112 @@
1
+ import ClientInputValidationLayer from './inputValidation';
2
+ import AttributesCacheInMemory from '../storage/AttributesCache/InMemory';
3
+ import { validateAttributesDeep } from '../utils/inputValidation/attributes';
4
+ import logFactory from '../utils/logger';
5
+ import objectAssign from 'object-assign';
6
+ const log = logFactory('splitio-client');
7
+
8
+ /**
9
+ * Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
10
+ */
11
+ export default function ClientAttributesDecorationLayer(context, isKeyBinded, isTTBinded) {
12
+
13
+ const client = ClientInputValidationLayer(context, isKeyBinded, isTTBinded);
14
+
15
+ const attributeStorage = new AttributesCacheInMemory();
16
+
17
+ // Keep a reference to the original methods
18
+ const clientGetTreatment = client.getTreatment;
19
+ const clientGetTreatmentWithConfig = client.getTreatmentWithConfig;
20
+ const clientGetTreatments = client.getTreatments;
21
+ const clientGetTreatmentsWithConfig = client.getTreatmentsWithConfig;
22
+
23
+ /**
24
+ * Add an attribute to client's in memory attributes storage
25
+ *
26
+ * @param {string} attributeName Attrinute name
27
+ * @param {string, number, boolean, list} attributeValue Attribute value
28
+ * @returns {boolean} true if the attribute was stored and false otherways
29
+ */
30
+ client.setAttribute = (attributeName, attributeValue) => {
31
+ const attribute = {};
32
+ attribute[attributeName] = attributeValue;
33
+ if (!validateAttributesDeep(attribute)) return false;
34
+ log.debug(`stored ${attributeValue} for attribute ${attributeName}`);
35
+ return attributeStorage.setAttribute(attributeName, attributeValue);
36
+ };
37
+
38
+ /**
39
+ * Returns the attribute with the given key
40
+ *
41
+ * @param {string} attributeName Attribute name
42
+ * @returns {Object} Attribute with the given key
43
+ */
44
+ client.getAttribute = (attributeName) => {
45
+ log.debug(`retrieved attribute ${attributeName}`);
46
+ return attributeStorage.getAttribute(attributeName + '');
47
+ };
48
+
49
+ /**
50
+ * Add to client's in memory attributes storage the attributes in 'attributes'
51
+ *
52
+ * @param {Object} attributes Object with attributes to store
53
+ * @returns true if attributes were stored an false otherways
54
+ */
55
+ client.setAttributes = (attributes) => {
56
+ if (!validateAttributesDeep(attributes)) return false;
57
+ return attributeStorage.setAttributes(attributes);
58
+ };
59
+
60
+ /**
61
+ * Return all the attributes stored in client's in memory attributes storage
62
+ *
63
+ * @returns {Object} returns all the stored attributes
64
+ */
65
+ client.getAttributes = () => {
66
+ return attributeStorage.getAll();
67
+ };
68
+
69
+ /**
70
+ * Removes from client's in memory attributes storage the attribute with the given key
71
+ *
72
+ * @param {string} attributeName
73
+ * @returns {boolean} true if attribute was removed and false otherways
74
+ */
75
+ client.removeAttribute = (attributeName) => {
76
+ log.debug(`removed attribute ${attributeName}`);
77
+ return attributeStorage.removeAttribute(attributeName + '');
78
+ };
79
+
80
+ /**
81
+ * Remove all the stored attributes in the client's in memory attribute storage
82
+ */
83
+ client.clearAttributes = () => {
84
+ return attributeStorage.clear();
85
+ };
86
+
87
+ client.getTreatment = (maybeKey, maybeSplit, maybeAttributes) => {
88
+ return clientGetTreatment(maybeKey, maybeSplit, combineAttributes(maybeAttributes));
89
+ };
90
+
91
+ client.getTreatmentWithConfig = (maybeKey, maybeSplit, maybeAttributes) => {
92
+ return clientGetTreatmentWithConfig(maybeKey, maybeSplit, combineAttributes(maybeAttributes));
93
+ };
94
+
95
+ client.getTreatments = (maybeKey, maybeSplits, maybeAttributes) => {
96
+ return clientGetTreatments(maybeKey, maybeSplits, combineAttributes(maybeAttributes));
97
+ };
98
+
99
+ client.getTreatmentsWithConfig = (maybeKey, maybeSplits, maybeAttributes) => {
100
+ return clientGetTreatmentsWithConfig(maybeKey, maybeSplits, combineAttributes(maybeAttributes));
101
+ };
102
+
103
+ function combineAttributes(maybeAttributes) {
104
+ const storedAttributes = attributeStorage.getAll();
105
+ if (Object.keys(storedAttributes).length > 0) {
106
+ return objectAssign({}, storedAttributes, maybeAttributes);
107
+ }
108
+ return maybeAttributes;
109
+ }
110
+
111
+ return client;
112
+ }
@@ -1,5 +1,5 @@
1
1
  import { get } from '../utils/lang';
2
- import ClientWithInputValidationLayer from './inputValidation';
2
+ import ClientAttributesDecorationLayer from './attributesDecoration';
3
3
  import { LOCALHOST_MODE } from '../utils/constants';
4
4
  import {
5
5
  validateKey,
@@ -25,7 +25,7 @@ function BrowserClientFactory(context) {
25
25
  trackBindings.push(tt);
26
26
  }
27
27
 
28
- const client = ClientWithInputValidationLayer(context, true, trackBindings.length > 1);
28
+ const client = ClientAttributesDecorationLayer(context, true, trackBindings.length > 1);
29
29
  client.isBrowserClient = true;
30
30
 
31
31
  // In the browser land, we can bind the key and the traffic type (if provided)
@@ -17,7 +17,6 @@ limitations under the License.
17
17
  import Engine from '../';
18
18
  import thenable from '../../utils/promise/thenable';
19
19
  import * as LabelsConstants from '../../utils/labels';
20
- import { get } from '../../utils/lang';
21
20
  import { CONTROL } from '../../utils/constants';
22
21
 
23
22
  const treatmentException = {
@@ -103,17 +102,17 @@ function getEvaluation(
103
102
  const split = Engine.parse(splitJSON, storage);
104
103
  evaluation = split.getTreatment(key, attributes, evaluateFeature);
105
104
 
106
- // If the storage is async, evaluation and changeNumber will return a thenable
105
+ // If the storage is async and the evaluated split uses segment, evaluation is thenable
107
106
  if (thenable(evaluation)) {
108
107
  return evaluation.then(result => {
109
108
  result.changeNumber = split.getChangeNumber();
110
- result.config = get(splitJSON, `configurations.${result.treatment}`, null);
109
+ result.config = splitJSON.configurations && splitJSON.configurations[result.treatment] || null;
111
110
 
112
111
  return result;
113
112
  });
114
113
  } else {
115
114
  evaluation.changeNumber = split.getChangeNumber(); // Always sync and optional
116
- evaluation.config = get(splitJSON, `configurations.${evaluation.treatment}`, null);
115
+ evaluation.config = splitJSON.configurations && splitJSON.configurations[evaluation.treatment] || null;
117
116
  }
118
117
  }
119
118
 
@@ -120,10 +120,11 @@ export default function SplitChangesUpdaterFactory(context, isNode = false) {
120
120
  return false;
121
121
  });
122
122
 
123
- // After triggering the requests, if we have cached splits information let's notify
124
- // that asynchronously, to let attach a listener for SDK_READY_FROM_CACHE
123
+ // After triggering the requests, if we have cached splits information let's notify that.
124
+ // There is no need to emit the event asynchronously to let attach a listener for SDK_READY_FROM_CACHE,
125
+ // because the current event-loop callback executes after SplitFactory is created.
125
126
  if (startingUp && storage.splits.checkCache()) {
126
- setTimeout(splitsEventEmitter.emit(splitsEventEmitter.SDK_SPLITS_CACHE_LOADED), 0);
127
+ splitsEventEmitter.emit(splitsEventEmitter.SDK_SPLITS_CACHE_LOADED);
127
128
  }
128
129
 
129
130
  return fetcherPromise;
@@ -37,6 +37,7 @@ function GateContext() {
37
37
  let status = 0;
38
38
 
39
39
  gate.on(Events.READINESS_GATE_CHECK_STATE, () => {
40
+ // @TODO catch and handle user callback errors. Required for localhost and consumer modes. In standalone mode, it is done in 'SplitChangesUpdater'.
40
41
  if (status !== SDK_FIRE_UPDATE && splitsStatus + segmentsStatus === SDK_FIRE_READY) {
41
42
  status = SDK_FIRE_UPDATE;
42
43
  gate.emit(Events.SDK_READY);
@@ -54,7 +55,12 @@ function GateContext() {
54
55
  });
55
56
 
56
57
  splits.once(Events.SDK_SPLITS_CACHE_LOADED, () => {
57
- gate.emit(Events.SDK_READY_FROM_CACHE);
58
+ try {
59
+ gate.emit(Events.SDK_READY_FROM_CACHE);
60
+ } catch (e) {
61
+ // handle user callback errors
62
+ setTimeout(() => { throw e; }, 0);
63
+ }
58
64
  });
59
65
 
60
66
  segments.on(Events.SDK_SEGMENTS_ARRIVED, () => {
@@ -0,0 +1,76 @@
1
+ import objectAssign from 'object-assign';
2
+
3
+ class AttributesCacheInMemory {
4
+
5
+ constructor() {
6
+ this.attributesCache = {};
7
+ }
8
+
9
+ /**
10
+ * Create or update the value for the given attribute
11
+ *
12
+ * @param {string} attributeName attribute name
13
+ * @param {Object} attributeValue attribute value
14
+ * @returns {boolean} the attribute was stored
15
+ */
16
+ setAttribute(attributeName, attributeValue) {
17
+ this.attributesCache[attributeName] = attributeValue;
18
+ return true;
19
+ }
20
+
21
+ /**
22
+ * Retrieves the value of a given attribute
23
+ *
24
+ * @param {string} attributeName attribute name
25
+ * @returns {Object?} stored attribute value
26
+ */
27
+ getAttribute(attributeName) {
28
+ return this.attributesCache[attributeName];
29
+ }
30
+
31
+ /**
32
+ * Create or update all the given attributes
33
+ *
34
+ * @param {[string, Object]} attributes attributes to create or update
35
+ * @returns {boolean} attributes were stored
36
+ */
37
+ setAttributes(attributes) {
38
+ this.attributesCache = objectAssign(this.attributesCache, attributes);
39
+ return true;
40
+ }
41
+
42
+ /**
43
+ * Retrieve the full attributes map
44
+ *
45
+ * @returns {Map<string, Object>} stored attributes
46
+ */
47
+ getAll() {
48
+ return this.attributesCache;
49
+ }
50
+
51
+ /**
52
+ * Removes a given attribute from the map
53
+ *
54
+ * @param {string} attributeName attribute to remove
55
+ * @returns {boolean} attribute removed
56
+ */
57
+ removeAttribute(attributeName) {
58
+ if (Object.keys(this.attributesCache).indexOf(attributeName) >= 0) {
59
+ delete this.attributesCache[attributeName];
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ /**
66
+ * Clears all attributes stored in the SDK
67
+ *
68
+ */
69
+ clear() {
70
+ this.attributesCache = {};
71
+ return true;
72
+ }
73
+
74
+ }
75
+
76
+ export default AttributesCacheInMemory;
@@ -0,0 +1,22 @@
1
+ import { isString, numberIsFinite, isBoolean } from '../lang';
2
+ import logFactory from '../logger';
3
+ const log = logFactory('');
4
+
5
+ export function validateAttribute(attributeKey, attributeValue, method) {
6
+ if (!isString(attributeKey) || attributeKey.length === 0){
7
+ log.warn(`${method}: you passed an invalid attribute name, attribute name must be a non-empty string.`);
8
+ return false;
9
+ }
10
+
11
+ const isStringVal = isString(attributeValue);
12
+ const isFiniteVal = numberIsFinite(attributeValue);
13
+ const isBoolVal = isBoolean(attributeValue);
14
+ const isArrayVal = Array.isArray(attributeValue);
15
+
16
+ if (!(isStringVal || isFiniteVal || isBoolVal || isArrayVal)) { // If it's not of valid type.
17
+ log.warn(`${method}: you passed an invalid attribute value for ${attributeKey}. Acceptable types are: string, number, boolean and array of strings.`);
18
+ return false;
19
+ }
20
+
21
+ return true;
22
+ }
@@ -1,5 +1,6 @@
1
1
  import { isObject } from '../lang';
2
2
  import logFactory from '../logger';
3
+ import { validateAttribute } from './attribute';
3
4
  const log = logFactory('');
4
5
 
5
6
  export function validateAttributes(maybeAttrs, method) {
@@ -10,3 +11,16 @@ export function validateAttributes(maybeAttrs, method) {
10
11
  log.error(`${method}: attributes must be a plain object.`);
11
12
  return false;
12
13
  }
14
+
15
+ export function validateAttributesDeep(maybeAttributes, method) {
16
+ if (!validateAttributes(maybeAttributes, method)) return false;
17
+
18
+ let result = true;
19
+ Object.keys(maybeAttributes).forEach(attributeKey => {
20
+ if (!validateAttribute(attributeKey, maybeAttributes[attributeKey], method))
21
+ result = false;
22
+ });
23
+
24
+ return result;
25
+
26
+ }
@@ -27,7 +27,7 @@ import { API } from '../../utils/logger';
27
27
  import { STANDALONE_MODE, STORAGE_MEMORY, CONSUMER_MODE, OPTIMIZED } from '../../utils/constants';
28
28
  import validImpressionsMode from './impressionsMode';
29
29
 
30
- const version = '10.16.1';
30
+ const version = '10.17.1';
31
31
  const eventsEndpointMatcher = /^\/(testImpressions|metrics|events)/;
32
32
  const authEndpointMatcher = /^\/v2\/auth/;
33
33
  const streamingEndpointMatcher = /^\/(sse|event-stream)/;
package/types/index.d.ts CHANGED
@@ -24,5 +24,5 @@ declare module JsSdk {
24
24
  * The settings parameter should be an object that complies with the SplitIO.IBrowserSettings.
25
25
  * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#configuration}
26
26
  */
27
- export function SplitFactory(settings: SplitIO.IBrowserSettings): SplitIO.ISDK;
27
+ export function SplitFactory(settings: SplitIO.IBrowserSettings): SplitIO.IBrowserSDK;
28
28
  }