@statsig/client-core 3.8.3 → 3.9.1
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/package.json +1 -1
- package/src/EvaluationOptions.d.ts +1 -0
- package/src/EventLogger.js +3 -3
- package/src/MemoKey.d.ts +2 -0
- package/src/MemoKey.js +29 -0
- package/src/NetworkCore.d.ts +2 -0
- package/src/NetworkCore.js +32 -0
- package/src/StatsigClientBase.d.ts +3 -1
- package/src/StatsigClientBase.js +19 -0
- package/src/StatsigMetadata.d.ts +1 -1
- package/src/StatsigMetadata.js +1 -1
- package/src/StatsigTypes.d.ts +1 -1
package/package.json
CHANGED
|
@@ -19,3 +19,4 @@ export type ExperimentEvaluationOptions = EvaluationOptionsCommon & {
|
|
|
19
19
|
};
|
|
20
20
|
export type LayerEvaluationOptions = EvaluationOptionsCommon & {};
|
|
21
21
|
export type ParameterStoreEvaluationOptions = EvaluationOptionsCommon & {};
|
|
22
|
+
export type AnyEvaluationOptions = FeatureGateEvaluationOptions | DynamicConfigEvaluationOptions | ExperimentEvaluationOptions | LayerEvaluationOptions | ParameterStoreEvaluationOptions;
|
package/src/EventLogger.js
CHANGED
|
@@ -19,11 +19,11 @@ const StatsigEvent_1 = require("./StatsigEvent");
|
|
|
19
19
|
const StorageProvider_1 = require("./StorageProvider");
|
|
20
20
|
const UrlConfiguration_1 = require("./UrlConfiguration");
|
|
21
21
|
const VisibilityObserving_1 = require("./VisibilityObserving");
|
|
22
|
-
const DEFAULT_QUEUE_SIZE =
|
|
22
|
+
const DEFAULT_QUEUE_SIZE = 100;
|
|
23
23
|
const DEFAULT_FLUSH_INTERVAL_MS = 10000;
|
|
24
24
|
const MAX_DEDUPER_KEYS = 1000;
|
|
25
|
-
const DEDUPER_WINDOW_DURATION_MS =
|
|
26
|
-
const MAX_FAILED_LOGS =
|
|
25
|
+
const DEDUPER_WINDOW_DURATION_MS = 600000;
|
|
26
|
+
const MAX_FAILED_LOGS = 1000;
|
|
27
27
|
const QUICK_FLUSH_WINDOW_MS = 200;
|
|
28
28
|
const EVENT_LOGGER_MAP = {};
|
|
29
29
|
const RetryFailedLogsTrigger = {
|
package/src/MemoKey.d.ts
ADDED
package/src/MemoKey.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMemoKey = void 0;
|
|
4
|
+
const EXIST_KEYS = new Set([
|
|
5
|
+
// Add keys that should be memoized based only on their existence, not their value
|
|
6
|
+
]);
|
|
7
|
+
const DO_NOT_MEMO_KEYS = new Set([
|
|
8
|
+
// Add keys that if exist, should not be memoized
|
|
9
|
+
'userPersistedValues',
|
|
10
|
+
]);
|
|
11
|
+
function createMemoKey(name, options) {
|
|
12
|
+
let cacheKey = name;
|
|
13
|
+
if (!options) {
|
|
14
|
+
return cacheKey;
|
|
15
|
+
}
|
|
16
|
+
for (const key of Object.keys(options)) {
|
|
17
|
+
if (DO_NOT_MEMO_KEYS.has(key)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (EXIST_KEYS.has(key)) {
|
|
21
|
+
cacheKey += `${key}=true`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
cacheKey += `${key}=${options[key]}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return cacheKey;
|
|
28
|
+
}
|
|
29
|
+
exports.createMemoKey = createMemoKey;
|
package/src/NetworkCore.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare class NetworkCore {
|
|
|
30
30
|
private readonly _netConfig;
|
|
31
31
|
private readonly _options;
|
|
32
32
|
private readonly _fallbackResolver;
|
|
33
|
+
private _leakyBucket;
|
|
33
34
|
private _errorBoundary;
|
|
34
35
|
constructor(options: AnyStatsigOptions | null, _emitter?: StatsigClientEmitEventFunc | undefined);
|
|
35
36
|
setErrorBoundary(errorBoundary: ErrorBoundary): void;
|
|
@@ -38,6 +39,7 @@ export declare class NetworkCore {
|
|
|
38
39
|
post(args: RequestArgsWithData): Promise<NetworkResponse | null>;
|
|
39
40
|
get(args: RequestArgs): Promise<NetworkResponse | null>;
|
|
40
41
|
private _sendRequest;
|
|
42
|
+
private _isRateLimited;
|
|
41
43
|
private _getPopulatedURL;
|
|
42
44
|
private _getPopulatedBody;
|
|
43
45
|
private _attemptToEncodeString;
|
package/src/NetworkCore.js
CHANGED
|
@@ -26,6 +26,9 @@ const VisibilityObserving_1 = require("./VisibilityObserving");
|
|
|
26
26
|
const DEFAULT_TIMEOUT_MS = 10000;
|
|
27
27
|
const BACKOFF_BASE_MS = 500;
|
|
28
28
|
const BACKOFF_MAX_MS = 30000;
|
|
29
|
+
const RATE_LIMIT_WINDOW_MS = 1000;
|
|
30
|
+
const RATE_LIMIT_MAX_REQ_COUNT = 50;
|
|
31
|
+
const LEAK_RATE = RATE_LIMIT_MAX_REQ_COUNT / RATE_LIMIT_WINDOW_MS;
|
|
29
32
|
const RETRYABLE_CODES = new Set([408, 500, 502, 503, 504, 522, 524, 599]);
|
|
30
33
|
class NetworkCore {
|
|
31
34
|
constructor(options, _emitter) {
|
|
@@ -33,6 +36,7 @@ class NetworkCore {
|
|
|
33
36
|
this._timeout = DEFAULT_TIMEOUT_MS;
|
|
34
37
|
this._netConfig = {};
|
|
35
38
|
this._options = {};
|
|
39
|
+
this._leakyBucket = {};
|
|
36
40
|
this._errorBoundary = null;
|
|
37
41
|
if (options) {
|
|
38
42
|
this._options = options;
|
|
@@ -91,6 +95,11 @@ class NetworkCore {
|
|
|
91
95
|
return null;
|
|
92
96
|
}
|
|
93
97
|
const { method, body, retries, attempt } = args;
|
|
98
|
+
const endpoint = args.urlConfig.endpoint;
|
|
99
|
+
if (this._isRateLimited(endpoint)) {
|
|
100
|
+
Log_1.Log.warn(`Request to ${endpoint} was blocked because you are making requests too frequently.`);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
94
103
|
const currentAttempt = attempt !== null && attempt !== void 0 ? attempt : 1;
|
|
95
104
|
const abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
96
105
|
const timeoutHandle = setTimeout(() => {
|
|
@@ -109,6 +118,11 @@ class NetworkCore {
|
|
|
109
118
|
keepalive,
|
|
110
119
|
};
|
|
111
120
|
_tryMarkInitStart(args, currentAttempt);
|
|
121
|
+
const bucket = this._leakyBucket[endpoint];
|
|
122
|
+
if (bucket) {
|
|
123
|
+
bucket.lastRequestTime = Date.now();
|
|
124
|
+
this._leakyBucket[endpoint] = bucket;
|
|
125
|
+
}
|
|
112
126
|
const func = (_a = this._netConfig.networkOverrideFunc) !== null && _a !== void 0 ? _a : fetch;
|
|
113
127
|
response = yield func(populatedUrl, config);
|
|
114
128
|
clearTimeout(timeoutHandle);
|
|
@@ -151,6 +165,24 @@ class NetworkCore {
|
|
|
151
165
|
}
|
|
152
166
|
});
|
|
153
167
|
}
|
|
168
|
+
_isRateLimited(endpoint) {
|
|
169
|
+
var _a;
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
const bucket = (_a = this._leakyBucket[endpoint]) !== null && _a !== void 0 ? _a : {
|
|
172
|
+
count: 0,
|
|
173
|
+
lastRequestTime: now,
|
|
174
|
+
};
|
|
175
|
+
const elapsed = now - bucket.lastRequestTime;
|
|
176
|
+
const leakedRequests = Math.floor(elapsed * LEAK_RATE);
|
|
177
|
+
bucket.count = Math.max(0, bucket.count - leakedRequests);
|
|
178
|
+
if (bucket.count >= RATE_LIMIT_MAX_REQ_COUNT) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
bucket.count += 1;
|
|
182
|
+
bucket.lastRequestTime = now;
|
|
183
|
+
this._leakyBucket[endpoint] = bucket;
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
154
186
|
_getPopulatedURL(args) {
|
|
155
187
|
var _a;
|
|
156
188
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './$_StatsigGlobal';
|
|
2
2
|
import { ErrorBoundary } from './ErrorBoundary';
|
|
3
|
-
import { EvaluationOptionsCommon } from './EvaluationOptions';
|
|
3
|
+
import { AnyEvaluationOptions, EvaluationOptionsCommon } from './EvaluationOptions';
|
|
4
4
|
import { EventLogger } from './EventLogger';
|
|
5
5
|
import { NetworkCore } from './NetworkCore';
|
|
6
6
|
import { OverrideAdapter } from './OverrideAdapter';
|
|
@@ -26,6 +26,7 @@ export declare abstract class StatsigClientBase<TAdapter extends EvaluationsData
|
|
|
26
26
|
protected readonly _errorBoundary: ErrorBoundary;
|
|
27
27
|
protected readonly _logger: EventLogger;
|
|
28
28
|
protected _initializePromise: Promise<void> | null;
|
|
29
|
+
protected _memoCache: Record<string, unknown>;
|
|
29
30
|
private _listeners;
|
|
30
31
|
constructor(sdkKey: string, adapter: TAdapter, network: NetworkCore, options: AnyStatsigOptions | null);
|
|
31
32
|
/**
|
|
@@ -66,5 +67,6 @@ export declare abstract class StatsigClientBase<TAdapter extends EvaluationsData
|
|
|
66
67
|
$emt(event: AnyStatsigClientEvent): void;
|
|
67
68
|
protected _setStatus(newStatus: StatsigLoadingStatus, values: DataAdapterResult | null): void;
|
|
68
69
|
protected _enqueueExposure(name: string, exposure: StatsigEventInternal, options?: EvaluationOptionsCommon): void;
|
|
70
|
+
protected _memoize<T, O extends AnyEvaluationOptions>(fn: (name: string, options?: O) => T): (name: string, options?: O) => T;
|
|
69
71
|
protected abstract _primeReadyRipcord(): void;
|
|
70
72
|
}
|
package/src/StatsigClientBase.js
CHANGED
|
@@ -15,9 +15,11 @@ const __StatsigGlobal_1 = require("./$_StatsigGlobal");
|
|
|
15
15
|
const ErrorBoundary_1 = require("./ErrorBoundary");
|
|
16
16
|
const EventLogger_1 = require("./EventLogger");
|
|
17
17
|
const Log_1 = require("./Log");
|
|
18
|
+
const MemoKey_1 = require("./MemoKey");
|
|
18
19
|
const SafeJs_1 = require("./SafeJs");
|
|
19
20
|
const SessionID_1 = require("./SessionID");
|
|
20
21
|
const StorageProvider_1 = require("./StorageProvider");
|
|
22
|
+
const MAX_MEMO_CACHE_SIZE = 3000;
|
|
21
23
|
class StatsigClientBase {
|
|
22
24
|
constructor(sdkKey, adapter, network, options) {
|
|
23
25
|
var _a;
|
|
@@ -32,6 +34,7 @@ class StatsigClientBase {
|
|
|
32
34
|
(options === null || options === void 0 ? void 0 : options.storageProvider) && StorageProvider_1.Storage._setProvider(options.storageProvider);
|
|
33
35
|
this._sdkKey = sdkKey;
|
|
34
36
|
this._options = options !== null && options !== void 0 ? options : {};
|
|
37
|
+
this._memoCache = {};
|
|
35
38
|
this.overrideAdapter = (_a = options === null || options === void 0 ? void 0 : options.overrideAdapter) !== null && _a !== void 0 ? _a : null;
|
|
36
39
|
this._logger = new EventLogger_1.EventLogger(sdkKey, emitter, network, options);
|
|
37
40
|
this._errorBoundary = new ErrorBoundary_1.ErrorBoundary(sdkKey, options, emitter);
|
|
@@ -134,6 +137,7 @@ class StatsigClientBase {
|
|
|
134
137
|
}
|
|
135
138
|
_setStatus(newStatus, values) {
|
|
136
139
|
this.loadingStatus = newStatus;
|
|
140
|
+
this._memoCache = {};
|
|
137
141
|
this.$emt({ name: 'values_updated', status: newStatus, values });
|
|
138
142
|
}
|
|
139
143
|
_enqueueExposure(name, exposure, options) {
|
|
@@ -143,6 +147,21 @@ class StatsigClientBase {
|
|
|
143
147
|
}
|
|
144
148
|
this._logger.enqueue(exposure);
|
|
145
149
|
}
|
|
150
|
+
_memoize(fn) {
|
|
151
|
+
return (name, options) => {
|
|
152
|
+
const memoKey = (0, MemoKey_1.createMemoKey)(name, options);
|
|
153
|
+
if (!memoKey) {
|
|
154
|
+
return fn(name, options);
|
|
155
|
+
}
|
|
156
|
+
if (!(memoKey in this._memoCache)) {
|
|
157
|
+
if (Object.keys(this._memoCache).length >= MAX_MEMO_CACHE_SIZE) {
|
|
158
|
+
this._memoCache = {};
|
|
159
|
+
}
|
|
160
|
+
this._memoCache[memoKey] = fn(name, options);
|
|
161
|
+
}
|
|
162
|
+
return this._memoCache[memoKey];
|
|
163
|
+
};
|
|
164
|
+
}
|
|
146
165
|
}
|
|
147
166
|
exports.StatsigClientBase = StatsigClientBase;
|
|
148
167
|
function _assignGlobalInstance(sdkKey, client) {
|
package/src/StatsigMetadata.d.ts
CHANGED
package/src/StatsigMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StatsigMetadataProvider = exports.SDK_VERSION = void 0;
|
|
4
|
-
exports.SDK_VERSION = '3.
|
|
4
|
+
exports.SDK_VERSION = '3.9.1';
|
|
5
5
|
let metadata = {
|
|
6
6
|
sdkVersion: exports.SDK_VERSION,
|
|
7
7
|
sdkType: 'js-mono', // js-mono is overwritten by Precomp and OnDevice clients
|
package/src/StatsigTypes.d.ts
CHANGED
|
@@ -38,4 +38,4 @@ export type ParameterStore = Flatten<{
|
|
|
38
38
|
readonly __configuration: ParamStoreConfig | null;
|
|
39
39
|
}>;
|
|
40
40
|
export type AnyConfigBasedStatsigType = DynamicConfig | Experiment | Layer;
|
|
41
|
-
export type AnyStatsigType = FeatureGate | AnyConfigBasedStatsigType;
|
|
41
|
+
export type AnyStatsigType = FeatureGate | AnyConfigBasedStatsigType | ParameterStore;
|