posthog-js-lite 3.0.0-beta.1 → 3.0.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/CHANGELOG.md CHANGED
@@ -1,11 +1,42 @@
1
+ # 3.0.0 - 2024-03-18
2
+
3
+ ## Added
4
+
5
+ 1. Adds a `disabled` option and the ability to change it later via `posthog.disabled = true`. Useful for disabling PostHog tracking for example in a testing environment without having complex conditional checking
6
+ 2. `shutdown` takes a `shutdownTimeoutMs` param with a default of 30000 (30s). This is the time to wait for flushing events before shutting down the client. If the timeout is reached, the client will be shut down regardless of pending events.
7
+ 3. Adds a new `featureFlagsRequestTimeoutMs` timeout parameter for feature flags which defaults to 10 seconds.
8
+ 4. Flushes will now try to flush up to `maxBatchSize` (default 100) in one go
9
+ 5. Queued events are limited up to `maxQueueSize` (default 1000) and the oldest events are dropped when the limit is reached
10
+
11
+ ## Removed
12
+
13
+ 1. Removes the `enable` option. You can now specify `defaultOptIn: false` to start the SDK opted out of tracking
14
+ 2. `flushAsync` and `shutdownAsync` are removed with `flush` and `shutdown` now being the async methods.
15
+
16
+ ## Changed
17
+
18
+ 1. `flush` and `shutdown` now being async methods.
19
+ 2. Many methods such as `capture` and `identify` no longer return the `this` object instead returning nothing
20
+
21
+ ## Fixed
22
+
23
+ 1. Fixed an issue where `shutdown` would potentially exit early if a flush was already in progress
24
+ 2. Fixes some typos in types
25
+
26
+ # 3.0.0-beta.2 - 2024-03-12
27
+
28
+ 1. `flushAsync` and `shutdownAsync` are removed with `flush` and `shutdown` now being the async methods.
29
+ 2. Fixed an issue where `shutdownAsync` would potentially exit early if a flush was already in progress
30
+ 3. Flushes will now try to flush up to `maxBatchSize` (default 100) in one go
31
+
1
32
  # 3.0.0-beta.1 - 2024-03-04
2
33
 
3
- - Removes the `enable` option. You can now specify `defaultOptIn: false` to start the SDK opted out of tracking
4
- - Adds a `disabled` option and the ability to change it later via `posthog.disabled = true`. Useful for disabling PostHog tracking for example in a testing environment without having complex conditional checking
5
- - Many methods such as `capture` and `identify` no longer return the `this` object instead returning nothing
6
- - Fixes some typos in types
7
- - `shutdown` and `shutdownAsync` takes a `shutdownTimeoutMs` param with a default of 30000 (30s). This is the time to wait for flushing events before shutting down the client. If the timeout is reached, the client will be shut down regardless of pending events.
8
- - Adds a new `featureFlagsRequestTimeoutMs` timeout parameter for feature flags which defaults to 10 seconds.
34
+ 1. Removes the `enable` option. You can now specify `defaultOptIn: false` to start the SDK opted out of tracking
35
+ 2. Adds a `disabled` option and the ability to change it later via `posthog.disabled = true`. Useful for disabling PostHog tracking for example in a testing environment without having complex conditional checking
36
+ 3. Many methods such as `capture` and `identify` no longer return the `this` object instead returning nothing
37
+ 4. Fixes some typos in types
38
+ 5. `shutdown` and `shutdownAsync` takes a `shutdownTimeoutMs` param with a default of 30000 (30s). This is the time to wait for flushing events before shutting down the client. If the timeout is reached, the client will be shut down regardless of pending events.
39
+ 6. Adds a new `featureFlagsRequestTimeoutMs` timeout parameter for feature flags which defaults to 10 seconds.
9
40
 
10
41
  # 2.6.2 - 2024-02-15
11
42
 
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,8 @@ 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);
962
+ this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
960
963
  this.flushInterval = options?.flushInterval ?? 10000;
961
964
  this.captureMode = options?.captureMode || 'form';
962
965
  // If enable is explicitly set to false we override the optout
