@splitsoftware/splitio-commons 3.1.1-rc.0 → 3.1.1-rc.10

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 (241) hide show
  1. package/CHANGES.txt +10 -1
  2. package/cjs/evaluator/condition/index.js +1 -1
  3. package/cjs/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +8 -5
  4. package/cjs/evaluator/index.js +69 -81
  5. package/cjs/listeners/browser.js +1 -2
  6. package/cjs/listeners/node.js +4 -4
  7. package/cjs/logger/constants.js +20 -15
  8. package/cjs/logger/messages/debug.js +6 -5
  9. package/cjs/logger/messages/error.js +3 -3
  10. package/cjs/logger/messages/info.js +4 -3
  11. package/cjs/logger/messages/warn.js +7 -6
  12. package/cjs/presets/serverSide.js +2 -2
  13. package/cjs/readiness/constants.js +4 -5
  14. package/cjs/readiness/readinessManager.js +23 -23
  15. package/cjs/readiness/sdkReadinessManager.js +17 -1
  16. package/cjs/sdkClient/client.js +34 -9
  17. package/cjs/sdkClient/clientInputValidation.js +17 -6
  18. package/cjs/sdkClient/sdkClient.js +60 -3
  19. package/cjs/sdkFactory/index.js +15 -11
  20. package/cjs/sdkManager/index.js +5 -5
  21. package/cjs/services/decorateHeaders.js +1 -2
  22. package/cjs/services/splitApi.js +1 -14
  23. package/cjs/services/splitHttpClient.js +5 -3
  24. package/cjs/storages/AbstractSplitsCacheAsync.js +51 -0
  25. package/cjs/storages/AbstractSplitsCacheSync.js +76 -0
  26. package/cjs/storages/KeyBuilder.js +6 -6
  27. package/cjs/storages/KeyBuilderCS.js +5 -5
  28. package/cjs/storages/KeyBuilderSS.js +2 -2
  29. package/cjs/storages/getRolloutPlan.js +3 -3
  30. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +2 -1
  31. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +10 -10
  32. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +194 -0
  33. package/cjs/storages/inLocalStorage/index.js +6 -6
  34. package/cjs/storages/inLocalStorage/validateCache.js +3 -3
  35. package/cjs/storages/inMemory/InMemoryStorage.js +4 -4
  36. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -5
  37. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +5 -5
  38. package/cjs/storages/inMemory/SegmentsCacheInMemory.js +1 -1
  39. package/cjs/storages/inMemory/SplitsCacheInMemory.js +121 -0
  40. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +5 -5
  41. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +1 -1
  42. package/cjs/storages/inRedis/RedisAdapter.js +8 -5
  43. package/cjs/storages/inRedis/{DefinitionsCacheInRedis.js → SplitsCacheInRedis.js} +77 -77
  44. package/cjs/storages/inRedis/index.js +2 -2
  45. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +1 -1
  46. package/cjs/storages/pluggable/SplitsCachePluggable.js +212 -0
  47. package/cjs/storages/pluggable/index.js +2 -2
  48. package/cjs/storages/setRolloutPlan.js +4 -4
  49. package/cjs/storages/utils.js +0 -1
  50. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +5 -5
  51. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +15 -24
  52. package/cjs/sync/polling/pollingManagerCS.js +20 -20
  53. package/cjs/sync/polling/pollingManagerSS.js +15 -15
  54. package/cjs/sync/polling/syncTasks/segmentsSyncTask.js +3 -2
  55. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +13 -0
  56. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +3 -3
  57. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +6 -6
  58. package/cjs/sync/polling/updaters/{definitionChangesUpdater.js → splitChangesUpdater.js} +68 -65
  59. package/cjs/sync/streaming/AuthClient/index.js +1 -1
  60. package/cjs/sync/streaming/SSEHandler/NotificationKeeper.js +1 -1
  61. package/cjs/sync/streaming/SSEHandler/index.js +1 -1
  62. package/cjs/sync/streaming/UpdateWorkers/{DefinitionsUpdateWorker.js → SplitsUpdateWorker.js} +19 -16
  63. package/cjs/sync/streaming/constants.js +3 -3
  64. package/cjs/sync/streaming/parseUtils.js +11 -11
  65. package/cjs/sync/streaming/pushManager.js +16 -16
  66. package/cjs/sync/submitters/eventsSubmitter.js +1 -1
  67. package/cjs/sync/submitters/impressionCountsSubmitter.js +1 -1
  68. package/cjs/sync/submitters/impressionsSubmitter.js +4 -5
  69. package/cjs/sync/submitters/telemetrySubmitter.js +4 -3
  70. package/cjs/sync/submitters/uniqueKeysSubmitter.js +1 -1
  71. package/cjs/sync/syncManagerOnline.js +7 -12
  72. package/cjs/utils/Backoff.js +0 -20
  73. package/cjs/utils/inputValidation/index.js +7 -7
  74. package/cjs/utils/inputValidation/isOperational.js +4 -4
  75. package/cjs/utils/inputValidation/{definition.js → split.js} +10 -10
  76. package/cjs/utils/inputValidation/{definitionExistence.js → splitExistence.js} +6 -6
  77. package/cjs/utils/inputValidation/splits.js +25 -0
  78. package/cjs/utils/labels/index.js +3 -3
  79. package/cjs/utils/settingsValidation/index.js +0 -2
  80. package/cjs/utils/settingsValidation/splitFilters.js +3 -3
  81. package/cjs/utils/settingsValidation/url.js +2 -6
  82. package/esm/evaluator/condition/index.js +1 -1
  83. package/esm/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +8 -5
  84. package/esm/evaluator/index.js +69 -80
  85. package/esm/listeners/browser.js +1 -2
  86. package/esm/listeners/node.js +4 -4
  87. package/esm/logger/constants.js +17 -12
  88. package/esm/logger/messages/debug.js +6 -5
  89. package/esm/logger/messages/error.js +3 -3
  90. package/esm/logger/messages/info.js +4 -3
  91. package/esm/logger/messages/warn.js +7 -6
  92. package/esm/presets/serverSide.js +2 -2
  93. package/esm/readiness/constants.js +3 -4
  94. package/esm/readiness/readinessManager.js +24 -24
  95. package/esm/readiness/sdkReadinessManager.js +18 -2
  96. package/esm/sdkClient/client.js +35 -9
  97. package/esm/sdkClient/clientInputValidation.js +19 -8
  98. package/esm/sdkClient/sdkClient.js +60 -3
  99. package/esm/sdkFactory/index.js +16 -12
  100. package/esm/sdkManager/index.js +6 -6
  101. package/esm/services/decorateHeaders.js +1 -2
  102. package/esm/services/splitApi.js +1 -14
  103. package/esm/services/splitHttpClient.js +5 -3
  104. package/esm/storages/AbstractSplitsCacheAsync.js +48 -0
  105. package/esm/storages/AbstractSplitsCacheSync.js +71 -0
  106. package/esm/storages/KeyBuilder.js +6 -6
  107. package/esm/storages/KeyBuilderCS.js +5 -5
  108. package/esm/storages/KeyBuilderSS.js +2 -2
  109. package/esm/storages/getRolloutPlan.js +3 -3
  110. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +2 -1
  111. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +7 -7
  112. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +191 -0
  113. package/esm/storages/inLocalStorage/index.js +6 -6
  114. package/esm/storages/inLocalStorage/validateCache.js +3 -3
  115. package/esm/storages/inMemory/InMemoryStorage.js +4 -4
  116. package/esm/storages/inMemory/InMemoryStorageCS.js +5 -5
  117. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +2 -2
  118. package/esm/storages/inMemory/SegmentsCacheInMemory.js +1 -1
  119. package/esm/storages/inMemory/SplitsCacheInMemory.js +118 -0
  120. package/esm/storages/inMemory/TelemetryCacheInMemory.js +5 -5
  121. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +1 -1
  122. package/esm/storages/inRedis/RedisAdapter.js +8 -5
  123. package/esm/storages/inRedis/{DefinitionsCacheInRedis.js → SplitsCacheInRedis.js} +76 -76
  124. package/esm/storages/inRedis/index.js +2 -2
  125. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +1 -1
  126. package/esm/storages/pluggable/SplitsCachePluggable.js +209 -0
  127. package/esm/storages/pluggable/index.js +2 -2
  128. package/esm/storages/setRolloutPlan.js +4 -4
  129. package/esm/storages/utils.js +0 -1
  130. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +6 -6
  131. package/esm/sync/polling/fetchers/splitChangesFetcher.js +16 -25
  132. package/esm/sync/polling/pollingManagerCS.js +17 -17
  133. package/esm/sync/polling/pollingManagerSS.js +15 -15
  134. package/esm/sync/polling/syncTasks/segmentsSyncTask.js +3 -2
  135. package/esm/sync/polling/syncTasks/splitsSyncTask.js +9 -0
  136. package/esm/sync/polling/updaters/mySegmentsUpdater.js +3 -3
  137. package/esm/sync/polling/updaters/segmentChangesUpdater.js +7 -7
  138. package/esm/sync/polling/updaters/{definitionChangesUpdater.js → splitChangesUpdater.js} +68 -65
  139. package/esm/sync/streaming/AuthClient/index.js +1 -1
  140. package/esm/sync/streaming/SSEHandler/NotificationKeeper.js +2 -2
  141. package/esm/sync/streaming/SSEHandler/index.js +2 -2
  142. package/esm/sync/streaming/UpdateWorkers/{DefinitionsUpdateWorker.js → SplitsUpdateWorker.js} +18 -15
  143. package/esm/sync/streaming/constants.js +2 -2
  144. package/esm/sync/streaming/parseUtils.js +9 -9
  145. package/esm/sync/streaming/pushManager.js +18 -18
  146. package/esm/sync/submitters/eventsSubmitter.js +1 -1
  147. package/esm/sync/submitters/impressionCountsSubmitter.js +1 -1
  148. package/esm/sync/submitters/impressionsSubmitter.js +4 -5
  149. package/esm/sync/submitters/telemetrySubmitter.js +4 -3
  150. package/esm/sync/submitters/uniqueKeysSubmitter.js +1 -1
  151. package/esm/sync/syncManagerOnline.js +6 -11
  152. package/esm/utils/Backoff.js +0 -20
  153. package/esm/utils/inputValidation/index.js +3 -3
  154. package/esm/utils/inputValidation/isOperational.js +4 -4
  155. package/esm/utils/inputValidation/{definition.js → split.js} +8 -8
  156. package/esm/utils/inputValidation/splitExistence.js +15 -0
  157. package/esm/utils/inputValidation/splits.js +21 -0
  158. package/esm/utils/labels/index.js +2 -2
  159. package/esm/utils/settingsValidation/index.js +0 -2
  160. package/esm/utils/settingsValidation/splitFilters.js +3 -3
  161. package/esm/utils/settingsValidation/url.js +2 -6
  162. package/package.json +6 -7
  163. package/src/dtos/types.ts +1 -1
  164. package/src/evaluator/Engine.ts +3 -3
  165. package/src/evaluator/condition/index.ts +2 -2
  166. package/src/evaluator/index.ts +27 -35
  167. package/src/evaluator/types.ts +2 -6
  168. package/src/presets/serverSide.ts +3 -3
  169. package/src/sdkClient/client.ts +22 -24
  170. package/src/sdkClient/sdkLifecycle.ts +3 -0
  171. package/src/services/authProvider.ts +41 -19
  172. package/src/services/secureSplitHttpClient.ts +10 -18
  173. package/src/services/serviceApi.ts +27 -24
  174. package/src/services/splitApi.ts +10 -9
  175. package/src/services/types.ts +5 -4
  176. package/src/storages/inRedis/RedisAdapter.ts +8 -5
  177. package/src/sync/polling/pollingManagerCS.ts +80 -78
  178. package/src/sync/polling/pollingManagerSS.ts +49 -45
  179. package/src/sync/polling/updaters/definitionChangesUpdater.ts +3 -3
  180. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  181. package/src/sync/streaming/AuthClient/index.ts +14 -15
  182. package/src/sync/streaming/AuthClient/types.ts +9 -13
  183. package/src/sync/streaming/SSEClient/index.ts +2 -2
  184. package/src/sync/streaming/SSEClient/types.ts +2 -2
  185. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  186. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  187. package/src/sync/streaming/constants.ts +1 -0
  188. package/src/sync/streaming/pushManager.ts +32 -42
  189. package/src/sync/streaming/types.ts +3 -2
  190. package/src/sync/submitters/impressionsSubmitter.ts +3 -3
  191. package/src/sync/submitters/types.ts +2 -2
  192. package/src/sync/syncManagerOnline.ts +2 -8
  193. package/src/utils/Backoff.ts +16 -4
  194. package/types/splitio.d.ts +5 -4
  195. package/cjs/platform/browser.js +0 -15
  196. package/cjs/platform/getEventSource/browser.js +0 -8
  197. package/cjs/platform/getEventSource/eventsource.js +0 -480
  198. package/cjs/platform/getEventSource/node.js +0 -26
  199. package/cjs/platform/getFetch/browser.js +0 -9
  200. package/cjs/platform/getFetch/node.js +0 -27
  201. package/cjs/platform/getOptions/node.js +0 -25
  202. package/cjs/platform/node.js +0 -17
  203. package/cjs/sdkClient/sdkLifecycle.js +0 -72
  204. package/cjs/sdkClient/trackMethod.js +0 -56
  205. package/cjs/services/authProvider.js +0 -67
  206. package/cjs/services/secureSplitHttpClient.js +0 -39
  207. package/cjs/services/serviceApi.js +0 -145
  208. package/cjs/storages/AbstractDefinitionsCacheAsync.js +0 -51
  209. package/cjs/storages/AbstractDefinitionsCacheSync.js +0 -76
  210. package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +0 -194
  211. package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +0 -121
  212. package/cjs/storages/pluggable/DefinitionsCachePluggable.js +0 -212
  213. package/cjs/sync/polling/syncTasks/definitionsSyncTask.js +0 -13
  214. package/cjs/utils/EventEmitter.js +0 -391
  215. package/cjs/utils/filter/bloomFilter.js +0 -37
  216. package/cjs/utils/inputValidation/definitions.js +0 -25
  217. package/cjs/utils/inputValidation/target.js +0 -22
  218. package/esm/platform/browser.js +0 -12
  219. package/esm/platform/getEventSource/browser.js +0 -4
  220. package/esm/platform/getEventSource/eventsource.js +0 -480
  221. package/esm/platform/getEventSource/node.js +0 -20
  222. package/esm/platform/getFetch/browser.js +0 -5
  223. package/esm/platform/getFetch/node.js +0 -22
  224. package/esm/platform/getOptions/node.js +0 -20
  225. package/esm/platform/node.js +0 -14
  226. package/esm/sdkClient/sdkLifecycle.js +0 -68
  227. package/esm/sdkClient/trackMethod.js +0 -52
  228. package/esm/services/authProvider.js +0 -63
  229. package/esm/services/secureSplitHttpClient.js +0 -35
  230. package/esm/services/serviceApi.js +0 -141
  231. package/esm/storages/AbstractDefinitionsCacheAsync.js +0 -48
  232. package/esm/storages/AbstractDefinitionsCacheSync.js +0 -71
  233. package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +0 -191
  234. package/esm/storages/inMemory/DefinitionsCacheInMemory.js +0 -118
  235. package/esm/storages/pluggable/DefinitionsCachePluggable.js +0 -209
  236. package/esm/sync/polling/syncTasks/definitionsSyncTask.js +0 -9
  237. package/esm/utils/EventEmitter.js +0 -387
  238. package/esm/utils/filter/bloomFilter.js +0 -33
  239. package/esm/utils/inputValidation/definitionExistence.js +0 -15
  240. package/esm/utils/inputValidation/definitions.js +0 -21
  241. package/esm/utils/inputValidation/target.js +0 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "3.1.1-rc.0",
