@statsig/client-core 3.31.2-beta.2 → 3.32.0

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.31.2-beta.2",
3
+ "version": "3.32.0",
4
4
  "license": "ISC",
5
5
  "homepage": "https://github.com/statsig-io/js-client-monorepo",
6
6
  "repository": {
@@ -0,0 +1,14 @@
1
+ import { EventBatch } from './EventBatch';
2
+ import { StatsigEventInternal } from './StatsigEvent';
3
+ export declare class BatchQueue {
4
+ private _batches;
5
+ private _batchSize;
6
+ constructor(batchSize?: number);
7
+ batchSize(): number;
8
+ requeueBatch(batch: EventBatch): number;
9
+ hasFullBatch(): boolean;
10
+ takeNextBatch(): EventBatch | undefined;
11
+ takeAllBatches(): EventBatch[];
12
+ createBatches(events: StatsigEventInternal[]): number;
13
+ private _enqueueBatch;
14
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BatchQueue = void 0;
4
+ const EventBatch_1 = require("./EventBatch");
5
+ const EventRetryConstants_1 = require("./EventRetryConstants");
6
+ class BatchQueue {
7
+ constructor(batchSize = EventRetryConstants_1.EventRetryConstants.DEFAULT_BATCH_SIZE) {
8
+ this._batches = [];
9
+ this._batchSize = batchSize;
10
+ }
11
+ batchSize() {
12
+ return this._batchSize;
13
+ }
14
+ requeueBatch(batch) {
15
+ return this._enqueueBatch(batch);
16
+ }
17
+ hasFullBatch() {
18
+ return this._batches.some((batch) => batch.events.length >= this._batchSize);
19
+ }
20
+ takeNextBatch() {
21
+ return this._batches.shift();
22
+ }
23
+ takeAllBatches() {
24
+ const batches = this._batches;
25
+ this._batches = [];
26
+ return batches;
27
+ }
28
+ createBatches(events) {
29
+ let i = 0;
30
+ let droppedCount = 0;
31
+ while (i < events.length) {
32
+ const chunk = events.slice(i, i + this._batchSize);
33
+ droppedCount += this._enqueueBatch(new EventBatch_1.EventBatch(chunk));
34
+ i += this._batchSize;
35
+ }
36
+ return droppedCount;
37
+ }
38
+ _enqueueBatch(batch) {
39
+ this._batches.push(batch);
40
+ let droppedEventCount = 0;
41
+ while (this._batches.length > EventRetryConstants_1.EventRetryConstants.MAX_PENDING_BATCHES) {
42
+ const dropped = this._batches.shift();
43
+ if (dropped) {
44
+ droppedEventCount += dropped.events.length;
45
+ }
46
+ }
47
+ return droppedEventCount;
48
+ }
49
+ }
50
+ exports.BatchQueue = BatchQueue;
@@ -1,18 +1,5 @@
1
- import type { SessionReplayPrivacySettings } from './InitializeResponse';
2
1
  import { ParamStoreConfig } from './ParamStoreTypes';
3
2
  import { StatsigUser } from './StatsigUser';
4
- export type SessionReplayTriggerSpec = {
5
- values?: string[];
6
- sampling_rate?: number;
7
- };
8
- export type SessionReplayInfoSpec = {
9
- recording_blocked?: boolean;
10
- sampling_rate?: number;
11
- targeting_gate?: string;
12
- session_recording_event_triggers?: Record<string, SessionReplayTriggerSpec>;
13
- session_recording_exposure_triggers?: Record<string, SessionReplayTriggerSpec>;
14
- session_recording_privacy_settings?: SessionReplayPrivacySettings;
15
- };
16
3
  export type SpecCondition = {
17
4
  type: string;
18
5
  targetValue: unknown;
@@ -71,5 +58,4 @@ export type DownloadConfigSpecsResponse = {
71
58
  experiments: string[];
72
59
  }>;
73
60
  app_id?: string;
74
- session_replay_info?: SessionReplayInfoSpec;
75
61
  };
@@ -10,6 +10,8 @@ export declare class ErrorBoundary {
10
10
  constructor(_sdkKey: string, _options: AnyStatsigOptions | null, _emitter?: StatsigClientEmitEventFunc | undefined, _lastSeenError?: Error | undefined);
11
11
  wrap(instance: unknown, namePrefix?: string): void;
12
12
  logError(tag: string, error: unknown): void;
13
+ logDroppedEvents(count: number, reason: string, metadata?: Record<string, unknown>): void;
14
+ logEventRequestFailure(count: number, reason: string, flushType: string, statusCode: number, retries: number): void;
13
15
  getLastSeenErrorAndReset(): Error | null;
14
16
  attachErrorIfNoneExists(error: unknown): void;
15
17
  private _capture;
@@ -44,6 +44,27 @@ class ErrorBoundary {
44
44
  logError(tag, error) {
45
45
  this._onError(tag, error);
46
46
  }
47
+ logDroppedEvents(count, reason, metadata) {
48
+ const extra = {
49
+ eventCount: String(count),
50
+ };
51
+ if (metadata) {
52
+ Object.keys(metadata).forEach((key) => {
53
+ extra[key] = String(metadata[key]);
54
+ });
55
+ }
56
+ this._onError(`statsig::log_event_dropped_event_count`, new Error(reason), true, extra);
57
+ }
58
+ logEventRequestFailure(count, reason, flushType, statusCode, retries) {
59
+ const extra = {
60
+ eventCount: String(count),
61
+ flushType: flushType,
62
+ statusCode: String(statusCode),
63
+ reason: reason,
64
+ retries: String(retries),
65
+ };
66
+ this._onError(`statsig::log_event_failed`, new Error(reason), true, extra);
67
+ }
47
68
  getLastSeenErrorAndReset() {
48
69
  const tempError = this._lastSeenError;
49
70
  this._lastSeenError = undefined;
@@ -68,7 +89,7 @@ class ErrorBoundary {
68
89
  return null;
69
90
  }
70
91
  }
71
- _onError(tag, error) {
92
+ _onError(tag, error, bypassDedupe = false, extra) {
72
93
  try {
73
94
  Log_1.Log.warn(`Caught error in ${tag}`, { error });
74
95
  const impl = () => __awaiter(this, void 0, void 0, function* () {
@@ -78,7 +99,7 @@ class ErrorBoundary {
78
99
  const name = isError ? unwrapped.name : 'No Name';
79
100
  const resolvedError = _resolveError(unwrapped);
80
101
  this._lastSeenError = resolvedError;
81
- if (this._seen.has(name)) {
102
+ if (!bypassDedupe && this._seen.has(name)) {
82
103
  return;
83
104
  }
84
105
  this._seen.add(name);
@@ -93,7 +114,7 @@ class ErrorBoundary {
93
114
  const sdkType = SDKType_1.SDKType._get(this._sdkKey);
94
115
  const statsigMetadata = StatsigMetadata_1.StatsigMetadataProvider.get();
95
116
  const info = isError ? unwrapped.stack : _getDescription(unwrapped);
96
- const body = Object.assign({ tag, exception: name, info, statsigOptions: _getStatsigOptionLoggingCopy(this._options) }, Object.assign(Object.assign({}, statsigMetadata), { sdkType }));
117
+ 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 : {}));
97
118
  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;
98
119
  yield func(exports.EXCEPTION_ENDPOINT, {
99
120
  method: 'POST',
@@ -1,3 +1,4 @@
1
+ import { ErrorBoundary } from './ErrorBoundary';
1
2
  import { NetworkCore } from './NetworkCore';
2
3
  import { StatsigClientEmitEventFunc } from './StatsigClientBase';
3
4
  import { StatsigEventInternal } from './StatsigEvent';
@@ -7,18 +8,19 @@ export declare class EventLogger {
7
8
  private _emitter;
8
9
  private _network;
9
10
  private _options;
10
- private _queue;
11
- private _flushIntervalId;
11
+ private _errorBoundary;
12
+ private _pendingEvents;
13
+ private _batchQueue;
14
+ private _flushCoordinator;
12
15
  private _lastExposureTimeMap;
13
16
  private _nonExposedChecks;
14
- private _maxQueueSize;
15
- private _hasRunQuickFlush;
16
- private _creationTime;
17
17
  private _loggingEnabled;
18
18
  private _logEventUrlConfig;
19
+ private _isShuttingDown;
20
+ private _storageKey;
21
+ private _pendingCompressionMode;
19
22
  private static _safeFlushAndForget;
20
- private static _safeRetryFailedLogs;
21
- constructor(_sdkKey: string, _emitter: StatsigClientEmitEventFunc, _network: NetworkCore, _options: StatsigOptionsCommon<NetworkConfigCommon> | null);
23
+ constructor(_sdkKey: string, _emitter: StatsigClientEmitEventFunc, _network: NetworkCore, _options: StatsigOptionsCommon<NetworkConfigCommon> | null, _errorBoundary: ErrorBoundary);
22
24
  setLogEventCompressionMode(mode: LogEventCompressionMode): void;
23
25
  setLoggingEnabled(loggingEnabled: LoggingEnabledOption): void;
24
26
  enqueue(event: StatsigEventInternal): void;
@@ -27,22 +29,13 @@ export declare class EventLogger {
27
29
  start(): void;
28
30
  stop(): Promise<void>;
29
31
  flush(): Promise<void>;
30
- /**
31
- * We 'Quick Flush' following the very first event enqueued
32
- * within the quick flush window
33
- */
34
- private _quickFlushIfNeeded;
32
+ appendAndResetNonExposedChecks(): void;
35
33
  private _shouldLogEvent;
36
- private _sendEvents;
37
- private _sendEventsViaPost;
38
- private _sendEventsViaBeacon;
39
- private _getRequestData;
40
- private _saveFailedLogsToStorage;
41
- private _getFailedLogsFromStorage;
42
- private _retryFailedLogs;
43
- private _getStorageKey;
44
- private _normalizeAndAppendEvent;
45
- private _appendAndResetNonExposedChecks;
46
34
  private _getCurrentPageUrl;
47
- private _startBackgroundFlushInterval;
35
+ private _getStorageKey;
36
+ private _initFlushCoordinator;
37
+ private _storeEventToStorage;
38
+ private _getEventsFromStorage;
39
+ private _loadStoredEvents;
40
+ private _normalizeEvent;
48
41
  }
@@ -10,23 +10,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.EventLogger = void 0;
13
+ const BatchedEventsQueue_1 = require("./BatchedEventsQueue");
13
14
  const CacheKey_1 = require("./CacheKey");
15
+ const EventRetryConstants_1 = require("./EventRetryConstants");
16
+ const FlushCoordinator_1 = require("./FlushCoordinator");
14
17
  const Hashing_1 = require("./Hashing");
15
18
  const Log_1 = require("./Log");
16
19
  const NetworkConfig_1 = require("./NetworkConfig");
20
+ const PendingEvents_1 = require("./PendingEvents");
17
21
  const SafeJs_1 = require("./SafeJs");
18
- const SessionID_1 = require("./SessionID");
19
22
  const StatsigEvent_1 = require("./StatsigEvent");
20
23
  const StatsigOptionsCommon_1 = require("./StatsigOptionsCommon");
21
24
  const StorageProvider_1 = require("./StorageProvider");
22
25
  const UrlConfiguration_1 = require("./UrlConfiguration");
23
26
  const VisibilityObserving_1 = require("./VisibilityObserving");
24
- const DEFAULT_QUEUE_SIZE = 100;
25
- const DEFAULT_FLUSH_INTERVAL_MS = 10000;
26
27
  const MAX_DEDUPER_KEYS = 1000;
27
28
  const DEDUPER_WINDOW_DURATION_MS = 600000;
28
- const MAX_FAILED_LOGS = 500;
29
- const QUICK_FLUSH_WINDOW_MS = 200;
30
29
  const EVENT_LOGGER_MAP = {};
31
30
  class EventLogger {
32
31
  static _safeFlushAndForget(sdkKey) {
@@ -35,21 +34,21 @@ class EventLogger {
35
34
  // noop
36
35
  });
37
36
  }
38
- static _safeRetryFailedLogs(sdkKey) {
37
+ constructor(_sdkKey, _emitter, _network, _options, _errorBoundary) {
39
38
  var _a;
40
- (_a = EVENT_LOGGER_MAP[sdkKey]) === null || _a === void 0 ? void 0 : _a._retryFailedLogs();
41
- }
42
- constructor(_sdkKey, _emitter, _network, _options) {
43
- var _a, _b;
44
39
  this._sdkKey = _sdkKey;
45
40
  this._emitter = _emitter;
46
41
  this._network = _network;
47
42
  this._options = _options;
48
- this._queue = [];
43
+ this._errorBoundary = _errorBoundary;
44
+ this._pendingEvents = null;
45
+ this._batchQueue = null;
46
+ this._flushCoordinator = null;
49
47
  this._lastExposureTimeMap = {};
50
48
  this._nonExposedChecks = {};
51
- this._hasRunQuickFlush = false;
52
- this._creationTime = Date.now();
49
+ this._isShuttingDown = false;
50
+ this._storageKey = null;
51
+ this._pendingCompressionMode = null;
53
52
  this._loggingEnabled =
54
53
  (_a = _options === null || _options === void 0 ? void 0 : _options.loggingEnabled) !== null && _a !== void 0 ? _a : ((_options === null || _options === void 0 ? void 0 : _options.disableLogging) === true
55
54
  ? StatsigOptionsCommon_1.LoggingEnabledOption.disabled
@@ -57,34 +56,49 @@ class EventLogger {
57
56
  if ((_options === null || _options === void 0 ? void 0 : _options.loggingEnabled) && _options.disableLogging !== undefined) {
58
57
  Log_1.Log.warn('Detected both loggingEnabled and disableLogging options. loggingEnabled takes precedence - please remove disableLogging.');
59
58
  }
60
- this._maxQueueSize = (_b = _options === null || _options === void 0 ? void 0 : _options.loggingBufferMaxSize) !== null && _b !== void 0 ? _b : DEFAULT_QUEUE_SIZE;
61
59
  const config = _options === null || _options === void 0 ? void 0 : _options.networkConfig;
62
60
  this._logEventUrlConfig = new UrlConfiguration_1.UrlConfiguration(NetworkConfig_1.Endpoint._rgstr, config === null || config === void 0 ? void 0 : config.logEventUrl, config === null || config === void 0 ? void 0 : config.api, config === null || config === void 0 ? void 0 : config.logEventFallbackUrls);
63
61
  }
64
62
  setLogEventCompressionMode(mode) {
65
- this._network.setLogEventCompressionMode(mode);
63
+ if (this._flushCoordinator) {
64
+ this._flushCoordinator.setLogEventCompressionMode(mode);
65
+ }
66
+ else {
67
+ this._pendingCompressionMode = mode;
68
+ }
66
69
  }
67
70
  setLoggingEnabled(loggingEnabled) {
68
- if (this._loggingEnabled === 'disabled' && loggingEnabled !== 'disabled') {
69
- // load any pre consented events into memory
70
- const storageKey = this._getStorageKey();
71
- const events = (0, StorageProvider_1._getObjectFromStorage)(storageKey);
72
- if (events) {
73
- this._queue.push(...events);
71
+ const wasDisabled = this._loggingEnabled === 'disabled';
72
+ const isNowEnabled = loggingEnabled !== 'disabled';
73
+ this._loggingEnabled = loggingEnabled;
74
+ if (this._flushCoordinator) {
75
+ this._flushCoordinator.setLoggingEnabled(loggingEnabled);
76
+ }
77
+ if (wasDisabled && isNowEnabled) {
78
+ const events = this._loadStoredEvents();
79
+ Log_1.Log.debug(`Loaded ${events.length} stored event(s) from storage`);
80
+ if (events.length > 0) {
81
+ events.forEach((event) => {
82
+ this._initFlushCoordinator().addEvent(event);
83
+ });
84
+ this.flush().catch((error) => {
85
+ Log_1.Log.warn('Failed to flush events after enabling logging:', error);
86
+ });
74
87
  }
75
- StorageProvider_1.Storage.removeItem(storageKey);
76
88
  }
77
- this._loggingEnabled = loggingEnabled;
78
89
  }
79
90
  enqueue(event) {
91
+ var _a;
80
92
  if (!this._shouldLogEvent(event)) {
81
93
  return;
82
94
  }
83
- this._normalizeAndAppendEvent(event);
84
- this._quickFlushIfNeeded();
85
- if (this._queue.length > this._maxQueueSize) {
86
- EventLogger._safeFlushAndForget(this._sdkKey);
95
+ const normalizedEvent = this._normalizeEvent(event);
96
+ if (this._loggingEnabled === 'disabled') {
97
+ this._storeEventToStorage(normalizedEvent);
98
+ return;
87
99
  }
100
+ this._initFlushCoordinator().addEvent(normalizedEvent);
101
+ (_a = this._flushCoordinator) === null || _a === void 0 ? void 0 : _a.checkQuickFlush();
88
102
  }
89
103
  incrementNonExposureCount(name) {
90
104
  var _a;
@@ -104,6 +118,7 @@ class EventLogger {
104
118
  if (isServerEnv && ((_a = this._options) === null || _a === void 0 ? void 0 : _a.loggingEnabled) !== 'always') {
105
119
  return;
106
120
  }
121
+ const flushCoordinator = this._initFlushCoordinator();
107
122
  EVENT_LOGGER_MAP[this._sdkKey] = this;
108
123
  if (!isServerEnv) {
109
124
  (0, VisibilityObserving_1._subscribeToVisiblityChanged)((visibility) => {
@@ -111,47 +126,53 @@ class EventLogger {
111
126
  EventLogger._safeFlushAndForget(this._sdkKey);
112
127
  }
113
128
  else if (visibility === 'foreground') {
114
- EventLogger._safeRetryFailedLogs(this._sdkKey);
129
+ flushCoordinator.startScheduledFlushCycle();
115
130
  }
116
131
  });
117
132
  }
118
- this._retryFailedLogs();
119
- this._startBackgroundFlushInterval();
133
+ flushCoordinator.loadAndRetryShutdownFailedEvents().catch((error) => {
134
+ Log_1.Log.warn('Failed to load failed shutdown events:', error);
135
+ });
136
+ flushCoordinator.startScheduledFlushCycle();
120
137
  }
121
138
  stop() {
122
139
  return __awaiter(this, void 0, void 0, function* () {
123
- if (this._flushIntervalId) {
124
- clearInterval(this._flushIntervalId);
125
- this._flushIntervalId = null;
140
+ this._isShuttingDown = true;
141
+ if (this._flushCoordinator) {
142
+ yield this._flushCoordinator.processShutdown();
126
143
  }
127
144
  delete EVENT_LOGGER_MAP[this._sdkKey];
128
- yield this.flush();
145
+ this._flushCoordinator = null;
146
+ this._pendingEvents = null;
147
+ this._batchQueue = null;
129
148
  });
130
149
  }
131
150
  flush() {
132
151
  return __awaiter(this, void 0, void 0, function* () {
133
- this._appendAndResetNonExposedChecks();
134
- if (this._queue.length === 0) {
152
+ if (!this._flushCoordinator) {
135
153
  return;
136
154
  }
137
- const events = this._queue;
138
- this._queue = [];
139
- yield this._sendEvents(events);
155
+ return this._flushCoordinator.processManualFlush();
140
156
  });
141
157
  }
142
- /**
143
- * We 'Quick Flush' following the very first event enqueued
144
- * within the quick flush window
145
- */
146
- _quickFlushIfNeeded() {
147
- if (this._hasRunQuickFlush) {
158
+ appendAndResetNonExposedChecks() {
159
+ if (Object.keys(this._nonExposedChecks).length === 0) {
148
160
  return;
149
161
  }
150
- this._hasRunQuickFlush = true;
151
- if (Date.now() - this._creationTime > QUICK_FLUSH_WINDOW_MS) {
162
+ const checks = Object.assign({}, this._nonExposedChecks);
163
+ this._nonExposedChecks = {};
164
+ const event = this._normalizeEvent({
165
+ eventName: 'statsig::non_exposed_checks',
166
+ user: null,
167
+ time: Date.now(),
168
+ metadata: {
169
+ checks,
170
+ },
171
+ });
172
+ if (!this._flushCoordinator) {
152
173
  return;
153
174
  }
154
- setTimeout(() => EventLogger._safeFlushAndForget(this._sdkKey), QUICK_FLUSH_WINDOW_MS);
175
+ this._flushCoordinator.addEvent(event);
155
176
  }
156
177
  _shouldLogEvent(event) {
157
178
  var _a;
@@ -186,118 +207,69 @@ class EventLogger {
186
207
  this._lastExposureTimeMap[key] = now;
187
208
  return true;
188
209
  }
189
- _sendEvents(events) {
190
- return __awaiter(this, void 0, void 0, function* () {
191
- var _a, _b;
192
- if (this._loggingEnabled === 'disabled') {
193
- this._saveFailedLogsToStorage(events);
194
- return false;
195
- }
196
- try {
197
- const isClosing = (0, VisibilityObserving_1._isUnloading)();
198
- const shouldUseBeacon = isClosing &&
199
- this._network.isBeaconSupported() &&
200
- ((_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.networkConfig) === null || _b === void 0 ? void 0 : _b.networkOverrideFunc) == null;
201
- this._emitter({
202
- name: 'pre_logs_flushed',
203
- events,
204
- });
205
- const response = shouldUseBeacon
206
- ? this._sendEventsViaBeacon(events)
207
- : yield this._sendEventsViaPost(events);
208
- if (response.success) {
209
- this._emitter({
210
- name: 'logs_flushed',
211
- events,
212
- });
213
- return true;
214
- }
215
- else {
216
- Log_1.Log.warn('Failed to flush events.');
217
- this._saveFailedLogsToStorage(events);
218
- return false;
219
- }
220
- }
221
- catch (_c) {
222
- Log_1.Log.warn('Failed to flush events.');
223
- return false;
224
- }
225
- });
226
- }
227
- _sendEventsViaPost(events) {
228
- return __awaiter(this, void 0, void 0, function* () {
229
- var _a;
230
- const result = yield this._network.post(this._getRequestData(events));
231
- const code = (_a = result === null || result === void 0 ? void 0 : result.code) !== null && _a !== void 0 ? _a : -1;
232
- return { success: code >= 200 && code < 300 };
233
- });
234
- }
235
- _sendEventsViaBeacon(events) {
236
- return {
237
- success: this._network.beacon(this._getRequestData(events)),
238
- };
210
+ _getCurrentPageUrl() {
211
+ var _a;
212
+ if (((_a = this._options) === null || _a === void 0 ? void 0 : _a.includeCurrentPageUrlWithEvents) === false) {
213
+ return;
214
+ }
215
+ return (0, SafeJs_1._getCurrentPageUrlSafe)();
239
216
  }
240
- _getRequestData(events) {
241
- return {
242
- sdkKey: this._sdkKey,
243
- data: {
244
- events,
245
- },
246
- urlConfig: this._logEventUrlConfig,
247
- retries: 3,
248
- isCompressable: true,
249
- params: {
250
- [NetworkConfig_1.NetworkParam.EventCount]: String(events.length),
251
- },
252
- credentials: 'same-origin',
253
- };
217
+ _getStorageKey() {
218
+ if (!this._storageKey) {
219
+ this._storageKey = `statsig.pending_events.${(0, Hashing_1._DJB2)(this._sdkKey)}`;
220
+ }
221
+ return this._storageKey;
254
222
  }
255
- _saveFailedLogsToStorage(events) {
256
- while (events.length > MAX_FAILED_LOGS) {
257
- events.shift();
223
+ _initFlushCoordinator() {
224
+ var _a, _b;
225
+ if (this._flushCoordinator) {
226
+ return this._flushCoordinator;
227
+ }
228
+ const batchSize = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.loggingBufferMaxSize) !== null && _b !== void 0 ? _b : EventRetryConstants_1.EventRetryConstants.DEFAULT_BATCH_SIZE;
229
+ this._pendingEvents = new PendingEvents_1.PendingEvents(batchSize);
230
+ this._batchQueue = new BatchedEventsQueue_1.BatchQueue(batchSize);
231
+ this._flushCoordinator = new FlushCoordinator_1.FlushCoordinator(this._batchQueue, this._pendingEvents, () => this.appendAndResetNonExposedChecks(), this._sdkKey, this._network, this._emitter, this._logEventUrlConfig, this._options, this._loggingEnabled, this._errorBoundary);
232
+ if (this._pendingCompressionMode) {
233
+ this._flushCoordinator.setLogEventCompressionMode(this._pendingCompressionMode);
234
+ this._pendingCompressionMode = null;
258
235
  }
236
+ return this._flushCoordinator;
237
+ }
238
+ _storeEventToStorage(event) {
259
239
  const storageKey = this._getStorageKey();
260
240
  try {
261
- const savedEvents = this._getFailedLogsFromStorage(storageKey);
262
- (0, StorageProvider_1._setObjectInStorage)(storageKey, [...savedEvents, ...events]);
241
+ let existingEvents = this._getEventsFromStorage(storageKey);
242
+ existingEvents.push(event);
243
+ if (existingEvents.length > EventRetryConstants_1.EventRetryConstants.MAX_LOCAL_STORAGE) {
244
+ existingEvents = existingEvents.slice(-EventRetryConstants_1.EventRetryConstants.MAX_LOCAL_STORAGE);
245
+ }
246
+ (0, StorageProvider_1._setObjectInStorage)(storageKey, existingEvents);
263
247
  }
264
- catch (_a) {
265
- Log_1.Log.warn('Unable to save failed logs to storage');
248
+ catch (error) {
249
+ Log_1.Log.warn('Unable to save events to storage');
266
250
  }
267
251
  }
268
- _getFailedLogsFromStorage(storageKey) {
269
- let savedEvents = [];
252
+ _getEventsFromStorage(storageKey) {
270
253
  try {
271
- const retrieved = (0, StorageProvider_1._getObjectFromStorage)(storageKey);
272
- if (Array.isArray(retrieved)) {
273
- savedEvents = retrieved;
254
+ const events = (0, StorageProvider_1._getObjectFromStorage)(storageKey);
255
+ if (Array.isArray(events)) {
256
+ return events;
274
257
  }
275
- return savedEvents;
258
+ return [];
276
259
  }
277
260
  catch (_a) {
278
261
  return [];
279
262
  }
280
263
  }
281
- _retryFailedLogs() {
264
+ _loadStoredEvents() {
282
265
  const storageKey = this._getStorageKey();
283
- (() => __awaiter(this, void 0, void 0, function* () {
284
- if (!StorageProvider_1.Storage.isReady()) {
285
- yield StorageProvider_1.Storage.isReadyResolver();
286
- }
287
- const events = (0, StorageProvider_1._getObjectFromStorage)(storageKey);
288
- if (!events) {
289
- return;
290
- }
266
+ const events = this._getEventsFromStorage(storageKey);
267
+ if (events.length > 0) {
291
268
  StorageProvider_1.Storage.removeItem(storageKey);
292
- yield this._sendEvents(events);
293
- }))().catch(() => {
294
- Log_1.Log.warn('Failed to flush stored logs');
295
- });
296
- }
297
- _getStorageKey() {
298
- return `statsig.failed_logs.${(0, Hashing_1._DJB2)(this._sdkKey)}`;
269
+ }
270
+ return events;
299
271
  }
300
- _normalizeAndAppendEvent(event) {
272
+ _normalizeEvent(event) {
301
273
  if (event.user) {
302
274
  event.user = Object.assign({}, event.user);
303
275
  delete event.user.privateAttributes;
@@ -307,45 +279,7 @@ class EventLogger {
307
279
  if (currentPage) {
308
280
  extras.statsigMetadata = { currentPage };
309
281
  }
310
- const final = Object.assign(Object.assign({}, event), extras);
311
- Log_1.Log.debug('Enqueued Event:', final);
312
- this._queue.push(final);
313
- }
314
- _appendAndResetNonExposedChecks() {
315
- if (Object.keys(this._nonExposedChecks).length === 0) {
316
- return;
317
- }
318
- this._normalizeAndAppendEvent({
319
- eventName: 'statsig::non_exposed_checks',
320
- user: null,
321
- time: Date.now(),
322
- metadata: {
323
- checks: Object.assign({}, this._nonExposedChecks),
324
- },
325
- });
326
- this._nonExposedChecks = {};
327
- }
328
- _getCurrentPageUrl() {
329
- var _a;
330
- if (((_a = this._options) === null || _a === void 0 ? void 0 : _a.includeCurrentPageUrlWithEvents) === false) {
331
- return;
332
- }
333
- return (0, SafeJs_1._getCurrentPageUrlSafe)();
334
- }
335
- _startBackgroundFlushInterval() {
336
- var _a, _b;
337
- const flushInterval = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.loggingIntervalMs) !== null && _b !== void 0 ? _b : DEFAULT_FLUSH_INTERVAL_MS;
338
- const intervalId = setInterval(() => {
339
- const logger = EVENT_LOGGER_MAP[this._sdkKey];
340
- if (!logger || logger._flushIntervalId !== intervalId) {
341
- clearInterval(intervalId);
342
- }
343
- else {
344
- EventLogger._safeFlushAndForget(this._sdkKey);
345
- SessionID_1.StatsigSession.checkForIdleSession(this._sdkKey);
346
- }
347
- }, flushInterval);
348
- this._flushIntervalId = intervalId;
282
+ return Object.assign(Object.assign({}, event), extras);
349
283
  }
350
284
  }
351
285
  exports.EventLogger = EventLogger;
@@ -1,7 +1,9 @@
1
1
  export declare const EventRetryConstants: {
2
2
  readonly MAX_RETRY_ATTEMPTS: 5;
3
3
  readonly DEFAULT_BATCH_SIZE: 100;
4
- readonly MAX_PENDING_BATCHES: 10;
4
+ readonly MAX_PENDING_BATCHES: 30;
5
5
  readonly TICK_INTERVAL_MS: 1000;
6
+ readonly QUICK_FLUSH_WINDOW_MS: 200;
7
+ readonly MAX_LOCAL_STORAGE: 500;
6
8
  readonly MAX_QUEUED_EVENTS: number;
7
9
  };
@@ -4,8 +4,10 @@ exports.EventRetryConstants = void 0;
4
4
  exports.EventRetryConstants = {
5
5
  MAX_RETRY_ATTEMPTS: 5,
6
6
  DEFAULT_BATCH_SIZE: 100,
7
- MAX_PENDING_BATCHES: 10,
7
+ MAX_PENDING_BATCHES: 30,
8
8
  TICK_INTERVAL_MS: 1000,
9
+ QUICK_FLUSH_WINDOW_MS: 200,
10
+ MAX_LOCAL_STORAGE: 500,
9
11
  get MAX_QUEUED_EVENTS() {
10
12
  return this.DEFAULT_BATCH_SIZE * this.MAX_PENDING_BATCHES;
11
13
  },