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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 3.0.0-beta.2 - 2024-03-12
2
+
3
+ - `flushAsync` and `shutdownAsync` are removed with `flush` and `shutdown` now being the async methods.
4
+ - Fixed an issue where `shutdownAsync` would potentially exit early if a flush was already in progress
5
+ - Flushes will now try to flush up to `maxBatchSize` (default 100) in one go
6
+
1
7
  # 3.0.0-beta.1 - 2024-03-04
2
8
 
3
9
  - Removes the `enable` option. You can now specify `defaultOptIn: false` to start the SDK opted out of tracking
package/lib/index.cjs.js CHANGED
@@ -946,6 +946,7 @@ function isPostHogFetchError(err) {
946
946
  }
947
947
  class PostHogCoreStateless {
948
948
  constructor(apiKey, options) {
949
+ this.flushPromise = null;
949
950
  this.disableGeoip = true;
950
951
  this.disabled = false;
951
952
  this.defaultOptIn = true;
@@ -957,6 +958,7 @@ class PostHogCoreStateless {
957
958
  this.apiKey = apiKey;
958
959
  this.host = removeTrailingSlash(options?.host || 'https://app.posthog.com');
959
960
  this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 20;
961
+ this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
960
962
  this.flushInterval = options?.flushInterval ?? 10000;
961
963
  this.captureMode = options?.captureMode || 'form';
962
964
  // If enable is explicitly set to false we override the optout
@@ -1035,9 +1037,12 @@ class PostHogCoreStateless {
1035
1037
  addPendingPromise(promise) {
1036
1038
  const promiseUUID = uuidv7();
1037
1039
  this.pendingPromises[promiseUUID] = promise;
1038
- promise.finally(() => {
1040
+ promise
1041
+ .catch(() => { })
1042
+ .finally(() => {
1039
1043
  delete this.pendingPromises[promiseUUID];
1040
1044
  });
1045
+ return promise;
1041
1046
  }
1042
1047
  /***
1043
1048
  *** TRACKING
@@ -1217,75 +1222,90 @@ class PostHogCoreStateless {
1217
1222
  this._events.emit(type, message);
1218
1223
  // Flush queued events if we meet the flushAt length
1219
1224
  if (queue.length >= this.flushAt) {
1220
- this.flush();
1225
+ this.flushBackground();
1221
1226
  }
1222
1227
  if (this.flushInterval && !this._flushTimer) {
1223
- this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval);
1228
+ this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
1224
1229
  }
1225
1230
  });
1226
1231
  }
1227
- async flushAsync() {
1228
- await this._initPromise;
1229
- return new Promise((resolve, reject) => {
1230
- this.flush((err, data) => {
1231
- return err ? reject(err) : resolve(data);
1232
+ clearFlushTimer() {
1233
+ if (this._flushTimer) {
1234
+ clearTimeout(this._flushTimer);
1235
+ this._flushTimer = undefined;
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Helper for flushing the queue in the background
1240
+ * Avoids unnecessary promise errors
1241
+ */
1242
+ flushBackground() {
1243
+ void this.flush().catch(() => { });
1244
+ }
1245
+ async flush() {
1246
+ if (!this.flushPromise) {
1247
+ this.flushPromise = this._flush().finally(() => {
1248
+ this.flushPromise = null;
1232
1249
  });
1233
- });
1250
+ this.addPendingPromise(this.flushPromise);
1251
+ }
1252
+ return this.flushPromise;
1234
1253
  }
1235
- flush(callback) {
1236
- this.wrap(() => {
1237
- if (this._flushTimer) {
1238
- clearTimeout(this._flushTimer);
1239
- this._flushTimer = null;
1240
- }
1241
- const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1242
- if (!queue.length) {
1243
- return callback?.();
1254
+ async _flush() {
1255
+ this.clearFlushTimer();
1256
+ await this._initPromise;
1257
+ const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1258
+ if (!queue.length) {
1259
+ return [];
1260
+ }
1261
+ const items = queue.slice(0, this.maxBatchSize);
1262
+ const messages = items.map((item) => item.message);
1263
+ const persistQueueChange = () => {
1264
+ const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1265
+ this.setPersistedProperty(PostHogPersistedProperty.Queue, refreshedQueue.slice(items.length));
1266
+ };
1267
+ const data = {
1268
+ api_key: this.apiKey,
1269
+ batch: messages,
1270
+ sent_at: currentISOTime(),
1271
+ };
1272
+ // Don't set the user agent if we're not on a browser. The latest spec allows
1273
+ // the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
1274
+ // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
1275
+ // but browsers such as Chrome and Safari have not caught up.
1276
+ this.getCustomUserAgent();
1277
+ const payload = JSON.stringify(data);
1278
+ const url = this.captureMode === 'form'
1279
+ ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1280
+ : `${this.host}/batch/`;
1281
+ const fetchOptions = this.captureMode === 'form'
1282
+ ? {
1283
+ method: 'POST',
1284
+ mode: 'no-cors',
1285
+ credentials: 'omit',
1286
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1287
+ body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1244
1288
  }
1245
- const items = queue.splice(0, this.flushAt);
1246
- this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
1247
- const messages = items.map((item) => item.message);
1248
- const data = {
1249
- api_key: this.apiKey,
1250
- batch: messages,
1251
- sent_at: currentISOTime(),
1289
+ : {
1290
+ method: 'POST',
1291
+ headers: { 'Content-Type': 'application/json' },
1292
+ body: payload,
1252
1293
  };
1253
- const done = (err) => {
1254
- if (err) {
1255
- this._events.emit('error', err);
1256
- }
1257
- callback?.(err, messages);
1258
- this._events.emit('flush', messages);
1259
- };
1260
- // Don't set the user agent if we're not on a browser. The latest spec allows
1261
- // the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
1262
- // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
1263
- // but browsers such as Chrome and Safari have not caught up.
1264
- this.getCustomUserAgent();
1265
- const payload = JSON.stringify(data);
1266
- const url = this.captureMode === 'form'
1267
- ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1268
- : `${this.host}/batch/`;
1269
- const fetchOptions = this.captureMode === 'form'
1270
- ? {
1271
- method: 'POST',
1272
- mode: 'no-cors',
1273
- credentials: 'omit',
1274
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1275
- body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1276
- }
1277
- : {
1278
- method: 'POST',
1279
- headers: { 'Content-Type': 'application/json' },
1280
- body: payload,
1281
- };
1282
- const requestPromise = this.fetchWithRetry(url, fetchOptions);
1283
- this.addPendingPromise(requestPromise
1284
- .then(() => done())
1285
- .catch((err) => {
1286
- done(err);
1287
- }));
1288
- });
1294
+ try {
1295
+ await this.fetchWithRetry(url, fetchOptions);
1296
+ }
1297
+ catch (err) {
1298
+ // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
1299
+ // 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
1300
+ if (!(err instanceof PostHogFetchNetworkError)) {
1301
+ persistQueueChange();
1302
+ }
1303
+ this._events.emit('error', err);
1304
+ throw err;
1305
+ }
1306
+ persistQueueChange();
1307
+ this._events.emit('flush', messages);
1308
+ return messages;
1289
1309
  }
1290
1310
  async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1291
1311
  var _a;
@@ -1316,15 +1336,12 @@ class PostHogCoreStateless {
1316
1336
  return res;
1317
1337
  }, { ...this._retryOptions, ...retryOptions });
1318
1338
  }
1319
- async shutdownAsync(shutdownTimeoutMs) {
1339
+ async shutdown(shutdownTimeoutMs = 30000) {
1320
1340
  await this._initPromise;
1321
- clearTimeout(this._flushTimer);
1341
+ this.clearFlushTimer();
1322
1342
  try {
1323
- await Promise.all(Object.values(this.pendingPromises).map((x) => x.catch(() => {
1324
- // ignore errors as we are shutting down and can't deal with them anyways.
1325
- })));
1326
- const timeout = shutdownTimeoutMs ?? 30000;
1327
- const startTimeWithDelay = Date.now() + timeout;
1343
+ await Promise.all(Object.values(this.pendingPromises));
1344
+ const startTimeWithDelay = Date.now() + shutdownTimeoutMs;
1328
1345
  while (true) {
1329
1346
  const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1330
1347
  if (queue.length === 0) {
@@ -1333,7 +1350,7 @@ class PostHogCoreStateless {
1333
1350
  // flush again to make sure we send all events, some of which might've been added
1334
1351
  // while we were waiting for the pending promises to resolve
1335
1352
  // For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
1336
- await this.flushAsync();
1353
+ await this.flush();
1337
1354
  // If we've been waiting for more than the shutdownTimeoutMs, stop it
1338
1355
  const now = Date.now();
1339
1356
  if (startTimeWithDelay < now) {
@@ -1348,9 +1365,6 @@ class PostHogCoreStateless {
1348
1365
  console.error('Error while shutting down PostHog', e);
1349
1366
  }
1350
1367
  }
1351
- shutdown(shutdownTimeoutMs) {
1352
- void this.shutdownAsync(shutdownTimeoutMs);
1353
- }
1354
1368
  }
1355
1369
  class PostHogCore extends PostHogCoreStateless {
1356
1370
  constructor(apiKey, options) {
@@ -1816,7 +1830,7 @@ class PostHogCore extends PostHogCoreStateless {
1816
1830
  }
1817
1831
  }
1818
1832
 
1819
- var version = "3.0.0-beta.1";
1833
+ var version = "3.0.0-beta.2";
1820
1834
 
1821
1835
  function getContext(window) {
1822
1836
  let context = {};