@splitsoftware/splitio-commons 3.1.1-rc.1 → 3.1.1-rc.11

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.
Files changed (72) hide show
  1. package/CHANGES.txt +12 -1
  2. package/cjs/evaluator/index.js +11 -6
  3. package/cjs/presets/serverSide.js +3 -3
  4. package/cjs/sdkClient/client.js +3 -5
  5. package/cjs/sdkClient/sdkLifecycle.js +2 -0
  6. package/cjs/services/authProvider.js +23 -10
  7. package/cjs/services/secureSplitHttpClient.js +10 -13
  8. package/cjs/services/serviceApi.js +23 -21
  9. package/cjs/services/splitApi.js +10 -9
  10. package/cjs/storages/inRedis/RedisAdapter.js +8 -5
  11. package/cjs/sync/polling/pollingManagerCS.js +84 -79
  12. package/cjs/sync/polling/pollingManagerSS.js +43 -35
  13. package/cjs/sync/polling/updaters/definitionChangesUpdater.js +2 -2
  14. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  15. package/cjs/sync/streaming/AuthClient/index.js +13 -14
  16. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  17. package/cjs/sync/streaming/constants.js +2 -1
  18. package/cjs/sync/streaming/pushManager.js +5 -10
  19. package/cjs/sync/submitters/impressionsSubmitter.js +1 -1
  20. package/cjs/sync/syncManagerOnline.js +2 -7
  21. package/cjs/utils/Backoff.js +16 -4
  22. package/esm/evaluator/index.js +11 -6
  23. package/esm/presets/serverSide.js +3 -3
  24. package/esm/sdkClient/client.js +3 -5
  25. package/esm/sdkClient/sdkLifecycle.js +2 -0
  26. package/esm/services/authProvider.js +24 -11
  27. package/esm/services/secureSplitHttpClient.js +10 -13
  28. package/esm/services/serviceApi.js +24 -22
  29. package/esm/services/splitApi.js +10 -9
  30. package/esm/storages/inRedis/RedisAdapter.js +8 -5
  31. package/esm/sync/polling/pollingManagerCS.js +84 -79
  32. package/esm/sync/polling/pollingManagerSS.js +43 -35
  33. package/esm/sync/polling/updaters/definitionChangesUpdater.js +3 -3
  34. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  35. package/esm/sync/streaming/AuthClient/index.js +11 -11
  36. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  37. package/esm/sync/streaming/constants.js +1 -0
  38. package/esm/sync/streaming/pushManager.js +6 -11
  39. package/esm/sync/submitters/impressionsSubmitter.js +1 -1
  40. package/esm/sync/syncManagerOnline.js +2 -7
  41. package/esm/utils/Backoff.js +16 -4
  42. package/package.json +6 -7
  43. package/src/dtos/types.ts +1 -1
  44. package/src/evaluator/index.ts +11 -5
  45. package/src/evaluator/types.ts +1 -0
  46. package/src/presets/serverSide.ts +3 -3
  47. package/src/sdkClient/client.ts +3 -4
  48. package/src/sdkClient/sdkLifecycle.ts +3 -0
  49. package/src/services/authProvider.ts +41 -19
  50. package/src/services/secureSplitHttpClient.ts +10 -18
  51. package/src/services/serviceApi.ts +27 -24
  52. package/src/services/splitApi.ts +10 -9
  53. package/src/services/types.ts +5 -4
  54. package/src/storages/inRedis/RedisAdapter.ts +8 -5
  55. package/src/sync/polling/pollingManagerCS.ts +80 -78
  56. package/src/sync/polling/pollingManagerSS.ts +49 -45
  57. package/src/sync/polling/updaters/definitionChangesUpdater.ts +3 -3
  58. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  59. package/src/sync/streaming/AuthClient/index.ts +14 -15
  60. package/src/sync/streaming/AuthClient/types.ts +9 -13
  61. package/src/sync/streaming/SSEClient/index.ts +2 -2
  62. package/src/sync/streaming/SSEClient/types.ts +2 -2
  63. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  64. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  65. package/src/sync/streaming/constants.ts +1 -0
  66. package/src/sync/streaming/pushManager.ts +32 -42
  67. package/src/sync/streaming/types.ts +3 -2
  68. package/src/sync/submitters/impressionsSubmitter.ts +3 -3
  69. package/src/sync/submitters/types.ts +2 -2
  70. package/src/sync/syncManagerOnline.ts +2 -8
  71. package/src/utils/Backoff.ts +16 -4
  72. package/types/splitio.d.ts +5 -4
@@ -4,46 +4,54 @@ exports.pollingManagerSSFactory = void 0;
4
4
  var definitionsSyncTask_1 = require("./syncTasks/definitionsSyncTask");
5
5
  var segmentsSyncTask_1 = require("./syncTasks/segmentsSyncTask");
6
6
  var constants_1 = require("../../logger/constants");
7
+ var segmentChangesFetcher_1 = require("./fetchers/segmentChangesFetcher");
8
+ var splitChangesFetcher_1 = require("./fetchers/splitChangesFetcher");
7
9
  /**
8
10
  * Expose start / stop mechanism for pulling data from services.
9
11
  */
