@featbit/js-client-sdk 3.0.13 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +301 -301
  3. package/dist/esm/FbClientCore.d.ts +6 -5
  4. package/dist/esm/FbClientCore.d.ts.map +1 -1
  5. package/dist/esm/FbClientCore.js +27 -4
  6. package/dist/esm/FbClientCore.js.map +1 -1
  7. package/dist/esm/IFbClientCore.d.ts +8 -7
  8. package/dist/esm/IFbClientCore.d.ts.map +1 -1
  9. package/dist/esm/version.d.ts +1 -1
  10. package/dist/esm/version.d.ts.map +1 -1
  11. package/dist/esm/version.js +1 -1
  12. package/dist/esm/version.js.map +1 -1
  13. package/dist/umd/featbit-js-client-sdk-4.0.0.js +2 -0
  14. package/dist/umd/featbit-js-client-sdk-4.0.0.js.map +1 -0
  15. package/dist/umd/featbit-js-client-sdk.js +1 -1
  16. package/dist/umd/featbit-js-client-sdk.js.map +1 -1
  17. package/package.json +46 -46
  18. package/src/Configuration.ts +232 -232
  19. package/src/Context.ts +61 -61
  20. package/src/FbClientBuilder.ts +167 -167
  21. package/src/FbClientCore.ts +428 -405
  22. package/src/IContextProperty.ts +3 -3
  23. package/src/IDataKind.ts +11 -11
  24. package/src/IFbClient.ts +29 -29
  25. package/src/IFbClientCore.ts +291 -290
  26. package/src/IVersionedData.ts +18 -18
  27. package/src/bootstrap/IBootstrapProvider.ts +4 -4
  28. package/src/bootstrap/JsonBootstrapProvider.ts +34 -34
  29. package/src/bootstrap/NullBootstrapProvider.ts +20 -20
  30. package/src/bootstrap/index.ts +2 -2
  31. package/src/constants.ts +1 -1
  32. package/src/data-sources/DataSourceUpdates.ts +116 -116
  33. package/src/data-sources/createStreamListeners.ts +67 -67
  34. package/src/data-sources/index.ts +1 -1
  35. package/src/data-sync/DataSyncMode.ts +3 -3
  36. package/src/data-sync/IDataSynchronizer.ts +15 -15
  37. package/src/data-sync/IRequestor.ts +10 -10
  38. package/src/data-sync/NullDataSynchronizer.ts +14 -14
  39. package/src/data-sync/PollingDataSynchronizer.ts +125 -125
  40. package/src/data-sync/Requestor.ts +61 -61
  41. package/src/data-sync/WebSocketDataSynchronizer.ts +77 -77
  42. package/src/data-sync/index.ts +8 -8
  43. package/src/data-sync/types.ts +19 -19
  44. package/src/data-sync/utils.ts +31 -31
  45. package/src/errors.ts +47 -47
  46. package/src/evaluation/EvalResult.ts +35 -35
  47. package/src/evaluation/Evaluator.ts +26 -26
  48. package/src/evaluation/IEvalDetail.ts +23 -23
  49. package/src/evaluation/ReasonKinds.ts +9 -9
  50. package/src/evaluation/data/IFlag.ts +29 -29
  51. package/src/evaluation/index.ts +4 -4
  52. package/src/events/DefaultEventProcessor.ts +83 -83
  53. package/src/events/DefaultEventQueue.ts +49 -49
  54. package/src/events/DefaultEventSender.ts +73 -73
  55. package/src/events/DefaultEventSerializer.ts +11 -11
  56. package/src/events/EventDispatcher.ts +127 -127
  57. package/src/events/EventSerializer.ts +4 -4
  58. package/src/events/IEventProcessor.ts +8 -8
  59. package/src/events/IEventQueue.ts +16 -16
  60. package/src/events/IEventSender.ts +13 -13
  61. package/src/events/NullEventProcessor.ts +15 -15
  62. package/src/events/event.ts +129 -129
  63. package/src/events/index.ts +11 -11
  64. package/src/index.ts +21 -21
  65. package/src/integrations/TestLogger.ts +24 -24
  66. package/src/integrations/index.ts +1 -1
  67. package/src/integrations/test_data/FlagBuilder.ts +59 -59
  68. package/src/integrations/test_data/TestData.ts +57 -57
  69. package/src/integrations/test_data/TestDataSynchronizer.ts +49 -49
  70. package/src/integrations/test_data/index.ts +4 -4
  71. package/src/logging/BasicLogger.ts +108 -108
  72. package/src/logging/IBasicLoggerOptions.ts +46 -46
  73. package/src/logging/ILogger.ts +49 -49
  74. package/src/logging/LogLevel.ts +8 -8
  75. package/src/logging/SafeLogger.ts +69 -69
  76. package/src/logging/format.ts +154 -154
  77. package/src/logging/index.ts +5 -5
  78. package/src/options/ClientContext.ts +39 -39
  79. package/src/options/IClientContext.ts +53 -53
  80. package/src/options/IOptions.ts +123 -123
  81. package/src/options/IUser.ts +6 -6
  82. package/src/options/IValidatedOptions.ts +29 -29
  83. package/src/options/OptionMessages.ts +35 -35
  84. package/src/options/UserBuilder.ts +35 -35
  85. package/src/options/Validators.ts +300 -300
  86. package/src/options/index.ts +7 -7
  87. package/src/platform/IInfo.ts +102 -102
  88. package/src/platform/IPlatform.ts +20 -20
  89. package/src/platform/IStore.ts +112 -112
  90. package/src/platform/IWebSocket.ts +22 -22
  91. package/src/platform/browser/BrowserInfo.ts +24 -24
  92. package/src/platform/browser/BrowserPlatform.ts +19 -19
  93. package/src/platform/browser/BrowserRequests.ts +6 -6
  94. package/src/platform/browser/BrowserWebSocket.ts +147 -147
  95. package/src/platform/browser/FbClient.ts +65 -65
  96. package/src/platform/browser/LocalStorageStore.ts +59 -59
  97. package/src/platform/index.ts +11 -11
  98. package/src/platform/requests.ts +76 -76
  99. package/src/store/BaseStore.ts +125 -125
  100. package/src/store/DataKinds.ts +6 -6
  101. package/src/store/IDataSourceUpdates.ts +68 -68
  102. package/src/store/InMemoryStore.ts +36 -36
  103. package/src/store/index.ts +5 -5
  104. package/src/store/serialization.ts +52 -52
  105. package/src/store/store.ts +37 -37
  106. package/src/utils/Emits.ts +75 -75
  107. package/src/utils/EventEmitter.ts +128 -128
  108. package/src/utils/IEventEmitter.ts +14 -14
  109. package/src/utils/Regex.ts +21 -21
  110. package/src/utils/ValueConverters.ts +55 -55
  111. package/src/utils/canonicalizeUri.ts +3 -3
  112. package/src/utils/debounce.ts +33 -33
  113. package/src/utils/http.ts +40 -40
  114. package/src/utils/index.ts +5 -5
  115. package/src/utils/isNullOrUndefined.ts +2 -2
  116. package/src/utils/serializeUser.ts +27 -27
  117. package/src/utils/sleep.ts +5 -5
  118. package/src/version.ts +1 -1
  119. package/dist/umd/featbit-js-client-sdk-3.0.13.js +0 -2
  120. package/dist/umd/featbit-js-client-sdk-3.0.13.js.map +0 -1
package/package.json CHANGED
@@ -1,46 +1,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
+ {
2
+ "name": "@featbit/js-client-sdk",
3
+ "version": "4.0.0",
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
  }