3
+ "version": "3.1.1-rc.10",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -20,8 +20,8 @@
20
20
  "check:lint": "eslint src types --ext .js,.ts",
21
21
  "check:types": "tsc --noEmit",
22
22
  "build": "npm run build:cjs && npm run build:esm",
23
- "build:esm": "rimraf esm && tsc -m es2015 --outDir esm",
24
- "build:cjs": "rimraf cjs && tsc -m CommonJS --outDir cjs",
23
+ "build:esm": "rimraf esm && tsc -p tsconfig.build.json -m es2015 --outDir esm",
24
+ "build:cjs": "rimraf cjs && tsc -p tsconfig.build.json -m CommonJS --outDir cjs",
25
25
  "test": "jest",
26
26
  "test:coverage": "jest --coverage",
27
27
  "all": "npm run check && npm run build && npm run test",
@@ -45,12 +45,12 @@
45
45
  "bugs": "https://github.com/splitio/javascript-commons/issues",
46
46
  "homepage": "https://github.com/splitio/javascript-commons#readme",
47
47
  "dependencies": {
48
- "@types/ioredis": "^4.28.0",
48
+ "@types/node": ">=14.0.0",
49
49
  "tslib": "^2.3.1"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "bloom-filters": "^3.0.0",
53
- "ioredis": "^4.28.0 || ^5.0.0",
53
+ "ioredis": "^4.28.0 || ^5.0.0 || ^6.0.0",
54
54
  "node-fetch": "^2.7.0"
55
55
  },