10
- function pollingManagerSSFactory(params, definitionChangesFetcher, segmentChangesFetcher) {
11
- var storage = params.storage, readiness = params.readiness, settings = params.settings;
12
- var log = settings.log;
13
- var segmentsSyncTask = (0, segmentsSyncTask_1.segmentsSyncTaskFactory)(segmentChangesFetcher, storage, readiness, settings);
14
- var definitionsSyncTask = (0, definitionsSyncTask_1.definitionsSyncTaskFactory)(definitionChangesFetcher, storage, readiness, settings, segmentsSyncTask);
15
- return {
16
- definitionsSyncTask: definitionsSyncTask,
17
- segmentsSyncTask: segmentsSyncTask,
18
- // Start periodic fetching (polling)
19
- start: function () {
20
- log.info(constants_1.POLLING_START);
21
- log.debug(constants_1.LOG_PREFIX_SYNC_POLLING + "".concat(definitionChangesFetcher.type, " will be refreshed each ").concat(settings.scheduler.featuresRefreshRate, " millis"));
22
- log.debug(constants_1.LOG_PREFIX_SYNC_POLLING + "segments will be refreshed each ".concat(settings.scheduler.segmentsRefreshRate, " millis"));
23
- var startingUp = definitionsSyncTask.start();
24
- if (startingUp) {
25
- startingUp.then(function () {
26
- if (definitionsSyncTask.isRunning())
27
- segmentsSyncTask.start();
12
+ function pollingManagerSSFactory(definitionFetcherFactory, segmentFetcherFactory) {
13
+ if (definitionFetcherFactory === void 0) { definitionFetcherFactory = splitChangesFetcher_1.splitChangesFetcherFactory; }
14
+ if (segmentFetcherFactory === void 0) { segmentFetcherFactory = function (params) { return (0, segmentChangesFetcher_1.segmentChangesFetcherFactory)(params.serviceApi.fetchSegmentChanges); }; }
15
+ return function (params) {
16
+ var storage = params.storage, readiness = params.readiness, settings = params.settings;
17
+ var log = settings.log;
18
+ var definitionChangesFetcher = definitionFetcherFactory(params);
19
+ var segmentChangesFetcher = segmentFetcherFactory(params);
20
+ var segmentsSyncTask = (0, segmentsSyncTask_1.segmentsSyncTaskFactory)(segmentChangesFetcher, storage, readiness, settings);
21
+ var definitionsSyncTask = (0, definitionsSyncTask_1.definitionsSyncTaskFactory)(definitionChangesFetcher, storage, readiness, settings, segmentsSyncTask);
22
+ return {
23
+ definitionsSyncTask: definitionsSyncTask,
24
+ segmentsSyncTask: segmentsSyncTask,
25
+ // Start periodic fetching (polling)
26
+ start: function () {
27
+ log.info(constants_1.POLLING_START);
28
+ log.debug(constants_1.LOG_PREFIX_SYNC_POLLING + "".concat(definitionChangesFetcher.type, " will be refreshed each ").concat(settings.scheduler.featuresRefreshRate, " millis"));
29
+ log.debug(constants_1.LOG_PREFIX_SYNC_POLLING + "segments will be refreshed each ".concat(settings.scheduler.segmentsRefreshRate, " millis"));
30
+ var startingUp = definitionsSyncTask.start();
31
+ if (startingUp) {
32
+ startingUp.then(function () {
33
+ if (definitionsSyncTask.isRunning())
34
+ segmentsSyncTask.start();
35
+ });
36
+ }
37
+ },
38
+ // Stop periodic fetching (polling)
39
+ stop: function () {
40
+ log.info(constants_1.POLLING_STOP);
41
+ if (definitionsSyncTask.isRunning())
42
+ definitionsSyncTask.stop();
43
+ if (segmentsSyncTask.isRunning())
44
+ segmentsSyncTask.stop();
45
+ },
46
+ // Used by SyncManager to know if running in polling mode.
47
+ isRunning: definitionsSyncTask.isRunning,
48
+ syncAll: function () {
49
+ // fetch definitions and segments. There is no need to catch this promise (`DefinitionChangesUpdater` is always resolved with a boolean value)
50
+ return definitionsSyncTask.execute().then(function () {
51
+ return segmentsSyncTask.execute();
28
52
  });
29
53
  }
30
- },
31
- // Stop periodic fetching (polling)
32
- stop: function () {
33
- log.info(constants_1.POLLING_STOP);
34
- if (definitionsSyncTask.isRunning())
35
- definitionsSyncTask.stop();
36
- if (segmentsSyncTask.isRunning())
37
- segmentsSyncTask.stop();
38
- },
39
- // Used by SyncManager to know if running in polling mode.
40
- isRunning: definitionsSyncTask.isRunning,
41
- syncAll: function () {
42
- // fetch definitions and segments. There is no need to catch this promise (`DefinitionChangesUpdater` is always resolved with a boolean value)
43
- return definitionsSyncTask.execute().then(function () {
44
- return segmentsSyncTask.execute();
45
- });
46
- }
54
+ };
47
55
  };
48
56
  }
49
57
  exports.pollingManagerSSFactory = pollingManagerSSFactory;
@@ -125,14 +125,14 @@ function definitionChangesUpdaterFactory(log, definitionChangesFetcher, storage,
125
125
  var since = sinces[0], rbSince = sinces[1];
126
126
  log.debug(constants_2.SYNC_FETCH, [definitionChangesFetcher.type, since, rbSince]);
127
127
  return Promise.resolve(instantUpdate ?
128
- instantUpdate.type === constants_4.SPLIT_UPDATE ?
128
+ instantUpdate.type === constants_4.RB_SEGMENT_UPDATE ?
129
+ { rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) } :
129
130
  // IFFU edge case: a change to definition that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
130
131
  Promise.resolve(rbSegments.contains(parseSegments(instantUpdate.payload, constants_3.IN_RULE_BASED_SEGMENT))).then(function (contains) {
131
132
  return contains ?
132
133
  { d: convertInstantUpdateToDefinitionChanges(instantUpdate) } :
133
134
  definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
134
135
  }) :
135
- { rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) } :
136
136
  definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator))
137
137
  .then(function (definitionChanges) {
138
138
  var usedSegments = new Set();
@@ -31,7 +31,7 @@ function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, read
31
31
  false :
32
32
  segmentChangesFetcher(since || -1, segmentName, noCache, till, _promiseDecorator).then(function (changes) {
33
33
  return Promise.all(changes.map(function (x) {
34
- log.debug("".concat(constants_2.LOG_PREFIX_SYNC, "Processing ").concat(segmentName, " with till = ").concat(x.till, ". Added: ").concat(x.added.length, ". Removed: ").concat(x.removed.length));
34
+ log.debug("".concat(constants_2.LOG_PREFIX_SYNC, "Processing ").concat(segmentName, " with since = ").concat(x.since, " and till = ").concat(x.till, ". Added: ").concat(x.added.length, ". Removed: ").concat(x.removed.length));
35
35
  return segments.update(segmentName, x.added, x.removed, x.till);
36
36
  })).then(function (updates) {
37
37
  return updates.some(function (update) { return update; });
@@ -1,16 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hashUserKey = exports.authenticateFactory = void 0;
3
+ exports.fetchAuthFactory = void 0;
4
4
  var objectAssign_1 = require("../../../utils/lang/objectAssign");
5
- var base64_1 = require("../../../utils/base64");
6
5
  var jwt_1 = require("../../../utils/jwt");
7
- var murmur3_1 = require("../../../utils/murmur3/murmur3");
8
6
  /**
9
7
  * Factory of authentication function.
10
8
  *
11
- * @param fetchAuth - `ServiceApi.fetchAuth` endpoint
9
+ * @param fetchAuth - /auth endpoint
12
10
  */
13
- function authenticateFactory(fetchAuth) {
11
+ function fetchAuthFactory(fetchAuth) {
14
12
  /**
15
13
  * Run authentication requests to Auth Server, and returns a promise that resolves with the decoded JTW token.
16
14
  * @param userKeys - set of user Keys to track membership updates. It is undefined for server-side API.
@@ -24,20 +22,21 @@ function authenticateFactory(fetchAuth) {
24
22
  if (typeof decodedToken.iat !== 'number' || typeof decodedToken.exp !== 'number')
25
23
  throw new Error('token properties "issuedAt" (iat) or "expiration" (exp) are missing or invalid');
26
24
  var channels = JSON.parse(decodedToken['x-ably-capability']);
27
- return (0, objectAssign_1.objectAssign)({
25
+ var credential = (0, objectAssign_1.objectAssign)({
28
26
  decodedToken: decodedToken,
29
27
  channels: channels
30
28
  }, json);
29
+ // The `/api/v3/auth` endpoint nests the streaming settings under `config.streaming`. Normalize
30
+ // them into the flat `pushEnabled`/`connDelay` properties used by the PushManager.
31
+ var streaming = credential.config && credential.config.streaming;
32
+ if (streaming) {
33
+ credential.pushEnabled = streaming.enabled;
34
+ credential.connDelay = streaming.delay;
35
+ }
36
+ return credential;
31
37
  }
32
38
  return json;
33
39
  });
34
40
  };
35
41
  }
36
- exports.authenticateFactory = authenticateFactory;
37
- /**
38
- * Returns the hash of a given user key
39
- */
40
- function hashUserKey(userKey) {
41
- return (0, base64_1.encodeToBase64)((0, murmur3_1.hash)(userKey, 0).toString());
42
- }
43
- exports.hashUserKey = hashUserKey;
42
+ exports.fetchAuthFactory = fetchAuthFactory;
@@ -74,6 +74,7 @@ function SSEHandlerFactory(log, pushEmitter, telemetryTracker) {
74
74
  switch (parsedData.type) {
75
75
  /* update events */
76
76
  case constants_1.SPLIT_UPDATE:
77
+ case constants_1.CONFIG_UPDATE:
77
78
  case constants_1.SEGMENT_UPDATE:
78
79
  case constants_1.MEMBERSHIPS_MS_UPDATE:
79
80
  case constants_1.MEMBERSHIPS_LS_UPDATE:
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ControlType = exports.OCCUPANCY = exports.CONTROL = exports.RB_SEGMENT_UPDATE = exports.SPLIT_UPDATE = exports.SPLIT_KILL = exports.SEGMENT_UPDATE = exports.MEMBERSHIPS_LS_UPDATE = exports.MEMBERSHIPS_MS_UPDATE = exports.PUSH_SUBSYSTEM_DOWN = exports.PUSH_SUBSYSTEM_UP = exports.PUSH_RETRYABLE_ERROR = exports.PUSH_NON_RETRYABLE_ERROR = exports.SECONDS_BEFORE_EXPIRATION = void 0;
3
+ exports.ControlType = exports.OCCUPANCY = exports.CONTROL = exports.RB_SEGMENT_UPDATE = exports.CONFIG_UPDATE = exports.SPLIT_UPDATE = exports.SPLIT_KILL = exports.SEGMENT_UPDATE = exports.MEMBERSHIPS_LS_UPDATE = exports.MEMBERSHIPS_MS_UPDATE = exports.PUSH_SUBSYSTEM_DOWN = exports.PUSH_SUBSYSTEM_UP = exports.PUSH_RETRYABLE_ERROR = exports.PUSH_NON_RETRYABLE_ERROR = exports.SECONDS_BEFORE_EXPIRATION = void 0;
4
4
  // time for refresh token
5
5
  exports.SECONDS_BEFORE_EXPIRATION = 600;
6
6
  // Internal SDK events, subscribed by SyncManager and PushManager
@@ -30,6 +30,7 @@ exports.MEMBERSHIPS_LS_UPDATE = 'MEMBERSHIPS_LS_UPDATE';
30
30
  exports.SEGMENT_UPDATE = 'SEGMENT_UPDATE';
31
31
  exports.SPLIT_KILL = 'SPLIT_KILL';
32
32
  exports.SPLIT_UPDATE = 'SPLIT_UPDATE';
33
+ exports.CONFIG_UPDATE = 'CONFIG_UPDATE';
33
34
  exports.RB_SEGMENT_UPDATE = 'RB_SEGMENT_UPDATE';
34
35
  // Control-type push notifications, handled by NotificationKeeper
35
36
  exports.CONTROL = 'CONTROL';
@@ -7,7 +7,6 @@ var SSEHandler_1 = require("./SSEHandler");
7
7
  var MySegmentsUpdateWorker_1 = require("./UpdateWorkers/MySegmentsUpdateWorker");
8
8
  var SegmentsUpdateWorker_1 = require("./UpdateWorkers/SegmentsUpdateWorker");
9
9
  var DefinitionsUpdateWorker_1 = require("./UpdateWorkers/DefinitionsUpdateWorker");
10
- var AuthClient_1 = require("./AuthClient");
11
10
  var lang_1 = require("../../utils/lang");
12
11
  var SSEClient_1 = require("./SSEClient");
13
12
  var key_1 = require("../../utils/key");
@@ -37,7 +36,6 @@ function pushManagerFactory(params, pollingManager) {
37
36
  log.warn(constants_2.STREAMING_FALLBACK, [e]);
38
37
  return;
39
38
  }
40
- var authenticate = (0, AuthClient_1.authenticateFactory)(serviceApi.fetchAuth);
41
39
  // init feedback loop
42
40
  var pushEmitter = new platform.EventEmitter();
43
41
  var sseHandler = (0, SSEHandler_1.SSEHandlerFactory)(log, pushEmitter, telemetryTracker);
@@ -47,8 +45,6 @@ function pushManagerFactory(params, pollingManager) {
47
45
  var segmentsUpdateWorker = userKey ? undefined : (0, SegmentsUpdateWorker_1.SegmentsUpdateWorker)(log, pollingManager.segmentsSyncTask, storage.segments);
48
46
  // For server-side we pass the segmentsSyncTask, used by DefinitionsUpdateWorker to fetch new segments
49
47
  var definitionsUpdateWorker = (0, DefinitionsUpdateWorker_1.DefinitionsUpdateWorker)(log, storage, pollingManager.definitionsSyncTask, readiness.definitions, telemetryTracker);
50
- // [Only for client-side] map of hashes to user keys, to dispatch membership update events to the corresponding MySegmentsUpdateWorker
51
- var userKeyHashes = {};
52
48
  // [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
53
49
  // Hash64 is used to process membership update events and dispatch actions to the corresponding MySegmentsUpdateWorker.
54
50
  var clients = {};
@@ -93,7 +89,7 @@ function pushManagerFactory(params, pollingManager) {
93
89
  log.info(constants_2.STREAMING_CONNECTING);
94
90
  disconnected = false;
95
91
  var userKeys = userKey ? Object.keys(clients) : undefined;
96
- authenticate(userKeys).then(function (authData) {
92
+ serviceApi.fetchAuth(userKeys).then(function (authData) {
97
93
  if (disconnected)
98
94
  return;
99
95
  // 'pushEnabled: false' is handled as a PUSH_NON_RETRYABLE_ERROR instead of PUSH_SUBSYSTEM_DOWN, in order to
@@ -181,8 +177,11 @@ function pushManagerFactory(params, pollingManager) {
181
177
  connectPush();
182
178
  });
183
179
  /** Functions related to synchronization (Queues and Workers in the spec) */
180
+ // Some notification types are specific to the Feature flags or Configs SDKs. Their handlers are
181
+ // registered unconditionally, since the SDK only subscribes to the channels of its own entities.
184
182
  pushEmitter.on(constants_1.SPLIT_KILL, definitionsUpdateWorker.killDefinition);
185
183
  pushEmitter.on(constants_1.SPLIT_UPDATE, definitionsUpdateWorker.put);
184
+ pushEmitter.on(constants_1.CONFIG_UPDATE, definitionsUpdateWorker.put);
186
185
  pushEmitter.on(constants_1.RB_SEGMENT_UPDATE, definitionsUpdateWorker.put);
187
186
  function handleMySegmentsUpdate(parsedData) {
188
187
  switch (parsedData.u) {
@@ -285,9 +284,7 @@ function pushManagerFactory(params, pollingManager) {
285
284
  },
286
285
  // [Only for client-side]
287
286
  add: function (userKey, mySegmentsSyncTask) {
288
- var hash = (0, AuthClient_1.hashUserKey)(userKey);
289
- if (!userKeyHashes[hash]) {
290
- userKeyHashes[hash] = userKey;
287
+ if (!clients[userKey]) {
291
288
  clients[userKey] = {
292
289
  hash64: (0, murmur3_64_1.hash64)(userKey),
293
290
  worker: (0, MySegmentsUpdateWorker_1.MySegmentsUpdateWorker)(log, storage, mySegmentsSyncTask, telemetryTracker)
@@ -308,8 +305,6 @@ function pushManagerFactory(params, pollingManager) {
308
305
  },
309
306
  // [Only for client-side]
310
307
  remove: function (userKey) {
311
- var hash = (0, AuthClient_1.hashUserKey)(userKey);
312
- delete userKeyHashes[hash];
313
308
  delete clients[userKey];
314
309
  }
315
310
  });
@@ -23,7 +23,7 @@ function fromImpressionsCollector(sendLabels, data) {
23
23
  b: entry.bucketingKey,
24
24
  pt: entry.pt,
25
25
  properties: entry.properties,
26
- // @ts-expect-error - entityType is not yet public. @TODO: add to SplitIO.ImpressionDTO type
26
+ // @TODO: add entityType to public SplitIO.ImpressionDTO type
27
27
  et: entry.entityType, // Definition type
28
28
  };
29
29
  })
@@ -8,8 +8,6 @@ var consent_1 = require("../consent");
8
8
  var constants_3 = require("../utils/constants");
9
9
  var constants_4 = require("../readiness/constants");
10
10
  var AbstractDefinitionsCacheSync_1 = require("../storages/AbstractDefinitionsCacheSync");
11
- var splitChangesFetcher_1 = require("./polling/fetchers/splitChangesFetcher");
12
- var segmentChangesFetcher_1 = require("./polling/fetchers/segmentChangesFetcher");
13
11
  /**
14
12
  * Online SyncManager factory.
15
13
  * Can be used for server-side API, and client-side API with or without multiple clients.
@@ -17,18 +15,15 @@ var segmentChangesFetcher_1 = require("./polling/fetchers/segmentChangesFetcher"
17
15
  * @param pollingManagerFactory - allows to specialize the SyncManager for server-side or client-side API by passing
18
16
  * `pollingManagerSSFactory` or `pollingManagerCSFactory` respectively.
19
17
  * @param pushManagerFactory - optional to build a SyncManager with or without streaming support
20
- * @param definitionChangesFetcherFactory - optional to replace the default split changes fetcher
21
18
  */
22
- function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory, definitionFetcherFactory, segmentFetcherFactory) {
23
- if (definitionFetcherFactory === void 0) { definitionFetcherFactory = splitChangesFetcher_1.splitChangesFetcherFactory; }
24
- if (segmentFetcherFactory === void 0) { segmentFetcherFactory = function (params) { return (0, segmentChangesFetcher_1.segmentChangesFetcherFactory)(params.serviceApi.fetchSegmentChanges); }; }
19
+ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
25
20
  /**
26
21
  * SyncManager factory for modular SDK
27
22
  */
28
23
  return function (params) {
29
24
  var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker, storage = params.storage, readiness = params.readiness;
30
25
  /** Polling Manager */
31
- var pollingManager = pollingManagerFactory && pollingManagerFactory(params, definitionFetcherFactory(params), segmentFetcherFactory(params));
26
+ var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
32
27
  /** Push Manager */
33
28
  var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
34
29
  pushManagerFactory(params, pollingManager) :
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Backoff = void 0;
4
4
  var Backoff = /** @class */ (function () {
5
5
  /**
6
- * Schedule function calls with exponential backoff
6
+ * Schedule function calls with exponential backoff.
7
+ * @param cb - The function to call. Must return a promise if used with `scheduleCallAsync`.
7
8
  */
8
9
  function Backoff(cb, baseMillis, maxMillis) {
9
10
  this.baseMillis = Backoff.__TEST__BASE_MILLIS || baseMillis || Backoff.DEFAULT_BASE_MILLIS;
@@ -20,11 +21,11 @@ var Backoff = /** @class */ (function () {
20
21
  var delayInMillis = Math.min(this.baseMillis * Math.pow(2, this.attempts), this.maxMillis);
21
22
  if (this.timeoutID)
22
23
  clearTimeout(this.timeoutID);
24
+ this.attempts++;
23
25
  this.timeoutID = setTimeout(function () {
24
26
  _this.timeoutID = undefined;
25
27
  _this.cb();
26
28
  }, delayInMillis);
27
- this.attempts++;
28
29
  return delayInMillis;
29
30
  };
30
31
  /**
@@ -38,21 +39,32 @@ var Backoff = /** @class */ (function () {
38
39
  clearTimeout(this.timeoutID);
39
40
  this.attempts++;
40
41
  return new Promise(function (resolve, reject) {
42
+ _this.pending = { resolve: resolve, reject: reject };
41
43
  _this.timeoutID = setTimeout(function () {
42
44
  _this.timeoutID = undefined;
45
+ _this.pending = undefined;
43
46
  _this.cb().then(resolve, reject);
44
47
  }, delayInMillis);
45
48
  });
46
49
  };
47
50
  /**
48
- * Reset the backoff attempts
51
+ * Reset the backoff attempts, canceling any scheduled `cb` call. If a `scheduleCallAsync` call is pending
52
+ * and the caller wants to settle it immediately instead of waiting for `cb` to be invoked, pass `settle`:
53
+ * `{ value }` to resolve, or `{ error }` to reject.
49
54
  */
50
- Backoff.prototype.reset = function () {
55
+ Backoff.prototype.reset = function (settle) {
51
56
  this.attempts = 0;
52
57
  if (this.timeoutID) {
53
58
  clearTimeout(this.timeoutID);
54
59
  this.timeoutID = undefined;
55
60
  }
61
+ if (settle && this.pending) {
62
+ if ('value' in settle)
63
+ this.pending.resolve(settle.value);
64
+ else
65
+ this.pending.reject(settle.error);
66
+ this.pending = undefined;
67
+ }
56
68
  };
57
69
  Backoff.DEFAULT_BASE_MILLIS = 1000; // 1 second
58
70
  Backoff.DEFAULT_MAX_MILLIS = 1800000; // 30 minutes
@@ -22,6 +22,12 @@ function treatmentsException(definitionNames) {
22
22
  });
23
23
  return evaluations;
24
24
  }
25
+ function setEvaluationDataFromDefinition(evaluation, definition) {
26
+ return objectAssign(evaluation, {
27
+ definition: definition,
28
+ config: definition.configurations && definition.configurations[evaluation.treatment] || null
29
+ });
30
+ }
25
31
  export function evaluateFeature(log, key, definitionName, attributes, storage) {
26
32
  var definition;
27
33
  try {
@@ -92,8 +98,8 @@ function getEvaluation(log, key, definition, attributes, storage) {
92
98
  var split = engineParser(log, definition, storage);
93
99
  var evaluation = split.getTreatment(key, attributes, evaluateFeature);
94
100
  return thenable(evaluation) ?
95
- evaluation.then(function (result) { return objectAssign(result, { definition: definition }); }) :
96
- objectAssign(evaluation, { definition: definition });
101
+ evaluation.then(function (result) { return setEvaluationDataFromDefinition(result, definition); }) :
102
+ setEvaluationDataFromDefinition(evaluation, definition);
97
103
  }
98
104
  return EVALUATION_DEFINITION_NOT_FOUND;
99
105
  }
@@ -124,11 +130,10 @@ export function evaluateDefaultTreatment(definitionName, storage) {
124
130
  }
125
131
  function getDefaultTreatment(definition) {
126
132
  if (definition) {
127
- return {
133
+ return setEvaluationDataFromDefinition({
128
134
  treatment: definition.defaultTreatment,
129
- label: NO_CONDITION_MATCH,
130
- definition: definition,
131
- };
135
+ label: NO_CONDITION_MATCH, // "default rule"
136
+ }, definition);
132
137
  }
133
138
  return EVALUATION_DEFINITION_NOT_FOUND;
134
139
  }
@@ -1,4 +1,4 @@
1
- import { serviceApiFactory } from '../services/serviceApi';
1
+ import { splitApiFactory } from '../services/splitApi';
2
2
  import { syncManagerOnlineFactory } from '../sync/syncManagerOnline';
3
3
  import { pushManagerFactory } from '../sync/streaming/pushManager';
4
4
  import { pollingManagerSSFactory } from '../sync/polling/pollingManagerSS';
@@ -6,10 +6,10 @@ import { InMemoryStorageFactory } from '../storages/inMemory/InMemoryStorage';
6
6
  import { sdkManagerFactory } from '../sdkManager';
7
7
  import { sdkClientMethodFactory } from '../sdkClient/sdkClientMethod';
8
8
  import { impressionObserverSSFactory } from '../trackers/impressionObserver/impressionObserverSS';
9
- var syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory, pushManagerFactory);
9
+ var syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory(), pushManagerFactory);
10
10
  export var serverSideModules = {
11
11
  storageFactory: InMemoryStorageFactory,
12
- serviceApiFactory: serviceApiFactory,
12
+ serviceApiFactory: splitApiFactory,
13
13
  syncManagerFactory: syncManagerOnlineSSFactory,
14
14
  sdkManagerFactory: sdkManagerFactory,
15
15
  sdkClientMethodFactory: sdkClientMethodFactory,
@@ -108,13 +108,11 @@ export function clientFactory(params) {
108
108
  }
109
109
  // Internal function
110
110
  function processEvaluation(evaluation, featureFlagName, key, withConfig, invokingMethodName, queue, options) {
111
- var _a;
112
111
  if (options === void 0) { options = {}; }
113
112
  var matchingKey = getMatching(key);
114
113
  var bucketingKey = getBucketing(key);
115
114
  var definition = evaluation.definition;
116
- var treatment = evaluation.treatment, label = evaluation.label;
117
- var config = ((_a = definition === null || definition === void 0 ? void 0 : definition.configurations) === null || _a === void 0 ? void 0 : _a[treatment]) || null;
115
+ var treatment = evaluation.treatment, label = evaluation.label, _a = evaluation.config, config = _a === void 0 ? null : _a;
118
116
  if (treatment === CONTROL) {
119
117
  var fallbackTreatment = fallbackCalculator(featureFlagName, label);
120
118
  treatment = fallbackTreatment.treatment;
@@ -131,10 +129,10 @@ export function clientFactory(params) {
131
129
  time: Date.now(),
132
130
  bucketingKey: bucketingKey,
133
131
  label: label,
134
- changeNumber: definition.changeNumber,
132
+ changeNumber: definition === null || definition === void 0 ? void 0 : definition.changeNumber,
135
133
  properties: stringify(options.properties)
136
134
  },
137
- disabled: options.impressionsDisabled || definition.impressionsDisabled
135
+ disabled: options.impressionsDisabled || (definition === null || definition === void 0 ? void 0 : definition.impressionsDisabled)
138
136
  });
139
137
  }
140
138
  if (withConfig) {
@@ -58,6 +58,8 @@ export function sdkLifecycleFactory(params, isSharedClient) {
58
58
  }
59
59
  // Stop background jobs
60
60
  syncManager && syncManager.stop();
61
+ // Stop the service API (the auth provider backoff retries specifically) before flushing,
62
+ // so that a pending/never-resolved auth retry doesn't block the flush forever
61
63
  serviceApi && serviceApi.stop();
62
64
  return __flush().then(function () {
63
65
  // Cleanup storage
@@ -1,4 +1,4 @@
1
- import { authenticateFactory } from '../sync/streaming/AuthClient';
1
+ import { fetchAuthFactory } from '../sync/streaming/AuthClient';
2
2
  import { Backoff } from '../utils/Backoff';
3
3
  import { LOG_PREFIX_SYNC_AUTH } from '../logger/constants';
4
4
  import { TOKEN } from '../utils/constants';
@@ -6,32 +6,35 @@ var SKEW_SECONDS = 30;
6
6
  function isExpired(credential) {
7
7
  return Date.now() / 1000 + SKEW_SECONDS >= credential.decodedToken.exp;
8
8
  }
9
+ function noCachedCredentialError() {
10
+ return new Error(LOG_PREFIX_SYNC_AUTH + 'stopped without a cached credential');
11
+ }
9
12
  /**
10
13
  * Factory of AuthProvider, which provides JWT credentials for authenticated HTTP requests.
11
14
  * Credentials are fetched lazily on demand, cached in memory, and retried with backoff on failure.
12
15
  */
13
16
  export function authProviderFactory(settings, splitHttpClient, telemetryTracker) {
14
17
  var urls = settings.urls, log = settings.log;
15
- function fetchAuth() {
18
+ var fetchAuth = fetchAuthFactory(function () {
16
19
  var url = "".concat(urls.auth, "/api/v3/auth?capabilities=config,aiconfig");
17
20
  return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN), false, true);
18
- }
19
- var authenticate = authenticateFactory(fetchAuth);
21
+ });
20
22
  var backoff = new Backoff(fetchCredential);
21
23
  var cachedCredential;
22
24
  var inFlightPromise;
23
25
  var stopped = false;
24
26
  function fetchCredential() {
25
- return authenticate().then(function (credential) {
27
+ return fetchAuth().then(function (credential) {
26
28
  log.info(LOG_PREFIX_SYNC_AUTH + 'credential fetched successfully');
27
29
  cachedCredential = credential;
28
30
  inFlightPromise = undefined;
29
31
  backoff.reset();
30
32
  return credential;
31
33
  }).catch(function (error) {
32
- // Avoid rejected promises and unnecessary retries after stop()
34
+ // Avoid unnecessary retries after stop(): fall back to any cached credential, or reject
35
+ // (never resolve with `undefined`, which callers would otherwise send as an unauthenticated request).
33
36
  if (stopped)
34
- return cachedCredential;
37
+ return cachedCredential || Promise.reject(noCachedCredentialError());
35
38
  if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
36
39
  log.error(LOG_PREFIX_SYNC_AUTH + 'non-retryable error fetching credential (status ' + error.statusCode + '): ' + error.message);
37
40
  inFlightPromise = undefined;
@@ -46,18 +49,28 @@ export function authProviderFactory(settings, splitHttpClient, telemetryTracker)
46
49
  if (cachedCredential && !isExpired(cachedCredential)) {
47
50
  return Promise.resolve(cachedCredential);
48
51
  }
52
+ // Reuse any fetch already in flight (e.g. one started just before stop()), instead of firing a redundant one.
53
+ if (inFlightPromise)
54
+ return inFlightPromise;
55
+ // No cached credential to fall back to and stopped: reject so callers don't send an unauthenticated request.
56
+ if (stopped && !cachedCredential) {
57
+ return Promise.reject(noCachedCredentialError());
58
+ }
49
59
  if (cachedCredential)
50
60
  log.debug(LOG_PREFIX_SYNC_AUTH + 'cached credential expired');
51
- return inFlightPromise || (inFlightPromise = fetchCredential());
61
+ // If stopped, this is a last, bounded refetch: fetchCredential()'s catch short-circuits (no backoff retry)
62
+ // once stopped, so it's a single attempt that falls back to the stale cached credential on failure.
63
+ return inFlightPromise = fetchCredential();
52
64
  },
53
65
  invalidate: function () {
54
66
  cachedCredential = undefined;
55
67
  },
56
68
  stop: function () {
57
69
  stopped = true;
58
- cachedCredential = undefined;
59
- inFlightPromise = undefined;
60
- backoff.reset();
70
+ // Keep any in-flight fetch and already-cached credential usable (e.g. by a final flush on destroy).
71
+ // If a retry is only pending (not actively in flight), settle it immediately instead of waiting out the
72
+ // backoff delay: with the cached credential if there is one, or rejected otherwise.
73
+ backoff.reset(cachedCredential ? { value: cachedCredential } : { error: noCachedCredentialError() });
61
74
  }
62
75
  };
63
76
  }
@@ -1,29 +1,26 @@
1
1
  import { __assign } from "tslib";
2
- import { splitHttpClientFactory } from './splitHttpClient';
3
- import { authProviderFactory } from './authProvider';
4
2
  /**
5
3
  * Factory of Secure HTTP client, which authenticates requests using a JWT token.
6
4
  * On 401 responses, invalidates the cached credential and retries once with a fresh token.
7
5
  *
8
- * @param settings - SDK settings
9
- * @param platform - object containing environment-specific dependencies
10
- * @param fetchAuth - function to fetch auth credentials from the /v2/auth endpoint
6
+ * @param splitHttpClient - `splitHttpClientFactory` to use for making requests
7
+ * @param authProvider - `authProviderFactory` to use for authentication
11
8
  */
12
- export function secureSplitHttpClientFactory(settings, platform, telemetryTracker) {
13
- var splitHttpClient = splitHttpClientFactory(settings, platform);
14
- var authProvider = authProviderFactory(settings, splitHttpClient, telemetryTracker);
15
- function makeRequest(url, options, latencyTracker, logErrorsAsInfo, token) {
16
- return splitHttpClient(url, __assign(__assign({}, options), { headers: __assign(__assign({}, options === null || options === void 0 ? void 0 : options.headers), { Authorization: "Bearer ".concat(token) }) }), latencyTracker, logErrorsAsInfo, true);
9
+ export function secureSplitHttpClientFactory(splitHttpClient, authProvider) {
10
+ function makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, token) {
11
+ return splitHttpClient(url, token ? __assign(__assign({}, options), { headers: __assign(__assign({}, options === null || options === void 0 ? void 0 : options.headers), { Authorization: "Bearer ".concat(token) }) }) : options, latencyTracker, logErrorsAsInfo, newVersionHeader);
17
12
  }
18
- var httpClient = function (url, options, latencyTracker, logErrorsAsInfo) {
13
+ var httpClient = function (url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt) {
14
+ if (newVersionHeader === void 0) { newVersionHeader = true; }
15
+ if (useJwt === void 0) { useJwt = true; }
19
16
  return authProvider.credential().then(function (credential) {
20
- return makeRequest(url, options, latencyTracker, logErrorsAsInfo, credential.token)
17
+ return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? credential.token : undefined)
21
18
  .catch(function (error) {
22
19
  if (error.statusCode === 401) {
23
20
  // retry once for 401, in case the token has just expired
24
21
  authProvider.invalidate();
25
22
  return authProvider.credential().then(function (newCredential) {
26
- return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newCredential.token);
23
+ return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? newCredential.token : undefined);
27
24
  });
28
25
  }
29
26
  throw error;