@statsig/client-core
Advanced tools
+1
-1
| { | ||
| "name": "@statsig/client-core", | ||
| "version": "3.32.5", | ||
| "version": "3.32.6", | ||
| "license": "ISC", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/statsig-io/js-client-monorepo", |
@@ -14,3 +14,3 @@ import { StatsigClientEmitEventFunc } from './StatsigClientBase'; | ||
| logDroppedEvents(count: number, reason: string, metadata?: Record<string, unknown>): void; | ||
| logEventRequestFailure(count: number, reason: string, flushType: string, statusCode: number, retries: number, failurePath?: string, failureErrorMessage?: string): void; | ||
| logEventRequestFailure(count: number, reason: string, flushType: string, statusCode: number, retries: number, failurePath?: string, failureErrorMessage?: string, failureDiagnosticBucket?: string, failureDiagnosticMetadata?: Record<string, string>): void; | ||
| getLastSeenErrorAndReset(): Error | null; | ||
@@ -17,0 +17,0 @@ attachErrorIfNoneExists(error: unknown): void; |
+16
-3
@@ -59,3 +59,3 @@ "use strict"; | ||
| } | ||
| logEventRequestFailure(count, reason, flushType, statusCode, retries, failurePath, failureErrorMessage) { | ||
| logEventRequestFailure(count, reason, flushType, statusCode, retries, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata) { | ||
| const extra = { | ||
@@ -75,2 +75,14 @@ eventCount: String(count), | ||
| } | ||
| if (typeof failureDiagnosticBucket === 'string' && | ||
| failureDiagnosticBucket.length > 0) { | ||
| extra['failureDiagnosticBucket'] = failureDiagnosticBucket; | ||
| } | ||
| if (failureDiagnosticMetadata) { | ||
| Object.keys(failureDiagnosticMetadata).forEach((key) => { | ||
| const value = failureDiagnosticMetadata[key]; | ||
| if (value.length > 0) { | ||
| extra[`failureDiagnostic_${key}`] = value; | ||
| } | ||
| }); | ||
| } | ||
| this._onError(`statsig::log_event_failed`, new Error(reason), true, extra); | ||
@@ -128,4 +140,5 @@ } | ||
| 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 : {})); | ||
| const func = (_f = (_e = (_d = this._options) === null || _d === void 0 ? void 0 : _d.networkConfig) === null || _e === void 0 ? void 0 : _e.networkOverrideFunc) !== null && _f !== void 0 ? _f : fetch; | ||
| yield func(exports.EXCEPTION_ENDPOINT, { | ||
| const networkConfig = (_d = this._options) === null || _d === void 0 ? void 0 : _d.networkConfig; | ||
| const func = (_e = networkConfig === null || networkConfig === void 0 ? void 0 : networkConfig.networkOverrideFunc) !== null && _e !== void 0 ? _e : fetch; | ||
| yield func((_f = networkConfig === null || networkConfig === void 0 ? void 0 : networkConfig.sdkExceptionUrl) !== null && _f !== void 0 ? _f : exports.EXCEPTION_ENDPOINT, { | ||
| method: 'POST', | ||
@@ -132,0 +145,0 @@ headers: { |
| export declare const EventRetryConstants: { | ||
| readonly MAX_RETRY_ATTEMPTS: 5; | ||
| readonly MAX_RETRY_ATTEMPTS: 8; | ||
| readonly DEFAULT_BATCH_SIZE: 100; | ||
| readonly MAX_PENDING_BATCHES: 30; | ||
| readonly MAX_PENDING_BATCHES: 40; | ||
| readonly TICK_INTERVAL_MS: 1000; | ||
@@ -6,0 +6,0 @@ readonly QUICK_FLUSH_WINDOW_MS: 200; |
@@ -5,5 +5,5 @@ "use strict"; | ||
| exports.EventRetryConstants = { | ||
| MAX_RETRY_ATTEMPTS: 5, | ||
| MAX_RETRY_ATTEMPTS: 8, | ||
| DEFAULT_BATCH_SIZE: 100, | ||
| MAX_PENDING_BATCHES: 30, | ||
| MAX_PENDING_BATCHES: 40, | ||
| TICK_INTERVAL_MS: 1000, | ||
@@ -10,0 +10,0 @@ QUICK_FLUSH_WINDOW_MS: 200, |
@@ -11,2 +11,4 @@ import { EventBatch } from './EventBatch'; | ||
| failureErrorMessage?: string; | ||
| failureDiagnosticBucket?: string; | ||
| failureDiagnosticMetadata?: Record<string, string>; | ||
| }; | ||
@@ -13,0 +15,0 @@ export declare class EventSender { |
+20
-4
@@ -58,4 +58,8 @@ "use strict"; | ||
| } | ||
| return Object.assign({ success: false, statusCode: response.statusCode, failurePath: response.failurePath }, (response.failureErrorMessage | ||
| return Object.assign(Object.assign(Object.assign({ success: false, statusCode: response.statusCode, failurePath: response.failurePath }, (response.failureErrorMessage | ||
| ? { failureErrorMessage: response.failureErrorMessage } | ||
| : {})), (response.failureDiagnosticBucket | ||
| ? { failureDiagnosticBucket: response.failureDiagnosticBucket } | ||
| : {})), (response.failureDiagnosticMetadata | ||
| ? { failureDiagnosticMetadata: response.failureDiagnosticMetadata } | ||
| : {})); | ||
@@ -65,4 +69,8 @@ } | ||
| Log_1.Log.warn('Failed to send batch:', error); | ||
| return Object.assign({ success: false, statusCode: -1, failurePath: (_c = transportFailure.path) !== null && _c !== void 0 ? _c : failurePath }, (transportFailure.errorMessage | ||
| return Object.assign(Object.assign(Object.assign({ success: false, statusCode: -1, failurePath: (_c = transportFailure.path) !== null && _c !== void 0 ? _c : failurePath }, (transportFailure.errorMessage | ||
| ? { failureErrorMessage: transportFailure.errorMessage } | ||
| : {})), (transportFailure.diagnosticBucket | ||
| ? { failureDiagnosticBucket: transportFailure.diagnosticBucket } | ||
| : {})), (transportFailure.diagnosticMetadata | ||
| ? { failureDiagnosticMetadata: transportFailure.diagnosticMetadata } | ||
| : {})); | ||
@@ -78,6 +86,10 @@ } | ||
| if (code === -1) { | ||
| return Object.assign({ success: false, statusCode: -1, failurePath: (_b = failureInfo.path) !== null && _b !== void 0 ? _b : (result === undefined | ||
| return Object.assign(Object.assign(Object.assign({ success: false, statusCode: -1, failurePath: (_b = failureInfo.path) !== null && _b !== void 0 ? _b : (result === undefined | ||
| ? 'event_sender_post_returned_undefined' | ||
| : 'event_sender_post_returned_null') }, (failureInfo.errorMessage | ||
| ? { failureErrorMessage: failureInfo.errorMessage } | ||
| : {})), (failureInfo.diagnosticBucket | ||
| ? { failureDiagnosticBucket: failureInfo.diagnosticBucket } | ||
| : {})), (failureInfo.diagnosticMetadata | ||
| ? { failureDiagnosticMetadata: failureInfo.diagnosticMetadata } | ||
| : {})); | ||
@@ -91,6 +103,10 @@ } | ||
| const success = this._network.beacon(this._getRequestData(batch), failureInfo); | ||
| return Object.assign({ success, statusCode: success ? 200 : -1, failurePath: success | ||
| return Object.assign(Object.assign(Object.assign({ success, statusCode: success ? 200 : -1, failurePath: success | ||
| ? undefined | ||
| : (_a = failureInfo.path) !== null && _a !== void 0 ? _a : 'beacon_send_false' }, (!success && failureInfo.errorMessage | ||
| ? { failureErrorMessage: failureInfo.errorMessage } | ||
| : {})), (!success && failureInfo.diagnosticBucket | ||
| ? { failureDiagnosticBucket: failureInfo.diagnosticBucket } | ||
| : {})), (!success && failureInfo.diagnosticMetadata | ||
| ? { failureDiagnosticMetadata: failureInfo.diagnosticMetadata } | ||
| : {})); | ||
@@ -97,0 +113,0 @@ } |
@@ -264,3 +264,3 @@ "use strict"; | ||
| this._flushInterval.adjustForFailure(); | ||
| this._handleFailure(batch, flushType, result.statusCode, result.failurePath, result.failureErrorMessage); | ||
| this._handleFailure(batch, flushType, result.statusCode, result.failurePath, result.failureErrorMessage, result.failureDiagnosticBucket, result.failureDiagnosticMetadata); | ||
| return false; | ||
@@ -305,3 +305,3 @@ }); | ||
| } | ||
| _handleFailure(batch, flushType, statusCode, failurePath, failureErrorMessage) { | ||
| _handleFailure(batch, flushType, statusCode, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata) { | ||
| if (flushType === FlushTypes_1.FlushType.Shutdown) { | ||
@@ -314,11 +314,13 @@ Log_1.Log.warn(`${flushType} flush failed during shutdown. ` + | ||
| if (!this._isRetryableBatch(statusCode, failurePath)) { | ||
| const reason = `non-retryable error`; | ||
| Log_1.Log.warn(`${flushType} flush failed after ${batch.attempts} attempt(s). ` + | ||
| `${batch.events.length} event(s) will be dropped. Non-retryable error: ${statusCode}`); | ||
| this._errorBoundary.logEventRequestFailure(batch.events.length, `non-retryable error`, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage); | ||
| this._errorBoundary.logEventRequestFailure(batch.events.length, reason, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata); | ||
| return; | ||
| } | ||
| if (batch.attempts >= EventRetryConstants_1.EventRetryConstants.MAX_RETRY_ATTEMPTS) { | ||
| const reason = `max retry attempts exceeded`; | ||
| Log_1.Log.warn(`${flushType} flush failed after ${batch.attempts} attempt(s). ` + | ||
| `${batch.events.length} event(s) will be dropped.`); | ||
| this._errorBoundary.logEventRequestFailure(batch.events.length, `max retry attempts exceeded`, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage); | ||
| this._errorBoundary.logEventRequestFailure(batch.events.length, reason, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage, failureDiagnosticBucket, failureDiagnosticMetadata); | ||
| return; | ||
@@ -325,0 +327,0 @@ } |
@@ -30,2 +30,4 @@ import './$_StatsigGlobal'; | ||
| errorMessage?: string; | ||
| diagnosticBucket?: string; | ||
| diagnosticMetadata?: Record<string, string>; | ||
| }; | ||
@@ -32,0 +34,0 @@ type BeaconRequestArgs = Pick<RequestArgsWithData, 'data' | 'sdkKey' | 'urlConfig' | 'params' | 'isCompressable' | 'attempt'>; |
+92
-0
@@ -141,2 +141,3 @@ "use strict"; | ||
| const populatedUrl = this._getPopulatedURL(args); | ||
| const startTime = Date.now(); | ||
| let response = null; | ||
@@ -217,2 +218,10 @@ const keepalive = (0, VisibilityObserving_1._isUnloading)(); | ||
| } | ||
| try { | ||
| const diagnostics = _getNoResponseDiagnostics(args, populatedUrl, timedOut, Date.now() - startTime); | ||
| failureInfo.diagnosticBucket = diagnostics.bucket; | ||
| failureInfo.diagnosticMetadata = diagnostics.metadata; | ||
| } | ||
| catch (_e) { | ||
| // Diagnostics should not affect request failure handling. | ||
| } | ||
| } | ||
@@ -394,2 +403,85 @@ } | ||
| } | ||
| function _getNoResponseDiagnostics(args, populatedUrl, timedOut, elapsedMs) { | ||
| var _a, _b, _c; | ||
| const win = (0, SafeJs_1._getWindowSafe)(); | ||
| const doc = win === null || win === void 0 ? void 0 : win.document; | ||
| const nav = typeof navigator !== 'undefined' ? navigator : null; | ||
| const isUnloading = (0, VisibilityObserving_1._isUnloading)(); | ||
| const online = nav && typeof nav.onLine === 'boolean' ? String(nav.onLine) : 'unknown'; | ||
| const visibilityState = (_a = doc === null || doc === void 0 ? void 0 : doc.visibilityState) !== null && _a !== void 0 ? _a : 'unknown'; | ||
| const hasCustomHeaders = Object.keys((_b = args.headers) !== null && _b !== void 0 ? _b : {}).length > 0; | ||
| const crossOrigin = _isCrossOrigin(populatedUrl, (_c = win === null || win === void 0 ? void 0 : win.location) === null || _c === void 0 ? void 0 : _c.origin); | ||
| const hasCustomUrl = args.urlConfig.customUrl != null; | ||
| const hasFallbackUrl = args.fallbackUrl != null; | ||
| const elapsedMsBucket = _bucketNumber(elapsedMs, [250, 1000, 5000, 10000]); | ||
| const bodySizeBucket = _bucketNumber(_getBodySize(args.body), [16384, 65536, 262144, 1048576]); | ||
| let bucket = 'unknown_no_response'; | ||
| if (timedOut) { | ||
| bucket = 'timeout'; | ||
| } | ||
| else if (online === 'false') { | ||
| bucket = 'browser_offline'; | ||
| } | ||
| else if (isUnloading) { | ||
| bucket = 'page_unloading'; | ||
| } | ||
| else if (visibilityState === 'hidden') { | ||
| bucket = 'page_hidden'; | ||
| } | ||
| else if (crossOrigin && hasCustomHeaders) { | ||
| bucket = 'cross_origin_custom_headers_preflight_risk'; | ||
| } | ||
| else if (hasCustomUrl || hasFallbackUrl) { | ||
| bucket = 'custom_url_no_response'; | ||
| } | ||
| else if (elapsedMs < 250) { | ||
| bucket = 'immediate_network_rejection'; | ||
| } | ||
| return { | ||
| bucket, | ||
| metadata: { | ||
| elapsedMsBucket, | ||
| bodySizeBucket, | ||
| online, | ||
| visibilityState, | ||
| isUnloading: String(isUnloading), | ||
| crossOrigin: String(crossOrigin), | ||
| hasCustomUrl: String(hasCustomUrl), | ||
| }, | ||
| }; | ||
| } | ||
| function _isCrossOrigin(url, currentOrigin) { | ||
| if (!currentOrigin) { | ||
| return true; | ||
| } | ||
| return (!url.startsWith(`${currentOrigin}/`) && | ||
| !url.startsWith(`${currentOrigin}?`) && | ||
| url !== currentOrigin); | ||
| } | ||
| function _getBodySize(body) { | ||
| if (body == null) { | ||
| return 0; | ||
| } | ||
| if (typeof body === 'string') { | ||
| return body.length; | ||
| } | ||
| if (body instanceof Uint8Array) { | ||
| return body.byteLength; | ||
| } | ||
| if (typeof Blob !== 'undefined' && body instanceof Blob) { | ||
| return body.size; | ||
| } | ||
| return -1; | ||
| } | ||
| function _bucketNumber(value, thresholds) { | ||
| if (value < 0) { | ||
| return 'unknown'; | ||
| } | ||
| for (const threshold of thresholds) { | ||
| if (value < threshold) { | ||
| return `<${threshold}`; | ||
| } | ||
| } | ||
| return `>=${thresholds[thresholds.length - 1]}`; | ||
| } | ||
| function _tryMarkInitStart(args, attempt) { | ||
@@ -396,0 +488,0 @@ if (args.urlConfig.endpoint !== NetworkConfig_1.Endpoint._initialize) { |
@@ -1,2 +0,2 @@ | ||
| export declare const SDK_VERSION = "3.32.5"; | ||
| export declare const SDK_VERSION = "3.32.6"; | ||
| export type StatsigMetadata = { | ||
@@ -3,0 +3,0 @@ readonly [key: string]: string | undefined | null; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.StatsigMetadataProvider = exports.SDK_VERSION = void 0; | ||
| exports.SDK_VERSION = '3.32.5'; | ||
| exports.SDK_VERSION = '3.32.6'; | ||
| let metadata = { | ||
@@ -6,0 +6,0 @@ sdkVersion: exports.SDK_VERSION, |
@@ -61,2 +61,8 @@ import { LogLevel } from './Log'; | ||
| /** | ||
| * The URL used to report SDK exceptions via a POST request. | ||
| * | ||
| * default: `https://statsigapi.net/v1/sdk_exception` | ||
| */ | ||
| sdkExceptionUrl?: string; | ||
| /** | ||
| * A list of URLs to try if the primary logEventUrl fails. | ||
@@ -63,0 +69,0 @@ */ |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
226472
2.85%5415
2.52%