posthog-js-lite 3.0.0-beta.1 → 3.0.0-beta.2

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/lib/index.d.ts CHANGED
@@ -5,6 +5,8 @@ declare type PostHogCoreOptions = {
5
5
  flushAt?: number;
6
6
  /** The interval in milliseconds between periodic flushes */
7
7
  flushInterval?: number;
8
+ /** The maximum number of queued messages to be flushed as part of a single batch (must be higher than `flushAt`) */
9
+ maxBatchSize?: number;
8
10
  /** If set to true the SDK is essentially disabled (useful for local environments where you don't want to track anything) */
9
11
  disabled?: boolean;
10
12
  /** If set to false the SDK will not track until the `optIn` function is called. */
@@ -127,7 +129,9 @@ declare abstract class PostHogCoreStateless {
127
129
  private apiKey;
128
130
  host: string;
129
131
  private flushAt;
132
+ private maxBatchSize;
130
133
  private flushInterval;
134
+ private flushPromise;
131
135
  private requestTimeout;
132
136
  private featureFlagsRequestTimeoutMs;
133
137
  private captureMode;
@@ -157,7 +161,7 @@ declare abstract class PostHogCoreStateless {
157
161
  debug(enabled?: boolean): void;
158
162
  get isDebug(): boolean;
159
163
  private buildPayload;
160
- protected addPendingPromise(promise: Promise<any>): void;
164
+ protected addPendingPromise<T>(promise: Promise<T>): Promise<T>;
161
165
  /***
162
166
  *** TRACKING
163
167
  ***/
@@ -189,11 +193,16 @@ declare abstract class PostHogCoreStateless {
189
193
  *** QUEUEING AND FLUSHING
190
194
  ***/
191
195
  protected enqueue(type: string, _message: any, options?: PostHogCaptureOptions): void;
192
- flushAsync(): Promise<any>;
193
- flush(callback?: (err?: any, data?: any) => void): void;
196
+ private clearFlushTimer;
197
+ /**
198
+ * Helper for flushing the queue in the background
199
+ * Avoids unnecessary promise errors
200
+ */
201
+ private flushBackground;
202
+ flush(): Promise<any[]>;
203
+ private _flush;
194
204
  private fetchWithRetry;
195
- shutdownAsync(shutdownTimeoutMs?: number): Promise<void>;
196
- shutdown(shutdownTimeoutMs?: number): void;
205
+ shutdown(shutdownTimeoutMs?: number): Promise<void>;
197
206
  }
198
207
  declare abstract class PostHogCore extends PostHogCoreStateless {
199
208
  private sendFeatureFlagEvent;
package/lib/index.esm.js CHANGED
@@ -942,6 +942,7 @@ function isPostHogFetchError(err) {
942
942
  }
943
943
  class PostHogCoreStateless {
944
944
  constructor(apiKey, options) {
945
+ this.flushPromise = null;
945
946
  this.disableGeoip = true;
946
947
  this.disabled = false;
947
948
  this.defaultOptIn = true;
@@ -953,6 +954,7 @@ class PostHogCoreStateless {
953
954
  this.apiKey = apiKey;
954
955
  this.host = removeTrailingSlash(options?.host || 'https://app.posthog.com');
955
956
  this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 20;
957
+ this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
956
958
  this.flushInterval = options?.flushInterval ?? 10000;
957
959
  this.captureMode = options?.captureMode || 'form';
958
960
  // If enable is explicitly set to false we override the optout
@@ -1031,9 +1033,12 @@ class PostHogCoreStateless {
1031
1033
  addPendingPromise(promise) {
1032
1034
  const promiseUUID = uuidv7();
1033
1035
  this.pendingPromises[promiseUUID] = promise;
1034
- promise.finally(() => {
1036
+ promise
1037
+ .catch(() => { })
1038
+ .finally(() => {
1035
1039
  delete this.pendingPromises[promiseUUID];
1036
1040
  });
1041
+ return promise;
1037
1042
  }
1038
1043
  /***
1039
1044
  *** TRACKING
@@ -1213,75 +1218,90 @@ class PostHogCoreStateless {
1213
1218
  this._events.emit(type, message);
1214
1219
  // Flush queued events if we meet the flushAt length
1215
1220
  if (queue.length >= this.flushAt) {
1216
- this.flush();
1221
+ this.flushBackground();
1217
1222
  }
1218
1223
  if (this.flushInterval && !this._flushTimer) {
1219
- this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval);
1224
+ this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
1220
1225
  }
1221
1226
  });
1222
1227
  }
1223
- async flushAsync() {
1224
- await this._initPromise;
1225
- return new Promise((resolve, reject) => {
1226
- this.flush((err, data) => {
1227
- return err ? reject(err) : resolve(data);
1228
+ clearFlushTimer() {
1229
+ if (this._flushTimer) {
1230
+ clearTimeout(this._flushTimer);
1231
+ this._flushTimer = undefined;
1232
+ }
1233
+ }
1234
+ /**
1235
+ * Helper for flushing the queue in the background
1236
+ * Avoids unnecessary promise errors
1237
+ */
1238
+ flushBackground() {
1239
+ void this.flush().catch(() => { });
1240
+ }
1241
+ async flush() {
1242
+ if (!this.flushPromise) {
1243
+ this.flushPromise = this._flush().finally(() => {
1244
+ this.flushPromise = null;
1228
1245
  });
1229
- });
1246
+ this.addPendingPromise(this.flushPromise);
1247
+ }
1248
+ return this.flushPromise;
1230
1249
  }
1231
- flush(callback) {
1232
- this.wrap(() => {
1233
- if (this._flushTimer) {
1234
- clearTimeout(this._flushTimer);
1235
- this._flushTimer = null;
1236
- }
1237
- const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1238
- if (!queue.length) {
1239
- return callback?.();
1250
+ async _flush() {
1251
+ this.clearFlushTimer();
1252
+ await this._initPromise;
1253
+ const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1254
+ if (!queue.length) {
1255
+ return [];
1256
+ }
1257
+ const items = queue.slice(0, this.maxBatchSize);
1258
+ const messages = items.map((item) => item.message);
1259
+ const persistQueueChange = () => {
1260
+ const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1261
+ this.setPersistedProperty(PostHogPersistedProperty.Queue, refreshedQueue.slice(items.length));
1262
+ };
1263
+ const data = {
1264
+ api_key: this.apiKey,
1265
+ batch: messages,
1266
+ sent_at: currentISOTime(),
1267
+ };
1268
+ // Don't set the user agent if we're not on a browser. The latest spec allows
1269
+ // the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
1270
+ // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
1271
+ // but browsers such as Chrome and Safari have not caught up.
1272
+ this.getCustomUserAgent();
1273
+ const payload = JSON.stringify(data);
1274
+ const url = this.captureMode === 'form'
1275
+ ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1276
+ : `${this.host}/batch/`;
1277
+ const fetchOptions = this.captureMode === 'form'
1278
+ ? {
1279
+ method: 'POST',
1280
+ mode: 'no-cors',
1281
+ credentials: 'omit',
1282
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1283
+ body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1240
1284
  }
1241
- const items = queue.splice(0, this.flushAt);
1242
- this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
1243
- const messages = items.map((item) => item.message);
1244
- const data = {
1245
- api_key: this.apiKey,
1246
- batch: messages,
1247
- sent_at: currentISOTime(),
1285
+ : {
1286
+ method: 'POST',
1287
+ headers: { 'Content-Type': 'application/json' },
1288
+ body: payload,
1248
1289
  };
1249
- const done = (err) => {
1250
- if (err) {
1251
- this._events.emit('error', err);
1252
- }
1253
- callback?.(err, messages);
1254
- this._events.emit('flush', messages);
1255
- };
1256
- // Don't set the user agent if we're not on a browser. The latest spec allows
1257
- // the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
1258
- // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
1259
- // but browsers such as Chrome and Safari have not caught up.
1260
- this.getCustomUserAgent();
1261
- const payload = JSON.stringify(data);
1262
- const url = this.captureMode === 'form'
1263
- ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1264
- : `${this.host}/batch/`;
1265
- const fetchOptions = this.captureMode === 'form'
1266
- ? {
1267
- method: 'POST',
1268
- mode: 'no-cors',
1269
- credentials: 'omit',
1270
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1271
- body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1272
- }
1273
- : {
1274
- method: 'POST',
1275
- headers: { 'Content-Type': 'application/json' },
1276
- body: payload,
1277
- };
1278
- const requestPromise = this.fetchWithRetry(url, fetchOptions);
1279
- this.addPendingPromise(requestPromise
1280
- .then(() => done())
1281
- .catch((err) => {
1282
- done(err);
1283
- }));
1284
- });
1290
+ try {
1291
+ await this.fetchWithRetry(url, fetchOptions);
1292
+ }
1293
+ catch (err) {
1294
+ // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
1295
+ // and this will be an endless loop, in this case, if the error isn't a network issue, we always remove the items from the queue
1296
+ if (!(err instanceof PostHogFetchNetworkError)) {
1297
+ persistQueueChange();
1298
+ }
1299
+ this._events.emit('error', err);
1300
+ throw err;
1301
+ }
1302
+ persistQueueChange();
1303
+ this._events.emit('flush', messages);
1304
+ return messages;
1285
1305
  }
1286
1306
  async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1287
1307
  var _a;
@@ -1312,15 +1332,12 @@ class PostHogCoreStateless {
1312
1332
  return res;
1313
1333
  }, { ...this._retryOptions, ...retryOptions });
1314
1334
  }
1315
- async shutdownAsync(shutdownTimeoutMs) {
1335
+ async shutdown(shutdownTimeoutMs = 30000) {
1316
1336
  await this._initPromise;
1317
- clearTimeout(this._flushTimer);
1337
+ this.clearFlushTimer();
1318
1338
  try {
1319
- await Promise.all(Object.values(this.pendingPromises).map((x) => x.catch(() => {
1320
- // ignore errors as we are shutting down and can't deal with them anyways.
1321
- })));
1322
- const timeout = shutdownTimeoutMs ?? 30000;
1323
- const startTimeWithDelay = Date.now() + timeout;
1339
+ await Promise.all(Object.values(this.pendingPromises));
1340
+ const startTimeWithDelay = Date.now() + shutdownTimeoutMs;
1324
1341
  while (true) {
1325
1342
  const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1326
1343
  if (queue.length === 0) {
@@ -1329,7 +1346,7 @@ class PostHogCoreStateless {
1329
1346
  // flush again to make sure we send all events, some of which might've been added
1330
1347
  // while we were waiting for the pending promises to resolve
1331
1348
  // For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
1332
- await this.flushAsync();
1349
+ await this.flush();
1333
1350
  // If we've been waiting for more than the shutdownTimeoutMs, stop it
1334
1351
  const now = Date.now();
1335
1352
  if (startTimeWithDelay < now) {
@@ -1344,9 +1361,6 @@ class PostHogCoreStateless {
1344
1361
  console.error('Error while shutting down PostHog', e);
1345
1362
  }
1346
1363
  }
1347
- shutdown(shutdownTimeoutMs) {
1348
- void this.shutdownAsync(shutdownTimeoutMs);
1349
- }
1350
1364
  }
1351
1365
  class PostHogCore extends PostHogCoreStateless {
1352
1366
  constructor(apiKey, options) {
@@ -1812,7 +1826,7 @@ class PostHogCore extends PostHogCoreStateless {
1812
1826
  }
1813
1827
  }
1814
1828
 
1815
- var version = "3.0.0-beta.1";
1829
+ var version = "3.0.0-beta.2";
1816
1830
 
1817
1831
  function getContext(window) {
1818
1832
  let context = {};