@statsig/client-core 3.32.3 → 3.32.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statsig/client-core",
3
- "version": "3.32.3",
3
+ "version": "3.32.5",
4
4
  "license": "ISC",
5
5
  "homepage": "https://github.com/statsig-io/js-client-monorepo",
6
6
  "repository": {
@@ -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): void;
14
+ logEventRequestFailure(count: number, reason: string, flushType: string, statusCode: number, retries: number, failurePath?: string, failureErrorMessage?: string): void;
15
15
  getLastSeenErrorAndReset(): Error | null;
16
16
  attachErrorIfNoneExists(error: unknown): void;
17
17
  private _capture;
@@ -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) {
59
+ logEventRequestFailure(count, reason, flushType, statusCode, retries, failurePath, failureErrorMessage) {
60
60
  const extra = {
61
61
  eventCount: String(count),
62
62
  flushType: flushType,
@@ -64,6 +64,13 @@ class ErrorBoundary {
64
64
  reason: reason,
65
65
  retries: String(retries),
66
66
  };
67
+ if (failurePath) {
68
+ extra['failurePath'] = failurePath;
69
+ }
70
+ if (typeof failureErrorMessage === 'string' &&
71
+ failureErrorMessage.length > 0) {
72
+ extra['failureErrorMessage'] = failureErrorMessage;
73
+ }
67
74
  this._onError(`statsig::log_event_failed`, new Error(reason), true, extra);
68
75
  }
69
76
  getLastSeenErrorAndReset() {
@@ -3,6 +3,12 @@ import { NetworkCore } from './NetworkCore';
3
3
  import { StatsigClientEmitEventFunc } from './StatsigClientBase';
4
4
  import { LogEventCompressionMode, NetworkConfigCommon, StatsigOptionsCommon } from './StatsigOptionsCommon';
5
5
  import { UrlConfiguration } from './UrlConfiguration';
6
+ type EventSendResult = {
7
+ success: boolean;
8
+ statusCode: number;
9
+ failurePath?: string;
10
+ failureErrorMessage?: string;
11
+ };
6
12
  export declare class EventSender {
7
13
  private _network;
8
14
  private _sdkKey;
@@ -11,11 +17,9 @@ export declare class EventSender {
11
17
  private _emitter;
12
18
  constructor(sdkKey: string, network: NetworkCore, emitter: StatsigClientEmitEventFunc, logEventUrlConfig: UrlConfiguration, options: StatsigOptionsCommon<NetworkConfigCommon> | null);
13
19
  setLogEventCompressionMode(mode: LogEventCompressionMode): void;
14
- sendBatch(batch: EventBatch): Promise<{
15
- success: boolean;
16
- statusCode: number;
17
- }>;
20
+ sendBatch(batch: EventBatch): Promise<EventSendResult>;
18
21
  private _sendEventsViaPost;
19
22
  private _sendEventsViaBeacon;
20
23
  private _getRequestData;
21
24
  }
25
+ export {};
@@ -12,6 +12,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.EventSender = void 0;
13
13
  const Log_1 = require("./Log");
14
14
  const NetworkConfig_1 = require("./NetworkConfig");
15
+ const SDKType_1 = require("./SDKType");
16
+ const StatsigMetadata_1 = require("./StatsigMetadata");
15
17
  const VisibilityObserving_1 = require("./VisibilityObserving");
16
18
  class EventSender {
17
19
  constructor(sdkKey, network, emitter, logEventUrlConfig, options) {
@@ -26,48 +28,68 @@ class EventSender {
26
28
  }
27
29
  sendBatch(batch) {
28
30
  return __awaiter(this, void 0, void 0, function* () {
29
- var _a, _b;
31
+ var _a, _b, _c;
32
+ let failurePath = 'event_sender_unexpected_exception';
33
+ const transportFailure = {};
30
34
  try {
31
35
  const isClosing = (0, VisibilityObserving_1._isUnloading)();
32
36
  const shouldUseBeacon = isClosing &&
33
37
  this._network.isBeaconSupported() &&
34
38
  ((_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.networkConfig) === null || _b === void 0 ? void 0 : _b.networkOverrideFunc) == null;
39
+ failurePath = 'event_sender_pre_logs_flushed_emitter_exception';
35
40
  this._emitter({
36
41
  name: 'pre_logs_flushed',
37
42
  events: batch.events,
38
43
  });
44
+ failurePath = shouldUseBeacon
45
+ ? 'event_sender_unexpected_exception'
46
+ : 'event_sender_post_exception';
39
47
  const response = shouldUseBeacon
40
- ? this._sendEventsViaBeacon(batch)
41
- : yield this._sendEventsViaPost(batch);
48
+ ? this._sendEventsViaBeacon(batch, transportFailure)
49
+ : yield this._sendEventsViaPost(batch, transportFailure);
42
50
  if (response.success) {
51
+ failurePath = 'event_sender_logs_flushed_emitter_exception';
43
52
  this._emitter({
44
53
  name: 'logs_flushed',
45
54
  events: batch.events,
46
55
  });
47
56
  return response;
48
57
  }
49
- return { success: false, statusCode: response.statusCode };
58
+ return Object.assign({ success: false, statusCode: response.statusCode, failurePath: response.failurePath }, (response.failureErrorMessage
59
+ ? { failureErrorMessage: response.failureErrorMessage }
60
+ : {}));
50
61
  }
51
62
  catch (error) {
52
63
  Log_1.Log.warn('Failed to send batch:', error);
53
- return { success: false, statusCode: -1 };
64
+ return Object.assign({ success: false, statusCode: -1, failurePath: (_c = transportFailure.path) !== null && _c !== void 0 ? _c : failurePath }, (transportFailure.errorMessage
65
+ ? { failureErrorMessage: transportFailure.errorMessage }
66
+ : {}));
54
67
  }
55
68
  });
56
69
  }
57
- _sendEventsViaPost(batch) {
70
+ _sendEventsViaPost(batch, failureInfo) {
58
71
  return __awaiter(this, void 0, void 0, function* () {
59
- var _a;
60
- const result = yield this._network.post(this._getRequestData(batch));
72
+ var _a, _b;
73
+ const result = yield this._network.post(this._getRequestData(batch), failureInfo);
61
74
  const code = (_a = result === null || result === void 0 ? void 0 : result.code) !== null && _a !== void 0 ? _a : -1;
75
+ if (code === -1) {
76
+ return Object.assign({ success: false, statusCode: -1, failurePath: (_b = failureInfo.path) !== null && _b !== void 0 ? _b : (result === undefined
77
+ ? 'event_sender_post_returned_undefined'
78
+ : 'event_sender_post_returned_null') }, (failureInfo.errorMessage
79
+ ? { failureErrorMessage: failureInfo.errorMessage }
80
+ : {}));
81
+ }
62
82
  return { success: code >= 200 && code < 300, statusCode: code };
63
83
  });
64
84
  }
65
- _sendEventsViaBeacon(batch) {
66
- const success = this._network.beacon(this._getRequestData(batch));
67
- return {
68
- success,
69
- statusCode: success ? 200 : -1,
70
- };
85
+ _sendEventsViaBeacon(batch, failureInfo) {
86
+ var _a;
87
+ const success = this._network.beacon(this._getRequestData(batch), failureInfo);
88
+ return Object.assign({ success, statusCode: success ? 200 : -1, failurePath: success
89
+ ? undefined
90
+ : (_a = failureInfo.path) !== null && _a !== void 0 ? _a : 'beacon_send_false' }, (!success && failureInfo.errorMessage
91
+ ? { failureErrorMessage: failureInfo.errorMessage }
92
+ : {}));
71
93
  }
72
94
  _getRequestData(batch) {
73
95
  return {
@@ -85,6 +107,8 @@ class EventSender {
85
107
  headers: {
86
108
  'statsig-event-count': String(batch.events.length),
87
109
  'statsig-retry-count': String(batch.attempts),
110
+ 'statsig-sdk-type': SDKType_1.SDKType._get(this._sdkKey),
111
+ 'statsig-sdk-version': StatsigMetadata_1.SDK_VERSION,
88
112
  },
89
113
  credentials: 'same-origin',
90
114
  };
@@ -44,6 +44,7 @@ export declare class FlushCoordinator {
44
44
  private _prepareQueueForFlush;
45
45
  containsAtLeastOneFullBatch(): boolean;
46
46
  convertPendingEventsToBatches(): number;
47
+ private _isRetryableBatch;
47
48
  private _handleFailure;
48
49
  loadAndRetryShutdownFailedEvents(): Promise<void>;
49
50
  private _getStorageKey;
@@ -21,6 +21,13 @@ const SafeJs_1 = require("./SafeJs");
21
21
  const SessionID_1 = require("./SessionID");
22
22
  const StatsigOptionsCommon_1 = require("./StatsigOptionsCommon");
23
23
  const StorageProvider_1 = require("./StorageProvider");
24
+ const RETRYABLE_NO_RESPONSE_FAILURE_PATHS = new Set([
25
+ 'network_request_timed_out_no_response',
26
+ 'network_request_exception_no_response',
27
+ 'event_sender_post_returned_null',
28
+ 'event_sender_post_returned_undefined',
29
+ 'event_sender_post_exception',
30
+ ]);
24
31
  class FlushCoordinator {
25
32
  constructor(batchQueue, pendingEvents, onPrepareFlush,
26
33
  // For Event Sender
@@ -254,7 +261,7 @@ class FlushCoordinator {
254
261
  return true;
255
262
  }
256
263
  this._flushInterval.adjustForFailure();
257
- this._handleFailure(batch, flushType, result.statusCode);
264
+ this._handleFailure(batch, flushType, result.statusCode, result.failurePath, result.failureErrorMessage);
258
265
  return false;
259
266
  });
260
267
  }
@@ -283,23 +290,34 @@ class FlushCoordinator {
283
290
  const allEvents = this._pendingEvents.takeAll();
284
291
  return this._batchQueue.createBatches(allEvents);
285
292
  }
286
- _handleFailure(batch, flushType, statusCode) {
293
+ _isRetryableBatch(statusCode, failurePath) {
294
+ if (NetworkCore_1.RETRYABLE_CODES.has(statusCode)) {
295
+ return true;
296
+ }
297
+ if (statusCode === -1 &&
298
+ failurePath &&
299
+ RETRYABLE_NO_RESPONSE_FAILURE_PATHS.has(failurePath)) {
300
+ return true;
301
+ }
302
+ return false;
303
+ }
304
+ _handleFailure(batch, flushType, statusCode, failurePath, failureErrorMessage) {
287
305
  if (flushType === FlushTypes_1.FlushType.Shutdown) {
288
306
  Log_1.Log.warn(`${flushType} flush failed during shutdown. ` +
289
307
  `${batch.events.length} event(s) will be saved to storage for retry in next session.`);
290
308
  this._saveShutdownFailedEventsToStorage(batch.events);
291
309
  return;
292
310
  }
293
- if (!NetworkCore_1.RETRYABLE_CODES.has(statusCode)) {
311
+ if (!this._isRetryableBatch(statusCode, failurePath)) {
294
312
  Log_1.Log.warn(`${flushType} flush failed after ${batch.attempts} attempt(s). ` +
295
313
  `${batch.events.length} event(s) will be dropped. Non-retryable error: ${statusCode}`);
296
- this._errorBoundary.logEventRequestFailure(batch.events.length, `non-retryable error`, flushType, statusCode, batch.attempts);
314
+ this._errorBoundary.logEventRequestFailure(batch.events.length, `non-retryable error`, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage);
297
315
  return;
298
316
  }
299
317
  if (batch.attempts >= EventRetryConstants_1.EventRetryConstants.MAX_RETRY_ATTEMPTS) {
300
318
  Log_1.Log.warn(`${flushType} flush failed after ${batch.attempts} attempt(s). ` +
301
319
  `${batch.events.length} event(s) will be dropped.`);
302
- this._errorBoundary.logEventRequestFailure(batch.events.length, `max retry attempts exceeded`, flushType, statusCode, batch.attempts);
320
+ this._errorBoundary.logEventRequestFailure(batch.events.length, `max retry attempts exceeded`, flushType, statusCode, batch.attempts, failurePath, failureErrorMessage);
303
321
  return;
304
322
  }
305
323
  batch.incrementAttempts();
@@ -24,6 +24,10 @@ type DataFlags = {
24
24
  export type RequestArgsWithData = Flatten<RequestArgs & {
25
25
  data: Record<string, unknown>;
26
26
  } & DataFlags>;
27
+ export type RequestFailureInfo = {
28
+ path?: string;
29
+ errorMessage?: string;
30
+ };
27
31
  type BeaconRequestArgs = Pick<RequestArgsWithData, 'data' | 'sdkKey' | 'urlConfig' | 'params' | 'isCompressable' | 'attempt'>;
28
32
  type NetworkResponse = {
29
33
  body: string | null;
@@ -43,8 +47,8 @@ export declare class NetworkCore {
43
47
  setErrorBoundary(errorBoundary: ErrorBoundary): void;
44
48
  isBeaconSupported(): boolean;
45
49
  getLastUsedInitUrlAndReset(): string | null;
46
- beacon(args: BeaconRequestArgs): boolean;
47
- post(args: RequestArgsWithData): Promise<NetworkResponse | null>;
50
+ beacon(args: BeaconRequestArgs, failureInfo?: RequestFailureInfo): boolean;
51
+ post(args: RequestArgsWithData, failureInfo?: RequestFailureInfo): Promise<NetworkResponse | null>;
48
52
  get(args: RequestArgs): Promise<NetworkResponse | null>;
49
53
  private _sendRequest;
50
54
  private _getLogEventCompressionMode;
@@ -73,40 +73,66 @@ class NetworkCore {
73
73
  this._lastUsedInitUrl = null;
74
74
  return tempUrl;
75
75
  }
76
- beacon(args) {
76
+ beacon(args, failureInfo) {
77
77
  if (!_ensureValidSdkKey(args)) {
78
+ if (failureInfo) {
79
+ failureInfo.path = 'beacon_invalid_sdk_key';
80
+ }
78
81
  return false;
79
82
  }
80
83
  const argsInternal = this._getInternalRequestArgs('POST', args);
81
84
  const url = this._getPopulatedURL(argsInternal);
82
85
  const nav = navigator;
83
- return nav.sendBeacon.bind(nav)(url, argsInternal.body);
86
+ try {
87
+ const success = nav.sendBeacon.bind(nav)(url, argsInternal.body);
88
+ if (!success) {
89
+ if (failureInfo) {
90
+ failureInfo.path = 'beacon_send_false';
91
+ }
92
+ }
93
+ return success;
94
+ }
95
+ catch (error) {
96
+ if (failureInfo) {
97
+ failureInfo.path = 'beacon_send_exception';
98
+ }
99
+ throw error;
100
+ }
84
101
  }
85
- post(args) {
102
+ post(args, failureInfo) {
86
103
  return __awaiter(this, void 0, void 0, function* () {
87
104
  const argsInternal = this._getInternalRequestArgs('POST', args);
88
105
  this._tryEncodeBody(argsInternal);
89
106
  yield this._tryToCompressBody(argsInternal);
90
- return this._sendRequest(argsInternal);
107
+ return this._sendRequest(argsInternal, failureInfo);
91
108
  });
92
109
  }
93
110
  get(args) {
94
111
  const argsInternal = this._getInternalRequestArgs('GET', args);
95
112
  return this._sendRequest(argsInternal);
96
113
  }
97
- _sendRequest(args) {
114
+ _sendRequest(args, failureInfo) {
98
115
  return __awaiter(this, void 0, void 0, function* () {
99
116
  var _a, _b, _c, _d;
100
117
  if (!_ensureValidSdkKey(args)) {
118
+ if (failureInfo) {
119
+ failureInfo.path = 'network_invalid_sdk_key';
120
+ }
101
121
  return null;
102
122
  }
103
123
  if (this._netConfig.preventAllNetworkTraffic) {
124
+ if (failureInfo) {
125
+ failureInfo.path = 'network_prevent_all_network_traffic';
126
+ }
104
127
  return null;
105
128
  }
106
129
  const { method, body, retries, attempt } = args;
107
130
  const endpoint = args.urlConfig.endpoint;
108
131
  if (this._isRateLimited(endpoint)) {
109
132
  Log_1.Log.warn(`Request to ${endpoint} was blocked because you are making requests too frequently.`);
133
+ if (failureInfo) {
134
+ failureInfo.path = 'network_rate_limited';
135
+ }
110
136
  return null;
111
137
  }
112
138
  const currentAttempt = attempt !== null && attempt !== void 0 ? attempt : 1;
@@ -179,10 +205,20 @@ class NetworkCore {
179
205
  code: response.status,
180
206
  };
181
207
  }
208
+ if (response == null) {
209
+ if (failureInfo) {
210
+ failureInfo.path = timedOut
211
+ ? 'network_request_timed_out_no_response'
212
+ : 'network_request_exception_no_response';
213
+ if (errorMessage) {
214
+ failureInfo.errorMessage = errorMessage;
215
+ }
216
+ }
217
+ }
182
218
  return null;
183
219
  }
184
220
  yield _exponentialBackoff(currentAttempt);
185
- return this._sendRequest(Object.assign(Object.assign({}, args), { retries, attempt: currentAttempt + 1 }));
221
+ return this._sendRequest(Object.assign(Object.assign({}, args), { retries, attempt: currentAttempt + 1 }), failureInfo);
186
222
  }
187
223
  });
188
224
  }
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "3.32.3";
1
+ export declare const SDK_VERSION = "3.32.5";
2
2
  export type StatsigMetadata = {
3
3
  readonly [key: string]: string | undefined | null;
4
4
  readonly appVersion?: string;
@@ -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.3';
4
+ exports.SDK_VERSION = '3.32.5';
5
5
  let metadata = {
6
6
  sdkVersion: exports.SDK_VERSION,
7
7
  sdkType: 'js-mono', // js-mono is overwritten by Precomp and OnDevice clients