@splitsoftware/splitio-commons 2.7.9-rc.0 → 2.7.9-rc.2
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/CHANGES.txt +9 -3
- package/cjs/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +8 -4
- package/cjs/logger/constants.js +2 -2
- package/cjs/logger/index.js +0 -3
- package/cjs/logger/messages/info.js +1 -1
- package/cjs/logger/messages/warn.js +3 -3
- package/cjs/readiness/readinessManager.js +3 -5
- package/cjs/readiness/sdkReadinessManager.js +46 -10
- package/cjs/sdkClient/client.js +4 -4
- package/cjs/sdkClient/clientInputValidation.js +1 -1
- package/cjs/sdkManager/index.js +3 -3
- package/cjs/storages/inMemory/TelemetryCacheInMemory.js +2 -1
- package/cjs/storages/pluggable/index.js +2 -1
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -1
- package/cjs/sync/streaming/SSEClient/index.js +2 -1
- package/cjs/sync/streaming/SSEHandler/index.js +1 -1
- package/cjs/sync/streaming/pushManager.js +1 -1
- package/cjs/sync/submitters/telemetrySubmitter.js +7 -6
- package/cjs/trackers/telemetryTracker.js +6 -6
- package/cjs/utils/inputValidation/index.js +2 -1
- package/cjs/utils/inputValidation/isOperational.js +9 -4
- package/cjs/utils/inputValidation/splitExistence.js +3 -3
- package/cjs/utils/key/index.js +5 -1
- package/esm/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.js +5 -1
- package/esm/logger/constants.js +1 -1
- package/esm/logger/index.js +0 -3
- package/esm/logger/messages/info.js +1 -1
- package/esm/logger/messages/warn.js +3 -3
- package/esm/readiness/readinessManager.js +3 -5
- package/esm/readiness/sdkReadinessManager.js +46 -10
- package/esm/sdkClient/client.js +4 -4
- package/esm/sdkClient/clientInputValidation.js +2 -2
- package/esm/sdkManager/index.js +4 -4
- package/esm/storages/inMemory/TelemetryCacheInMemory.js +2 -1
- package/esm/storages/pluggable/index.js +2 -1
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -1
- package/esm/sync/streaming/SSEClient/index.js +2 -1
- package/esm/sync/streaming/SSEHandler/index.js +1 -1
- package/esm/sync/streaming/pushManager.js +2 -2
- package/esm/sync/submitters/telemetrySubmitter.js +7 -6
- package/esm/trackers/telemetryTracker.js +6 -6
- package/esm/utils/inputValidation/index.js +1 -1
- package/esm/utils/inputValidation/isOperational.js +8 -4
- package/esm/utils/inputValidation/splitExistence.js +3 -3
- package/esm/utils/key/index.js +3 -0
- package/package.json +1 -1
- package/src/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.ts +4 -1
- package/src/logger/constants.ts +1 -1
- package/src/logger/index.ts +0 -2
- package/src/logger/messages/info.ts +1 -1
- package/src/logger/messages/warn.ts +3 -3
- package/src/readiness/readinessManager.ts +3 -4
- package/src/readiness/sdkReadinessManager.ts +43 -10
- package/src/readiness/types.ts +1 -2
- package/src/sdkClient/client.ts +4 -4
- package/src/sdkClient/clientInputValidation.ts +3 -3
- package/src/sdkManager/index.ts +4 -4
- package/src/storages/inMemory/TelemetryCacheInMemory.ts +2 -1
- package/src/storages/pluggable/index.ts +2 -1
- package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
- package/src/sync/streaming/SSEClient/index.ts +2 -1
- package/src/sync/streaming/SSEHandler/index.ts +1 -1
- package/src/sync/streaming/pushManager.ts +2 -2
- package/src/sync/submitters/telemetrySubmitter.ts +7 -6
- package/src/trackers/telemetryTracker.ts +6 -6
- package/src/types.ts +0 -15
- package/src/utils/inputValidation/index.ts +1 -1
- package/src/utils/inputValidation/isOperational.ts +9 -4
- package/src/utils/inputValidation/splitExistence.ts +3 -3
- package/src/utils/key/index.ts +5 -0
- package/types/splitio.d.ts +77 -10
- package/cjs/evaluator/fallbackTreatmentsCalculator/constants.js +0 -8
- package/esm/evaluator/fallbackTreatmentsCalculator/constants.js +0 -5
- package/src/evaluator/fallbackTreatmentsCalculator/constants.ts +0 -4
|
@@ -9,6 +9,7 @@ import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO
|
|
|
9
9
|
|
|
10
10
|
const NEW_LISTENER_EVENT = 'newListener';
|
|
11
11
|
const REMOVE_LISTENER_EVENT = 'removeListener';
|
|
12
|
+
const TIMEOUT_ERROR = new Error(SDK_READY_TIMED_OUT);
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc).
|
|
@@ -38,6 +39,8 @@ export function sdkReadinessManagerFactory(
|
|
|
38
39
|
} else if (event === SDK_READY) {
|
|
39
40
|
readyCbCount++;
|
|
40
41
|
}
|
|
42
|
+
} else if (event === SDK_READY_FROM_CACHE && readinessManager.isReadyFromCache()) {
|
|
43
|
+
log.error(ERROR_CLIENT_LISTENER, ['SDK_READY_FROM_CACHE']);
|
|
41
44
|
}
|
|
42
45
|
});
|
|
43
46
|
|
|
@@ -69,6 +72,17 @@ export function sdkReadinessManagerFactory(
|
|
|
69
72
|
return promise;
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
function getStatus() {
|
|
76
|
+
return {
|
|
77
|
+
isReady: readinessManager.isReady(),
|
|
78
|
+
isReadyFromCache: readinessManager.isReadyFromCache(),
|
|
79
|
+
isTimedout: readinessManager.isTimedout(),
|
|
80
|
+
hasTimedout: readinessManager.hasTimedout(),
|
|
81
|
+
isDestroyed: readinessManager.isDestroyed(),
|
|
82
|
+
isOperational: readinessManager.isOperational(),
|
|
83
|
+
lastUpdate: readinessManager.lastUpdate(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
72
86
|
|
|
73
87
|
return {
|
|
74
88
|
readinessManager,
|
|
@@ -93,6 +107,7 @@ export function sdkReadinessManagerFactory(
|
|
|
93
107
|
SDK_READY_TIMED_OUT,
|
|
94
108
|
},
|
|
95
109
|
|
|
110
|
+
// @TODO: remove in next major
|
|
96
111
|
ready() {
|
|
97
112
|
if (readinessManager.hasTimedout()) {
|
|
98
113
|
if (!readinessManager.isReady()) {
|
|
@@ -104,17 +119,35 @@ export function sdkReadinessManagerFactory(
|
|
|
104
119
|
return readyPromise;
|
|
105
120
|
},
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
whenReady() {
|
|
123
|
+
return new Promise<void>((resolve, reject) => {
|
|
124
|
+
if (readinessManager.isReady()) {
|
|
125
|
+
resolve();
|
|
126
|
+
} else if (readinessManager.hasTimedout()) {
|
|
127
|
+
reject(TIMEOUT_ERROR);
|
|
128
|
+
} else {
|
|
129
|
+
readinessManager.gate.once(SDK_READY, resolve);
|
|
130
|
+
readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
whenReadyFromCache() {
|
|
136
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
137
|
+
if (readinessManager.isReadyFromCache()) {
|
|
138
|
+
resolve(readinessManager.isReady());
|
|
139
|
+
} else if (readinessManager.hasTimedout()) {
|
|
140
|
+
reject(TIMEOUT_ERROR);
|
|
141
|
+
} else {
|
|
142
|
+
readinessManager.gate.once(SDK_READY_FROM_CACHE, () => resolve(readinessManager.isReady()));
|
|
143
|
+
readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
117
146
|
},
|
|
147
|
+
|
|
148
|
+
getStatus,
|
|
149
|
+
// @TODO: remove in next major
|
|
150
|
+
__getStatus: getStatus
|
|
118
151
|
}
|
|
119
152
|
)
|
|
120
153
|
};
|
package/src/readiness/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { IStatusInterface } from '../types';
|
|
2
1
|
import SplitIO from '../../types/splitio';
|
|
3
2
|
|
|
4
3
|
/** Splits data emitter */
|
|
@@ -72,7 +71,7 @@ export interface IReadinessManager {
|
|
|
72
71
|
|
|
73
72
|
export interface ISdkReadinessManager {
|
|
74
73
|
readinessManager: IReadinessManager
|
|
75
|
-
sdkStatus: IStatusInterface
|
|
74
|
+
sdkStatus: SplitIO.IStatusInterface
|
|
76
75
|
|
|
77
76
|
/**
|
|
78
77
|
* Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -51,7 +51,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
51
51
|
return treatment;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const evaluation = readinessManager.
|
|
54
|
+
const evaluation = readinessManager.isReadyFromCache() ?
|
|
55
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) :
|
|
@@ -80,7 +80,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
80
80
|
return treatments;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
const evaluations = readinessManager.
|
|
83
|
+
const evaluations = readinessManager.isReadyFromCache() ?
|
|
84
84
|
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
85
85
|
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
86
|
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
@@ -109,7 +109,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
109
109
|
return treatments;
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
-
const evaluations = readinessManager.
|
|
112
|
+
const evaluations = readinessManager.isReadyFromCache() ?
|
|
113
113
|
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
114
114
|
isAsync ?
|
|
115
115
|
Promise.resolve({}) :
|
|
@@ -149,7 +149,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
149
149
|
if (treatment === CONTROL) {
|
|
150
150
|
const fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label);
|
|
151
151
|
treatment = fallbackTreatment.treatment;
|
|
152
|
-
label = fallbackTreatment.label
|
|
152
|
+
label = fallbackTreatment.label;
|
|
153
153
|
config = fallbackTreatment.config;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
validateSplits,
|
|
9
9
|
validateTrafficType,
|
|
10
10
|
validateIfNotDestroyed,
|
|
11
|
-
|
|
11
|
+
validateIfReadyFromCache,
|
|
12
12
|
validateEvaluationOptions
|
|
13
13
|
} from '../utils/inputValidation';
|
|
14
14
|
import { startsWith } from '../utils/lang';
|
|
@@ -46,7 +46,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
46
46
|
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
|
|
47
47
|
const options = validateEvaluationOptions(log, maybeOptions, methodName);
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
validateIfReadyFromCache(log, readinessManager, methodName, nameOrNames);
|
|
50
50
|
|
|
51
51
|
const valid = isNotDestroyed && key && nameOrNames && attributes !== false;
|
|
52
52
|
|
|
@@ -60,7 +60,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function evaluateFallBackTreatment(featureFlagName: string, withConfig: boolean): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
|
|
63
|
-
const {treatment, config} = fallbackTreatmentsCalculator.resolve(featureFlagName, '');
|
|
63
|
+
const { treatment, config } = fallbackTreatmentsCalculator.resolve(featureFlagName, '');
|
|
64
64
|
|
|
65
65
|
if (withConfig) {
|
|
66
66
|
return {
|
package/src/sdkManager/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { find } from '../utils/lang';
|
|
4
|
-
import { validateSplit, validateSplitExistence,
|
|
4
|
+
import { validateSplit, validateSplitExistence, validateIfOperational } from '../utils/inputValidation';
|
|
5
5
|
import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
|
|
6
6
|
import { ISdkReadinessManager } from '../readiness/types';
|
|
7
7
|
import { ISplit } from '../dtos/types';
|
|
@@ -66,7 +66,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
66
66
|
*/
|
|
67
67
|
split(featureFlagName: string) {
|
|
68
68
|
const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
|
|
69
|
-
if (!
|
|
69
|
+
if (!validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
|
|
70
70
|
return isAsync ? Promise.resolve(null) : null;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -87,7 +87,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
87
87
|
* Get the feature flag objects present on the factory storage
|
|
88
88
|
*/
|
|
89
89
|
splits() {
|
|
90
|
-
if (!
|
|
90
|
+
if (!validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
|
|
91
91
|
return isAsync ? Promise.resolve([]) : [];
|
|
92
92
|
}
|
|
93
93
|
const currentSplits = splits.getAll();
|
|
@@ -100,7 +100,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
100
100
|
* Get the feature flag names present on the factory storage
|
|
101
101
|
*/
|
|
102
102
|
names() {
|
|
103
|
-
if (!
|
|
103
|
+
if (!validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
|
|
104
104
|
return isAsync ? Promise.resolve([]) : [];
|
|
105
105
|
}
|
|
106
106
|
const splitNames = splits.getSplitNames();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies, TelemetryUsageStatsPayload, UpdatesFromSSEEnum, UpdatesFromSSE } from '../../sync/submitters/types';
|
|
2
2
|
import { DEDUPED, DROPPED, LOCALHOST_MODE, QUEUED } from '../../utils/constants';
|
|
3
|
+
import { checkIfServerSide } from '../../utils/key';
|
|
3
4
|
import { findLatencyIndex } from '../findLatencyIndex';
|
|
4
5
|
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageFactoryParams, ITelemetryCacheSync } from '../types';
|
|
5
6
|
|
|
@@ -20,7 +21,7 @@ const ACCEPTANCE_RANGE = 0.001;
|
|
|
20
21
|
* All factory instances track telemetry on server-side, and 0.1% on client-side.
|
|
21
22
|
*/
|
|
22
23
|
export function shouldRecordTelemetry({ settings }: IStorageFactoryParams) {
|
|
23
|
-
return settings.mode !== LOCALHOST_MODE && (settings
|
|
24
|
+
return settings.mode !== LOCALHOST_MODE && (checkIfServerSide(settings) || Math.random() <= ACCEPTANCE_RANGE);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export class TelemetryCacheInMemory implements ITelemetryCacheSync {
|
|
@@ -21,6 +21,7 @@ import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS
|
|
|
21
21
|
import { metadataBuilder } from '../utils';
|
|
22
22
|
import { LOG_PREFIX } from '../pluggable/constants';
|
|
23
23
|
import { RBSegmentsCachePluggable } from './RBSegmentsCachePluggable';
|
|
24
|
+
import { checkIfServerSide } from '../../utils/key';
|
|
24
25
|
|
|
25
26
|
const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
|
|
26
27
|
const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
|
|
@@ -83,7 +84,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
83
84
|
new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper);
|
|
84
85
|
|
|
85
86
|
const uniqueKeysCache = isPartialConsumer ?
|
|
86
|
-
settings
|
|
87
|
+
checkIfServerSide(settings) ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
|
|
87
88
|
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper);
|
|
88
89
|
|
|
89
90
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
@@ -6,6 +6,7 @@ import { FLAG_SPEC_VERSION } from '../../../utils/constants';
|
|
|
6
6
|
import { base } from '../../../utils/settingsValidation';
|
|
7
7
|
import { ISplitChangesFetcher } from './types';
|
|
8
8
|
import { LOG_PREFIX_SYNC_SPLITS } from '../../../logger/constants';
|
|
9
|
+
import { checkIfServerSide } from '../../../utils/key';
|
|
9
10
|
|
|
10
11
|
const PROXY_CHECK_INTERVAL_MILLIS_CS = 60 * 60 * 1000; // 1 hour in Client Side
|
|
11
12
|
const PROXY_CHECK_INTERVAL_MILLIS_SS = 24 * PROXY_CHECK_INTERVAL_MILLIS_CS; // 24 hours in Server Side
|
|
@@ -22,7 +23,7 @@ function sdkEndpointOverridden(settings: ISettings) {
|
|
|
22
23
|
export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges, settings: ISettings, storage: Pick<IStorageBase, 'splits' | 'rbSegments'>): ISplitChangesFetcher {
|
|
23
24
|
|
|
24
25
|
const log = settings.log;
|
|
25
|
-
const PROXY_CHECK_INTERVAL_MILLIS = settings
|
|
26
|
+
const PROXY_CHECK_INTERVAL_MILLIS = checkIfServerSide(settings) ? PROXY_CHECK_INTERVAL_MILLIS_SS : PROXY_CHECK_INTERVAL_MILLIS_CS;
|
|
26
27
|
let lastProxyCheckTimestamp: number | undefined;
|
|
27
28
|
|
|
28
29
|
return function splitChangesFetcher(
|
|
@@ -2,6 +2,7 @@ import { IPlatform } from '../../../sdkFactory/types';
|
|
|
2
2
|
import { decorateHeaders } from '../../../services/decorateHeaders';
|
|
3
3
|
import { IEventSourceConstructor } from '../../../services/types';
|
|
4
4
|
import { ISettings } from '../../../types';
|
|
5
|
+
import { checkIfServerSide } from '../../../utils/key';
|
|
5
6
|
import { isString } from '../../../utils/lang';
|
|
6
7
|
import { objectAssign } from '../../../utils/lang/objectAssign';
|
|
7
8
|
import { IAuthTokenPushEnabled } from '../AuthClient/types';
|
|
@@ -73,7 +74,7 @@ export class SSEClient implements ISSEClient {
|
|
|
73
74
|
return encodeURIComponent(params + channel);
|
|
74
75
|
}).join(',');
|
|
75
76
|
const url = `${this.settings.urls.streaming}/sse?channels=${channelsQueryParam}&accessToken=${authToken.token}&v=${ABLY_API_VERSION}&heartbeats=true`; // same results using `&heartbeats=false`
|
|
76
|
-
const isServerSide =
|
|
77
|
+
const isServerSide = checkIfServerSide(this.settings);
|
|
77
78
|
|
|
78
79
|
this.connection = new this.eventSource!(
|
|
79
80
|
// For client-side SDKs, metadata is passed as query param to avoid CORS issues and because native EventSource implementations in browsers do not support headers
|
|
@@ -25,7 +25,7 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter,
|
|
|
25
25
|
const code = error.parsedData.code;
|
|
26
26
|
telemetryTracker.streamingEvent(ABLY_ERROR, code);
|
|
27
27
|
|
|
28
|
-
// 401 errors due to invalid or expired token (e.g., if refresh token
|
|
28
|
+
// 401 errors due to invalid or expired token (e.g., if refresh token couldn't be executed)
|
|
29
29
|
if (40140 <= code && code <= 40149) return true;
|
|
30
30
|
// Others 4XX errors (e.g., bad request from the SDK)
|
|
31
31
|
if (40000 <= code && code <= 49999) return false;
|
|
@@ -10,7 +10,7 @@ import { SplitsUpdateWorker } from './UpdateWorkers/SplitsUpdateWorker';
|
|
|
10
10
|
import { authenticateFactory, hashUserKey } from './AuthClient';
|
|
11
11
|
import { forOwn } from '../../utils/lang';
|
|
12
12
|
import { SSEClient } from './SSEClient';
|
|
13
|
-
import { getMatching } from '../../utils/key';
|
|
13
|
+
import { checkIfServerSide, getMatching } from '../../utils/key';
|
|
14
14
|
import { MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, RB_SEGMENT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType } from './constants';
|
|
15
15
|
import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STREAMING_DISABLED, ERROR_STREAMING_AUTH, STREAMING_DISCONNECTING, STREAMING_RECONNECT, STREAMING_PARSING_MEMBERSHIPS_UPDATE } from '../../logger/constants';
|
|
16
16
|
import { IMembershipMSUpdateData, IMembershipLSUpdateData, KeyList, UpdateStrategy } from './SSEHandler/types';
|
|
@@ -34,7 +34,7 @@ export function pushManagerFactory(
|
|
|
34
34
|
|
|
35
35
|
// `userKey` is the matching key of main client in client-side SDK.
|
|
36
36
|
// It can be used to check if running on client-side or server-side SDK.
|
|
37
|
-
const userKey = settings
|
|
37
|
+
const userKey = checkIfServerSide(settings) ? undefined : getMatching(settings.core.key);
|
|
38
38
|
const log = settings.log;
|
|
39
39
|
|
|
40
40
|
let sseClient: ISSEClient;
|
|
@@ -11,6 +11,7 @@ import { timer } from '../../utils/timeTracker/timer';
|
|
|
11
11
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
12
12
|
import { objectAssign } from '../../utils/lang/objectAssign';
|
|
13
13
|
import { ISplitFiltersValidation } from '../../dtos/types';
|
|
14
|
+
import { checkIfServerSide } from '../../utils/key';
|
|
14
15
|
|
|
15
16
|
const OPERATION_MODE_MAP = {
|
|
16
17
|
[STANDALONE_MODE]: STANDALONE_ENUM,
|
|
@@ -72,7 +73,7 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
|
|
|
72
73
|
|
|
73
74
|
pop(): TelemetryConfigStatsPayload {
|
|
74
75
|
const { urls, scheduler } = settings;
|
|
75
|
-
const
|
|
76
|
+
const isServerSide = checkIfServerSide(settings);
|
|
76
77
|
|
|
77
78
|
const { flagSetsTotal, flagSetsIgnored } = getTelemetryFlagSetsStats(settings.sync.__splitFiltersValidation);
|
|
78
79
|
|
|
@@ -80,8 +81,8 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
|
|
|
80
81
|
sE: settings.streamingEnabled,
|
|
81
82
|
rR: {
|
|
82
83
|
sp: scheduler.featuresRefreshRate / 1000,
|
|
83
|
-
se:
|
|
84
|
-
ms:
|
|
84
|
+
se: isServerSide ? scheduler.segmentsRefreshRate / 1000 : undefined,
|
|
85
|
+
ms: isServerSide ? undefined : scheduler.segmentsRefreshRate / 1000,
|
|
85
86
|
im: scheduler.impressionsRefreshRate / 1000,
|
|
86
87
|
ev: scheduler.eventsPushRate / 1000,
|
|
87
88
|
te: scheduler.telemetryRefreshRate / 1000,
|
|
@@ -119,7 +120,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
119
120
|
if (!telemetry || !now) return; // No submitter created if telemetry cache is not defined
|
|
120
121
|
|
|
121
122
|
const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, readiness, sdkReadinessManager } = params;
|
|
122
|
-
const
|
|
123
|
+
const stopTimer = timer(now);
|
|
123
124
|
|
|
124
125
|
const submitter = firstPushWindowDecorator(
|
|
125
126
|
submitterFactory(
|
|
@@ -131,12 +132,12 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
131
132
|
);
|
|
132
133
|
|
|
133
134
|
readiness.gate.once(SDK_READY_FROM_CACHE, () => {
|
|
134
|
-
telemetry.recordTimeUntilReadyFromCache(
|
|
135
|
+
telemetry.recordTimeUntilReadyFromCache(stopTimer());
|
|
135
136
|
});
|
|
136
137
|
|
|
137
138
|
sdkReadinessManager.incInternalReadyCbCount();
|
|
138
139
|
readiness.gate.once(SDK_READY, () => {
|
|
139
|
-
telemetry.recordTimeUntilReady(
|
|
140
|
+
telemetry.recordTimeUntilReady(stopTimer());
|
|
140
141
|
|
|
141
142
|
// Post config data when the SDK is ready and if the telemetry submitter was started
|
|
142
143
|
if (submitter.isRunning()) {
|
|
@@ -11,11 +11,11 @@ export function telemetryTrackerFactory(
|
|
|
11
11
|
): ITelemetryTracker {
|
|
12
12
|
|
|
13
13
|
if (telemetryCache && now) {
|
|
14
|
-
const
|
|
14
|
+
const sessionTimer = timer(now);
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
17
|
trackEval(method) {
|
|
18
|
-
const
|
|
18
|
+
const evalTimer = timer(now);
|
|
19
19
|
|
|
20
20
|
return (label) => {
|
|
21
21
|
switch (label) {
|
|
@@ -25,20 +25,20 @@ export function telemetryTrackerFactory(
|
|
|
25
25
|
case SDK_NOT_READY: // @ts-ignore ITelemetryCacheAsync doesn't implement the method
|
|
26
26
|
if (telemetryCache.recordNonReadyUsage) telemetryCache.recordNonReadyUsage();
|
|
27
27
|
}
|
|
28
|
-
telemetryCache.recordLatency(method,
|
|
28
|
+
telemetryCache.recordLatency(method, evalTimer());
|
|
29
29
|
};
|
|
30
30
|
},
|
|
31
31
|
trackHttp(operation) {
|
|
32
|
-
const
|
|
32
|
+
const httpTimer = timer(now);
|
|
33
33
|
|
|
34
34
|
return (error) => {
|
|
35
|
-
(telemetryCache as ITelemetryCacheSync).recordHttpLatency(operation,
|
|
35
|
+
(telemetryCache as ITelemetryCacheSync).recordHttpLatency(operation, httpTimer());
|
|
36
36
|
if (error && error.statusCode) (telemetryCache as ITelemetryCacheSync).recordHttpError(operation, error.statusCode);
|
|
37
37
|
else (telemetryCache as ITelemetryCacheSync).recordSuccessfulSync(operation, Date.now());
|
|
38
38
|
};
|
|
39
39
|
},
|
|
40
40
|
sessionLength() { // @ts-ignore ITelemetryCacheAsync doesn't implement the method
|
|
41
|
-
if (telemetryCache.recordSessionLength) telemetryCache.recordSessionLength(
|
|
41
|
+
if (telemetryCache.recordSessionLength) telemetryCache.recordSessionLength(sessionTimer());
|
|
42
42
|
},
|
|
43
43
|
streamingEvent(e, d) {
|
|
44
44
|
if (e === AUTH_REJECTION) {
|
package/src/types.ts
CHANGED
|
@@ -14,21 +14,6 @@ export interface ISettings extends SplitIO.ISettings {
|
|
|
14
14
|
readonly initialRolloutPlan?: RolloutPlan;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* SplitIO.IStatusInterface interface extended with private properties for internal use
|
|
19
|
-
*/
|
|
20
|
-
export interface IStatusInterface extends SplitIO.IStatusInterface {
|
|
21
|
-
// Expose status for internal purposes only. Not considered part of the public API, and might be updated eventually.
|
|
22
|
-
__getStatus(): {
|
|
23
|
-
isReady: boolean;
|
|
24
|
-
isReadyFromCache: boolean;
|
|
25
|
-
isTimedout: boolean;
|
|
26
|
-
hasTimedout: boolean;
|
|
27
|
-
isDestroyed: boolean;
|
|
28
|
-
isOperational: boolean;
|
|
29
|
-
lastUpdate: number;
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
17
|
/**
|
|
33
18
|
* SplitIO.IBasicClient interface extended with private properties for internal use
|
|
34
19
|
*/
|
|
@@ -7,7 +7,7 @@ export { validateKey } from './key';
|
|
|
7
7
|
export { validateSplit } from './split';
|
|
8
8
|
export { validateSplits } from './splits';
|
|
9
9
|
export { validateTrafficType } from './trafficType';
|
|
10
|
-
export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
|
|
10
|
+
export { validateIfNotDestroyed, validateIfReadyFromCache, validateIfOperational } from './isOperational';
|
|
11
11
|
export { validateSplitExistence } from './splitExistence';
|
|
12
12
|
export { validateTrafficTypeExistence } from './trafficTypeExistence';
|
|
13
13
|
export { validateEvaluationOptions } from './eventProperties';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ERROR_CLIENT_DESTROYED,
|
|
1
|
+
import { ERROR_CLIENT_DESTROYED, CLIENT_NOT_READY_FROM_CACHE } from '../../logger/constants';
|
|
2
2
|
import { ILogger } from '../../logger/types';
|
|
3
3
|
import { IReadinessManager } from '../../readiness/types';
|
|
4
4
|
|
|
@@ -9,9 +9,14 @@ export function validateIfNotDestroyed(log: ILogger, readinessManager: IReadines
|
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function
|
|
13
|
-
if (readinessManager.
|
|
12
|
+
export function validateIfReadyFromCache(log: ILogger, readinessManager: IReadinessManager, method: string, featureFlagNameOrNames?: string | string[] | false) {
|
|
13
|
+
if (readinessManager.isReadyFromCache()) return true;
|
|
14
14
|
|
|
15
|
-
log.warn(
|
|
15
|
+
log.warn(CLIENT_NOT_READY_FROM_CACHE, [method, featureFlagNameOrNames ? ` for feature flag ${featureFlagNameOrNames.toString()}` : '']);
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
// Operational means that the SDK is ready to evaluate (not destroyed and ready from cache)
|
|
20
|
+
export function validateIfOperational(log: ILogger, readinessManager: IReadinessManager, method: string, featureFlagNameOrNames?: string | string[] | false) {
|
|
21
|
+
return validateIfNotDestroyed(log, readinessManager, method) && validateIfReadyFromCache(log, readinessManager, method, featureFlagNameOrNames);
|
|
22
|
+
}
|
|
@@ -5,11 +5,11 @@ import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* This is defined here and in this format mostly because of the logger and the fact that it's considered a validation at product level.
|
|
8
|
-
* But it's not going to run on the input validation layer. In any case, the most
|
|
8
|
+
* But it's not going to run on the input validation layer. In any case, the most compelling reason to use it as we do is to avoid going to Redis and get a split twice.
|
|
9
9
|
*/
|
|
10
10
|
export function validateSplitExistence(log: ILogger, readinessManager: IReadinessManager, splitName: string, labelOrSplitObj: any, method: string): boolean {
|
|
11
|
-
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is
|
|
12
|
-
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj
|
|
11
|
+
if (readinessManager.isReady()) { // Only if it's ready (synced with BE) we validate this, otherwise it may just be that the SDK is still syncing
|
|
12
|
+
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj === FALLBACK_SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
13
13
|
log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
14
14
|
return false;
|
|
15
15
|
}
|
package/src/utils/key/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import SplitIO from '../../../types/splitio';
|
|
2
|
+
import { ISettings } from '../../types';
|
|
2
3
|
import { isObject } from '../lang';
|
|
3
4
|
|
|
4
5
|
// function isSplitKeyObject(key: any): key is SplitIO.SplitKeyObject {
|
|
@@ -32,3 +33,7 @@ export function keyParser(key: SplitIO.SplitKey): SplitIO.SplitKeyObject {
|
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
|
|
37
|
+
export function checkIfServerSide(settings: ISettings) {
|
|
38
|
+
return !settings.core.key;
|
|
39
|
+
}
|
package/types/splitio.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ interface ISharedSettings {
|
|
|
93
93
|
urls?: SplitIO.UrlSettings;
|
|
94
94
|
/**
|
|
95
95
|
* Custom logger object. If not provided, the SDK will use the default `console.log` method for all log levels.
|
|
96
|
+
* Set together with `debug` option to `true` or a log level string to enable logging.
|
|
96
97
|
*/
|
|
97
98
|
logger?: SplitIO.Logger;
|
|
98
99
|
}
|
|
@@ -145,8 +146,6 @@ interface IPluggableSharedSettings {
|
|
|
145
146
|
* config.debug = ErrorLogger()
|
|
146
147
|
* ```
|
|
147
148
|
*
|
|
148
|
-
* When combined with the `logger` option, any log level other than `NONE` (false) will be set to `DEBUG` (true), delegating log level control to the custom logger.
|
|
149
|
-
*
|
|
150
149
|
* @defaultValue `false`
|
|
151
150
|
*/
|
|
152
151
|
debug?: boolean | SplitIO.LogLevel | SplitIO.ILogger;
|
|
@@ -170,8 +169,6 @@ interface INonPluggableSharedSettings {
|
|
|
170
169
|
* config.debug = 'WARN'
|
|
171
170
|
* ```
|
|
172
171
|
*
|
|
173
|
-
* When combined with the `logger` option, any log level other than `NONE` (false) will be set to `DEBUG` (true), delegating log level control to the custom logger.
|
|
174
|
-
*
|
|
175
172
|
* @defaultValue `false`
|
|
176
173
|
*/
|
|
177
174
|
debug?: boolean | SplitIO.LogLevel;
|
|
@@ -528,19 +525,19 @@ declare namespace SplitIO {
|
|
|
528
525
|
*/
|
|
529
526
|
type EventConsts = {
|
|
530
527
|
/**
|
|
531
|
-
* The ready event.
|
|
528
|
+
* The ready event emitted once the SDK is ready to evaluate feature flags with cache synchronized with the backend.
|
|
532
529
|
*/
|
|
533
530
|
SDK_READY: 'init::ready';
|
|
534
531
|
/**
|
|
535
|
-
* The ready event
|
|
532
|
+
* The ready event emitted once the SDK is ready to evaluate feature flags with cache that could be stale. Use SDK_READY if you want to be sure the cache is in sync with the backend.
|
|
536
533
|
*/
|
|
537
534
|
SDK_READY_FROM_CACHE: 'init::cache-ready';
|
|
538
535
|
/**
|
|
539
|
-
* The timeout event.
|
|
536
|
+
* The timeout event emitted after `startup.readyTimeout` seconds if the SDK_READY event was not emitted.
|
|
540
537
|
*/
|
|
541
538
|
SDK_READY_TIMED_OUT: 'init::timeout';
|
|
542
539
|
/**
|
|
543
|
-
* The update event.
|
|
540
|
+
* The update event emitted when the SDK cache is updated with new data from the backend.
|
|
544
541
|
*/
|
|
545
542
|
SDK_UPDATE: 'state::update';
|
|
546
543
|
};
|
|
@@ -698,6 +695,52 @@ declare namespace SplitIO {
|
|
|
698
695
|
[status in ConsentStatus]: ConsentStatus;
|
|
699
696
|
};
|
|
700
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* Readiness Status interface. It represents the readiness state of an SDK client.
|
|
700
|
+
*/
|
|
701
|
+
interface ReadinessStatus {
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* `isReady` indicates if the client has triggered an `SDK_READY` event and
|
|
705
|
+
* thus is ready to evaluate with cached data synchronized with the backend.
|
|
706
|
+
*/
|
|
707
|
+
isReady: boolean;
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and
|
|
711
|
+
* thus is ready to evaluate with cached data, although the data in cache might be stale, not synchronized with the backend.
|
|
712
|
+
*/
|
|
713
|
+
isReadyFromCache: boolean;
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* `isTimedout` indicates if the client has triggered an `SDK_READY_TIMED_OUT` event and is not ready to evaluate.
|
|
717
|
+
* In other words, `isTimedout` is equivalent to `hasTimedout && !isReady`.
|
|
718
|
+
*/
|
|
719
|
+
isTimedout: boolean;
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* `hasTimedout` indicates if the client has ever triggered an `SDK_READY_TIMED_OUT` event.
|
|
723
|
+
* It's meant to keep a reference that the SDK emitted a timeout at some point, not the current state.
|
|
724
|
+
*/
|
|
725
|
+
hasTimedout: boolean;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* `isDestroyed` indicates if the client has been destroyed, i.e., `destroy` method has been called.
|
|
729
|
+
*/
|
|
730
|
+
isDestroyed: boolean;
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* `isOperational` indicates if the client can evaluate feature flags.
|
|
734
|
+
* In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed.
|
|
735
|
+
* It's equivalent to `isReadyFromCache && !isDestroyed`.
|
|
736
|
+
*/
|
|
737
|
+
isOperational: boolean;
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* `lastUpdate` indicates the timestamp of the most recent status event.
|
|
741
|
+
*/
|
|
742
|
+
lastUpdate: number;
|
|
743
|
+
}
|
|
701
744
|
/**
|
|
702
745
|
* Common API for entities that expose status handlers.
|
|
703
746
|
*/
|
|
@@ -707,7 +750,13 @@ declare namespace SplitIO {
|
|
|
707
750
|
*/
|
|
708
751
|
Event: EventConsts;
|
|
709
752
|
/**
|
|
710
|
-
*
|
|
753
|
+
* Gets the readiness status.
|
|
754
|
+
*
|
|
755
|
+
* @returns The current readiness status.
|
|
756
|
+
*/
|
|
757
|
+
getStatus(): ReadinessStatus;
|
|
758
|
+
/**
|
|
759
|
+
* Returns a promise that resolves when the SDK has finished initial synchronization with the backend (`SDK_READY` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted).
|
|
711
760
|
* As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready.
|
|
712
761
|
*
|
|
713
762
|
* Caveats: the method was designed to avoid an unhandled Promise rejection if the rejection case is not handled, so that `onRejected` handler is optional when using promises.
|
|
@@ -722,8 +771,26 @@ declare namespace SplitIO {
|
|
|
722
771
|
* ```
|
|
723
772
|
*
|
|
724
773
|
* @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout.
|
|
774
|
+
* @deprecated Use `whenReady` instead.
|
|
725
775
|
*/
|
|
726
776
|
ready(): Promise<void>;
|
|
777
|
+
/**
|
|
778
|
+
* Returns a promise that resolves when the SDK has finished initial synchronization with the backend (`SDK_READY` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted).
|
|
779
|
+
* As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready.
|
|
780
|
+
* You must handle the promise rejection to avoid an unhandled promise rejection error, or set the `startup.readyTimeout` configuration option to 0 to avoid the timeout and thus the rejection.
|
|
781
|
+
*
|
|
782
|
+
* @returns A promise that resolves once the SDK_READY event is emitted or rejects if the SDK has timedout.
|
|
783
|
+
*/
|
|
784
|
+
whenReady(): Promise<void>;
|
|
785
|
+
/**
|
|
786
|
+
* Returns a promise that resolves when the SDK is ready for evaluations using cached data, which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted), or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted).
|
|
787
|
+
* As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache.
|
|
788
|
+
* You must handle the promise rejection to avoid an unhandled promise rejection error, or set the `startup.readyTimeout` configuration option to 0 to avoid the timeout and thus the rejection.
|
|
789
|
+
*
|
|
790
|
+
* @returns A promise that resolves once the SDK_READY_FROM_CACHE event is emitted or rejects if the SDK has timedout. The promise resolves with a boolean value that
|
|
791
|
+
* indicates whether the SDK_READY_FROM_CACHE event was emitted together with the SDK_READY event (i.e., the SDK is ready and synchronized with the backend) or not.
|
|
792
|
+
*/
|
|
793
|
+
whenReadyFromCache(): Promise<boolean>;
|
|
727
794
|
}
|
|
728
795
|
/**
|
|
729
796
|
* Common definitions between clients for different environments interface.
|
|
@@ -1666,7 +1733,7 @@ declare namespace SplitIO {
|
|
|
1666
1733
|
* Wait for the SDK client to be ready before calling this method.
|
|
1667
1734
|
*
|
|
1668
1735
|
* ```js
|
|
1669
|
-
* await factory.client().
|
|
1736
|
+
* await factory.client().whenReady();
|
|
1670
1737
|
* const rolloutPlan = factory.getRolloutPlan();
|
|
1671
1738
|
* ```
|
|
1672
1739
|
*
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FallbackDiscardReason = void 0;
|
|
4
|
-
var FallbackDiscardReason;
|
|
5
|
-
(function (FallbackDiscardReason) {
|
|
6
|
-
FallbackDiscardReason["FlagName"] = "Invalid flag name (max 100 chars, no spaces)";
|
|
7
|
-
FallbackDiscardReason["Treatment"] = "Invalid treatment (max 100 chars and must match pattern)";
|
|
8
|
-
})(FallbackDiscardReason = exports.FallbackDiscardReason || (exports.FallbackDiscardReason = {}));
|