@statsig/client-core 3.32.5 → 3.32.6
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/ErrorBoundary.d.ts +1 -1
- package/src/ErrorBoundary.js +16 -3
- package/src/EventRetryConstants.d.ts +2 -2
- package/src/EventRetryConstants.js +2 -2
- package/src/EventSender.d.ts +2 -0
- package/src/EventSender.js +20 -4
- package/src/FlushCoordinator.js +6 -4
- package/src/NetworkCore.d.ts +2 -0
- package/src/NetworkCore.js +92 -0
- package/src/StatsigMetadata.d.ts +1 -1
- package/src/StatsigMetadata.js +1 -1
- package/src/StatsigOptionsCommon.d.ts +6 -0
package/package.json
CHANGED
package/src/ErrorBoundary.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare class ErrorBoundary {
|
|
|
11
11
|
wrap(instance: unknown, namePrefix?: string): void;
|
|
12
12
|
logError(tag: string, error: unknown): void;
|
|
13
13
|
logDroppedEvents(count: number, reason: string, metadata?: Record<string, unknown>): void;
|
|
14
|
-
logEventRequestFailure(count: number, reason: string, flushType: string, statusCode: number, retries: number, failurePath?: string, failureErrorMessage?: string): void;
|
|
14
|
+
logEventRequestFailure(count: number, reason: string, flushType: string, statusCode: number, retries: number, failurePath?: string, failureErrorMessage?: string, failureDiagnosticBucket?: string, failureDiagnosticMetadata?: Record<string, string>): void;
|
|
15
15
|
getLastSeenErrorAndReset(): Error | null;
|
|
16
16
|
attachErrorIfNoneExists(error: unknown): void;
|
|
17
17
|
private _capture;
|
package/src/ErrorBoundary.js
CHANGED
|
@@ -56,7 +56,7 @@ class ErrorBoundary {
|
|
|
56
56
|
}
|
|
57
57
|
this._onError(`statsig::log_event_dropped_event_count`, new Error(reason), true, extra);
|
|
58
58
|
}
|
|
59
|
-
logEventRequestFailure(count, reason, flushType, statusCode, retries, failurePath, failureErrorMessage) {
|
|
59
|
+
logEventRequestFailure(count, reason, flushType, statusCode, retries, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata) {
|
|
60
60
|
const extra = {
|
|
61
61
|
eventCount: String(count),
|
|
62
62
|
flushType: flushType,
|
|
@@ -71,6 +71,18 @@ class ErrorBoundary {
|
|
|
71
71
|
failureErrorMessage.length > 0) {
|
|
72
72
|
extra['failureErrorMessage'] = failureErrorMessage;
|
|
73
73
|
}
|
|
74
|
+
if (typeof failureDiagnosticBucket === 'string' &&
|
|
75
|
+
failureDiagnosticBucket.length > 0) {
|
|
76
|
+
extra['failureDiagnosticBucket'] = failureDiagnosticBucket;
|
|
77
|
+
}
|
|
78
|
+
if (failureDiagnosticMetadata) {
|
|
79
|
+
Object.keys(failureDiagnosticMetadata).forEach((key) => {
|
|
80
|
+
const value = failureDiagnosticMetadata[key];
|
|
81
|
+
if (value.length > 0) {
|
|
82
|
+
extra[`failureDiagnostic_${key}`] = value;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
74
86
|
this._onError(`statsig::log_event_failed`, new Error(reason), true, extra);
|
|
75
87
|
}
|
|
76
88
|
getLastSeenErrorAndReset() {
|
|
@@ -123,8 +135,9 @@ class ErrorBoundary {
|
|
|
123
135
|
const statsigMetadata = StatsigMetadata_1.StatsigMetadataProvider.get();
|
|
124
136
|
const info = isError ? unwrapped.stack : _getDescription(unwrapped);
|
|
125
137
|
const body = Object.assign(Object.assign({ tag, exception: name, info, statsigOptions: _getStatsigOptionLoggingCopy(this._options) }, Object.assign(Object.assign({}, statsigMetadata), { sdkType })), (extra !== null && extra !== void 0 ? extra : {}));
|
|
126
|
-
const
|
|
127
|
-
|
|
138
|
+
const networkConfig = (_d = this._options) === null || _d === void 0 ? void 0 : _d.networkConfig;
|
|
139
|
+
const func = (_e = networkConfig === null || networkConfig === void 0 ? void 0 : networkConfig.networkOverrideFunc) !== null && _e !== void 0 ? _e : fetch;
|
|
140
|
+
yield func((_f = networkConfig === null || networkConfig === void 0 ? void 0 : networkConfig.sdkExceptionUrl) !== null && _f !== void 0 ? _f : exports.EXCEPTION_ENDPOINT, {
|
|
128
141
|
method: 'POST',
|
|
129
142
|
headers: {
|
|
130
143
|
'STATSIG-API-KEY': this._sdkKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const EventRetryConstants: {
|
|
2
|
-
readonly MAX_RETRY_ATTEMPTS:
|
|
2
|
+
readonly MAX_RETRY_ATTEMPTS: 8;
|
|
3
3
|
readonly DEFAULT_BATCH_SIZE: 100;
|
|
4
|
-
readonly MAX_PENDING_BATCHES:
|
|
4
|
+
readonly MAX_PENDING_BATCHES: 40;
|
|
5
5
|
readonly TICK_INTERVAL_MS: 1000;
|
|
6
6
|
readonly QUICK_FLUSH_WINDOW_MS: 200;
|
|
7
7
|
readonly MAX_LOCAL_STORAGE: 500;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.EventRetryConstants = void 0;
|
|
4
4
|
exports.EventRetryConstants = {
|
|
5
|
-
MAX_RETRY_ATTEMPTS:
|
|
5
|
+
MAX_RETRY_ATTEMPTS: 8,
|
|
6
6
|
DEFAULT_BATCH_SIZE: 100,
|
|
7
|
-
MAX_PENDING_BATCHES:
|
|
7
|
+
MAX_PENDING_BATCHES: 40,
|
|
8
8
|
TICK_INTERVAL_MS: 1000,
|
|
9
9
|
QUICK_FLUSH_WINDOW_MS: 200,
|
|
10
10
|
MAX_LOCAL_STORAGE: 500,
|
package/src/EventSender.d.ts
CHANGED
package/src/EventSender.js
CHANGED
|
@@ -55,14 +55,22 @@ class EventSender {
|
|
|
55
55
|
});
|
|
56
56
|
return response;
|
|
57
57
|
}
|
|
58
|
-
return Object.assign({ success: false, statusCode: response.statusCode, failurePath: response.failurePath }, (response.failureErrorMessage
|
|
58
|
+
return Object.assign(Object.assign(Object.assign({ success: false, statusCode: response.statusCode, failurePath: response.failurePath }, (response.failureErrorMessage
|
|
59
59
|
? { failureErrorMessage: response.failureErrorMessage }
|
|
60
|
+
: {})), (response.failureDiagnosticBucket
|
|
61
|
+
? { failureDiagnosticBucket: response.failureDiagnosticBucket }
|
|
62
|
+
: {})), (response.failureDiagnosticMetadata
|
|
63
|
+
? { failureDiagnosticMetadata: response.failureDiagnosticMetadata }
|
|
60
64
|
: {}));
|
|
61
65
|
}
|
|
62
66
|
catch (error) {
|
|
63
67
|
Log_1.Log.warn('Failed to send batch:', error);
|
|
64
|
-
return Object.assign({ success: false, statusCode: -1, failurePath: (_c = transportFailure.path) !== null && _c !== void 0 ? _c : failurePath }, (transportFailure.errorMessage
|
|
68
|
+
return Object.assign(Object.assign(Object.assign({ success: false, statusCode: -1, failurePath: (_c = transportFailure.path) !== null && _c !== void 0 ? _c : failurePath }, (transportFailure.errorMessage
|
|
65
69
|
? { failureErrorMessage: transportFailure.errorMessage }
|
|
70
|
+
: {})), (transportFailure.diagnosticBucket
|
|
71
|
+
? { failureDiagnosticBucket: transportFailure.diagnosticBucket }
|
|
72
|
+
: {})), (transportFailure.diagnosticMetadata
|
|
73
|
+
? { failureDiagnosticMetadata: transportFailure.diagnosticMetadata }
|
|
66
74
|
: {}));
|
|
67
75
|
}
|
|
68
76
|
});
|
|
@@ -73,10 +81,14 @@ class EventSender {
|
|
|
73
81
|
const result = yield this._network.post(this._getRequestData(batch), failureInfo);
|
|
74
82
|
const code = (_a = result === null || result === void 0 ? void 0 : result.code) !== null && _a !== void 0 ? _a : -1;
|
|
75
83
|
if (code === -1) {
|
|
76
|
-
return Object.assign({ success: false, statusCode: -1, failurePath: (_b = failureInfo.path) !== null && _b !== void 0 ? _b : (result === undefined
|
|
84
|
+
return Object.assign(Object.assign(Object.assign({ success: false, statusCode: -1, failurePath: (_b = failureInfo.path) !== null && _b !== void 0 ? _b : (result === undefined
|
|
77
85
|
? 'event_sender_post_returned_undefined'
|
|
78
86
|
: 'event_sender_post_returned_null') }, (failureInfo.errorMessage
|
|
79
87
|
? { failureErrorMessage: failureInfo.errorMessage }
|
|
88
|
+
: {})), (failureInfo.diagnosticBucket
|
|
89
|
+
? { failureDiagnosticBucket: failureInfo.diagnosticBucket }
|
|
90
|
+
: {})), (failureInfo.diagnosticMetadata
|
|
91
|
+
? { failureDiagnosticMetadata: failureInfo.diagnosticMetadata }
|
|
80
92
|
: {}));
|
|
81
93
|
}
|
|
82
94
|
return { success: code >= 200 && code < 300, statusCode: code };
|
|
@@ -85,10 +97,14 @@ class EventSender {
|
|
|
85
97
|
_sendEventsViaBeacon(batch, failureInfo) {
|
|
86
98
|
var _a;
|
|
87
99
|
const success = this._network.beacon(this._getRequestData(batch), failureInfo);
|
|
88
|
-
return Object.assign({ success, statusCode: success ? 200 : -1, failurePath: success
|
|
100
|
+
return Object.assign(Object.assign(Object.assign({ success, statusCode: success ? 200 : -1, failurePath: success
|
|
89
101
|
? undefined
|
|
90
102
|
: (_a = failureInfo.path) !== null && _a !== void 0 ? _a : 'beacon_send_false' }, (!success && failureInfo.errorMessage
|
|
91
103
|
? { failureErrorMessage: failureInfo.errorMessage }
|
|
104
|
+
: {})), (!success && failureInfo.diagnosticBucket
|
|
105
|
+
? { failureDiagnosticBucket: failureInfo.diagnosticBucket }
|
|
106
|
+
: {})), (!success && failureInfo.diagnosticMetadata
|
|
107
|
+
? { failureDiagnosticMetadata: failureInfo.diagnosticMetadata }
|
|
92
108
|
: {}));
|
|
93
109
|
}
|
|
94
110
|
_getRequestData(batch) {
|
package/src/FlushCoordinator.js
CHANGED
|
@@ -261,7 +261,7 @@ class FlushCoordinator {
|
|
|
261
261
|
return true;
|
|
262
262
|
}
|
|
263
263
|
this._flushInterval.adjustForFailure();
|
|
264
|
-
this._handleFailure(batch, flushType, result.statusCode, result.failurePath, result.failureErrorMessage);
|
|
264
|
+
this._handleFailure(batch, flushType, result.statusCode, result.failurePath, result.failureErrorMessage, result.failureDiagnosticBucket, result.failureDiagnosticMetadata);
|
|
265
265
|
return false;
|
|
266
266
|
});
|
|
267
267
|
}
|
|
@@ -301,7 +301,7 @@ class FlushCoordinator {
|
|
|
301
301
|
}
|
|
302
302
|
return false;
|
|
303
303
|
}
|
|
304
|
-
_handleFailure(batch, flushType, statusCode, failurePath, failureErrorMessage) {
|
|
304
|
+
_handleFailure(batch, flushType, statusCode, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata) {
|
|
305
305
|
if (flushType === FlushTypes_1.FlushType.Shutdown) {
|
|
306
306
|
Log_1.Log.warn(`${flushType} flush failed during shutdown. ` +
|
|
307
307
|
`${batch.events.length} event(s) will be saved to storage for retry in next session.`);
|
|
@@ -309,15 +309,17 @@ class FlushCoordinator {
|
|
|
309
309
|
return;
|
|
310
310
|
}
|
|
311
311
|
if (!this._isRetryableBatch(statusCode, failurePath)) {
|
|
312
|
+
const reason = `non-retryable error`;
|
|
312
313
|
Log_1.Log.warn(`${flushType} flush failed after ${batch.attempts} attempt(s). ` +
|
|
313
314
|
`${batch.events.length} event(s) will be dropped. Non-retryable error: ${statusCode}`);
|
|
314
|
-
this._errorBoundary.logEventRequestFailure(batch.events.length,
|
|
315
|
+
this._errorBoundary.logEventRequestFailure(batch.events.length, reason, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata);
|
|
315
316
|
return;
|
|
316
317
|
}
|
|
317
318
|
if (batch.attempts >= EventRetryConstants_1.EventRetryConstants.MAX_RETRY_ATTEMPTS) {
|
|
319
|
+
const reason = `max retry attempts exceeded`;
|
|
318
320
|
Log_1.Log.warn(`${flushType} flush failed after ${batch.attempts} attempt(s). ` +
|
|
319
321
|
`${batch.events.length} event(s) will be dropped.`);
|
|
320
|
-
this._errorBoundary.logEventRequestFailure(batch.events.length,
|
|
322
|
+
this._errorBoundary.logEventRequestFailure(batch.events.length, reason, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata);
|
|
321
323
|
return;
|
|
322
324
|
}
|
|
323
325
|
batch.incrementAttempts();
|
package/src/NetworkCore.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export type RequestArgsWithData = Flatten<RequestArgs & {
|
|
|
27
27
|
export type RequestFailureInfo = {
|
|
28
28
|
path?: string;
|
|
29
29
|
errorMessage?: string;
|
|
30
|
+
diagnosticBucket?: string;
|
|
31
|
+
diagnosticMetadata?: Record<string, string>;
|
|
30
32
|
};
|
|
31
33
|
type BeaconRequestArgs = Pick<RequestArgsWithData, 'data' | 'sdkKey' | 'urlConfig' | 'params' | 'isCompressable' | 'attempt'>;
|
|
32
34
|
type NetworkResponse = {
|
package/src/NetworkCore.js
CHANGED
|
@@ -138,6 +138,7 @@ class NetworkCore {
|
|
|
138
138
|
const currentAttempt = attempt !== null && attempt !== void 0 ? attempt : 1;
|
|
139
139
|
let reqTimedOut = false;
|
|
140
140
|
const populatedUrl = this._getPopulatedURL(args);
|
|
141
|
+
const startTime = Date.now();
|
|
141
142
|
let response = null;
|
|
142
143
|
const keepalive = (0, VisibilityObserving_1._isUnloading)();
|
|
143
144
|
try {
|
|
@@ -213,6 +214,14 @@ class NetworkCore {
|
|
|
213
214
|
if (errorMessage) {
|
|
214
215
|
failureInfo.errorMessage = errorMessage;
|
|
215
216
|
}
|
|
217
|
+
try {
|
|
218
|
+
const diagnostics = _getNoResponseDiagnostics(args, populatedUrl, timedOut, Date.now() - startTime);
|
|
219
|
+
failureInfo.diagnosticBucket = diagnostics.bucket;
|
|
220
|
+
failureInfo.diagnosticMetadata = diagnostics.metadata;
|
|
221
|
+
}
|
|
222
|
+
catch (_e) {
|
|
223
|
+
// Diagnostics should not affect request failure handling.
|
|
224
|
+
}
|
|
216
225
|
}
|
|
217
226
|
}
|
|
218
227
|
return null;
|
|
@@ -389,6 +398,89 @@ function _didTimeout(errorMsg, abortedByTimeout) {
|
|
|
389
398
|
const timeout = errorMsg.includes('Timeout'); // probably not needed but just in case
|
|
390
399
|
return timeout || abortedByTimeout;
|
|
391
400
|
}
|
|
401
|
+
function _getNoResponseDiagnostics(args, populatedUrl, timedOut, elapsedMs) {
|
|
402
|
+
var _a, _b, _c;
|
|
403
|
+
const win = (0, SafeJs_1._getWindowSafe)();
|
|
404
|
+
const doc = win === null || win === void 0 ? void 0 : win.document;
|
|
405
|
+
const nav = typeof navigator !== 'undefined' ? navigator : null;
|
|
406
|
+
const isUnloading = (0, VisibilityObserving_1._isUnloading)();
|
|
407
|
+
const online = nav && typeof nav.onLine === 'boolean' ? String(nav.onLine) : 'unknown';
|
|
408
|
+
const visibilityState = (_a = doc === null || doc === void 0 ? void 0 : doc.visibilityState) !== null && _a !== void 0 ? _a : 'unknown';
|
|
409
|
+
const hasCustomHeaders = Object.keys((_b = args.headers) !== null && _b !== void 0 ? _b : {}).length > 0;
|
|
410
|
+
const crossOrigin = _isCrossOrigin(populatedUrl, (_c = win === null || win === void 0 ? void 0 : win.location) === null || _c === void 0 ? void 0 : _c.origin);
|
|
411
|
+
const hasCustomUrl = args.urlConfig.customUrl != null;
|
|
412
|
+
const hasFallbackUrl = args.fallbackUrl != null;
|
|
413
|
+
const elapsedMsBucket = _bucketNumber(elapsedMs, [250, 1000, 5000, 10000]);
|
|
414
|
+
const bodySizeBucket = _bucketNumber(_getBodySize(args.body), [16384, 65536, 262144, 1048576]);
|
|
415
|
+
let bucket = 'unknown_no_response';
|
|
416
|
+
if (timedOut) {
|
|
417
|
+
bucket = 'timeout';
|
|
418
|
+
}
|
|
419
|
+
else if (online === 'false') {
|
|
420
|
+
bucket = 'browser_offline';
|
|
421
|
+
}
|
|
422
|
+
else if (isUnloading) {
|
|
423
|
+
bucket = 'page_unloading';
|
|
424
|
+
}
|
|
425
|
+
else if (visibilityState === 'hidden') {
|
|
426
|
+
bucket = 'page_hidden';
|
|
427
|
+
}
|
|
428
|
+
else if (crossOrigin && hasCustomHeaders) {
|
|
429
|
+
bucket = 'cross_origin_custom_headers_preflight_risk';
|
|
430
|
+
}
|
|
431
|
+
else if (hasCustomUrl || hasFallbackUrl) {
|
|
432
|
+
bucket = 'custom_url_no_response';
|
|
433
|
+
}
|
|
434
|
+
else if (elapsedMs < 250) {
|
|
435
|
+
bucket = 'immediate_network_rejection';
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
bucket,
|
|
439
|
+
metadata: {
|
|
440
|
+
elapsedMsBucket,
|
|
441
|
+
bodySizeBucket,
|
|
442
|
+
online,
|
|
443
|
+
visibilityState,
|
|
444
|
+
isUnloading: String(isUnloading),
|
|
445
|
+
crossOrigin: String(crossOrigin),
|
|
446
|
+
hasCustomUrl: String(hasCustomUrl),
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function _isCrossOrigin(url, currentOrigin) {
|
|
451
|
+
if (!currentOrigin) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
return (!url.startsWith(`${currentOrigin}/`) &&
|
|
455
|
+
!url.startsWith(`${currentOrigin}?`) &&
|
|
456
|
+
url !== currentOrigin);
|
|
457
|
+
}
|
|
458
|
+
function _getBodySize(body) {
|
|
459
|
+
if (body == null) {
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
if (typeof body === 'string') {
|
|
463
|
+
return body.length;
|
|
464
|
+
}
|
|
465
|
+
if (body instanceof Uint8Array) {
|
|
466
|
+
return body.byteLength;
|
|
467
|
+
}
|
|
468
|
+
if (typeof Blob !== 'undefined' && body instanceof Blob) {
|
|
469
|
+
return body.size;
|
|
470
|
+
}
|
|
471
|
+
return -1;
|
|
472
|
+
}
|
|
473
|
+
function _bucketNumber(value, thresholds) {
|
|
474
|
+
if (value < 0) {
|
|
475
|
+
return 'unknown';
|
|
476
|
+
}
|
|
477
|
+
for (const threshold of thresholds) {
|
|
478
|
+
if (value < threshold) {
|
|
479
|
+
return `<${threshold}`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return `>=${thresholds[thresholds.length - 1]}`;
|
|
483
|
+
}
|
|
392
484
|
function _tryMarkInitStart(args, attempt) {
|
|
393
485
|
if (args.urlConfig.endpoint !== NetworkConfig_1.Endpoint._initialize) {
|
|
394
486
|
return;
|
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.32.
|
|
4
|
+
exports.SDK_VERSION = '3.32.6';
|
|
5
5
|
let metadata = {
|
|
6
6
|
sdkVersion: exports.SDK_VERSION,
|
|
7
7
|
sdkType: 'js-mono', // js-mono is overwritten by Precomp and OnDevice clients
|
|
@@ -57,6 +57,12 @@ export type NetworkConfigCommon = {
|
|
|
57
57
|
* default: `https://prodregistryv2.org/v1/rgstr`
|
|
58
58
|
*/
|
|
59
59
|
logEventUrl?: string;
|
|
60
|
+
/**
|
|
61
|
+
* The URL used to report SDK exceptions via a POST request.
|
|
62
|
+
*
|
|
63
|
+
* default: `https://statsigapi.net/v1/sdk_exception`
|
|
64
|
+
*/
|
|
65
|
+
sdkExceptionUrl?: string;
|
|
60
66
|
/**
|
|
61
67
|
* A list of URLs to try if the primary logEventUrl fails.
|
|
62
68
|
*/
|