@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.
- package/LICENSE +21 -21
- package/README.md +301 -301
- package/dist/esm/FbClientCore.d.ts.map +1 -1
- package/dist/esm/FbClientCore.js +1 -1
- package/dist/esm/FbClientCore.js.map +1 -1
- package/dist/esm/data-sources/DataSourceUpdates.d.ts +2 -2
- package/dist/esm/data-sources/DataSourceUpdates.d.ts.map +1 -1
- package/dist/esm/data-sources/DataSourceUpdates.js +55 -51
- package/dist/esm/data-sources/DataSourceUpdates.js.map +1 -1
- package/dist/esm/data-sources/createStreamListeners.d.ts +0 -9
- package/dist/esm/data-sources/createStreamListeners.d.ts.map +1 -1
- package/dist/esm/data-sources/createStreamListeners.js +10 -10
- package/dist/esm/data-sources/createStreamListeners.js.map +1 -1
- package/dist/esm/data-sync/IDataSynchronizer.d.ts +1 -1
- package/dist/esm/data-sync/IDataSynchronizer.d.ts.map +1 -1
- package/dist/esm/data-sync/NullDataSynchronizer.d.ts +1 -1
- package/dist/esm/data-sync/NullDataSynchronizer.d.ts.map +1 -1
- package/dist/esm/data-sync/NullDataSynchronizer.js +11 -0
- package/dist/esm/data-sync/NullDataSynchronizer.js.map +1 -1
- package/dist/esm/data-sync/PollingDataSynchronizer.d.ts +1 -1
- package/dist/esm/data-sync/PollingDataSynchronizer.d.ts.map +1 -1
- package/dist/esm/data-sync/PollingDataSynchronizer.js +42 -22
- package/dist/esm/data-sync/PollingDataSynchronizer.js.map +1 -1
- package/dist/esm/data-sync/WebSocketDataSynchronizer.d.ts +2 -1
- package/dist/esm/data-sync/WebSocketDataSynchronizer.d.ts.map +1 -1
- package/dist/esm/data-sync/WebSocketDataSynchronizer.js +20 -6
- package/dist/esm/data-sync/WebSocketDataSynchronizer.js.map +1 -1
- package/dist/esm/data-sync/types.d.ts +1 -1
- package/dist/esm/data-sync/types.d.ts.map +1 -1
- package/dist/esm/integrations/test_data/TestDataSynchronizer.d.ts +1 -1
- package/dist/esm/integrations/test_data/TestDataSynchronizer.d.ts.map +1 -1
- package/dist/esm/integrations/test_data/TestDataSynchronizer.js +3 -1
- package/dist/esm/integrations/test_data/TestDataSynchronizer.js.map +1 -1
- package/dist/esm/platform/browser/BrowserWebSocket.d.ts.map +1 -1
- package/dist/esm/platform/browser/BrowserWebSocket.js +4 -0
- package/dist/esm/platform/browser/BrowserWebSocket.js.map +1 -1
- package/dist/esm/store/IDataSourceUpdates.d.ts +2 -2
- package/dist/esm/store/IDataSourceUpdates.d.ts.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/featbit-js-client-sdk-3.0.13.js +2 -0
- package/dist/umd/featbit-js-client-sdk-3.0.13.js.map +1 -0
- package/dist/umd/featbit-js-client-sdk.js +1 -1
- package/dist/umd/featbit-js-client-sdk.js.map +1 -1
- package/package.json +46 -46
- package/src/Configuration.ts +232 -232
- package/src/Context.ts +61 -61
- package/src/FbClientBuilder.ts +167 -167
- package/src/FbClientCore.ts +405 -401
- package/src/IContextProperty.ts +3 -3
- package/src/IDataKind.ts +11 -11
- package/src/IFbClient.ts +29 -29
- package/src/IFbClientCore.ts +290 -290
- package/src/IVersionedData.ts +18 -18
- package/src/bootstrap/IBootstrapProvider.ts +4 -4
- package/src/bootstrap/JsonBootstrapProvider.ts +34 -34
- package/src/bootstrap/NullBootstrapProvider.ts +20 -20
- package/src/bootstrap/index.ts +2 -2
- package/src/constants.ts +1 -1
- package/src/data-sources/DataSourceUpdates.ts +116 -116
- package/src/data-sources/createStreamListeners.ts +67 -66
- package/src/data-sources/index.ts +1 -1
- package/src/data-sync/DataSyncMode.ts +3 -3
- package/src/data-sync/IDataSynchronizer.ts +15 -15
- package/src/data-sync/IRequestor.ts +10 -10
- package/src/data-sync/NullDataSynchronizer.ts +14 -14
- package/src/data-sync/PollingDataSynchronizer.ts +125 -116
- package/src/data-sync/Requestor.ts +61 -61
- package/src/data-sync/WebSocketDataSynchronizer.ts +77 -73
- package/src/data-sync/index.ts +8 -8
- package/src/data-sync/types.ts +19 -19
- package/src/data-sync/utils.ts +31 -31
- package/src/errors.ts +47 -47
- package/src/evaluation/EvalResult.ts +35 -35
- package/src/evaluation/Evaluator.ts +26 -26
- package/src/evaluation/IEvalDetail.ts +23 -23
- package/src/evaluation/ReasonKinds.ts +9 -9
- package/src/evaluation/data/IFlag.ts +29 -29
- package/src/evaluation/index.ts +4 -4
- package/src/events/DefaultEventProcessor.ts +83 -83
- package/src/events/DefaultEventQueue.ts +49 -49
- package/src/events/DefaultEventSender.ts +73 -73
- package/src/events/DefaultEventSerializer.ts +11 -11
- package/src/events/EventDispatcher.ts +127 -127
- package/src/events/EventSerializer.ts +4 -4
- package/src/events/IEventProcessor.ts +8 -8
- package/src/events/IEventQueue.ts +16 -16
- package/src/events/IEventSender.ts +13 -13
- package/src/events/NullEventProcessor.ts +15 -15
- package/src/events/event.ts +129 -129
- package/src/events/index.ts +11 -11
- package/src/index.ts +21 -21
- package/src/integrations/TestLogger.ts +24 -24
- package/src/integrations/index.ts +1 -1
- package/src/integrations/test_data/FlagBuilder.ts +59 -59
- package/src/integrations/test_data/TestData.ts +57 -57
- package/src/integrations/test_data/TestDataSynchronizer.ts +49 -49
- package/src/integrations/test_data/index.ts +4 -4
- package/src/logging/BasicLogger.ts +108 -108
- package/src/logging/IBasicLoggerOptions.ts +46 -46
- package/src/logging/ILogger.ts +49 -49
- package/src/logging/LogLevel.ts +8 -8
- package/src/logging/SafeLogger.ts +69 -69
- package/src/logging/format.ts +154 -154
- package/src/logging/index.ts +5 -5
- package/src/options/ClientContext.ts +39 -39
- package/src/options/IClientContext.ts +53 -53
- package/src/options/IOptions.ts +123 -123
- package/src/options/IUser.ts +6 -6
- package/src/options/IValidatedOptions.ts +29 -29
- package/src/options/OptionMessages.ts +35 -35
- package/src/options/UserBuilder.ts +35 -35
- package/src/options/Validators.ts +300 -300
- package/src/options/index.ts +7 -7
- package/src/platform/IInfo.ts +102 -102
- package/src/platform/IPlatform.ts +20 -20
- package/src/platform/IStore.ts +112 -112
- package/src/platform/IWebSocket.ts +22 -22
- package/src/platform/browser/BrowserInfo.ts +24 -24
- package/src/platform/browser/BrowserPlatform.ts +19 -19
- package/src/platform/browser/BrowserRequests.ts +6 -6
- package/src/platform/browser/BrowserWebSocket.ts +147 -142
- package/src/platform/browser/FbClient.ts +65 -65
- package/src/platform/browser/LocalStorageStore.ts +59 -59
- package/src/platform/index.ts +11 -11
- package/src/platform/requests.ts +76 -76
- package/src/store/BaseStore.ts +125 -125
- package/src/store/DataKinds.ts +6 -6
- package/src/store/IDataSourceUpdates.ts +68 -68
- package/src/store/InMemoryStore.ts +36 -36
- package/src/store/index.ts +5 -5
- package/src/store/serialization.ts +52 -52
- package/src/store/store.ts +37 -37
- package/src/utils/Emits.ts +75 -75
- package/src/utils/EventEmitter.ts +128 -128
- package/src/utils/IEventEmitter.ts +14 -14
- package/src/utils/Regex.ts +21 -21
- package/src/utils/ValueConverters.ts +55 -55
- package/src/utils/canonicalizeUri.ts +3 -3
- package/src/utils/debounce.ts +33 -33
- package/src/utils/http.ts +40 -40
- package/src/utils/index.ts +5 -5
- package/src/utils/isNullOrUndefined.ts +2 -2
- package/src/utils/serializeUser.ts +27 -27
- package/src/utils/sleep.ts +5 -5
- package/src/version.ts +1 -1
- package/dist/umd/featbit-js-client-sdk-3.0.12.js +0 -2
- 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.
|
|
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
|
+
}
|
package/src/Configuration.ts
CHANGED
|
@@ -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
|
}
|