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 +37 -6
- package/lib/index.cjs.js +100 -74
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +22 -5
- package/lib/index.esm.js +100 -74
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +16 -5
- package/lib/posthog-core/src/types.d.ts +6 -0
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ 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;
|
|
10
|
+
/** The maximum number of cached messages either in memory or on the local storage.
|
|
11
|
+
* Defaults to 1000, (must be higher than `flushAt`)
|
|
12
|
+
*/
|
|
13
|
+
maxQueueSize?: number;
|
|
8
14
|
/** If set to true the SDK is essentially disabled (useful for local environments where you don't want to track anything) */
|
|
9
15
|
disabled?: boolean;
|
|
10
16
|
/** If set to false the SDK will not track until the `optIn` function is called. */
|
|
@@ -127,7 +133,10 @@ declare abstract class PostHogCoreStateless {
|
|
|
127
133
|
private apiKey;
|
|
128
134
|
host: string;
|
|
129
135
|
private flushAt;
|
|
136
|
+
private maxBatchSize;
|
|
137
|
+
private maxQueueSize;
|
|
130
138
|
private flushInterval;
|
|
139
|
+
private flushPromise;
|
|
131
140
|
private requestTimeout;
|
|
132
141
|
private featureFlagsRequestTimeoutMs;
|
|
133
142
|
private captureMode;
|
|
@@ -157,7 +166,7 @@ declare abstract class PostHogCoreStateless {
|
|
|
157
166
|
debug(enabled?: boolean): void;
|
|
158
167
|
get isDebug(): boolean;
|
|
159
168
|
private buildPayload;
|
|
160
|
-
protected addPendingPromise(promise: Promise<
|
|
169
|
+
protected addPendingPromise<T>(promise: Promise<T>): Promise<T>;
|
|
161
170
|
/***
|
|
162
171
|
*** TRACKING
|
|
163
172
|
***/
|
|
@@ -189,11 +198,19 @@ declare abstract class PostHogCoreStateless {
|
|
|
189
198
|
*** QUEUEING AND FLUSHING
|
|
190
199
|
***/
|
|
191
200
|
protected enqueue(type: string, _message: any, options?: PostHogCaptureOptions): void;
|
|
192
|
-
|
|
193
|
-
|
|
201
|
+
private clearFlushTimer;
|
|
202
|
+
/**
|
|
203
|
+
* Helper for flushing the queue in the background
|
|
204
|
+
* Avoids unnecessary promise errors
|
|
205
|
+
*/
|
|
206
|
+
private flushBackground;
|
|
207
|
+
flush(): Promise<any[]>;
|
|
208
|
+
protected getCustomHeaders(): {
|
|
209
|
+
[key: string]: string;
|
|
210
|
+
};
|
|
211
|
+
private _flush;
|
|
194
212
|
private fetchWithRetry;
|
|
195
|
-
|
|
196
|
-
shutdown(shutdownTimeoutMs?: number): void;
|
|
213
|
+
shutdown(shutdownTimeoutMs?: number): Promise<void>;
|
|
197
214
|
}
|
|
198
215
|
declare abstract class PostHogCore extends PostHogCoreStateless {
|
|
199
216
|
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,8 @@ 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);
|
|
958
|
+
this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
|
|
956
959
|
this.flushInterval = options?.flushInterval ?? 10000;
|
|
957
960
|
this.captureMode = options?.captureMode || 'form';
|
|
958
961
|
// If enable is explicitly set to false we override the optout
|
|
@@ -1031,9 +1034,12 @@ class PostHogCoreStateless {
|
|
|
1031
1034
|
addPendingPromise(promise) {
|
|
1032
1035
|
const promiseUUID = uuidv7();
|
|
1033
1036
|
this.pendingPromises[promiseUUID] = promise;
|
|
1034
|
-
promise
|
|
1037
|
+
promise
|
|
1038
|
+
.catch(() => { })
|
|
1039
|
+
.finally(() => {
|
|
1035
1040
|
delete this.pendingPromises[promiseUUID];
|
|
1036
1041
|
});
|
|
1042
|
+
return promise;
|
|
1037
1043
|
}
|
|
1038
1044
|
/***
|
|
1039
1045
|
*** TRACKING
|
|
@@ -1098,7 +1104,7 @@ class PostHogCoreStateless {
|
|
|
1098
1104
|
const url = `${this.host}/decide/?v=3`;
|
|
1099
1105
|
const fetchOptions = {
|
|
1100
1106
|
method: 'POST',
|
|
1101
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1107
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1102
1108
|
body: JSON.stringify({
|
|
1103
1109
|
token: this.apiKey,
|
|
1104
1110
|
distinct_id: distinctId,
|
|
@@ -1208,80 +1214,106 @@ class PostHogCoreStateless {
|
|
|
1208
1214
|
delete message.distinctId;
|
|
1209
1215
|
}
|
|
1210
1216
|
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1217
|
+
if (queue.length >= this.maxQueueSize) {
|
|
1218
|
+
queue.shift();
|
|
1219
|
+
console.info('Queue is full, the oldest event is dropped.');
|
|
1220
|
+
}
|
|
1211
1221
|
queue.push({ message });
|
|
1212
1222
|
this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
|
|
1213
1223
|
this._events.emit(type, message);
|
|
1214
1224
|
// Flush queued events if we meet the flushAt length
|
|
1215
1225
|
if (queue.length >= this.flushAt) {
|
|
1216
|
-
this.
|
|
1226
|
+
this.flushBackground();
|
|
1217
1227
|
}
|
|
1218
1228
|
if (this.flushInterval && !this._flushTimer) {
|
|
1219
|
-
this._flushTimer = safeSetTimeout(() => this.
|
|
1229
|
+
this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
|
|
1220
1230
|
}
|
|
1221
1231
|
});
|
|
1222
1232
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
this.
|
|
1227
|
-
|
|
1233
|
+
clearFlushTimer() {
|
|
1234
|
+
if (this._flushTimer) {
|
|
1235
|
+
clearTimeout(this._flushTimer);
|
|
1236
|
+
this._flushTimer = undefined;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Helper for flushing the queue in the background
|
|
1241
|
+
* Avoids unnecessary promise errors
|
|
1242
|
+
*/
|
|
1243
|
+
flushBackground() {
|
|
1244
|
+
void this.flush().catch(() => { });
|
|
1245
|
+
}
|
|
1246
|
+
async flush() {
|
|
1247
|
+
if (!this.flushPromise) {
|
|
1248
|
+
this.flushPromise = this._flush().finally(() => {
|
|
1249
|
+
this.flushPromise = null;
|
|
1228
1250
|
});
|
|
1229
|
-
|
|
1251
|
+
this.addPendingPromise(this.flushPromise);
|
|
1252
|
+
}
|
|
1253
|
+
return this.flushPromise;
|
|
1230
1254
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1255
|
+
getCustomHeaders() {
|
|
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
|
+
const customUserAgent = this.getCustomUserAgent();
|
|
1261
|
+
const headers = {};
|
|
1262
|
+
if (customUserAgent && customUserAgent !== '') {
|
|
1263
|
+
headers['User-Agent'] = customUserAgent;
|
|
1264
|
+
}
|
|
1265
|
+
return headers;
|
|
1266
|
+
}
|
|
1267
|
+
async _flush() {
|
|
1268
|
+
this.clearFlushTimer();
|
|
1269
|
+
await this._initPromise;
|
|
1270
|
+
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1271
|
+
if (!queue.length) {
|
|
1272
|
+
return [];
|
|
1273
|
+
}
|
|
1274
|
+
const items = queue.slice(0, this.maxBatchSize);
|
|
1275
|
+
const messages = items.map((item) => item.message);
|
|
1276
|
+
const persistQueueChange = () => {
|
|
1277
|
+
const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1278
|
+
this.setPersistedProperty(PostHogPersistedProperty.Queue, refreshedQueue.slice(items.length));
|
|
1279
|
+
};
|
|
1280
|
+
const data = {
|
|
1281
|
+
api_key: this.apiKey,
|
|
1282
|
+
batch: messages,
|
|
1283
|
+
sent_at: currentISOTime(),
|
|
1284
|
+
};
|
|
1285
|
+
const payload = JSON.stringify(data);
|
|
1286
|
+
const url = this.captureMode === 'form'
|
|
1287
|
+
? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
|
|
1288
|
+
: `${this.host}/batch/`;
|
|
1289
|
+
const fetchOptions = this.captureMode === 'form'
|
|
1290
|
+
? {
|
|
1291
|
+
method: 'POST',
|
|
1292
|
+
mode: 'no-cors',
|
|
1293
|
+
credentials: 'omit',
|
|
1294
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1295
|
+
body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
|
|
1240
1296
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
api_key: this.apiKey,
|
|
1246
|
-
batch: messages,
|
|
1247
|
-
sent_at: currentISOTime(),
|
|
1248
|
-
};
|
|
1249
|
-
const done = (err) => {
|
|
1250
|
-
if (err) {
|
|
1251
|
-
this._events.emit('error', err);
|
|
1252
|
-
}
|
|
1253
|
-
callback?.(err, messages);
|
|
1254
|
-
this._events.emit('flush', messages);
|
|
1297
|
+
: {
|
|
1298
|
+
method: 'POST',
|
|
1299
|
+
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1300
|
+
body: payload,
|
|
1255
1301
|
};
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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
|
-
});
|
|
1302
|
+
try {
|
|
1303
|
+
await this.fetchWithRetry(url, fetchOptions);
|
|
1304
|
+
}
|
|
1305
|
+
catch (err) {
|
|
1306
|
+
// depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
|
|
1307
|
+
// 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
|
|
1308
|
+
if (!(err instanceof PostHogFetchNetworkError)) {
|
|
1309
|
+
persistQueueChange();
|
|
1310
|
+
}
|
|
1311
|
+
this._events.emit('error', err);
|
|
1312
|
+
throw err;
|
|
1313
|
+
}
|
|
1314
|
+
persistQueueChange();
|
|
1315
|
+
this._events.emit('flush', messages);
|
|
1316
|
+
return messages;
|
|
1285
1317
|
}
|
|
1286
1318
|
async fetchWithRetry(url, options, retryOptions, requestTimeout) {
|
|
1287
1319
|
var _a;
|
|
@@ -1312,15 +1344,12 @@ class PostHogCoreStateless {
|
|
|
1312
1344
|
return res;
|
|
1313
1345
|
}, { ...this._retryOptions, ...retryOptions });
|
|
1314
1346
|
}
|
|
1315
|
-
async
|
|
1347
|
+
async shutdown(shutdownTimeoutMs = 30000) {
|
|
1316
1348
|
await this._initPromise;
|
|
1317
|
-
|
|
1349
|
+
this.clearFlushTimer();
|
|
1318
1350
|
try {
|
|
1319
|
-
await Promise.all(Object.values(this.pendingPromises)
|
|
1320
|
-
|
|
1321
|
-
})));
|
|
1322
|
-
const timeout = shutdownTimeoutMs ?? 30000;
|
|
1323
|
-
const startTimeWithDelay = Date.now() + timeout;
|
|
1351
|
+
await Promise.all(Object.values(this.pendingPromises));
|
|
1352
|
+
const startTimeWithDelay = Date.now() + shutdownTimeoutMs;
|
|
1324
1353
|
while (true) {
|
|
1325
1354
|
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
1326
1355
|
if (queue.length === 0) {
|
|
@@ -1329,7 +1358,7 @@ class PostHogCoreStateless {
|
|
|
1329
1358
|
// flush again to make sure we send all events, some of which might've been added
|
|
1330
1359
|
// while we were waiting for the pending promises to resolve
|
|
1331
1360
|
// For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
|
|
1332
|
-
await this.
|
|
1361
|
+
await this.flush();
|
|
1333
1362
|
// If we've been waiting for more than the shutdownTimeoutMs, stop it
|
|
1334
1363
|
const now = Date.now();
|
|
1335
1364
|
if (startTimeWithDelay < now) {
|
|
@@ -1344,9 +1373,6 @@ class PostHogCoreStateless {
|
|
|
1344
1373
|
console.error('Error while shutting down PostHog', e);
|
|
1345
1374
|
}
|
|
1346
1375
|
}
|
|
1347
|
-
shutdown(shutdownTimeoutMs) {
|
|
1348
|
-
void this.shutdownAsync(shutdownTimeoutMs);
|
|
1349
|
-
}
|
|
1350
1376
|
}
|
|
1351
1377
|
class PostHogCore extends PostHogCoreStateless {
|
|
1352
1378
|
constructor(apiKey, options) {
|
|
@@ -1812,7 +1838,7 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
1812
1838
|
}
|
|
1813
1839
|
}
|
|
1814
1840
|
|
|
1815
|
-
var version = "3.0.0
|
|
1841
|
+
var version = "3.0.0";
|
|
1816
1842
|
|
|
1817
1843
|
function getContext(window) {
|
|
1818
1844
|
let context = {};
|