56
56
  "peerDependenciesMeta": {
@@ -65,7 +65,6 @@
65
65
  }
66
66
  },
67
67
  "devDependencies": {
68
- "@types/ioredis": "^4.28.0",
69
68
  "@types/jest": "^27.0.0",
70
69
  "@types/lodash": "^4.14.162",
71
70
  "@typescript-eslint/eslint-plugin": "^7.18.0",
@@ -77,7 +76,7 @@
77
76
  "eslint-plugin-import": "^2.25.3",
78
77
  "eslint-plugin-tsdoc": "^0.3.0",
79
78
  "fetch-mock": "^9.11.0",
80
- "ioredis": "^5.0.0",
79
+ "ioredis": "^6.0.0",
81
80
  "jest": "^27.2.3",
82
81
  "jest-localstorage-mock": "^2.4.3",
83
82
  "lodash": "^4.17.21",
package/src/dtos/types.ts CHANGED
@@ -236,7 +236,7 @@ export interface IDefinition extends TargetingEntity {
236
236
  } | null;
237
237
  /** Definition classification. Absent means a feature flag. */
238
238
  type?: ConfigType;
239
- /** Only meaningful when `type === 'AI'`. */
239
+ /** Only meaningful when `type === 'ai'`. */
240
240
  subtype?: ConfigSubtype;
241
241
  }