@@ -1035,9 +1038,12 @@ class PostHogCoreStateless {
1035
1038
  addPendingPromise(promise) {
1036
1039
  const promiseUUID = uuidv7();
1037
1040
  this.pendingPromises[promiseUUID] = promise;
1038
- promise.finally(() => {
1041
+ promise
1042
+ .catch(() => { })
1043
+ .finally(() => {
1039
1044
  delete this.pendingPromises[promiseUUID];
1040
1045
  });
1046
+ return promise;
1041
1047
  }
1042
1048
  /***
1043
1049
  *** TRACKING
@@ -1102,7 +1108,7 @@ class PostHogCoreStateless {
1102
1108
  const url = `${this.host}/decide/?v=3`;
1103
1109
  const fetchOptions = {
1104
1110
  method: 'POST',
1105
- headers: { 'Content-Type': 'application/json' },
1111
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1106
1112
  body: JSON.stringify({
1107
1113
  token: this.apiKey,
1108
1114
  distinct_id: distinctId,
@@ -1212,80 +1218,106 @@ class PostHogCoreStateless {
1212
1218
  delete message.distinctId;
1213
1219
  }
1214
1220
  const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1221
+ if (queue.length >= this.maxQueueSize) {
1222
+ queue.shift();
1223
+ console.info('Queue is full, the oldest event is dropped.');
1224
+ }
1215
1225
  queue.push({ message });
1216
1226
  this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
1217
1227
  this._events.emit(type, message);
1218
1228
  // Flush queued events if we meet the flushAt length
1219
1229
  if (queue.length >= this.flushAt) {
1220
- this.flush();
1230
+ this.flushBackground();
1221
1231
  }
1222
1232
  if (this.flushInterval && !this._flushTimer) {
1223
- this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval);
1233
+ this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
1224
1234
  }
1225
1235
  });
1226
1236
  }
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);
1237
+ clearFlushTimer() {
1238
+ if (this._flushTimer) {
1239
+ clearTimeout(this._flushTimer);
1240
+ this._flushTimer = undefined;
1241
+ }
1242
+ }
1243
+ /**
1244
+ * Helper for flushing the queue in the background
1245
+ * Avoids unnecessary promise errors
1246
+ */
1247
+ flushBackground() {
1248
+ void this.flush().catch(() => { });
1249
+ }
1250
+ async flush() {
1251
+ if (!this.flushPromise) {
1252
+ this.flushPromise = this._flush().finally(() => {
1253
+ this.flushPromise = null;
1232
1254
  });
1233
- });
1255
+ this.addPendingPromise(this.flushPromise);
1256
+ }
1257
+ return this.flushPromise;
1234
1258
  }
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?.();
1259
+ getCustomHeaders() {
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
+ const customUserAgent = this.getCustomUserAgent();
1265
+ const headers = {};
1266
+ if (customUserAgent && customUserAgent !== '') {
1267
+ headers['User-Agent'] = customUserAgent;
1268
+ }
1269
+ return headers;
1270
+ }
1271
+ async _flush() {
1272
+ this.clearFlushTimer();
1273
+ await this._initPromise;
1274
+ const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1275
+ if (!queue.length) {
1276
+ return [];
1277
+ }
1278
+ const items = queue.slice(0, this.maxBatchSize);
1279
+ const messages = items.map((item) => item.message);
1280
+ const persistQueueChange = () => {
1281
+ const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1282
+ this.setPersistedProperty(PostHogPersistedProperty.Queue, refreshedQueue.slice(items.length));
1283
+ };
1284
+ const data = {
1285
+ api_key: this.apiKey,
1286
+ batch: messages,
1287
+ sent_at: currentISOTime(),
1288
+ };
1289
+ const payload = JSON.stringify(data);
1290
+ const url = this.captureMode === 'form'
1291
+ ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1292
+ : `${this.host}/batch/`;
1293
+ const fetchOptions = this.captureMode === 'form'
1294
+ ? {
1295
+ method: 'POST',
1296
+ mode: 'no-cors',
1297
+ credentials: 'omit',
1298
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1299
+ body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1244
1300
  }
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(),
1252
- };
1253
- const done = (err) => {
1254
- if (err) {
1255
- this._events.emit('error', err);
1256
- }
1257
- callback?.(err, messages);
1258
- this._events.emit('flush', messages);
1301
+ : {
1302
+ method: 'POST',
1303
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1304
+ body: payload,
1259
1305
  };
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
- });
1306
+ try {
1307
+ await this.fetchWithRetry(url, fetchOptions);
1308
+ }
1309
+ catch (err) {
1310
+ // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
1311
+ // 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
1312
+ if (!(err instanceof PostHogFetchNetworkError)) {
1313
+ persistQueueChange();
1314
+ }
1315
+ this._events.emit('error', err);
1316
+ throw err;
1317
+ }
1318
+ persistQueueChange();
1319
+ this._events.emit('flush', messages);
1320
+ return messages;
1289
1321
  }
1290
1322
  async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1291
1323
  var _a;
@@ -1316,15 +1348,12 @@ class PostHogCoreStateless {
1316
1348
  return res;
1317
1349
  }, { ...this._retryOptions, ...retryOptions });
1318
1350
  }
1319
- async shutdownAsync(shutdownTimeoutMs) {
1351
+ async shutdown(shutdownTimeoutMs = 30000) {
1320
1352
  await this._initPromise;
1321
- clearTimeout(this._flushTimer);
1353
+ this.clearFlushTimer();
1322
1354
  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;
1355
+ await Promise.all(Object.values(this.pendingPromises));
1356
+ const startTimeWithDelay = Date.now() + shutdownTimeoutMs;
1328
1357
  while (true) {
1329
1358
  const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1330
1359
  if (queue.length === 0) {
@@ -1333,7 +1362,7 @@ class PostHogCoreStateless {
1333
1362
  // flush again to make sure we send all events, some of which might've been added
1334
1363
  // while we were waiting for the pending promises to resolve
1335
1364
  // For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
1336
- await this.flushAsync();
1365
+ await this.flush();
1337
1366
  // If we've been waiting for more than the shutdownTimeoutMs, stop it
1338
1367
  const now = Date.now();
1339
1368
  if (startTimeWithDelay < now) {
@@ -1348,9 +1377,6 @@ class PostHogCoreStateless {
1348
1377
  console.error('Error while shutting down PostHog', e);
1349
1378
  }
1350
1379
  }
1351
- shutdown(shutdownTimeoutMs) {
1352
- void this.shutdownAsync(shutdownTimeoutMs);
1353
- }
1354
1380
  }
1355
1381
  class PostHogCore extends PostHogCoreStateless {
1356
1382
  constructor(apiKey, options) {
@@ -1816,7 +1842,7 @@ class PostHogCore extends PostHogCoreStateless {
1816
1842
  }
1817
1843
  }
1818
1844
 
1819
- var version = "3.0.0-beta.1";
1845
+ var version = "3.0.0";
1820
1846
 
1821
1847
  function getContext(window) {
1822
1848
  let context = {};