@featbit/js-client-sdk 3.0.12 → 3.0.13

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 (148) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +301 -301
  3. package/dist/esm/FbClientCore.d.ts.map +1 -1
  4. package/dist/esm/FbClientCore.js +1 -1
  5. package/dist/esm/FbClientCore.js.map +1 -1
  6. package/dist/esm/data-sources/DataSourceUpdates.d.ts +2 -2
  7. package/dist/esm/data-sources/DataSourceUpdates.d.ts.map +1 -1
  8. package/dist/esm/data-sources/DataSourceUpdates.js +55 -51
  9. package/dist/esm/data-sources/DataSourceUpdates.js.map +1 -1
  10. package/dist/esm/data-sources/createStreamListeners.d.ts +0 -9
  11. package/dist/esm/data-sources/createStreamListeners.d.ts.map +1 -1
  12. package/dist/esm/data-sources/createStreamListeners.js +10 -10
  13. package/dist/esm/data-sources/createStreamListeners.js.map +1 -1
  14. package/dist/esm/data-sync/IDataSynchronizer.d.ts +1 -1
  15. package/dist/esm/data-sync/IDataSynchronizer.d.ts.map +1 -1
  16. package/dist/esm/data-sync/NullDataSynchronizer.d.ts +1 -1
  17. package/dist/esm/data-sync/NullDataSynchronizer.d.ts.map +1 -1
  18. package/dist/esm/data-sync/NullDataSynchronizer.js +11 -0
  19. package/dist/esm/data-sync/NullDataSynchronizer.js.map +1 -1
  20. package/dist/esm/data-sync/PollingDataSynchronizer.d.ts +1 -1
  21. package/dist/esm/data-sync/PollingDataSynchronizer.d.ts.map +1 -1
  22. package/dist/esm/data-sync/PollingDataSynchronizer.js +42 -22
  23. package/dist/esm/data-sync/PollingDataSynchronizer.js.map +1 -1
  24. package/dist/esm/data-sync/WebSocketDataSynchronizer.d.ts +2 -1
  25. package/dist/esm/data-sync/WebSocketDataSynchronizer.d.ts.map +1 -1
  26. package/dist/esm/data-sync/WebSocketDataSynchronizer.js +20 -6
  27. package/dist/esm/data-sync/WebSocketDataSynchronizer.js.map +1 -1
  28. package/dist/esm/data-sync/types.d.ts +1 -1
  29. package/dist/esm/data-sync/types.d.ts.map +1 -1
  30. package/dist/esm/integrations/test_data/TestDataSynchronizer.d.ts +1 -1
  31. package/dist/esm/integrations/test_data/TestDataSynchronizer.d.ts.map +1 -1
  32. package/dist/esm/integrations/test_data/TestDataSynchronizer.js +3 -1
  33. package/dist/esm/integrations/test_data/TestDataSynchronizer.js.map +1 -1
  34. package/dist/esm/platform/browser/BrowserWebSocket.d.ts.map +1 -1
  35. package/dist/esm/platform/browser/BrowserWebSocket.js +4 -0
  36. package/dist/esm/platform/browser/BrowserWebSocket.js.map +1 -1
  37. package/dist/esm/store/IDataSourceUpdates.d.ts +2 -2
  38. package/dist/esm/store/IDataSourceUpdates.d.ts.map +1 -1
  39. package/dist/esm/version.d.ts +1 -1
  40. package/dist/esm/version.js +1 -1
  41. package/dist/umd/featbit-js-client-sdk-3.0.13.js +2 -0
  42. package/dist/umd/featbit-js-client-sdk-3.0.13.js.map +1 -0
  43. package/dist/umd/featbit-js-client-sdk.js +1 -1
  44. package/dist/umd/featbit-js-client-sdk.js.map +1 -1
  45. package/package.json +46 -46
  46. package/src/Configuration.ts +232 -232
  47. package/src/Context.ts +61 -61
  48. package/src/FbClientBuilder.ts +167 -167
  49. package/src/FbClientCore.ts +405 -401
  50. package/src/IContextProperty.ts +3 -3
  51. package/src/IDataKind.ts +11 -11
  52. package/src/IFbClient.ts +29 -29
  53. package/src/IFbClientCore.ts +290 -290
  54. package/src/IVersionedData.ts +18 -18
  55. package/src/bootstrap/IBootstrapProvider.ts +4 -4
  56. package/src/bootstrap/JsonBootstrapProvider.ts +34 -34
  57. package/src/bootstrap/NullBootstrapProvider.ts +20 -20
  58. package/src/bootstrap/index.ts +2 -2
  59. package/src/constants.ts +1 -1
  60. package/src/data-sources/DataSourceUpdates.ts +116 -116
  61. package/src/data-sources/createStreamListeners.ts +67 -66
  62. package/src/data-sources/index.ts +1 -1
  63. package/src/data-sync/DataSyncMode.ts +3 -3
  64. package/src/data-sync/IDataSynchronizer.ts +15 -15
  65. package/src/data-sync/IRequestor.ts +10 -10
  66. package/src/data-sync/NullDataSynchronizer.ts +14 -14
  67. package/src/data-sync/PollingDataSynchronizer.ts +125 -116
  68. package/src/data-sync/Requestor.ts +61 -61
  69. package/src/data-sync/WebSocketDataSynchronizer.ts +77 -73
  70. package/src/data-sync/index.ts +8 -8
  71. package/src/data-sync/types.ts +19 -19
  72. package/src/data-sync/utils.ts +31 -31
  73. package/src/errors.ts +47 -47
  74. package/src/evaluation/EvalResult.ts +35 -35
  75. package/src/evaluation/Evaluator.ts +26 -26
  76. package/src/evaluation/IEvalDetail.ts +23 -23
  77. package/src/evaluation/ReasonKinds.ts +9 -9
  78. package/src/evaluation/data/IFlag.ts +29 -29
  79. package/src/evaluation/index.ts +4 -4
  80. package/src/events/DefaultEventProcessor.ts +83 -83
  81. package/src/events/DefaultEventQueue.ts +49 -49
  82. package/src/events/DefaultEventSender.ts +73 -73
  83. package/src/events/DefaultEventSerializer.ts +11 -11
  84. package/src/events/EventDispatcher.ts +127 -127
  85. package/src/events/EventSerializer.ts +4 -4
  86. package/src/events/IEventProcessor.ts +8 -8
  87. package/src/events/IEventQueue.ts +16 -16
  88. package/src/events/IEventSender.ts +13 -13
  89. package/src/events/NullEventProcessor.ts +15 -15
  90. package/src/events/event.ts +129 -129
  91. package/src/events/index.ts +11 -11
  92. package/src/index.ts +21 -21
  93. package/src/integrations/TestLogger.ts +24 -24
  94. package/src/integrations/index.ts +1 -1
  95. package/src/integrations/test_data/FlagBuilder.ts +59 -59
  96. package/src/integrations/test_data/TestData.ts +57 -57
  97. package/src/integrations/test_data/TestDataSynchronizer.ts +49 -49
  98. package/src/integrations/test_data/index.ts +4 -4
  99. package/src/logging/BasicLogger.ts +108 -108
  100. package/src/logging/IBasicLoggerOptions.ts +46 -46
  101. package/src/logging/ILogger.ts +49 -49
  102. package/src/logging/LogLevel.ts +8 -8
  103. package/src/logging/SafeLogger.ts +69 -69
  104. package/src/logging/format.ts +154 -154
  105. package/src/logging/index.ts +5 -5
  106. package/src/options/ClientContext.ts +39 -39
  107. package/src/options/IClientContext.ts +53 -53
  108. package/src/options/IOptions.ts +123 -123
  109. package/src/options/IUser.ts +6 -6
  110. package/src/options/IValidatedOptions.ts +29 -29
  111. package/src/options/OptionMessages.ts +35 -35
  112. package/src/options/UserBuilder.ts +35 -35
  113. package/src/options/Validators.ts +300 -300
  114. package/src/options/index.ts +7 -7
  115. package/src/platform/IInfo.ts +102 -102
  116. package/src/platform/IPlatform.ts +20 -20
  117. package/src/platform/IStore.ts +112 -112
  118. package/src/platform/IWebSocket.ts +22 -22
  119. package/src/platform/browser/BrowserInfo.ts +24 -24
  120. package/src/platform/browser/BrowserPlatform.ts +19 -19
  121. package/src/platform/browser/BrowserRequests.ts +6 -6
  122. package/src/platform/browser/BrowserWebSocket.ts +147 -142
  123. package/src/platform/browser/FbClient.ts +65 -65
  124. package/src/platform/browser/LocalStorageStore.ts +59 -59
  125. package/src/platform/index.ts +11 -11
  126. package/src/platform/requests.ts +76 -76
  127. package/src/store/BaseStore.ts +125 -125
  128. package/src/store/DataKinds.ts +6 -6
  129. package/src/store/IDataSourceUpdates.ts +68 -68
  130. package/src/store/InMemoryStore.ts +36 -36
  131. package/src/store/index.ts +5 -5
  132. package/src/store/serialization.ts +52 -52
  133. package/src/store/store.ts +37 -37
  134. package/src/utils/Emits.ts +75 -75
  135. package/src/utils/EventEmitter.ts +128 -128
  136. package/src/utils/IEventEmitter.ts +14 -14
  137. package/src/utils/Regex.ts +21 -21
  138. package/src/utils/ValueConverters.ts +55 -55
  139. package/src/utils/canonicalizeUri.ts +3 -3
  140. package/src/utils/debounce.ts +33 -33
  141. package/src/utils/http.ts +40 -40
  142. package/src/utils/index.ts +5 -5
  143. package/src/utils/isNullOrUndefined.ts +2 -2
  144. package/src/utils/serializeUser.ts +27 -27
  145. package/src/utils/sleep.ts +5 -5
  146. package/src/version.ts +1 -1
  147. package/dist/umd/featbit-js-client-sdk-3.0.12.js +0 -2
  148. package/dist/umd/featbit-js-client-sdk-3.0.12.js.map +0 -1
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
- {
2
- "name": "@featbit/js-client-sdk",
3
- "version": "3.0.12",
4
- "description": "https://github.com/featbit/featbit-js-client-sdk",
5
- "main": "./dist/esm/index.js",
6
- "module": "./dist/esm/index.js",
7
- "files": [
8
- "dist",
9
- "src"
10
- ],
11
- "scripts": {
12
- "prebuild": "node -p \"'export const version = \\\"' + require('./package.json').version + '\\\"; export const name = \\\"' + require('./package.json').name + '\\\";'\" > src/version.ts",
13
- "build": "rimraf dist && rimraf examples/web-app/umd && tsc -p tsconfig.json && webpack --mode production",
14
- "test": "npx jest --ci",
15
- "test-coverage": "npx jest --ci --coverage",
16
- "prepublishOnly": "npm run build"
17
- },
18
- "repository": {
19
- "type": "git",
20
- "url": "git+https://github.com/featbit/featbit-js-client-sdk.git"
21
- },
22
- "keywords": [
23
- "featbit",
24
- "client sdk",
25
- "feature flags",
26
- "feature management"
27
- ],
28
- "author": "featbit.co",
29
- "license": "MIT",
30
- "bugs": {
31
- "url": "https://github.com/featbit/featbit-js-client-sdk/issues"
32
- },
33
- "homepage": "https://github.com/featbit/featbit-js-client-sdk#readme",
34
- "devDependencies": {
35
- "@types/jest": "^29.5.11",
36
- "jest": "^29.7.0",
37
- "jest-environment-jsdom": "^29.7.0",
38
- "rimraf": "^5.0.5",
39
- "string-replace-loader": "^3.1.0",
40
- "ts-jest": "^29.1.1",
41
- "ts-loader": "^9.5.1",
42
- "typescript": "^5.2.2",
43
- "webpack": "^5.91.0",
44
- "webpack-cli": "^5.1.4"
45
- }
46
- }
1
+ {
2
+ "name": "@featbit/js-client-sdk",
3
+ "version": "3.0.13",
4
+ "description": "https://github.com/featbit/featbit-js-client-sdk",
5
+ "main": "./dist/esm/index.js",
6
+ "module": "./dist/esm/index.js",
7
+ "files": [
8
+ "dist",
9
+ "src"
10
+ ],
11
+ "scripts": {
12
+ "prebuild": "node -p \"'export const version = \\\"' + require('./package.json').version + '\\\"; export const name = \\\"' + require('./package.json').name + '\\\";'\" > src/version.ts",
13
+ "build": "rimraf dist && rimraf examples/web-app/umd && tsc -p tsconfig.json && webpack --mode production",
14
+ "test": "npx jest --ci",
15
+ "test-coverage": "npx jest --ci --coverage",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/featbit/featbit-js-client-sdk.git"
21
+ },
22
+ "keywords": [
23
+ "featbit",
24
+ "client sdk",
25
+ "feature flags",
26
+ "feature management"
27
+ ],
28
+ "author": "featbit.co",
29
+ "license": "MIT",
30
+ "bugs": {
31
+ "url": "https://github.com/featbit/featbit-js-client-sdk/issues"
32
+ },
33
+ "homepage": "https://github.com/featbit/featbit-js-client-sdk#readme",
34
+ "devDependencies": {
35
+ "@types/jest": "^29.5.11",
36
+ "jest": "^29.7.0",
37
+ "jest-environment-jsdom": "^29.7.0",
38
+ "rimraf": "^5.0.5",
39
+ "string-replace-loader": "^3.1.0",
40
+ "ts-jest": "^29.1.1",
41
+ "ts-loader": "^9.5.1",
42
+ "typescript": "^5.2.2",
43
+ "webpack": "^5.91.0",
44
+ "webpack-cli": "^5.1.4"
45
+ }
46
+ }
@@ -1,233 +1,233 @@
1
- import { IOptions } from "./options/IOptions";
2
- import { ILogger } from "./logging/ILogger";
3
- import { IValidatedOptions } from "./options/IValidatedOptions";
4
- import { NumberWithMinimum, TypeValidator, TypeValidators, UserValidator } from "./options/Validators";
5
- import OptionMessages from "./options/OptionMessages";
6
- import { IStore } from "./platform/IStore";
7
- import { IClientContext } from "./options/IClientContext";
8
- import { IDataSynchronizer } from "./data-sync/IDataSynchronizer";
9
- import { IDataSourceUpdates } from "./store/IDataSourceUpdates";
10
- import InMemoryStore from "./store/InMemoryStore";
11
- import { VoidFunction } from "./utils/VoidFunction";
12
- import { isNullOrUndefined } from "./utils/isNullOrUndefined";
13
- import { canonicalizeUri } from "./utils/canonicalizeUri";
14
- import { IBootstrapProvider } from "./bootstrap/IBootstrapProvider";
15
- import { NullBootstrapProvider } from "./bootstrap/NullBootstrapProvider";
16
- import { EmptyString } from "./constants";
17
- import { DataSyncModeEnum } from "./data-sync/DataSyncMode";
18
- import { IUser } from "./options/IUser";
19
- import { JsonBootstrapProvider } from "./bootstrap";
20
-
21
- // Once things are internal to the implementation of the SDK we can depend on
22
- // types. Calls to the SDK could contain anything without any regard to typing.
23
- // So, data we take from external sources must be normalized into something
24
- // that can be trusted.
25
-
26
- /**
27
- * These perform cursory validations. Complex objects are implemented with classes
28
- * and these should allow for conditional construction.
29
- */
30
- const validations: Record<string, TypeValidator> = {
31
- startWaitTime: TypeValidators.Number,
32
- sdkKey: TypeValidators.String,
33
- pollingUri: TypeValidators.String,
34
- streamingUri: TypeValidators.String,
35
- eventsUri: TypeValidators.String,
36
- webSocketPingInterval: TypeValidators.Number,
37
- logger: TypeValidators.Object,
38
- store: TypeValidators.ObjectOrFactory,
39
- dataSynchronizer: TypeValidators.ObjectOrFactory,
40
- flushInterval: TypeValidators.Number,
41
- maxEventsInQueue: TypeValidators.Number,
42
- pollingInterval: TypeValidators.Number,
43
- offline: TypeValidators.Boolean,
44
- dataSyncMode: TypeValidators.String,
45
- bootstrap: TypeValidators.Bootstrap,
46
- user: TypeValidators.User
47
- };
48
-
49
- /**
50
- * @internal
51
- */
52
- export const defaultValues: IValidatedOptions = {
53
- startWaitTime: 5000,
54
- sdkKey: '',
55
- pollingUri: '',
56
- streamingUri: '',
57
- eventsUri: '',
58
- dataSyncMode: DataSyncModeEnum.STREAMING,
59
- sendEvents: true,
60
- webSocketPingInterval: 18 * 1000,
61
- flushInterval: 2000,
62
- maxEventsInQueue: 10000,
63
- pollingInterval: 30000,
64
- offline: false,
65
- store: (options: IOptions) => new InMemoryStore(),
66
- bootstrap: undefined,
67
- user: undefined,
68
- };
69
-
70
- function validateTypesAndNames(options: IOptions): {
71
- errors: string[];
72
- validatedOptions: IValidatedOptions;
73
- } {
74
- let errors: string[] = [];
75
- const validatedOptions: IValidatedOptions = {...defaultValues};
76
- Object.keys(options).forEach((optionName) => {
77
- // We need to tell typescript it doesn't actually know what options are.
78
- // If we don't then it complains we are doing crazy things with it.
79
- const optionValue = (options as unknown as any)[optionName];
80
- const validator = validations[optionName];
81
- if (validator) {
82
- if (!validator.is(optionValue)) {
83
- if (validator.getType() === 'boolean') {
84
- errors.push(OptionMessages.wrongOptionTypeBoolean(optionName, typeof optionValue));
85
- validatedOptions[optionName] = !!optionValue;
86
- } else if (
87
- validator instanceof NumberWithMinimum &&
88
- TypeValidators.Number.is(optionValue)
89
- ) {
90
- const {min} = validator as NumberWithMinimum;
91
- errors.push(OptionMessages.optionBelowMinimum(optionName, optionValue, min));
92
- validatedOptions[optionName] = min;
93
- } else if (validator instanceof UserValidator) {
94
- errors = [...errors, ...validator.messages];
95
- validatedOptions[optionName] = defaultValues[optionName];
96
- } else {
97
- errors.push(
98
- OptionMessages.wrongOptionType(optionName, validator.getType(), typeof optionValue),
99
- );
100
- validatedOptions[optionName] = defaultValues[optionName];
101
- }
102
- } else {
103
- validatedOptions[optionName] = optionValue;
104
- }
105
- } else {
106
- options.logger?.warn(OptionMessages.unknownOption(optionName));
107
- }
108
- });
109
- return {errors, validatedOptions};
110
- }
111
-
112
- function validateEndpoints(options: IOptions, validatedOptions: IValidatedOptions) {
113
- const {streamingUri, pollingUri, eventsUri} = options;
114
- const streamingUriMissing = isNullOrUndefined(streamingUri) || streamingUri === EmptyString;
115
- const pollingUriMissing = isNullOrUndefined(pollingUri) || pollingUri === EmptyString;
116
- const eventsUriMissing = isNullOrUndefined(eventsUri) || eventsUri === EmptyString;
117
-
118
- if (!validatedOptions.offline && (eventsUriMissing || (streamingUriMissing && pollingUriMissing))) {
119
- if (eventsUriMissing) {
120
- validatedOptions.logger?.error(OptionMessages.partialEndpoint('eventsUri'));
121
- }
122
-
123
- if (validatedOptions.dataSyncMode === DataSyncModeEnum.STREAMING && streamingUriMissing) {
124
- validatedOptions.logger?.error(OptionMessages.partialEndpoint('streamingUri'));
125
- }
126
-
127
- if (validatedOptions.dataSyncMode === DataSyncModeEnum.POLLING && pollingUriMissing) {
128
- validatedOptions.logger?.error(OptionMessages.partialEndpoint('pollingUri'));
129
- }
130
- }
131
- }
132
-
133
- export default class Configuration {
134
- public readonly startWaitTime: number;
135
-
136
- public readonly sdkKey: string;
137
-
138
- public readonly streamingUri: string;
139
-
140
- public readonly pollingUri: string;
141
-
142
- public readonly eventsUri: string;
143
-
144
- public readonly webSocketPingInterval: number;
145
-
146
- public readonly logger?: ILogger;
147
-
148
- public readonly flushInterval: number;
149
-
150
- public readonly maxEventsInQueue: number;
151
-
152
- public readonly pollingInterval: number;
153
-
154
- public readonly offline: boolean;
155
-
156
- public readonly dataSyncMode: DataSyncModeEnum;
157
-
158
- public readonly bootstrapProvider: IBootstrapProvider = new NullBootstrapProvider();
159
-
160
- public user: IUser;
161
-
162
- public readonly storeFactory: (clientContext: IClientContext) => IStore;
163
-
164
- public readonly dataSynchronizerFactory?: (
165
- clientContext: IClientContext,
166
- store: IStore,
167
- dataSourceUpdates: IDataSourceUpdates,
168
- initSuccessHandler: VoidFunction,
169
- errorHandler?: (e: Error) => void,
170
- ) => IDataSynchronizer;
171
-
172
- constructor(options: IOptions = {}) {
173
- // The default will handle undefined, but not null.
174
- // Because we can be called from JS we need to be extra defensive.
175
- options = options || {};
176
- // If there isn't a valid logger from the platform, then logs would go nowhere.
177
- this.logger = options.logger;
178
-
179
- const {errors, validatedOptions} = validateTypesAndNames(options);
180
- errors.forEach((error) => {
181
- this.logger?.warn(error);
182
- });
183
-
184
- this.user = options.user!;
185
-
186
- validateEndpoints(options, validatedOptions);
187
- this.streamingUri = `${ canonicalizeUri(validatedOptions.streamingUri) }/streaming`;
188
- this.pollingUri = `${ canonicalizeUri(validatedOptions.pollingUri) }/api/public/sdk/client/latest-all`;
189
- this.eventsUri = `${ canonicalizeUri(validatedOptions.eventsUri) }/api/public/insight/track`;
190
-
191
- this.startWaitTime = validatedOptions.startWaitTime;
192
-
193
- this.sdkKey = validatedOptions.sdkKey;
194
- this.webSocketPingInterval = validatedOptions.webSocketPingInterval!;
195
-
196
- this.flushInterval = validatedOptions.flushInterval;
197
- this.maxEventsInQueue = validatedOptions.maxEventsInQueue;
198
- this.pollingInterval = validatedOptions.pollingInterval;
199
-
200
- this.offline = validatedOptions.offline;
201
- if (validatedOptions.bootstrap && validatedOptions.bootstrap.length > 0) {
202
- try {
203
- this.bootstrapProvider = new JsonBootstrapProvider(validatedOptions.bootstrap);
204
- } catch (_) {
205
- this.logger?.error('Failed to parse bootstrap JSON, use NullBootstrapProvider.');
206
- }
207
- }
208
-
209
- if (this.offline) {
210
- this.logger?.info('Offline mode enabled. No data synchronization with the FeatBit server will occur.');
211
- }
212
-
213
- this.dataSyncMode = validatedOptions.dataSyncMode;
214
-
215
- if (TypeValidators.Function.is(validatedOptions.dataSynchronizer)) {
216
- // @ts-ignore
217
- this.dataSynchronizerFactory = validatedOptions.dataSynchronizer;
218
- } else {
219
- // The processor is already created, just have the method return it.
220
- // @ts-ignore
221
- this.dataSynchronizerFactory = () => validatedOptions.dataSynchronizer;
222
- }
223
-
224
- if (TypeValidators.Function.is(validatedOptions.store)) {
225
- // @ts-ignore
226
- this.storeFactory = validatedOptions.store;
227
- } else {
228
- // The store is already created, just have the method return it.
229
- // @ts-ignore
230
- this.storeFactory = () => validatedOptions.store;
231
- }
232
- }
1
+ import { IOptions } from "./options/IOptions";
2
+ import { ILogger } from "./logging/ILogger";
3
+ import { IValidatedOptions } from "./options/IValidatedOptions";
4
+ import { NumberWithMinimum, TypeValidator, TypeValidators, UserValidator } from "./options/Validators";
5
+ import OptionMessages from "./options/OptionMessages";
6
+ import { IStore } from "./platform/IStore";
7
+ import { IClientContext } from "./options/IClientContext";
8
+ import { IDataSynchronizer } from "./data-sync/IDataSynchronizer";
9
+ import { IDataSourceUpdates } from "./store/IDataSourceUpdates";
10
+ import InMemoryStore from "./store/InMemoryStore";
11
+ import { VoidFunction } from "./utils/VoidFunction";
12
+ import { isNullOrUndefined } from "./utils/isNullOrUndefined";
13
+ import { canonicalizeUri } from "./utils/canonicalizeUri";
14
+ import { IBootstrapProvider } from "./bootstrap/IBootstrapProvider";
15
+ import { NullBootstrapProvider } from "./bootstrap/NullBootstrapProvider";
16
+ import { EmptyString } from "./constants";
17
+ import { DataSyncModeEnum } from "./data-sync/DataSyncMode";
18
+ import { IUser } from "./options/IUser";
19
+ import { JsonBootstrapProvider } from "./bootstrap";
20
+
21
+ // Once things are internal to the implementation of the SDK we can depend on
22
+ // types. Calls to the SDK could contain anything without any regard to typing.
23
+ // So, data we take from external sources must be normalized into something
24
+ // that can be trusted.
25
+
26
+ /**
27
+ * These perform cursory validations. Complex objects are implemented with classes
28
+ * and these should allow for conditional construction.
29
+ */
30
+ const validations: Record<string, TypeValidator> = {
31
+ startWaitTime: TypeValidators.Number,
32
+ sdkKey: TypeValidators.String,
33
+ pollingUri: TypeValidators.String,
34
+ streamingUri: TypeValidators.String,
35
+ eventsUri: TypeValidators.String,
36
+ webSocketPingInterval: TypeValidators.Number,
37
+ logger: TypeValidators.Object,
38
+ store: TypeValidators.ObjectOrFactory,
39
+ dataSynchronizer: TypeValidators.ObjectOrFactory,
40
+ flushInterval: TypeValidators.Number,
41
+ maxEventsInQueue: TypeValidators.Number,
42
+ pollingInterval: TypeValidators.Number,
43
+ offline: TypeValidators.Boolean,
44
+ dataSyncMode: TypeValidators.String,
45
+ bootstrap: TypeValidators.Bootstrap,
46
+ user: TypeValidators.User
47
+ };
48
+
49
+ /**
50
+ * @internal
51
+ */
52
+ export const defaultValues: IValidatedOptions = {
53
+ startWaitTime: 5000,
54
+ sdkKey: '',
55
+ pollingUri: '',
56
+ streamingUri: '',
57
+ eventsUri: '',
58
+ dataSyncMode: DataSyncModeEnum.STREAMING,
59
+ sendEvents: true,
60
+ webSocketPingInterval: 18 * 1000,
61
+ flushInterval: 2000,
62
+ maxEventsInQueue: 10000,
63
+ pollingInterval: 30000,
64
+ offline: false,
65
+ store: (options: IOptions) => new InMemoryStore(),
66
+ bootstrap: undefined,
67
+ user: undefined,
68
+ };
69
+
70
+ function validateTypesAndNames(options: IOptions): {
71
+ errors: string[];
72
+ validatedOptions: IValidatedOptions;
73
+ } {
74
+ let errors: string[] = [];
75
+ const validatedOptions: IValidatedOptions = {...defaultValues};
76
+ Object.keys(options).forEach((optionName) => {
77
+ // We need to tell typescript it doesn't actually know what options are.
78
+ // If we don't then it complains we are doing crazy things with it.
79
+ const optionValue = (options as unknown as any)[optionName];
80
+ const validator = validations[optionName];
81
+ if (validator) {
82
+ if (!validator.is(optionValue)) {
83
+ if (validator.getType() === 'boolean') {
84
+ errors.push(OptionMessages.wrongOptionTypeBoolean(optionName, typeof optionValue));
85
+ validatedOptions[optionName] = !!optionValue;
86
+ } else if (
87
+ validator instanceof NumberWithMinimum &&
88
+ TypeValidators.Number.is(optionValue)
89
+ ) {
90
+ const {min} = validator as NumberWithMinimum;
91
+ errors.push(OptionMessages.optionBelowMinimum(optionName, optionValue, min));
92
+ validatedOptions[optionName] = min;
93
+ } else if (validator instanceof UserValidator) {
94
+ errors = [...errors, ...validator.messages];
95
+ validatedOptions[optionName] = defaultValues[optionName];
96
+ } else {
97
+ errors.push(
98
+ OptionMessages.wrongOptionType(optionName, validator.getType(), typeof optionValue),
99
+ );
100
+ validatedOptions[optionName] = defaultValues[optionName];
101
+ }
102
+ } else {
103
+ validatedOptions[optionName] = optionValue;
104
+ }
105
+ } else {
106
+ options.logger?.warn(OptionMessages.unknownOption(optionName));
107
+ }
108
+ });
109
+ return {errors, validatedOptions};
110
+ }
111
+
112
+ function validateEndpoints(options: IOptions, validatedOptions: IValidatedOptions) {
113
+ const {streamingUri, pollingUri, eventsUri} = options;
114
+ const streamingUriMissing = isNullOrUndefined(streamingUri) || streamingUri === EmptyString;
115
+ const pollingUriMissing = isNullOrUndefined(pollingUri) || pollingUri === EmptyString;
116
+ const eventsUriMissing = isNullOrUndefined(eventsUri) || eventsUri === EmptyString;
117
+
118
+ if (!validatedOptions.offline && (eventsUriMissing || (streamingUriMissing && pollingUriMissing))) {
119
+ if (eventsUriMissing) {
120
+ validatedOptions.logger?.error(OptionMessages.partialEndpoint('eventsUri'));
121
+ }
122
+
123
+ if (validatedOptions.dataSyncMode === DataSyncModeEnum.STREAMING && streamingUriMissing) {
124
+ validatedOptions.logger?.error(OptionMessages.partialEndpoint('streamingUri'));
125
+ }
126
+
127
+ if (validatedOptions.dataSyncMode === DataSyncModeEnum.POLLING && pollingUriMissing) {
128
+ validatedOptions.logger?.error(OptionMessages.partialEndpoint('pollingUri'));
129
+ }
130
+ }
131
+ }
132
+
133
+ export default class Configuration {
134
+ public readonly startWaitTime: number;
135
+
136
+ public readonly sdkKey: string;
137
+
138
+ public readonly streamingUri: string;
139
+
140
+ public readonly pollingUri: string;
141
+
142
+ public readonly eventsUri: string;
143
+
144
+ public readonly webSocketPingInterval: number;
145
+
146
+ public readonly logger?: ILogger;
147
+
148
+ public readonly flushInterval: number;
149
+
150
+ public readonly maxEventsInQueue: number;
151
+
152
+ public readonly pollingInterval: number;
153
+
154
+ public readonly offline: boolean;
155
+
156
+ public readonly dataSyncMode: DataSyncModeEnum;
157
+
158
+ public readonly bootstrapProvider: IBootstrapProvider = new NullBootstrapProvider();
159
+
160
+ public user: IUser;
161
+
162
+ public readonly storeFactory: (clientContext: IClientContext) => IStore;
163
+
164
+ public readonly dataSynchronizerFactory?: (
165
+ clientContext: IClientContext,
166
+ store: IStore,
167
+ dataSourceUpdates: IDataSourceUpdates,
168
+ initSuccessHandler: VoidFunction,
169
+ errorHandler?: (e: Error) => void,
170
+ ) => IDataSynchronizer;
171
+
172
+ constructor(options: IOptions = {}) {
173
+ // The default will handle undefined, but not null.
174
+ // Because we can be called from JS we need to be extra defensive.
175
+ options = options || {};
176
+ // If there isn't a valid logger from the platform, then logs would go nowhere.
177
+ this.logger = options.logger;
178
+
179
+ const {errors, validatedOptions} = validateTypesAndNames(options);
180
+ errors.forEach((error) => {
181
+ this.logger?.warn(error);
182
+ });
183
+
184
+ this.user = options.user!;
185
+
186
+ validateEndpoints(options, validatedOptions);
187
+ this.streamingUri = `${ canonicalizeUri(validatedOptions.streamingUri) }/streaming`;
188
+ this.pollingUri = `${ canonicalizeUri(validatedOptions.pollingUri) }/api/public/sdk/client/latest-all`;
189
+ this.eventsUri = `${ canonicalizeUri(validatedOptions.eventsUri) }/api/public/insight/track`;
190
+
191
+ this.startWaitTime = validatedOptions.startWaitTime;
192
+
193
+ this.sdkKey = validatedOptions.sdkKey;
194
+ this.webSocketPingInterval = validatedOptions.webSocketPingInterval!;
195
+
196
+ this.flushInterval = validatedOptions.flushInterval;
197
+ this.maxEventsInQueue = validatedOptions.maxEventsInQueue;
198
+ this.pollingInterval = validatedOptions.pollingInterval;
199
+
200
+ this.offline = validatedOptions.offline;
201
+ if (validatedOptions.bootstrap && validatedOptions.bootstrap.length > 0) {
202
+ try {
203
+ this.bootstrapProvider = new JsonBootstrapProvider(validatedOptions.bootstrap);
204
+ } catch (_) {
205
+ this.logger?.error('Failed to parse bootstrap JSON, use NullBootstrapProvider.');
206
+ }
207
+ }
208
+
209
+ if (this.offline) {
210
+ this.logger?.info('Offline mode enabled. No data synchronization with the FeatBit server will occur.');
211
+ }
212
+
213
+ this.dataSyncMode = validatedOptions.dataSyncMode;
214
+
215
+ if (TypeValidators.Function.is(validatedOptions.dataSynchronizer)) {
216
+ // @ts-ignore
217
+ this.dataSynchronizerFactory = validatedOptions.dataSynchronizer;
218
+ } else {
219
+ // The processor is already created, just have the method return it.
220
+ // @ts-ignore
221
+ this.dataSynchronizerFactory = () => validatedOptions.dataSynchronizer;
222
+ }
223
+
224
+ if (TypeValidators.Function.is(validatedOptions.store)) {
225
+ // @ts-ignore
226
+ this.storeFactory = validatedOptions.store;
227
+ } else {
228
+ // The store is already created, just have the method return it.
229
+ // @ts-ignore
230
+ this.storeFactory = () => validatedOptions.store;
231
+ }
232
+ }
233
233
  }