242
242
 
@@ -7,12 +7,12 @@ import { CONTROL } from '../utils/constants';
7
7
  import { IDefinition, MaybeThenable } from '../dtos/types';
8
8
  import SplitIO from '../../types/splitio';
9
9
  import { IStorageAsync, IStorageSync } from '../storages/types';
10
- import { IEvaluation, IEvaluationResult, IDefinitionEvaluator } from './types';
10
+ import { IEvaluation, IDefinitionEvaluator } from './types';
11
11
  import { ILogger } from '../logger/types';
12
12
  import { ENGINE_DEFAULT } from '../logger/constants';
13
13
  import { prerequisitesMatcherContext } from './matchers/prerequisites';
14
14
 
15
- function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string): IEvaluationResult {
15
+ function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string): IEvaluation {
16
16
  return {
17
17
  treatment: get(result, 'treatment', defaultTreatment),
18
18
  label: get(result, 'label', NO_CONDITION_MATCH)
@@ -29,7 +29,7 @@ export function engineParser(log: ILogger, split: IDefinition, storage: IStorage
29
29
 
30
30
  return {
31
31
 
32
- getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<IEvaluationResult> {
32
+ getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<IEvaluation> {
33
33
 
34
34
  const parsedKey = keyParser(key);
35
35
 
@@ -29,9 +29,9 @@ export function conditionContext(log: ILogger, matcherEvaluator: (key: SplitIO.S
29
29
  // Whitelisting has more priority than traffic allocation, so we don't apply this filtering to those conditions.
30
30
  if (conditionType === 'ROLLOUT' && !shouldApplyRollout(trafficAllocation!, key.bucketingKey, trafficAllocationSeed!)) {
31
31
  return {
32
- treatment: undefined, // treatment value is assigned later
32
+ // treatment value is assigned later, at Engine's evaluationResult
33
33
  label: NOT_IN_SPLIT
34
- };
34
+ } as IEvaluation;
35
35
  }
36
36
 
37
37
  // matcherEvaluator could be Async, this relays on matchers return value, so we need
@@ -4,11 +4,12 @@ import { EXCEPTION, NO_CONDITION_MATCH, DEFINITION_NOT_FOUND } from '../utils/la
4
4
  import { CONTROL } from '../utils/constants';
5
5
  import { IDefinition, MaybeThenable } from '../dtos/types';
6
6
  import { IStorageAsync, IStorageSync } from '../storages/types';
7
- import { IEvaluationResult } from './types';
7
+ import { IEvaluation } from './types';
8
8
  import SplitIO from '../../types/splitio';
9
9
  import { ILogger } from '../logger/types';
10
10
  import { returnSetsUnion, setToArray } from '../utils/lang/sets';
11
11
  import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
12
+ import { objectAssign } from '../utils/lang/objectAssign';
12
13
 
13
14
  const EVALUATION_EXCEPTION = {
14
15
  treatment: CONTROL,
@@ -23,21 +24,27 @@ const EVALUATION_DEFINITION_NOT_FOUND = {
23
24
  };
24
25
 
25
26
  function treatmentsException(definitionNames: string[]) {
26
- const evaluations: Record<string, IEvaluationResult> = {};
27
+ const evaluations: Record<string, IEvaluation> = {};
27
28
  definitionNames.forEach(definitionName => {
28
29
  evaluations[definitionName] = EVALUATION_EXCEPTION;
29
30
  });
30
31
  return evaluations;
31
32
  }
32
33
 
34
+ function setEvaluationDataFromDefinition(evaluation: IEvaluation, definition: IDefinition): IEvaluation {
35
+ return objectAssign(evaluation, {
36
+ definition,
37
+ config: definition.configurations && definition.configurations[evaluation.treatment] || null
38
+ });
39
+ }
40
+
33
41
  export function evaluateFeature(
34
42
  log: ILogger,
35
43
  key: SplitIO.SplitKey,
36
44
  definitionName: string,
37
45
  attributes: SplitIO.Attributes | undefined,
38
46
  storage: IStorageSync | IStorageAsync,
39
- options?: SplitIO.EvaluationOptions
40
- ): MaybeThenable<IEvaluationResult> {
47
+ ): MaybeThenable<IEvaluation> {
41
48
  let definition;
42
49
 
43
50
  try {
@@ -48,13 +55,13 @@ export function evaluateFeature(
48
55
  }
49
56
 
50
57
  return thenable(definition) ?
51
- definition.then((definition) => getEvaluation(log, key, definition, attributes, storage, options))
58
+ definition.then((definition) => getEvaluation(log, key, definition, attributes, storage))
52
59
  .catch(
53
60
  // Exception on async storage. For example, when the storage is redis or
54
61
  // pluggable and there is a connection issue and we can't retrieve the split to be evaluated
55
62
  () => EVALUATION_EXCEPTION
56
63
  ) :
57
- getEvaluation(log, key, definition, attributes, storage, options);
64
+ getEvaluation(log, key, definition, attributes, storage);
58
65
  }
59
66
 
60
67
  export function evaluateFeatures(
@@ -63,8 +70,7 @@ export function evaluateFeatures(
63
70
  definitionNames: string[],
64
71
  attributes: SplitIO.Attributes | undefined,
65
72
  storage: IStorageSync | IStorageAsync,
66
- options?: SplitIO.EvaluationOptions,
67
- ): MaybeThenable<Record<string, IEvaluationResult>> {
73
+ ): MaybeThenable<Record<string, IEvaluation>> {
68
74
  let definitions;
69
75
 
70
76
  try {
@@ -75,13 +81,13 @@ export function evaluateFeatures(
75
81
  }
76
82
 
77
83
  return thenable(definitions) ?
78
- definitions.then(definitions => getEvaluations(log, key, definitionNames, definitions, attributes, storage, options))
84
+ definitions.then(definitions => getEvaluations(log, key, definitionNames, definitions, attributes, storage))
79
85
  .catch(
80
86
  // Exception on async storage. For example, when the storage is redis or
81
87
  // pluggable and there is a connection issue and we can't retrieve the split to be evaluated
82
88
  () => treatmentsException(definitionNames)
83
89
  ) :
84
- getEvaluations(log, key, definitionNames, definitions, attributes, storage, options);
90
+ getEvaluations(log, key, definitionNames, definitions, attributes, storage);
85
91
  }
86
92
 
87
93
  export function evaluateFeaturesByFlagSets(
@@ -91,8 +97,7 @@ export function evaluateFeaturesByFlagSets(
91
97
  attributes: SplitIO.Attributes | undefined,
92
98
  storage: IStorageSync | IStorageAsync,
93
99
  method: string,
94
- options?: SplitIO.EvaluationOptions,
95
- ): MaybeThenable<Record<string, IEvaluationResult>> {
100
+ ): MaybeThenable<Record<string, IEvaluation>> {
96
101
  let storedFlagNames: MaybeThenable<Set<string>[]>;
97
102
 
98
103
  function evaluate(featureFlagsByFlagSets: Set<string>[]) {
@@ -107,7 +112,7 @@ export function evaluateFeaturesByFlagSets(
107
112
  }
108
113
 
109
114
  return featureFlags.size ?
110
- evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage, options) :
115
+ evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
111
116
  {};
112
117
  }
113
118
 
@@ -126,33 +131,21 @@ export function evaluateFeaturesByFlagSets(
126
131
  evaluate(storedFlagNames);
127
132
  }
128
133
 
129
- function setEvaluationDataFromDefinition(evaluation: IEvaluationResult, definition: IDefinition, options?: SplitIO.EvaluationOptions): IEvaluationResult {
130
- evaluation.changeNumber = definition.changeNumber;
131
- evaluation.config = definition.configurations && definition.configurations[evaluation.treatment] || null;
132
- evaluation.type = definition.type;
133
- evaluation.subtype = definition.subtype;
134
- // @ts-expect-error impressionsDisabled is not exposed in the public typings yet.
135
- evaluation.impressionsDisabled = options?.impressionsDisabled || definition.impressionsDisabled;
136
-
137
- return evaluation;
138
- }
139
-
140
134
  function getEvaluation(
141
135
  log: ILogger,
142
136
  key: SplitIO.SplitKey,
143
137
  definition: IDefinition | null,
144
138
  attributes: SplitIO.Attributes | undefined,
145
139
  storage: IStorageSync | IStorageAsync,
146
- options?: SplitIO.EvaluationOptions,
147
- ): MaybeThenable<IEvaluationResult> {
140
+ ): MaybeThenable<IEvaluation> {
148
141
 
149
142
  if (definition) {
150
143
  const split = engineParser(log, definition, storage);
151
144
  const evaluation = split.getTreatment(key, attributes, evaluateFeature);
152
145
 
153
146
  return thenable(evaluation) ?
154
- evaluation.then(result => setEvaluationDataFromDefinition(result, definition, options)) :
155
- setEvaluationDataFromDefinition(evaluation, definition, options);
147
+ evaluation.then(result => setEvaluationDataFromDefinition(result, definition)) :
148
+ setEvaluationDataFromDefinition(evaluation, definition);
156
149
  }
157
150
 
158
151
  return EVALUATION_DEFINITION_NOT_FOUND;
@@ -165,12 +158,11 @@ function getEvaluations(
165
158
  definitions: Record<string, IDefinition | null>,
166
159
  attributes: SplitIO.Attributes | undefined,
167
160
  storage: IStorageSync | IStorageAsync,
168
- options?: SplitIO.EvaluationOptions,
169
- ): MaybeThenable<Record<string, IEvaluationResult>> {
170
- const result: Record<string, IEvaluationResult> = {};
161
+ ): MaybeThenable<Record<string, IEvaluation>> {
162
+ const result: Record<string, IEvaluation> = {};
171
163
  const thenables: Promise<void>[] = [];
172
164
  definitionNames.forEach(definitionName => {
173
- const evaluation = getEvaluation(log, key, definitions[definitionName], attributes, storage, options);
165
+ const evaluation = getEvaluation(log, key, definitions[definitionName], attributes, storage);
174
166
  thenable(evaluation) ?
175
167
  thenables.push(evaluation.then(res => {
176
168
  result[definitionName] = res;
@@ -184,7 +176,7 @@ function getEvaluations(
184
176
  export function evaluateDefaultTreatment(
185
177
  definitionName: string,
186
178
  storage: IStorageSync | IStorageAsync,
187
- ): MaybeThenable<IEvaluationResult> {
179
+ ): MaybeThenable<IEvaluation> {
188
180
  let definition;
189
181
 
190
182
  try {
@@ -200,11 +192,11 @@ export function evaluateDefaultTreatment(
200
192
 
201
193
  function getDefaultTreatment(
202
194
  definition: IDefinition | null,
203
- ): MaybeThenable<IEvaluationResult> {
195
+ ): MaybeThenable<IEvaluation> {
204
196
  if (definition) {
205
197
  return setEvaluationDataFromDefinition({
206
198
  treatment: definition.defaultTreatment,
207
- label: NO_CONDITION_MATCH // "default rule"
199
+ label: NO_CONDITION_MATCH, // "default rule"
208
200
  }, definition);
209
201
  }
210
202
 
@@ -19,16 +19,12 @@ export interface IMatcherDto {
19
19
  }
20
20
 
21
21
  export interface IEvaluation {
22
- treatment?: string,
22
+ treatment: string,
23
23
  label: string,
24
- changeNumber?: number,
24
+ definition?: IDefinition
25
25
  config?: string | null | SplitIO.JsonObject
26
- type?: IDefinition['type']
27
- subtype?: IDefinition['subtype']
28
26
  }
29
27
 
30
- export type IEvaluationResult = IEvaluation & { treatment: string; impressionsDisabled?: boolean }
31
-
32
28
  export type IDefinitionEvaluator = (log: ILogger, key: SplitIO.SplitKey, definitionName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
33
29
 
34
30
  export type IEvaluator = (key: SplitIO.SplitKeyObject, seed?: number, trafficAllocation?: number, trafficAllocationSeed?: number, attributes?: SplitIO.Attributes, splitEvaluator?: IDefinitionEvaluator) => MaybeThenable<IEvaluation | boolean | undefined>
@@ -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';
@@ -7,11 +7,11 @@ import { sdkManagerFactory } from '../sdkManager';
7
7
  import { sdkClientMethodFactory } from '../sdkClient/sdkClientMethod';
8
8
  import { impressionObserverSSFactory } from '../trackers/impressionObserver/impressionObserverSS';
9
9
 
10
- const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory, pushManagerFactory);
10
+ const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory(), pushManagerFactory);
11
11
 
12
12
  export const serverSideModules = {
13
13
  storageFactory: InMemoryStorageFactory,
14
- serviceApiFactory,
14
+ serviceApiFactory: splitApiFactory,
15
15
  syncManagerFactory: syncManagerOnlineSSFactory,
16
16
  sdkManagerFactory,
17
17
  sdkClientMethodFactory,
@@ -4,7 +4,7 @@ import { getMatching, getBucketing } from '../utils/key';
4
4
  import { validateDefinitionExistence } from '../utils/inputValidation/definitionExistence';
5
5
  import { SDK_NOT_READY } from '../utils/labels';
6
6
  import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS } from '../utils/constants';
7
- import { IEvaluationResult } from '../evaluator/types';
7
+ import { IEvaluation } from '../evaluator/types';
8
8
  import SplitIO from '../../types/splitio';
9
9
  import { IMPRESSION_QUEUEING } from '../logger/constants';
10
10
  import { ISdkFactoryContext } from '../sdkFactory/types';
@@ -16,17 +16,17 @@ import { trackMethodFactory } from './trackMethod';
16
16
  const treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };
17
17
 
18
18
  function treatmentsNotReady(featureFlagNames: string[]) {
19
- const evaluations: Record<string, IEvaluationResult> = {};
19
+ const evaluations: Record<string, IEvaluation> = {};
20
20
  featureFlagNames.forEach(featureFlagName => {
21
21
  evaluations[featureFlagName] = treatmentNotReady;
22
22
  });
23
23
  return evaluations;
24
24
  }
25
25
 
26
- export function stringify(options?: SplitIO.EvaluationOptions) {
27
- if (options && options.properties) {
26
+ export function stringify(properties?: SplitIO.Properties) {
27
+ if (properties) {
28
28
  try {
29
- return JSON.stringify(options.properties);
29
+ return JSON.stringify(properties);
30
30
  } catch { /* JSON.stringify should never throw with validated options, but handling just in case */ }
31
31
  }
32
32
  }
@@ -42,9 +42,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
42
42
  function getTreatment(key: SplitIO.SplitKey, featureFlagName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENT) {
43
43
  const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);
44
44
 
45
- const wrapUp = (evaluationResult: IEvaluationResult) => {
45
+ const wrapUp = (evaluationResult: IEvaluation) => {
46
46
  const queue: ImpressionDecorated[] = [];
47
- const treatment = processEvaluation(evaluationResult, featureFlagName, key, stringify(options), withConfig, methodName, queue);
47
+ const treatment = processEvaluation(evaluationResult, featureFlagName, key, withConfig, methodName, queue, options);
48
48
  impressionsTracker.track(queue, attributes);
49
49
 
50
50
  stopTelemetryTracker(queue[0] && queue[0].imp.label);
@@ -52,7 +52,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
52
52
  };
53
53
 
54
54
  const evaluation = readinessManager.isReadyFromCache() ?
55
- evaluateFeature(log, key, featureFlagName, attributes, storage, options) :
55
+ evaluateFeature(log, key, featureFlagName, attributes, storage) :
56
56
  isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
57
57
  Promise.resolve(treatmentNotReady) :
58
58
  treatmentNotReady;
@@ -67,12 +67,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
67
67
  function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENTS) {
68
68
  const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
69
69
 
70
- const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
70
+ const wrapUp = (evaluationResults: Record<string, IEvaluation>) => {
71
71
  const queue: ImpressionDecorated[] = [];
72
72
  const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {};
73
- const properties = stringify(options);
74
73
  Object.keys(evaluationResults).forEach(featureFlagName => {
75
- treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue);
74
+ treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, withConfig, methodName, queue, options);
76
75
  });
77
76
  impressionsTracker.track(queue, attributes);
78
77
 
@@ -81,7 +80,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
81
80
  };
82
81
 
83
82
  const evaluations = readinessManager.isReadyFromCache() ?
84
- evaluateFeatures(log, key, featureFlagNames, attributes, storage, options) :
83
+ evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
85
84
  isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
86
85
  Promise.resolve(treatmentsNotReady(featureFlagNames)) :
87
86
  treatmentsNotReady(featureFlagNames);
@@ -96,12 +95,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
96
95
  function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) {
97
96
  const stopTelemetryTracker = telemetryTracker.trackEval(method);
98
97
 
99
- const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
98
+ const wrapUp = (evaluationResults: Record<string, IEvaluation>) => {
100
99
  const queue: ImpressionDecorated[] = [];
101
100
  const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {};
102
- const properties = stringify(options);
103
101
  Object.keys(evaluationResults).forEach(featureFlagName => {
104
- treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue);
102
+ treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, withConfig, methodName, queue, options);
105
103
  });
106
104
  impressionsTracker.track(queue, attributes);
107
105
 
@@ -110,7 +108,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
110
108
  };
111
109
 
112
110
  const evaluations = readinessManager.isReadyFromCache() ?
113
- evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName, options) :
111
+ evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
114
112
  isAsync ?
115
113
  Promise.resolve({}) :
116
114
  {};
@@ -132,18 +130,18 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
132
130
 
133
131
  // Internal function
134
132
  function processEvaluation(
135
- evaluation: IEvaluationResult,
133
+ evaluation: IEvaluation,
136
134
  featureFlagName: string,
137
135
  key: SplitIO.SplitKey,
138
- properties: string | undefined,
139
136
  withConfig: boolean,
140
137
  invokingMethodName: string,
141
- queue: ImpressionDecorated[]
138
+ queue: ImpressionDecorated[],
139
+ options: SplitIO.EvaluationOptions = {}
142
140
  ): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
143
141
  const matchingKey = getMatching(key);
144
142
  const bucketingKey = getBucketing(key);
145
143
 
146
- const { changeNumber, impressionsDisabled } = evaluation;
144
+ const { definition } = evaluation;
147
145
  let { treatment, label, config = null } = evaluation;
148
146
 
149
147
  if (treatment === CONTROL) {
@@ -163,10 +161,10 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
163
161
  time: Date.now(),
164
162
  bucketingKey,
165
163
  label,
166
- changeNumber: changeNumber as number,
167
- properties
168
- },
169
- disabled: impressionsDisabled
164
+ changeNumber: definition?.changeNumber,
165
+ properties: stringify(options.properties)
166
+ }, // @ts-expect-error impressionsDisabled is not exposed in the public typings yet.
167
+ disabled: options.impressionsDisabled || definition?.impressionsDisabled
170
168
  });
171
169
  }
172
170
 
@@ -69,6 +69,9 @@ export function sdkLifecycleFactory(params: ISdkFactoryContext, isSharedClient?:
69
69
 
70
70
  // Stop background jobs
71
71
  syncManager && syncManager.stop();
72
+
73
+ // Stop the service API (the auth provider backoff retries specifically) before flushing,
74
+ // so that a pending/never-resolved auth retry doesn't block the flush forever
72
75
  serviceApi && serviceApi.stop();
73
76
 
74
77
  return __flush().then(() => {
@@ -1,6 +1,6 @@
1
1
  import { ISplitHttpClient, NetworkError } from './types';
2
- import { IJwtCredentialV3 } from '../sync/streaming/AuthClient/types';
3
- import { authenticateFactory } from '../sync/streaming/AuthClient';
2
+ import { IJwtCredential } from '../sync/streaming/AuthClient/types';
3
+ import { fetchAuthFactory } from '../sync/streaming/AuthClient';
4
4
  import { Backoff } from '../utils/Backoff';
5
5
  import { LOG_PREFIX_SYNC_AUTH } from '../logger/constants';
6
6
  import { ISettings } from '../types';
@@ -9,12 +9,24 @@ import { ITelemetryTracker } from '../trackers/types';
9
9
 
10
10
  const SKEW_SECONDS = 30;
11
11
 
12
- function isExpired(credential: IJwtCredentialV3): boolean {
12
+ function isExpired(credential: IJwtCredential): boolean {
13
13
  return Date.now() / 1000 + SKEW_SECONDS >= credential.decodedToken.exp;
14
14
  }
15
15
 
16
+ function noCachedCredentialError() {
17
+ return new Error(LOG_PREFIX_SYNC_AUTH + 'stopped without a cached credential');
18
+ }
19
+
16
20
  export interface IAuthProvider {
17
- credential(): Promise<IJwtCredentialV3>;
21
+ /**
22
+ * Returns the cached credential, or fetches a new one if there isn't one cached or it's expired,
23
+ * retrying with backoff on recoverable errors. Used by `secureSplitHttpClient` and `serviceApi.fetchAuth`.
24
+ */
25
+ credential(): Promise<IJwtCredential>;
26
+ /**
27
+ * Invalidates/clears the cached credential. Used by `secureSplitHttpClient` in the special case of 401 error,
28
+ * and by `serviceApi.fetchAuth` to force a credential/token refresh.
29
+ */
18
30
  invalidate(): void;
19
31
  stop(): void;
20
32
  }
@@ -27,28 +39,27 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
27
39
 
28
40
  const { urls, log } = settings;
29
41
 
30
- function fetchAuth() {
42
+ const fetchAuth = fetchAuthFactory(() => {
31
43
  let url = `${urls.auth}/api/v3/auth?capabilities=config,aiconfig`;
32
44
  return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN), false, true);
33
- }
34
-
35
- const authenticate = authenticateFactory(fetchAuth);
45
+ });
36
46
  const backoff = new Backoff(fetchCredential);
37
47
 
38
- let cachedCredential: IJwtCredentialV3 | undefined;
39
- let inFlightPromise: Promise<IJwtCredentialV3> | undefined;
48
+ let cachedCredential: IJwtCredential | undefined;
49
+ let inFlightPromise: Promise<IJwtCredential> | undefined;
40
50
  let stopped = false;
41
51
 
42
- function fetchCredential(): Promise<IJwtCredentialV3> {
43
- return authenticate().then((credential: IJwtCredentialV3) => {
52
+ function fetchCredential(): Promise<IJwtCredential> {
53
+ return fetchAuth().then((credential: IJwtCredential) => {
44
54
  log.info(LOG_PREFIX_SYNC_AUTH + 'credential fetched successfully');
45
55
  cachedCredential = credential;
46
56
  inFlightPromise = undefined;
47
57
  backoff.reset();
48
58
  return credential;
49
59
  }).catch((error: NetworkError) => {
50
- // Avoid rejected promises and unnecessary retries after stop()
51
- if (stopped) return cachedCredential!;
60
+ // Avoid unnecessary retries after stop(): fall back to any cached credential, or reject
61
+ // (never resolve with `undefined`, which callers would otherwise send as an unauthenticated request).
62
+ if (stopped) return cachedCredential || Promise.reject(noCachedCredentialError());
52
63
 
53
64
  if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
54
65
  log.error(LOG_PREFIX_SYNC_AUTH + 'non-retryable error fetching credential (status ' + error.statusCode + '): ' + error.message);
@@ -62,14 +73,24 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
62
73
  }
63
74
 
64
75
  return {
65
- credential(): Promise<IJwtCredentialV3> {
76
+ credential(): Promise<IJwtCredential> {
66
77
  if (cachedCredential && !isExpired(cachedCredential)) {
67
78
  return Promise.resolve(cachedCredential);
68
79
  }
69
80
 
81
+ // Reuse any fetch already in flight (e.g. one started just before stop()), instead of firing a redundant one.
82
+ if (inFlightPromise) return inFlightPromise;
83
+
84
+ // No cached credential to fall back to and stopped: reject so callers don't send an unauthenticated request.
85
+ if (stopped && !cachedCredential) {
86
+ return Promise.reject(noCachedCredentialError());
87
+ }
88
+
70
89
  if (cachedCredential) log.debug(LOG_PREFIX_SYNC_AUTH + 'cached credential expired');
71
90
 
72
- return inFlightPromise || (inFlightPromise = fetchCredential());
91
+ // If stopped, this is a last, bounded refetch: fetchCredential()'s catch short-circuits (no backoff retry)
92
+ // once stopped, so it's a single attempt that falls back to the stale cached credential on failure.
93
+ return inFlightPromise = fetchCredential();
73
94
  },
74
95
 
75
96
  invalidate() {
@@ -78,9 +99,10 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
78
99
 
79
100
  stop() {
80
101
  stopped = true;
81
- cachedCredential = undefined;
82
- inFlightPromise = undefined;
83
- backoff.reset();
102
+ // Keep any in-flight fetch and already-cached credential usable (e.g. by a final flush on destroy).
103
+ // If a retry is only pending (not actively in flight), settle it immediately instead of waiting out the
104
+ // backoff delay: with the cached credential if there is one, or rejected otherwise.
105
+ backoff.reset(cachedCredential ? { value: cachedCredential } : { error: noCachedCredentialError() });
84
106
  }
85
107
  };
86
108
  }
@@ -1,36 +1,28 @@
1
- import { IRequestOptions, IResponse, ISecureSplitHttpClient, NetworkError } from './types';
2
- import { ISettings } from '../types';
3
- import { IPlatform } from '../sdkFactory/types';
4
- import { splitHttpClientFactory } from './splitHttpClient';
5
- import { authProviderFactory } from './authProvider';
6
- import { ITelemetryTracker } from '../trackers/types';
1
+ import { IRequestOptions, IResponse, ISecureSplitHttpClient, ISplitHttpClient, NetworkError } from './types';
2
+ import { IAuthProvider } from './authProvider';
7
3
 
8
4
  /**
9
5
  * Factory of Secure HTTP client, which authenticates requests using a JWT token.
10
6
  * On 401 responses, invalidates the cached credential and retries once with a fresh token.
11
7
  *
12
- * @param settings - SDK settings
13
- * @param platform - object containing environment-specific dependencies
14
- * @param fetchAuth - function to fetch auth credentials from the /v2/auth endpoint
8
+ * @param splitHttpClient - `splitHttpClientFactory` to use for making requests
9
+ * @param authProvider - `authProviderFactory` to use for authentication
15
10
  */
16
- export function secureSplitHttpClientFactory(settings: ISettings, platform: Pick<IPlatform, 'getOptions' | 'getFetch'>, telemetryTracker: ITelemetryTracker): ISecureSplitHttpClient {
11
+ export function secureSplitHttpClientFactory(splitHttpClient: ISplitHttpClient, authProvider: IAuthProvider): ISecureSplitHttpClient {
17
12
 
18
- const splitHttpClient = splitHttpClientFactory(settings, platform);
19
- const authProvider = authProviderFactory(settings, splitHttpClient, telemetryTracker);
20
-
21
- function makeRequest(url: string, options: IRequestOptions | undefined, latencyTracker: ((error?: NetworkError) => void) | undefined, logErrorsAsInfo: boolean | undefined, token: string): Promise<IResponse> {
22
- return splitHttpClient(url, { ...options, headers: { ...options?.headers, Authorization: `Bearer ${token}` } }, latencyTracker, logErrorsAsInfo, true);
13
+ function makeRequest(url: string, options: IRequestOptions | undefined, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean, newVersionHeader?: boolean, token?: string): Promise<IResponse> {
14
+ return splitHttpClient(url, token ? { ...options, headers: { ...options?.headers, Authorization: `Bearer ${token}` } } : options, latencyTracker, logErrorsAsInfo, newVersionHeader);
23
15
  }
24
16
 
25
- const httpClient = function (url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean): Promise<IResponse> {
17
+ const httpClient = function (url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean, newVersionHeader = true, useJwt = true): Promise<IResponse> {
26
18
  return authProvider.credential().then(credential => {
27
- return makeRequest(url, options, latencyTracker, logErrorsAsInfo, credential.token)
19
+ return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? credential.token : undefined)
28
20
  .catch((error: NetworkError) => {
29
21
  if (error.statusCode === 401) {
30
22
  // retry once for 401, in case the token has just expired
31
23
  authProvider.invalidate();
32
24
  return authProvider.credential().then(newCredential => {
33
- return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newCredential.token);
25
+ return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newVersionHeader, useJwt ? newCredential.token : undefined);
34
26
  });
35
27
  }
36
28
  throw error;