posthog-node 5.5.1 → 5.7.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.
@@ -1,47 +1,29 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
3
+ var core = require('@posthog/core');
4
4
 
5
- function _interopNamespace(e) {
6
- if (e && e.__esModule) return e;
7
- var n = Object.create(null);
8
- if (e) {
9
- Object.keys(e).forEach(function (k) {
10
- if (k !== 'default') {
11
- var d = Object.getOwnPropertyDescriptor(e, k);
12
- Object.defineProperty(n, k, d.get ? d : {
13
- enumerable: true,
14
- get: function () { return e[k]; }
15
- });
16
- }
17
- });
18
- }
19
- n["default"] = e;
20
- return Object.freeze(n);
21
- }
22
-
23
- /**
24
- * @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
5
+ /**
6
+ * @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
25
7
  */
26
- /**
27
- * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
28
- *
29
- * ### Usage
30
- *
31
- * Sentry.init({
32
- * dsn: 'https://example',
33
- * integrations: [
34
- * new PostHogSentryIntegration(posthog)
35
- * ]
36
- * })
37
- *
38
- * Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
39
- *
40
- * @param {Object} [posthog] The posthog object
41
- * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
42
- * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
43
- * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
44
- * @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
8
+ /**
9
+ * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
10
+ *
11
+ * ### Usage
12
+ *
13
+ * Sentry.init({
14
+ * dsn: 'https://example',
15
+ * integrations: [
16
+ * new PostHogSentryIntegration(posthog)
17
+ * ]
18
+ * })
19
+ *
20
+ * Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
21
+ *
22
+ * @param {Object} [posthog] The posthog object
23
+ * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
24
+ * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
25
+ * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
26
+ * @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
45
27
  */
46
28
  const NAME = 'posthog-node';
47
29
  function createEventProcessor(_posthog, {
@@ -135,411 +117,175 @@ class PostHogSentryIntegration {
135
117
  }
136
118
  PostHogSentryIntegration.POSTHOG_ID_TAG = 'posthog_distinct_id';
137
119
 
138
- // vendor from: https://github.com/LiosK/uuidv7/blob/f30b7a7faff73afbce0b27a46c638310f96912ba/src/index.ts
139
- // https://github.com/LiosK/uuidv7#license
140
- /**
141
- * uuidv7: An experimental implementation of the proposed UUID Version 7
142
- *
143
- * @license Apache-2.0
144
- * @copyright 2021-2023 LiosK
145
- * @packageDocumentation
146
- */
147
- const DIGITS = "0123456789abcdef";
148
- /** Represents a UUID as a 16-byte byte array. */
149
- class UUID {
150
- /** @param bytes - The 16-byte byte array representation. */
151
- constructor(bytes) {
152
- this.bytes = bytes;
153
- }
154
- /**
155
- * Creates an object from the internal representation, a 16-byte byte array
156
- * containing the binary UUID representation in the big-endian byte order.
157
- *
158
- * This method does NOT shallow-copy the argument, and thus the created object
159
- * holds the reference to the underlying buffer.
160
- *
161
- * @throws TypeError if the length of the argument is not 16.
162
- */
163
- static ofInner(bytes) {
164
- if (bytes.length !== 16) {
165
- throw new TypeError("not 128-bit length");
166
- }
167
- else {
168
- return new UUID(bytes);
169
- }
170
- }
171
- /**
172
- * Builds a byte array from UUIDv7 field values.
173
- *
174
- * @param unixTsMs - A 48-bit `unix_ts_ms` field value.
175
- * @param randA - A 12-bit `rand_a` field value.
176
- * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value.
177
- * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value.
178
- * @throws RangeError if any field value is out of the specified range.
179
- */
180
- static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
181
- if (!Number.isInteger(unixTsMs) ||
182
- !Number.isInteger(randA) ||
183
- !Number.isInteger(randBHi) ||
184
- !Number.isInteger(randBLo) ||
185
- unixTsMs < 0 ||
186
- randA < 0 ||
187
- randBHi < 0 ||
188
- randBLo < 0 ||
189
- unixTsMs > 281474976710655 ||
190
- randA > 0xfff ||
191
- randBHi > 1073741823 ||
192
- randBLo > 4294967295) {
193
- throw new RangeError("invalid field value");
194
- }
195
- const bytes = new Uint8Array(16);
196
- bytes[0] = unixTsMs / 2 ** 40;
197
- bytes[1] = unixTsMs / 2 ** 32;
198
- bytes[2] = unixTsMs / 2 ** 24;
199
- bytes[3] = unixTsMs / 2 ** 16;
200
- bytes[4] = unixTsMs / 2 ** 8;
201
- bytes[5] = unixTsMs;
202
- bytes[6] = 0x70 | (randA >>> 8);
203
- bytes[7] = randA;
204
- bytes[8] = 0x80 | (randBHi >>> 24);
205
- bytes[9] = randBHi >>> 16;
206
- bytes[10] = randBHi >>> 8;
207
- bytes[11] = randBHi;
208
- bytes[12] = randBLo >>> 24;
209
- bytes[13] = randBLo >>> 16;
210
- bytes[14] = randBLo >>> 8;
211
- bytes[15] = randBLo;
212
- return new UUID(bytes);
213
- }
214
- /**
215
- * Builds a byte array from a string representation.
216
- *
217
- * This method accepts the following formats:
218
- *
219
- * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b`
220
- * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b`
221
- * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}`
222
- * - RFC 4122 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b`
223
- *
224
- * Leading and trailing whitespaces represents an error.
225
- *
226
- * @throws SyntaxError if the argument could not parse as a valid UUID string.
227
- */
228
- static parse(uuid) {
229
- let hex = undefined;
230
- switch (uuid.length) {
231
- case 32:
232
- hex = /^[0-9a-f]{32}$/i.exec(uuid)?.[0];
233
- break;
234
- case 36:
235
- hex =
236
- /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
237
- .exec(uuid)
238
- ?.slice(1, 6)
239
- .join("");
240
- break;
241
- case 38:
242
- hex =
243
- /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i
244
- .exec(uuid)
245
- ?.slice(1, 6)
246
- .join("");
247
- break;
248
- case 45:
249
- hex =
250
- /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
251
- .exec(uuid)
252
- ?.slice(1, 6)
253
- .join("");
254
- break;
255
- }
256
- if (hex) {
257
- const inner = new Uint8Array(16);
258
- for (let i = 0; i < 16; i += 4) {
259
- const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
260
- inner[i + 0] = n >>> 24;
261
- inner[i + 1] = n >>> 16;
262
- inner[i + 2] = n >>> 8;
263
- inner[i + 3] = n;
264
- }
265
- return new UUID(inner);
266
- }
267
- else {
268
- throw new SyntaxError("could not parse UUID string");
269
- }
270
- }
271
- /**
272
- * @returns The 8-4-4-4-12 canonical hexadecimal string representation
273
- * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`).
274
- */
275
- toString() {
276
- let text = "";
277
- for (let i = 0; i < this.bytes.length; i++) {
278
- text += DIGITS.charAt(this.bytes[i] >>> 4);
279
- text += DIGITS.charAt(this.bytes[i] & 0xf);
280
- if (i === 3 || i === 5 || i === 7 || i === 9) {
281
- text += "-";
282
- }
283
- }
284
- return text;
285
- }
286
- /**
287
- * @returns The 32-digit hexadecimal representation without hyphens
288
- * (`0189dcd553117d408db09496a2eef37b`).
289
- */
290
- toHex() {
291
- let text = "";
292
- for (let i = 0; i < this.bytes.length; i++) {
293
- text += DIGITS.charAt(this.bytes[i] >>> 4);
294
- text += DIGITS.charAt(this.bytes[i] & 0xf);
295
- }
296
- return text;
297
- }
298
- /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */
299
- toJSON() {
300
- return this.toString();
301
- }
302
- /**
303
- * Reports the variant field value of the UUID or, if appropriate, "NIL" or
304
- * "MAX".
305
- *
306
- * For convenience, this method reports "NIL" or "MAX" if `this` represents
307
- * the Nil or Max UUID, although the Nil and Max UUIDs are technically
308
- * subsumed under the variants `0b0` and `0b111`, respectively.
309
- */
310
- getVariant() {
311
- const n = this.bytes[8] >>> 4;
312
- if (n < 0) {
313
- throw new Error("unreachable");
314
- }
315
- else if (n <= 0b0111) {
316
- return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0";
317
- }
318
- else if (n <= 0b1011) {
319
- return "VAR_10";
320
- }
321
- else if (n <= 0b1101) {
322
- return "VAR_110";
323
- }
324
- else if (n <= 0b1111) {
325
- return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED";
326
- }
327
- else {
328
- throw new Error("unreachable");
329
- }
330
- }
331
- /**
332
- * Returns the version field value of the UUID or `undefined` if the UUID does
333
- * not have the variant field value of `0b10`.
334
- */
335
- getVersion() {
336
- return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined;
337
- }
338
- /** Creates an object from `this`. */
339
- clone() {
340
- return new UUID(this.bytes.slice(0));
341
- }
342
- /** Returns true if `this` is equivalent to `other`. */
343
- equals(other) {
344
- return this.compareTo(other) === 0;
345
- }
346
- /**
347
- * Returns a negative integer, zero, or positive integer if `this` is less
348
- * than, equal to, or greater than `other`, respectively.
349
- */
350
- compareTo(other) {
351
- for (let i = 0; i < 16; i++) {
352
- const diff = this.bytes[i] - other.bytes[i];
353
- if (diff !== 0) {
354
- return Math.sign(diff);
355
- }
356
- }
357
- return 0;
358
- }
359
- }
360
- /**
361
- * Encapsulates the monotonic counter state.
362
- *
363
- * This class provides APIs to utilize a separate counter state from that of the
364
- * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to
365
- * the default {@link generate} method, this class has {@link generateOrAbort}
366
- * that is useful to absolutely guarantee the monotonically increasing order of
367
- * generated UUIDs. See their respective documentation for details.
368
- */
369
- class V7Generator {
370
- /**
371
- * Creates a generator object with the default random number generator, or
372
- * with the specified one if passed as an argument. The specified random
373
- * number generator should be cryptographically strong and securely seeded.
374
- */
375
- constructor(randomNumberGenerator) {
376
- this.timestamp = 0;
377
- this.counter = 0;
378
- this.random = randomNumberGenerator ?? getDefaultRandom();
379
- }
380
- /**
381
- * Generates a new UUIDv7 object from the current timestamp, or resets the
382
- * generator upon significant timestamp rollback.
383
- *
384
- * This method returns a monotonically increasing UUID by reusing the previous
385
- * timestamp even if the up-to-date timestamp is smaller than the immediately
386
- * preceding UUID's. However, when such a clock rollback is considered
387
- * significant (i.e., by more than ten seconds), this method resets the
388
- * generator and returns a new UUID based on the given timestamp, breaking the
389
- * increasing order of UUIDs.
390
- *
391
- * See {@link generateOrAbort} for the other mode of generation and
392
- * {@link generateOrResetCore} for the low-level primitive.
393
- */
394
- generate() {
395
- return this.generateOrResetCore(Date.now(), 10000);
396
- }
397
- /**
398
- * Generates a new UUIDv7 object from the current timestamp, or returns
399
- * `undefined` upon significant timestamp rollback.
400
- *
401
- * This method returns a monotonically increasing UUID by reusing the previous
402
- * timestamp even if the up-to-date timestamp is smaller than the immediately
403
- * preceding UUID's. However, when such a clock rollback is considered
404
- * significant (i.e., by more than ten seconds), this method aborts and
405
- * returns `undefined` immediately.
406
- *
407
- * See {@link generate} for the other mode of generation and
408
- * {@link generateOrAbortCore} for the low-level primitive.
409
- */
410
- generateOrAbort() {
411
- return this.generateOrAbortCore(Date.now(), 10000);
412
- }
413
- /**
414
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the
415
- * generator upon significant timestamp rollback.
416
- *
417
- * This method is equivalent to {@link generate} except that it takes a custom
418
- * timestamp and clock rollback allowance.
419
- *
420
- * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
421
- * considered significant. A suggested value is `10_000` (milliseconds).
422
- * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
423
- */
424
- generateOrResetCore(unixTsMs, rollbackAllowance) {
425
- let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
426
- if (value === undefined) {
427
- // reset state and resume
428
- this.timestamp = 0;
429
- value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
430
- }
431
- return value;
432
- }
433
- /**
434
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns
435
- * `undefined` upon significant timestamp rollback.
436
- *
437
- * This method is equivalent to {@link generateOrAbort} except that it takes a
438
- * custom timestamp and clock rollback allowance.
439
- *
440
- * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
441
- * considered significant. A suggested value is `10_000` (milliseconds).
442
- * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
443
- */
444
- generateOrAbortCore(unixTsMs, rollbackAllowance) {
445
- const MAX_COUNTER = 4398046511103;
446
- if (!Number.isInteger(unixTsMs) ||
447
- unixTsMs < 1 ||
448
- unixTsMs > 281474976710655) {
449
- throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
450
- }
451
- else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) {
452
- throw new RangeError("`rollbackAllowance` out of reasonable range");
453
- }
454
- if (unixTsMs > this.timestamp) {
455
- this.timestamp = unixTsMs;
456
- this.resetCounter();
457
- }
458
- else if (unixTsMs + rollbackAllowance >= this.timestamp) {
459
- // go on with previous timestamp if new one is not much smaller
460
- this.counter++;
461
- if (this.counter > MAX_COUNTER) {
462
- // increment timestamp at counter overflow
463
- this.timestamp++;
464
- this.resetCounter();
465
- }
466
- }
467
- else {
468
- // abort if clock went backwards to unbearable extent
469
- return undefined;
470
- }
471
- return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32());
472
- }
473
- /** Initializes the counter at a 42-bit random integer. */
474
- resetCounter() {
475
- this.counter =
476
- this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff);
477
- }
478
- /**
479
- * Generates a new UUIDv4 object utilizing the random number generator inside.
480
- *
481
- * @internal
482
- */
483
- generateV4() {
484
- const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
485
- bytes[6] = 0x40 | (bytes[6] >>> 4);
486
- bytes[8] = 0x80 | (bytes[8] >>> 2);
487
- return UUID.ofInner(bytes);
488
- }
489
- }
490
- /** A global flag to force use of cryptographically strong RNG. */
491
- // declare const UUIDV7_DENY_WEAK_RNG: boolean;
492
- /** Returns the default random number generator available in the environment. */
493
- const getDefaultRandom = () => {
494
- // fix: crypto isn't available in react-native, always use Math.random
495
- // // detect Web Crypto API
496
- // if (
497
- // typeof crypto !== "undefined" &&
498
- // typeof crypto.getRandomValues !== "undefined"
499
- // ) {
500
- // return new BufferedCryptoRandom();
501
- // } else {
502
- // // fall back on Math.random() unless the flag is set to true
503
- // if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) {
504
- // throw new Error("no cryptographically strong RNG available");
505
- // }
506
- // return {
507
- // nextUint32: (): number =>
508
- // Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +
509
- // Math.trunc(Math.random() * 0x1_0000),
510
- // };
511
- // }
512
- return {
513
- nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 +
514
- Math.trunc(Math.random() * 65536),
515
- };
516
- };
517
- // /**
518
- // * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small
519
- // * buffer by default to avoid both unbearable throughput decline in some
520
- // * environments and the waste of time and space for unused values.
521
- // */
522
- // class BufferedCryptoRandom {
523
- // private readonly buffer = new Uint32Array(8);
524
- // private cursor = 0xffff;
525
- // nextUint32(): number {
526
- // if (this.cursor >= this.buffer.length) {
527
- // crypto.getRandomValues(this.buffer);
528
- // this.cursor = 0;
529
- // }
530
- // return this.buffer[this.cursor++];
531
- // }
532
- // }
533
- let defaultGenerator;
534
- /**
535
- * Generates a UUIDv7 string.
536
- *
537
- * @returns The 8-4-4-4-12 canonical hexadecimal string representation
538
- * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").
539
- */
540
- const uuidv7 = () => uuidv7obj().toString();
541
- /** Generates a UUIDv7 object. */
542
- const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
120
+ /*! For license information please see uuidv7.mjs.LICENSE.txt */
121
+ /**
122
+ * uuidv7: An experimental implementation of the proposed UUID Version 7
123
+ *
124
+ * @license Apache-2.0
125
+ * @copyright 2021-2023 LiosK
126
+ * @packageDocumentation
127
+ */ const DIGITS = "0123456789abcdef";
128
+ class UUID {
129
+ static ofInner(bytes) {
130
+ if (16 === bytes.length) return new UUID(bytes);
131
+ throw new TypeError("not 128-bit length");
132
+ }
133
+ static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
134
+ if (!Number.isInteger(unixTsMs) || !Number.isInteger(randA) || !Number.isInteger(randBHi) || !Number.isInteger(randBLo) || unixTsMs < 0 || randA < 0 || randBHi < 0 || randBLo < 0 || unixTsMs > 0xffffffffffff || randA > 0xfff || randBHi > 0x3fffffff || randBLo > 0xffffffff) throw new RangeError("invalid field value");
135
+ const bytes = new Uint8Array(16);
136
+ bytes[0] = unixTsMs / 2 ** 40;
137
+ bytes[1] = unixTsMs / 2 ** 32;
138
+ bytes[2] = unixTsMs / 2 ** 24;
139
+ bytes[3] = unixTsMs / 2 ** 16;
140
+ bytes[4] = unixTsMs / 256;
141
+ bytes[5] = unixTsMs;
142
+ bytes[6] = 0x70 | randA >>> 8;
143
+ bytes[7] = randA;
144
+ bytes[8] = 0x80 | randBHi >>> 24;
145
+ bytes[9] = randBHi >>> 16;
146
+ bytes[10] = randBHi >>> 8;
147
+ bytes[11] = randBHi;
148
+ bytes[12] = randBLo >>> 24;
149
+ bytes[13] = randBLo >>> 16;
150
+ bytes[14] = randBLo >>> 8;
151
+ bytes[15] = randBLo;
152
+ return new UUID(bytes);
153
+ }
154
+ static parse(uuid) {
155
+ let hex;
156
+ switch(uuid.length){
157
+ case 32:
158
+ var _exec;
159
+ hex = null == (_exec = /^[0-9a-f]{32}$/i.exec(uuid)) ? void 0 : _exec[0];
160
+ break;
161
+ case 36:
162
+ var _exec1;
163
+ hex = null == (_exec1 = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)) ? void 0 : _exec1.slice(1, 6).join("");
164
+ break;
165
+ case 38:
166
+ var _exec2;
167
+ hex = null == (_exec2 = /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i.exec(uuid)) ? void 0 : _exec2.slice(1, 6).join("");
168
+ break;
169
+ case 45:
170
+ var _exec3;
171
+ hex = null == (_exec3 = /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)) ? void 0 : _exec3.slice(1, 6).join("");
172
+ break;
173
+ }
174
+ if (hex) {
175
+ const inner = new Uint8Array(16);
176
+ for(let i = 0; i < 16; i += 4){
177
+ const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
178
+ inner[i + 0] = n >>> 24;
179
+ inner[i + 1] = n >>> 16;
180
+ inner[i + 2] = n >>> 8;
181
+ inner[i + 3] = n;
182
+ }
183
+ return new UUID(inner);
184
+ }
185
+ throw new SyntaxError("could not parse UUID string");
186
+ }
187
+ toString() {
188
+ let text = "";
189
+ for(let i = 0; i < this.bytes.length; i++){
190
+ text += DIGITS.charAt(this.bytes[i] >>> 4);
191
+ text += DIGITS.charAt(0xf & this.bytes[i]);
192
+ if (3 === i || 5 === i || 7 === i || 9 === i) text += "-";
193
+ }
194
+ return text;
195
+ }
196
+ toHex() {
197
+ let text = "";
198
+ for(let i = 0; i < this.bytes.length; i++){
199
+ text += DIGITS.charAt(this.bytes[i] >>> 4);
200
+ text += DIGITS.charAt(0xf & this.bytes[i]);
201
+ }
202
+ return text;
203
+ }
204
+ toJSON() {
205
+ return this.toString();
206
+ }
207
+ getVariant() {
208
+ const n = this.bytes[8] >>> 4;
209
+ if (n < 0) throw new Error("unreachable");
210
+ if (n <= 7) return this.bytes.every((e)=>0 === e) ? "NIL" : "VAR_0";
211
+ if (n <= 11) return "VAR_10";
212
+ if (n <= 13) return "VAR_110";
213
+ if (n <= 15) return this.bytes.every((e)=>0xff === e) ? "MAX" : "VAR_RESERVED";
214
+ else throw new Error("unreachable");
215
+ }
216
+ getVersion() {
217
+ return "VAR_10" === this.getVariant() ? this.bytes[6] >>> 4 : void 0;
218
+ }
219
+ clone() {
220
+ return new UUID(this.bytes.slice(0));
221
+ }
222
+ equals(other) {
223
+ return 0 === this.compareTo(other);
224
+ }
225
+ compareTo(other) {
226
+ for(let i = 0; i < 16; i++){
227
+ const diff = this.bytes[i] - other.bytes[i];
228
+ if (0 !== diff) return Math.sign(diff);
229
+ }
230
+ return 0;
231
+ }
232
+ constructor(bytes){
233
+ this.bytes = bytes;
234
+ }
235
+ }
236
+ class V7Generator {
237
+ generate() {
238
+ return this.generateOrResetCore(Date.now(), 10000);
239
+ }
240
+ generateOrAbort() {
241
+ return this.generateOrAbortCore(Date.now(), 10000);
242
+ }
243
+ generateOrResetCore(unixTsMs, rollbackAllowance) {
244
+ let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
245
+ if (void 0 === value) {
246
+ this.timestamp = 0;
247
+ value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
248
+ }
249
+ return value;
250
+ }
251
+ generateOrAbortCore(unixTsMs, rollbackAllowance) {
252
+ const MAX_COUNTER = 0x3ffffffffff;
253
+ if (!Number.isInteger(unixTsMs) || unixTsMs < 1 || unixTsMs > 0xffffffffffff) throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
254
+ if (rollbackAllowance < 0 || rollbackAllowance > 0xffffffffffff) throw new RangeError("`rollbackAllowance` out of reasonable range");
255
+ if (unixTsMs > this.timestamp) {
256
+ this.timestamp = unixTsMs;
257
+ this.resetCounter();
258
+ } else {
259
+ if (!(unixTsMs + rollbackAllowance >= this.timestamp)) return;
260
+ this.counter++;
261
+ if (this.counter > MAX_COUNTER) {
262
+ this.timestamp++;
263
+ this.resetCounter();
264
+ }
265
+ }
266
+ return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & 2 ** 30 - 1, this.random.nextUint32());
267
+ }
268
+ resetCounter() {
269
+ this.counter = 0x400 * this.random.nextUint32() + (0x3ff & this.random.nextUint32());
270
+ }
271
+ generateV4() {
272
+ const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
273
+ bytes[6] = 0x40 | bytes[6] >>> 4;
274
+ bytes[8] = 0x80 | bytes[8] >>> 2;
275
+ return UUID.ofInner(bytes);
276
+ }
277
+ constructor(randomNumberGenerator){
278
+ this.timestamp = 0;
279
+ this.counter = 0;
280
+ this.random = null != randomNumberGenerator ? randomNumberGenerator : getDefaultRandom();
281
+ }
282
+ }
283
+ const getDefaultRandom = ()=>({
284
+ nextUint32: ()=>0x10000 * Math.trunc(0x10000 * Math.random()) + Math.trunc(0x10000 * Math.random())
285
+ });
286
+ let defaultGenerator;
287
+ const uuidv7 = ()=>uuidv7obj().toString();
288
+ const uuidv7obj = ()=>(defaultGenerator || (defaultGenerator = new V7Generator())).generate();
543
289
 
544
290
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
545
291
  // Licensed under the MIT License
@@ -664,6 +410,7 @@ function isBuiltin(candidate, className) {
664
410
  }
665
411
 
666
412
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
413
+ // Licensed under the MIT License
667
414
  async function propertiesFromUnknownInput(stackParser, frameModifiers, input, hint) {
668
415
  const providedMechanism = hint && hint.mechanism;
669
416
  const mechanism = providedMechanism || {
@@ -752,10 +499,10 @@ function getObjectClassName(obj) {
752
499
  // ignore errors here
753
500
  }
754
501
  }
755
- /**
756
- * Given any captured exception, extract its keys and create a sorted
757
- * and truncated list that will be used inside the event message.
758
- * eg. `Non-error exception captured with keys: foo, bar, baz`
502
+ /**
503
+ * Given any captured exception, extract its keys and create a sorted
504
+ * and truncated list that will be used inside the event message.
505
+ * eg. `Non-error exception captured with keys: foo, bar, baz`
759
506
  */
760
507
  function extractExceptionKeysForMessage(exception, maxLength = 40) {
761
508
  const keys = Object.keys(convertToPlainObject(exception));
@@ -785,13 +532,13 @@ function truncate(str, max = 0) {
785
532
  }
786
533
  return str.length <= max ? str : `${str.slice(0, max)}...`;
787
534
  }
788
- /**
789
- * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
790
- * non-enumerable properties attached.
791
- *
792
- * @param value Initial source that we have to transform in order for it to be usable by the serializer
793
- * @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor
794
- * an Error.
535
+ /**
536
+ * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
537
+ * non-enumerable properties attached.
538
+ *
539
+ * @param value Initial source that we have to transform in order for it to be usable by the serializer
540
+ * @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor
541
+ * an Error.
795
542
  */
796
543
  function convertToPlainObject(value) {
797
544
  if (isError(value)) {
@@ -839,8 +586,8 @@ function serializeEventTarget(target) {
839
586
  return '<unknown>';
840
587
  }
841
588
  }
842
- /**
843
- * Extracts stack frames from the error and builds an Exception
589
+ /**
590
+ * Extracts stack frames from the error and builds an Exception
844
591
  */
845
592
  async function exceptionFromError(stackParser, frameModifiers, error) {
846
593
  const exception = {
@@ -859,8 +606,8 @@ async function exceptionFromError(stackParser, frameModifiers, error) {
859
606
  }
860
607
  return exception;
861
608
  }
862
- /**
863
- * Extracts stack frames from the error.stack string
609
+ /**
610
+ * Extracts stack frames from the error.stack string
864
611
  */
865
612
  function parseStackFrames(stackParser, error) {
866
613
  return applyChunkIds(stackParser(error.stack || '', 1), stackParser);
@@ -937,1092 +684,18 @@ function setupExpressErrorHandler(_posthog, app) {
937
684
  });
938
685
  }
939
686
 
940
- var version = "5.5.1";
941
-
942
- var PostHogPersistedProperty;
943
- (function (PostHogPersistedProperty) {
944
- PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
945
- PostHogPersistedProperty["DistinctId"] = "distinct_id";
946
- PostHogPersistedProperty["Props"] = "props";
947
- PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
948
- PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
949
- PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
950
- PostHogPersistedProperty["BootstrapFeatureFlagDetails"] = "bootstrap_feature_flag_details";
951
- PostHogPersistedProperty["BootstrapFeatureFlags"] = "bootstrap_feature_flags";
952
- PostHogPersistedProperty["BootstrapFeatureFlagPayloads"] = "bootstrap_feature_flag_payloads";
953
- PostHogPersistedProperty["OverrideFeatureFlags"] = "override_feature_flags";
954
- PostHogPersistedProperty["Queue"] = "queue";
955
- PostHogPersistedProperty["OptedOut"] = "opted_out";
956
- PostHogPersistedProperty["SessionId"] = "session_id";
957
- PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
958
- PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
959
- PostHogPersistedProperty["PersonProperties"] = "person_properties";
960
- PostHogPersistedProperty["GroupProperties"] = "group_properties";
961
- PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
962
- PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
963
- PostHogPersistedProperty["SessionReplay"] = "session_replay";
964
- PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
965
- PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
966
- PostHogPersistedProperty["Surveys"] = "surveys";
967
- PostHogPersistedProperty["RemoteConfig"] = "remote_config";
968
- PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
969
- })(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
970
- // Any key prefixed with `attr__` can be added
971
- var Compression;
972
- (function (Compression) {
973
- Compression["GZipJS"] = "gzip-js";
974
- Compression["Base64"] = "base64";
975
- })(Compression || (Compression = {}));
976
- var SurveyPosition;
977
- (function (SurveyPosition) {
978
- SurveyPosition["TopLeft"] = "top_left";
979
- SurveyPosition["TopCenter"] = "top_center";
980
- SurveyPosition["TopRight"] = "top_right";
981
- SurveyPosition["MiddleLeft"] = "middle_left";
982
- SurveyPosition["MiddleCenter"] = "middle_center";
983
- SurveyPosition["MiddleRight"] = "middle_right";
984
- SurveyPosition["Left"] = "left";
985
- SurveyPosition["Right"] = "right";
986
- SurveyPosition["Center"] = "center";
987
- })(SurveyPosition || (SurveyPosition = {}));
988
- var SurveyWidgetType;
989
- (function (SurveyWidgetType) {
990
- SurveyWidgetType["Button"] = "button";
991
- SurveyWidgetType["Tab"] = "tab";
992
- SurveyWidgetType["Selector"] = "selector";
993
- })(SurveyWidgetType || (SurveyWidgetType = {}));
994
- var SurveyType;
995
- (function (SurveyType) {
996
- SurveyType["Popover"] = "popover";
997
- SurveyType["API"] = "api";
998
- SurveyType["Widget"] = "widget";
999
- })(SurveyType || (SurveyType = {}));
1000
- var SurveyQuestionDescriptionContentType;
1001
- (function (SurveyQuestionDescriptionContentType) {
1002
- SurveyQuestionDescriptionContentType["Html"] = "html";
1003
- SurveyQuestionDescriptionContentType["Text"] = "text";
1004
- })(SurveyQuestionDescriptionContentType || (SurveyQuestionDescriptionContentType = {}));
1005
- var SurveyRatingDisplay;
1006
- (function (SurveyRatingDisplay) {
1007
- SurveyRatingDisplay["Number"] = "number";
1008
- SurveyRatingDisplay["Emoji"] = "emoji";
1009
- })(SurveyRatingDisplay || (SurveyRatingDisplay = {}));
1010
- var SurveyQuestionType;
1011
- (function (SurveyQuestionType) {
1012
- SurveyQuestionType["Open"] = "open";
1013
- SurveyQuestionType["MultipleChoice"] = "multiple_choice";
1014
- SurveyQuestionType["SingleChoice"] = "single_choice";
1015
- SurveyQuestionType["Rating"] = "rating";
1016
- SurveyQuestionType["Link"] = "link";
1017
- })(SurveyQuestionType || (SurveyQuestionType = {}));
1018
- var SurveyQuestionBranchingType;
1019
- (function (SurveyQuestionBranchingType) {
1020
- SurveyQuestionBranchingType["NextQuestion"] = "next_question";
1021
- SurveyQuestionBranchingType["End"] = "end";
1022
- SurveyQuestionBranchingType["ResponseBased"] = "response_based";
1023
- SurveyQuestionBranchingType["SpecificQuestion"] = "specific_question";
1024
- })(SurveyQuestionBranchingType || (SurveyQuestionBranchingType = {}));
1025
- var SurveyMatchType;
1026
- (function (SurveyMatchType) {
1027
- SurveyMatchType["Regex"] = "regex";
1028
- SurveyMatchType["NotRegex"] = "not_regex";
1029
- SurveyMatchType["Exact"] = "exact";
1030
- SurveyMatchType["IsNot"] = "is_not";
1031
- SurveyMatchType["Icontains"] = "icontains";
1032
- SurveyMatchType["NotIcontains"] = "not_icontains";
1033
- })(SurveyMatchType || (SurveyMatchType = {}));
1034
- /** Sync with plugin-server/src/types.ts */
1035
- var ActionStepStringMatching;
1036
- (function (ActionStepStringMatching) {
1037
- ActionStepStringMatching["Contains"] = "contains";
1038
- ActionStepStringMatching["Exact"] = "exact";
1039
- ActionStepStringMatching["Regex"] = "regex";
1040
- })(ActionStepStringMatching || (ActionStepStringMatching = {}));
1041
-
1042
- const normalizeFlagsResponse = (flagsResponse) => {
1043
- if ('flags' in flagsResponse) {
1044
- // Convert v2 format to v1 format
1045
- const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
1046
- const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
1047
- return {
1048
- ...flagsResponse,
1049
- featureFlags,
1050
- featureFlagPayloads,
1051
- };
1052
- }
1053
- else {
1054
- // Convert v1 format to v2 format
1055
- const featureFlags = flagsResponse.featureFlags ?? {};
1056
- const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
1057
- const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
1058
- key,
1059
- getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
1060
- ]));
1061
- return {
1062
- ...flagsResponse,
1063
- featureFlags,
1064
- featureFlagPayloads,
1065
- flags,
1066
- };
1067
- }
1068
- };
1069
- function getFlagDetailFromFlagAndPayload(key, value, payload) {
1070
- return {
1071
- key: key,
1072
- enabled: typeof value === 'string' ? true : value,
1073
- variant: typeof value === 'string' ? value : undefined,
1074
- reason: undefined,
1075
- metadata: {
1076
- id: undefined,
1077
- version: undefined,
1078
- payload: payload ? JSON.stringify(payload) : undefined,
1079
- description: undefined,
1080
- },
1081
- };
1082
- }
1083
- /**
1084
- * Get the flag values from the flags v4 response.
1085
- * @param flags - The flags
1086
- * @returns The flag values
1087
- */
1088
- const getFlagValuesFromFlags = (flags) => {
1089
- return Object.fromEntries(Object.entries(flags ?? {})
1090
- .map(([key, detail]) => [key, getFeatureFlagValue(detail)])
1091
- .filter(([, value]) => value !== undefined));
1092
- };
1093
- /**
1094
- * Get the payloads from the flags v4 response.
1095
- * @param flags - The flags
1096
- * @returns The payloads
1097
- */
1098
- const getPayloadsFromFlags = (flags) => {
1099
- const safeFlags = flags ?? {};
1100
- return Object.fromEntries(Object.keys(safeFlags)
1101
- .filter((flag) => {
1102
- const details = safeFlags[flag];
1103
- return details.enabled && details.metadata && details.metadata.payload !== undefined;
1104
- })
1105
- .map((flag) => {
1106
- const payload = safeFlags[flag].metadata?.payload;
1107
- return [flag, payload ? parsePayload(payload) : undefined];
1108
- }));
1109
- };
1110
- const getFeatureFlagValue = (detail) => {
1111
- return detail === undefined ? undefined : detail.variant ?? detail.enabled;
1112
- };
1113
- const parsePayload = (response) => {
1114
- if (typeof response !== 'string') {
1115
- return response;
1116
- }
1117
- try {
1118
- return JSON.parse(response);
1119
- }
1120
- catch {
1121
- return response;
1122
- }
1123
- };
1124
-
1125
- const STRING_FORMAT = 'utf8';
1126
- function assert(truthyValue, message) {
1127
- if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
1128
- throw new Error(message);
1129
- }
1130
- }
1131
- function isEmpty(truthyValue) {
1132
- if (truthyValue.trim().length === 0) {
1133
- return true;
1134
- }
1135
- return false;
1136
- }
1137
- function removeTrailingSlash(url) {
1138
- return url?.replace(/\/+$/, '');
1139
- }
1140
- async function retriable(fn, props) {
1141
- let lastError = null;
1142
- for (let i = 0; i < props.retryCount + 1; i++) {
1143
- if (i > 0) {
1144
- // don't wait when it's the last try
1145
- await new Promise((r) => setTimeout(r, props.retryDelay));
1146
- }
1147
- try {
1148
- const res = await fn();
1149
- return res;
1150
- }
1151
- catch (e) {
1152
- lastError = e;
1153
- if (!props.retryCheck(e)) {
1154
- throw e;
1155
- }
1156
- }
1157
- }
1158
- throw lastError;
1159
- }
1160
- function currentISOTime() {
1161
- return new Date().toISOString();
1162
- }
1163
- function safeSetTimeout(fn, timeout) {
1164
- // NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``
1165
- // rather than setImmediate.
1166
- const t = setTimeout(fn, timeout);
1167
- // We unref if available to prevent Node.js hanging on exit
1168
- t?.unref && t?.unref();
1169
- return t;
1170
- }
1171
- function allSettled(promises) {
1172
- return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
1173
- }
1174
-
1175
- /**
1176
- * Older browsers and some runtimes don't support this yet
1177
- * This API (as of 2025-05-07) is not available on React Native.
1178
- */
1179
- function isGzipSupported() {
1180
- return 'CompressionStream' in globalThis;
1181
- }
1182
- /**
1183
- * Gzip a string using Compression Streams API if it's available
1184
- */
1185
- async function gzipCompress(input, isDebug = true) {
1186
- try {
1187
- // Turn the string into a stream using a Blob, and then compress it
1188
- const dataStream = new Blob([input], {
1189
- type: 'text/plain',
1190
- }).stream();
1191
- const compressedStream = dataStream.pipeThrough(new CompressionStream('gzip'));
1192
- // Using a Response to easily extract the readablestream value. Decoding into a string for fetch
1193
- return await new Response(compressedStream).blob();
1194
- }
1195
- catch (error) {
1196
- if (isDebug) {
1197
- console.error('Failed to gzip compress data', error);
1198
- }
1199
- return null;
1200
- }
1201
- }
1202
-
1203
- class SimpleEventEmitter {
1204
- constructor() {
1205
- this.events = {};
1206
- this.events = {};
1207
- }
1208
- on(event, listener) {
1209
- if (!this.events[event]) {
1210
- this.events[event] = [];
1211
- }
1212
- this.events[event].push(listener);
1213
- return () => {
1214
- this.events[event] = this.events[event].filter((x) => x !== listener);
1215
- };
1216
- }
1217
- emit(event, payload) {
1218
- for (const listener of this.events[event] || []) {
1219
- listener(payload);
1220
- }
1221
- for (const listener of this.events['*'] || []) {
1222
- listener(event, payload);
1223
- }
1224
- }
1225
- }
1226
-
1227
- class PostHogFetchHttpError extends Error {
1228
- constructor(response, reqByteLength) {
1229
- super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength);
1230
- this.response = response;
1231
- this.reqByteLength = reqByteLength;
1232
- this.name = 'PostHogFetchHttpError';
1233
- }
1234
- get status() {
1235
- return this.response.status;
1236
- }
1237
- get text() {
1238
- return this.response.text();
1239
- }
1240
- get json() {
1241
- return this.response.json();
1242
- }
1243
- }
1244
- class PostHogFetchNetworkError extends Error {
1245
- constructor(error) {
1246
- // TRICKY: "cause" is a newer property but is just ignored otherwise. Cast to any to ignore the type issue.
1247
- // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
1248
- // @ts-ignore
1249
- super('Network error while fetching PostHog', error instanceof Error ? { cause: error } : {});
1250
- this.error = error;
1251
- this.name = 'PostHogFetchNetworkError';
1252
- }
1253
- }
1254
- async function logFlushError(err) {
1255
- if (err instanceof PostHogFetchHttpError) {
1256
- let text = '';
1257
- try {
1258
- text = await err.text;
1259
- }
1260
- catch { }
1261
- console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
1262
- }
1263
- else {
1264
- console.error('Error while flushing PostHog', err);
1265
- }
1266
- return Promise.resolve();
1267
- }
1268
- function isPostHogFetchError(err) {
1269
- return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
1270
- }
1271
- function isPostHogFetchContentTooLargeError(err) {
1272
- return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413;
1273
- }
1274
- var QuotaLimitedFeature;
1275
- (function (QuotaLimitedFeature) {
1276
- QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
1277
- QuotaLimitedFeature["Recordings"] = "recordings";
1278
- })(QuotaLimitedFeature || (QuotaLimitedFeature = {}));
1279
- class PostHogCoreStateless {
1280
- constructor(apiKey, options) {
1281
- this.flushPromise = null;
1282
- this.shutdownPromise = null;
1283
- this.pendingPromises = {};
1284
- // internal
1285
- this._events = new SimpleEventEmitter();
1286
- this._isInitialized = false;
1287
- assert(apiKey, "You must pass your PostHog project's api key.");
1288
- this.apiKey = apiKey;
1289
- this.host = removeTrailingSlash(options?.host || 'https://us.i.posthog.com');
1290
- this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 20;
1291
- this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
1292
- this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
1293
- this.flushInterval = options?.flushInterval ?? 10000;
1294
- this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
1295
- // If enable is explicitly set to false we override the optout
1296
- this.defaultOptIn = options?.defaultOptIn ?? true;
1297
- this.disableSurveys = options?.disableSurveys ?? false;
1298
- this._retryOptions = {
1299
- retryCount: options?.fetchRetryCount ?? 3,
1300
- retryDelay: options?.fetchRetryDelay ?? 3000,
1301
- retryCheck: isPostHogFetchError,
1302
- };
1303
- this.requestTimeout = options?.requestTimeout ?? 10000; // 10 seconds
1304
- this.featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 3000; // 3 seconds
1305
- this.remoteConfigRequestTimeoutMs = options?.remoteConfigRequestTimeoutMs ?? 3000; // 3 seconds
1306
- this.disableGeoip = options?.disableGeoip ?? true;
1307
- this.disabled = options?.disabled ?? false;
1308
- this.historicalMigration = options?.historicalMigration ?? false;
1309
- // Init promise allows the derived class to block calls until it is ready
1310
- this._initPromise = Promise.resolve();
1311
- this._isInitialized = true;
1312
- this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
1313
- }
1314
- logMsgIfDebug(fn) {
1315
- if (this.isDebug) {
1316
- fn();
1317
- }
1318
- }
1319
- wrap(fn) {
1320
- if (this.disabled) {
1321
- this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
1322
- return;
1323
- }
1324
- if (this._isInitialized) {
1325
- // NOTE: We could also check for the "opt in" status here...
1326
- return fn();
1327
- }
1328
- this._initPromise.then(() => fn());
1329
- }
1330
- getCommonEventProperties() {
1331
- return {
1332
- $lib: this.getLibraryId(),
1333
- $lib_version: this.getLibraryVersion(),
1334
- };
1335
- }
1336
- get optedOut() {
1337
- return this.getPersistedProperty(PostHogPersistedProperty.OptedOut) ?? !this.defaultOptIn;
1338
- }
1339
- async optIn() {
1340
- this.wrap(() => {
1341
- this.setPersistedProperty(PostHogPersistedProperty.OptedOut, false);
1342
- });
1343
- }
1344
- async optOut() {
1345
- this.wrap(() => {
1346
- this.setPersistedProperty(PostHogPersistedProperty.OptedOut, true);
1347
- });
1348
- }
1349
- on(event, cb) {
1350
- return this._events.on(event, cb);
1351
- }
1352
- debug(enabled = true) {
1353
- this.removeDebugCallback?.();
1354
- if (enabled) {
1355
- const removeDebugCallback = this.on('*', (event, payload) => console.log('PostHog Debug', event, payload));
1356
- this.removeDebugCallback = () => {
1357
- removeDebugCallback();
1358
- this.removeDebugCallback = undefined;
1359
- };
1360
- }
1361
- }
1362
- get isDebug() {
1363
- return !!this.removeDebugCallback;
1364
- }
1365
- get isDisabled() {
1366
- return this.disabled;
1367
- }
1368
- buildPayload(payload) {
1369
- return {
1370
- distinct_id: payload.distinct_id,
1371
- event: payload.event,
1372
- properties: {
1373
- ...(payload.properties || {}),
1374
- ...this.getCommonEventProperties(), // Common PH props
1375
- },
1376
- };
1377
- }
1378
- addPendingPromise(promise) {
1379
- const promiseUUID = uuidv7();
1380
- this.pendingPromises[promiseUUID] = promise;
1381
- promise
1382
- .catch(() => { })
1383
- .finally(() => {
1384
- delete this.pendingPromises[promiseUUID];
1385
- });
1386
- return promise;
1387
- }
1388
- /***
1389
- *** TRACKING
1390
- ***/
1391
- identifyStateless(distinctId, properties, options) {
1392
- this.wrap(() => {
1393
- // The properties passed to identifyStateless are event properties.
1394
- // To add person properties, pass in all person properties to the `$set` and `$set_once` keys.
1395
- const payload = {
1396
- ...this.buildPayload({
1397
- distinct_id: distinctId,
1398
- event: '$identify',
1399
- properties,
1400
- }),
1401
- };
1402
- this.enqueue('identify', payload, options);
1403
- });
1404
- }
1405
- async identifyStatelessImmediate(distinctId, properties, options) {
1406
- const payload = {
1407
- ...this.buildPayload({
1408
- distinct_id: distinctId,
1409
- event: '$identify',
1410
- properties,
1411
- }),
1412
- };
1413
- await this.sendImmediate('identify', payload, options);
1414
- }
1415
- captureStateless(distinctId, event, properties, options) {
1416
- this.wrap(() => {
1417
- const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
1418
- this.enqueue('capture', payload, options);
1419
- });
1420
- }
1421
- async captureStatelessImmediate(distinctId, event, properties, options) {
1422
- const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
1423
- await this.sendImmediate('capture', payload, options);
1424
- }
1425
- aliasStateless(alias, distinctId, properties, options) {
1426
- this.wrap(() => {
1427
- const payload = this.buildPayload({
1428
- event: '$create_alias',
1429
- distinct_id: distinctId,
1430
- properties: {
1431
- ...(properties || {}),
1432
- distinct_id: distinctId,
1433
- alias,
1434
- },
1435
- });
1436
- this.enqueue('alias', payload, options);
1437
- });
1438
- }
1439
- async aliasStatelessImmediate(alias, distinctId, properties, options) {
1440
- const payload = this.buildPayload({
1441
- event: '$create_alias',
1442
- distinct_id: distinctId,
1443
- properties: {
1444
- ...(properties || {}),
1445
- distinct_id: distinctId,
1446
- alias,
1447
- },
1448
- });
1449
- await this.sendImmediate('alias', payload, options);
1450
- }
1451
- /***
1452
- *** GROUPS
1453
- ***/
1454
- groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties) {
1455
- this.wrap(() => {
1456
- const payload = this.buildPayload({
1457
- distinct_id: distinctId || `$${groupType}_${groupKey}`,
1458
- event: '$groupidentify',
1459
- properties: {
1460
- $group_type: groupType,
1461
- $group_key: groupKey,
1462
- $group_set: groupProperties || {},
1463
- ...(eventProperties || {}),
1464
- },
1465
- });
1466
- this.enqueue('capture', payload, options);
1467
- });
1468
- }
1469
- async getRemoteConfig() {
1470
- await this._initPromise;
1471
- let host = this.host;
1472
- if (host === 'https://us.i.posthog.com') {
1473
- host = 'https://us-assets.i.posthog.com';
1474
- }
1475
- else if (host === 'https://eu.i.posthog.com') {
1476
- host = 'https://eu-assets.i.posthog.com';
1477
- }
1478
- const url = `${host}/array/${this.apiKey}/config`;
1479
- const fetchOptions = {
1480
- method: 'GET',
1481
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1482
- };
1483
- // Don't retry remote config API calls
1484
- return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.remoteConfigRequestTimeoutMs)
1485
- .then((response) => response.json())
1486
- .catch((error) => {
1487
- this.logMsgIfDebug(() => console.error('Remote config could not be loaded', error));
1488
- this._events.emit('error', error);
1489
- return undefined;
1490
- });
1491
- }
1492
- /***
1493
- *** FEATURE FLAGS
1494
- ***/
1495
- async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
1496
- await this._initPromise;
1497
- const url = `${this.host}/flags/?v=2&config=true`;
1498
- const fetchOptions = {
1499
- method: 'POST',
1500
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1501
- body: JSON.stringify({
1502
- token: this.apiKey,
1503
- distinct_id: distinctId,
1504
- groups,
1505
- person_properties: personProperties,
1506
- group_properties: groupProperties,
1507
- ...extraPayload,
1508
- }),
1509
- };
1510
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Flags URL', url));
1511
- // Don't retry /flags API calls
1512
- return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
1513
- .then((response) => response.json())
1514
- .then((response) => normalizeFlagsResponse(response))
1515
- .catch((error) => {
1516
- this._events.emit('error', error);
1517
- return undefined;
1518
- });
1519
- }
1520
- async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1521
- await this._initPromise;
1522
- const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
1523
- if (flagDetailResponse === undefined) {
1524
- // If we haven't loaded flags yet, or errored out, we respond with undefined
1525
- return {
1526
- response: undefined,
1527
- requestId: undefined,
1528
- };
1529
- }
1530
- let response = getFeatureFlagValue(flagDetailResponse.response);
1531
- if (response === undefined) {
1532
- // For cases where the flag is unknown, return false
1533
- response = false;
1534
- }
1535
- // If we have flags we either return the value (true or string) or false
1536
- return {
1537
- response,
1538
- requestId: flagDetailResponse.requestId,
1539
- };
1540
- }
1541
- async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1542
- await this._initPromise;
1543
- const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1544
- if (flagsResponse === undefined) {
1545
- return undefined;
1546
- }
1547
- const featureFlags = flagsResponse.flags;
1548
- const flagDetail = featureFlags[key];
1549
- return {
1550
- response: flagDetail,
1551
- requestId: flagsResponse.requestId,
1552
- };
1553
- }
1554
- async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1555
- await this._initPromise;
1556
- const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1557
- if (!payloads) {
1558
- return undefined;
1559
- }
1560
- const response = payloads[key];
1561
- // Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
1562
- if (response === undefined) {
1563
- return null;
1564
- }
1565
- return response;
1566
- }
1567
- async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1568
- await this._initPromise;
1569
- const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate)).payloads;
1570
- return payloads;
1571
- }
1572
- async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1573
- await this._initPromise;
1574
- return await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1575
- }
1576
- async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1577
- await this._initPromise;
1578
- const featureFlagDetails = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1579
- if (!featureFlagDetails) {
1580
- return {
1581
- flags: undefined,
1582
- payloads: undefined,
1583
- requestId: undefined,
1584
- };
1585
- }
1586
- return {
1587
- flags: featureFlagDetails.featureFlags,
1588
- payloads: featureFlagDetails.featureFlagPayloads,
1589
- requestId: featureFlagDetails.requestId,
1590
- };
1591
- }
1592
- async getFeatureFlagDetailsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1593
- await this._initPromise;
1594
- const extraPayload = {};
1595
- if (disableGeoip ?? this.disableGeoip) {
1596
- extraPayload['geoip_disable'] = true;
1597
- }
1598
- if (flagKeysToEvaluate) {
1599
- extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
1600
- }
1601
- const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
1602
- if (flagsResponse === undefined) {
1603
- // We probably errored out, so return undefined
1604
- return undefined;
1605
- }
1606
- // if there's an error on the flagsResponse, log a console error, but don't throw an error
1607
- if (flagsResponse.errorsWhileComputingFlags) {
1608
- console.error('[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices');
1609
- }
1610
- // Add check for quota limitation on feature flags
1611
- if (flagsResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
1612
- console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - feature flags unavailable. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
1613
- return {
1614
- flags: {},
1615
- featureFlags: {},
1616
- featureFlagPayloads: {},
1617
- requestId: flagsResponse?.requestId,
1618
- };
1619
- }
1620
- return flagsResponse;
1621
- }
1622
- /***
1623
- *** SURVEYS
1624
- ***/
1625
- async getSurveysStateless() {
1626
- await this._initPromise;
1627
- if (this.disableSurveys === true) {
1628
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Loading surveys is disabled.'));
1629
- return [];
1630
- }
1631
- const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
1632
- const fetchOptions = {
1633
- method: 'GET',
1634
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1635
- };
1636
- const response = await this.fetchWithRetry(url, fetchOptions)
1637
- .then((response) => {
1638
- if (response.status !== 200 || !response.json) {
1639
- const msg = `Surveys API could not be loaded: ${response.status}`;
1640
- const error = new Error(msg);
1641
- this.logMsgIfDebug(() => console.error(error));
1642
- this._events.emit('error', new Error(msg));
1643
- return undefined;
1644
- }
1645
- return response.json();
1646
- })
1647
- .catch((error) => {
1648
- this.logMsgIfDebug(() => console.error('Surveys API could not be loaded', error));
1649
- this._events.emit('error', error);
1650
- return undefined;
1651
- });
1652
- const newSurveys = response?.surveys;
1653
- if (newSurveys) {
1654
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from API: ', JSON.stringify(newSurveys)));
1655
- }
1656
- return newSurveys ?? [];
1657
- }
1658
- get props() {
1659
- if (!this._props) {
1660
- this._props = this.getPersistedProperty(PostHogPersistedProperty.Props);
1661
- }
1662
- return this._props || {};
1663
- }
1664
- set props(val) {
1665
- this._props = val;
1666
- }
1667
- async register(properties) {
1668
- this.wrap(() => {
1669
- this.props = {
1670
- ...this.props,
1671
- ...properties,
1672
- };
1673
- this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
1674
- });
1675
- }
1676
- async unregister(property) {
1677
- this.wrap(() => {
1678
- delete this.props[property];
1679
- this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
1680
- });
1681
- }
1682
- /***
1683
- *** QUEUEING AND FLUSHING
1684
- ***/
1685
- enqueue(type, _message, options) {
1686
- this.wrap(() => {
1687
- if (this.optedOut) {
1688
- this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
1689
- return;
1690
- }
1691
- const message = this.prepareMessage(type, _message, options);
1692
- const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1693
- if (queue.length >= this.maxQueueSize) {
1694
- queue.shift();
1695
- this.logMsgIfDebug(() => console.info('Queue is full, the oldest event is dropped.'));
1696
- }
1697
- queue.push({ message });
1698
- this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
1699
- this._events.emit(type, message);
1700
- // Flush queued events if we meet the flushAt length
1701
- if (queue.length >= this.flushAt) {
1702
- this.flushBackground();
1703
- }
1704
- if (this.flushInterval && !this._flushTimer) {
1705
- this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
1706
- }
1707
- });
1708
- }
1709
- async sendImmediate(type, _message, options) {
1710
- if (this.disabled) {
1711
- this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
1712
- return;
1713
- }
1714
- if (!this._isInitialized) {
1715
- await this._initPromise;
1716
- }
1717
- if (this.optedOut) {
1718
- this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
1719
- return;
1720
- }
1721
- const data = {
1722
- api_key: this.apiKey,
1723
- batch: [this.prepareMessage(type, _message, options)],
1724
- sent_at: currentISOTime(),
1725
- };
1726
- if (this.historicalMigration) {
1727
- data.historical_migration = true;
1728
- }
1729
- const payload = JSON.stringify(data);
1730
- const url = `${this.host}/batch/`;
1731
- const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
1732
- const fetchOptions = {
1733
- method: 'POST',
1734
- headers: {
1735
- ...this.getCustomHeaders(),
1736
- 'Content-Type': 'application/json',
1737
- ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
1738
- },
1739
- body: gzippedPayload || payload,
1740
- };
1741
- try {
1742
- await this.fetchWithRetry(url, fetchOptions);
1743
- }
1744
- catch (err) {
1745
- this._events.emit('error', err);
1746
- }
1747
- }
1748
- prepareMessage(type, _message, options) {
1749
- const message = {
1750
- ..._message,
1751
- type: type,
1752
- library: this.getLibraryId(),
1753
- library_version: this.getLibraryVersion(),
1754
- timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
1755
- uuid: options?.uuid ? options.uuid : uuidv7(),
1756
- };
1757
- const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
1758
- if (addGeoipDisableProperty) {
1759
- if (!message.properties) {
1760
- message.properties = {};
1761
- }
1762
- message['properties']['$geoip_disable'] = true;
1763
- }
1764
- if (message.distinctId) {
1765
- message.distinct_id = message.distinctId;
1766
- delete message.distinctId;
1767
- }
1768
- return message;
1769
- }
1770
- clearFlushTimer() {
1771
- if (this._flushTimer) {
1772
- clearTimeout(this._flushTimer);
1773
- this._flushTimer = undefined;
1774
- }
1775
- }
1776
- /**
1777
- * Helper for flushing the queue in the background
1778
- * Avoids unnecessary promise errors
1779
- */
1780
- flushBackground() {
1781
- void this.flush().catch(async (err) => {
1782
- await logFlushError(err);
1783
- });
1784
- }
1785
- /**
1786
- * Flushes the queue
1787
- *
1788
- * This function will return a promise that will resolve when the flush is complete,
1789
- * or reject if there was an error (for example if the server or network is down).
1790
- *
1791
- * If there is already a flush in progress, this function will wait for that flush to complete.
1792
- *
1793
- * It's recommended to do error handling in the callback of the promise.
1794
- *
1795
- * @example
1796
- * posthog.flush().then(() => {
1797
- * console.log('Flush complete')
1798
- * }).catch((err) => {
1799
- * console.error('Flush failed', err)
1800
- * })
1801
- *
1802
- *
1803
- * @throws PostHogFetchHttpError
1804
- * @throws PostHogFetchNetworkError
1805
- * @throws Error
1806
- */
1807
- async flush() {
1808
- // Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
1809
- // Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
1810
- // Use a custom allSettled implementation to avoid issues with patching Promise on RN
1811
- const nextFlushPromise = allSettled([this.flushPromise]).then(() => {
1812
- return this._flush();
1813
- });
1814
- this.flushPromise = nextFlushPromise;
1815
- void this.addPendingPromise(nextFlushPromise);
1816
- allSettled([nextFlushPromise]).then(() => {
1817
- // If there are no others waiting to flush, clear the promise.
1818
- // We don't strictly need to do this, but it could make debugging easier
1819
- if (this.flushPromise === nextFlushPromise) {
1820
- this.flushPromise = null;
1821
- }
1822
- });
1823
- return nextFlushPromise;
1824
- }
1825
- getCustomHeaders() {
1826
- // Don't set the user agent if we're not on a browser. The latest spec allows
1827
- // the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
1828
- // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
1829
- // but browsers such as Chrome and Safari have not caught up.
1830
- const customUserAgent = this.getCustomUserAgent();
1831
- const headers = {};
1832
- if (customUserAgent && customUserAgent !== '') {
1833
- headers['User-Agent'] = customUserAgent;
1834
- }
1835
- return headers;
1836
- }
1837
- async _flush() {
1838
- this.clearFlushTimer();
1839
- await this._initPromise;
1840
- let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1841
- if (!queue.length) {
1842
- return;
1843
- }
1844
- const sentMessages = [];
1845
- const originalQueueLength = queue.length;
1846
- while (queue.length > 0 && sentMessages.length < originalQueueLength) {
1847
- const batchItems = queue.slice(0, this.maxBatchSize);
1848
- const batchMessages = batchItems.map((item) => item.message);
1849
- const persistQueueChange = () => {
1850
- const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1851
- const newQueue = refreshedQueue.slice(batchItems.length);
1852
- this.setPersistedProperty(PostHogPersistedProperty.Queue, newQueue);
1853
- queue = newQueue;
1854
- };
1855
- const data = {
1856
- api_key: this.apiKey,
1857
- batch: batchMessages,
1858
- sent_at: currentISOTime(),
1859
- };
1860
- if (this.historicalMigration) {
1861
- data.historical_migration = true;
1862
- }
1863
- const payload = JSON.stringify(data);
1864
- const url = `${this.host}/batch/`;
1865
- const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
1866
- const fetchOptions = {
1867
- method: 'POST',
1868
- headers: {
1869
- ...this.getCustomHeaders(),
1870
- 'Content-Type': 'application/json',
1871
- ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
1872
- },
1873
- body: gzippedPayload || payload,
1874
- };
1875
- const retryOptions = {
1876
- retryCheck: (err) => {
1877
- // don't automatically retry on 413 errors, we want to reduce the batch size first
1878
- if (isPostHogFetchContentTooLargeError(err)) {
1879
- return false;
1880
- }
1881
- // otherwise, retry on network errors
1882
- return isPostHogFetchError(err);
1883
- },
1884
- };
1885
- try {
1886
- await this.fetchWithRetry(url, fetchOptions, retryOptions);
1887
- }
1888
- catch (err) {
1889
- if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
1890
- // if we get a 413 error, we want to reduce the batch size and try again
1891
- this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
1892
- this.logMsgIfDebug(() => console.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`));
1893
- // do not persist the queue change, we want to retry the same batch
1894
- continue;
1895
- }
1896
- // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
1897
- // 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
1898
- if (!(err instanceof PostHogFetchNetworkError)) {
1899
- persistQueueChange();
1900
- }
1901
- this._events.emit('error', err);
1902
- throw err;
1903
- }
1904
- persistQueueChange();
1905
- sentMessages.push(...batchMessages);
1906
- }
1907
- this._events.emit('flush', sentMessages);
1908
- }
1909
- async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1910
- var _a;
1911
- (_a = AbortSignal).timeout ?? (_a.timeout = function timeout(ms) {
1912
- const ctrl = new AbortController();
1913
- setTimeout(() => ctrl.abort(), ms);
1914
- return ctrl.signal;
1915
- });
1916
- const body = options.body ? options.body : '';
1917
- let reqByteLength = -1;
1918
- try {
1919
- if (body instanceof Blob) {
1920
- reqByteLength = body.size;
1921
- }
1922
- else {
1923
- reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1924
- }
1925
- }
1926
- catch {
1927
- if (body instanceof Blob) {
1928
- reqByteLength = body.size;
1929
- }
1930
- else {
1931
- const encoded = new TextEncoder().encode(body);
1932
- reqByteLength = encoded.length;
1933
- }
1934
- }
1935
- return await retriable(async () => {
1936
- let res = null;
1937
- try {
1938
- res = await this.fetch(url, {
1939
- signal: AbortSignal.timeout(requestTimeout ?? this.requestTimeout),
1940
- ...options,
1941
- });
1942
- }
1943
- catch (e) {
1944
- // fetch will only throw on network errors or on timeouts
1945
- throw new PostHogFetchNetworkError(e);
1946
- }
1947
- // If we're in no-cors mode, we can't access the response status
1948
- // We only throw on HTTP errors if we're not in no-cors mode
1949
- // https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
1950
- const isNoCors = options.mode === 'no-cors';
1951
- if (!isNoCors && (res.status < 200 || res.status >= 400)) {
1952
- throw new PostHogFetchHttpError(res, reqByteLength);
1953
- }
1954
- return res;
1955
- }, { ...this._retryOptions, ...retryOptions });
1956
- }
1957
- async _shutdown(shutdownTimeoutMs = 30000) {
1958
- // A little tricky - we want to have a max shutdown time and enforce it, even if that means we have some
1959
- // dangling promises. We'll keep track of the timeout and resolve/reject based on that.
1960
- await this._initPromise;
1961
- let hasTimedOut = false;
1962
- this.clearFlushTimer();
1963
- const doShutdown = async () => {
1964
- try {
1965
- await Promise.all(Object.values(this.pendingPromises));
1966
- while (true) {
1967
- const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1968
- if (queue.length === 0) {
1969
- break;
1970
- }
1971
- // flush again to make sure we send all events, some of which might've been added
1972
- // while we were waiting for the pending promises to resolve
1973
- // For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
1974
- await this.flush();
1975
- if (hasTimedOut) {
1976
- break;
1977
- }
1978
- }
1979
- }
1980
- catch (e) {
1981
- if (!isPostHogFetchError(e)) {
1982
- throw e;
1983
- }
1984
- await logFlushError(e);
1985
- }
1986
- };
1987
- return Promise.race([
1988
- new Promise((_, reject) => {
1989
- safeSetTimeout(() => {
1990
- this.logMsgIfDebug(() => console.error('Timed out while shutting down PostHog'));
1991
- hasTimedOut = true;
1992
- reject('Timeout while shutting down PostHog. Some events may not have been sent.');
1993
- }, shutdownTimeoutMs);
1994
- }),
1995
- doShutdown(),
1996
- ]);
1997
- }
1998
- /**
1999
- * Call shutdown() once before the node process exits, so ensure that all events have been sent and all promises
2000
- * have resolved. Do not use this function if you intend to keep using this PostHog instance after calling it.
2001
- * @param shutdownTimeoutMs
2002
- */
2003
- async shutdown(shutdownTimeoutMs = 30000) {
2004
- if (this.shutdownPromise) {
2005
- this.logMsgIfDebug(() => console.warn('shutdown() called while already shutting down. shutdown() is meant to be called once before process exit - use flush() for per-request cleanup'));
2006
- }
2007
- else {
2008
- this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
2009
- this.shutdownPromise = null;
2010
- });
2011
- }
2012
- return this.shutdownPromise;
2013
- }
2014
- }
687
+ var version = "5.7.0";
2015
688
 
2016
- /**
2017
- * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
689
+ /**
690
+ * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
2018
691
  */
2019
692
  class Lazy {
2020
693
  constructor(factory) {
2021
694
  this.factory = factory;
2022
695
  }
2023
- /**
2024
- * Gets the value, initializing it if necessary.
2025
- * Multiple concurrent calls will share the same initialization promise.
696
+ /**
697
+ * Gets the value, initializing it if necessary.
698
+ * Multiple concurrent calls will share the same initialization promise.
2026
699
  */
2027
700
  async getValue() {
2028
701
  if (this.value !== undefined) {
@@ -2042,15 +715,15 @@ class Lazy {
2042
715
  }
2043
716
  return this.initializationPromise;
2044
717
  }
2045
- /**
2046
- * Returns true if the value has been initialized.
718
+ /**
719
+ * Returns true if the value has been initialized.
2047
720
  */
2048
721
  isInitialized() {
2049
722
  return this.value !== undefined;
2050
723
  }
2051
- /**
2052
- * Returns a promise that resolves when the value is initialized.
2053
- * If already initialized, resolves immediately.
724
+ /**
725
+ * Returns a promise that resolves when the value is initialized.
726
+ * If already initialized, resolves immediately.
2054
727
  */
2055
728
  async waitForInitialization() {
2056
729
  if (this.isInitialized()) {
@@ -2063,7 +736,7 @@ class Lazy {
2063
736
  /// <reference lib="dom" />
2064
737
  const nodeCrypto = new Lazy(async () => {
2065
738
  try {
2066
- return await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('crypto')); });
739
+ return await import('crypto');
2067
740
  } catch {
2068
741
  return undefined;
2069
742
  }
@@ -2176,15 +849,11 @@ class FeatureFlagsPoller {
2176
849
  if (!this.loadedSuccessfullyOnce) {
2177
850
  return response;
2178
851
  }
2179
- for (const flag of this.featureFlags) {
2180
- if (key === flag.key) {
2181
- featureFlag = flag;
2182
- break;
2183
- }
2184
- }
852
+ featureFlag = this.featureFlagsByKey[key];
2185
853
  if (featureFlag !== undefined) {
2186
854
  try {
2187
- response = await this.computeFlagLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
855
+ const result = await this.computeFlagAndPayloadLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
856
+ response = result.value;
2188
857
  this.logMsgIfDebug(() => console.debug(`Successfully computed flag locally: ${key} -> ${response}`));
2189
858
  } catch (e) {
2190
859
  if (e instanceof InconclusiveMatchError) {
@@ -2196,37 +865,19 @@ class FeatureFlagsPoller {
2196
865
  }
2197
866
  return response;
2198
867
  }
2199
- async computeFeatureFlagPayloadLocally(key, matchValue) {
2200
- await this.loadFeatureFlags();
2201
- let response = undefined;
2202
- if (!this.loadedSuccessfullyOnce) {
2203
- return undefined;
2204
- }
2205
- if (typeof matchValue == 'boolean') {
2206
- response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue.toString()];
2207
- } else if (typeof matchValue == 'string') {
2208
- response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue];
2209
- }
2210
- // Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
2211
- if (response === undefined || response === null) {
2212
- return null;
2213
- }
2214
- try {
2215
- return JSON.parse(response);
2216
- } catch {
2217
- return response;
2218
- }
2219
- }
2220
- async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
868
+ async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}, flagKeysToExplicitlyEvaluate) {
2221
869
  await this.loadFeatureFlags();
2222
870
  const response = {};
2223
871
  const payloads = {};
2224
872
  let fallbackToFlags = this.featureFlags.length == 0;
2225
- await Promise.all(this.featureFlags.map(async flag => {
873
+ const flagsToEvaluate = flagKeysToExplicitlyEvaluate ? flagKeysToExplicitlyEvaluate.map(key => this.featureFlagsByKey[key]).filter(Boolean) : this.featureFlags;
874
+ await Promise.all(flagsToEvaluate.map(async flag => {
2226
875
  try {
2227
- const matchValue = await this.computeFlagLocally(flag, distinctId, groups, personProperties, groupProperties);
876
+ const {
877
+ value: matchValue,
878
+ payload: matchPayload
879
+ } = await this.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties);
2228
880
  response[flag.key] = matchValue;
2229
- const matchPayload = await this.computeFeatureFlagPayloadLocally(flag.key, matchValue);
2230
881
  if (matchPayload) {
2231
882
  payloads[flag.key] = matchPayload;
2232
883
  }
@@ -2245,7 +896,30 @@ class FeatureFlagsPoller {
2245
896
  fallbackToFlags
2246
897
  };
2247
898
  }
2248
- async computeFlagLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
899
+ async computeFlagAndPayloadLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}, matchValue) {
900
+ // Always ensure flags are loaded for payload computation
901
+ await this.loadFeatureFlags();
902
+ if (!this.loadedSuccessfullyOnce) {
903
+ return {
904
+ value: false,
905
+ payload: null
906
+ };
907
+ }
908
+ let flagValue;
909
+ // If matchValue is provided, use it directly; otherwise evaluate the flag
910
+ if (matchValue !== undefined) {
911
+ flagValue = matchValue;
912
+ } else {
913
+ flagValue = await this.computeFlagValueLocally(flag, distinctId, groups, personProperties, groupProperties);
914
+ }
915
+ // Always compute payload based on the final flagValue (whether provided or computed)
916
+ const payload = this.getFeatureFlagPayload(flag.key, flagValue);
917
+ return {
918
+ value: flagValue,
919
+ payload
920
+ };
921
+ }
922
+ async computeFlagValueLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
2249
923
  if (flag.ensure_experience_continuity) {
2250
924
  throw new InconclusiveMatchError('Flag has experience continuity enabled');
2251
925
  }
@@ -2270,6 +944,34 @@ class FeatureFlagsPoller {
2270
944
  return await this.matchFeatureFlagProperties(flag, distinctId, personProperties);
2271
945
  }
2272
946
  }
947
+ getFeatureFlagPayload(key, flagValue) {
948
+ let payload = null;
949
+ if (flagValue !== false && flagValue !== null && flagValue !== undefined) {
950
+ if (typeof flagValue == 'boolean') {
951
+ payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue.toString()] || null;
952
+ } else if (typeof flagValue == 'string') {
953
+ payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue] || null;
954
+ }
955
+ if (payload !== null && payload !== undefined) {
956
+ // If payload is already an object, return it directly
957
+ if (typeof payload === 'object') {
958
+ return payload;
959
+ }
960
+ // If payload is a string, try to parse it as JSON
961
+ if (typeof payload === 'string') {
962
+ try {
963
+ return JSON.parse(payload);
964
+ } catch {
965
+ // If parsing fails, return the string as is
966
+ return payload;
967
+ }
968
+ }
969
+ // For other types, return as is
970
+ return payload;
971
+ }
972
+ }
973
+ return null;
974
+ }
2273
975
  async matchFeatureFlagProperties(flag, distinctId, properties) {
2274
976
  const flagFilters = flag.filters || {};
2275
977
  const flagConditions = flagFilters.groups || [];
@@ -2380,18 +1082,18 @@ class FeatureFlagsPoller {
2380
1082
  await this._loadFeatureFlags();
2381
1083
  }
2382
1084
  }
2383
- /**
2384
- * Returns true if the feature flags poller has loaded successfully at least once and has more than 0 feature flags.
2385
- * This is useful to check if local evaluation is ready before calling getFeatureFlag.
1085
+ /**
1086
+ * Returns true if the feature flags poller has loaded successfully at least once and has more than 0 feature flags.
1087
+ * This is useful to check if local evaluation is ready before calling getFeatureFlag.
2386
1088
  */
2387
1089
  isLocalEvaluationReady() {
2388
1090
  return (this.loadedSuccessfullyOnce ?? false) && (this.featureFlags?.length ?? 0) > 0;
2389
1091
  }
2390
- /**
2391
- * If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
2392
- * until a successful request is made, up to a maximum of 60 seconds.
2393
- *
2394
- * @returns The polling interval to use for the next request.
1092
+ /**
1093
+ * If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
1094
+ * until a successful request is made, up to a maximum of 60 seconds.
1095
+ *
1096
+ * @returns The polling interval to use for the next request.
2395
1097
  */
2396
1098
  getPollingInterval() {
2397
1099
  if (!this.shouldBeginExponentialBackoff) {
@@ -2492,7 +1194,7 @@ class FeatureFlagsPoller {
2492
1194
  let abortTimeout = null;
2493
1195
  if (this.timeout && typeof this.timeout === 'number') {
2494
1196
  const controller = new AbortController();
2495
- abortTimeout = safeSetTimeout(() => {
1197
+ abortTimeout = core.safeSetTimeout(() => {
2496
1198
  controller.abort();
2497
1199
  }, this.timeout);
2498
1200
  options.signal = controller.signal;
@@ -2785,7 +1487,7 @@ const MINIMUM_POLLING_INTERVAL = 100;
2785
1487
  const THIRTY_SECONDS = 30 * 1000;
2786
1488
  const MAX_CACHE_SIZE = 50 * 1000;
2787
1489
  // The actual exported Nodejs API.
2788
- class PostHogBackendClient extends PostHogCoreStateless {
1490
+ class PostHogBackendClient extends core.PostHogCoreStateless {
2789
1491
  constructor(apiKey, options = {}) {
2790
1492
  super(apiKey, options);
2791
1493
  this._memoryStorage = new PostHogMemoryStorage();
@@ -2803,6 +1505,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
2803
1505
  personalApiKey: options.personalApiKey,
2804
1506
  projectApiKey: apiKey,
2805
1507
  timeout: options.requestTimeout ?? 10000,
1508
+ // 10 seconds
2806
1509
  host: this.host,
2807
1510
  fetch: options.fetch,
2808
1511
  onError: err => {
@@ -2858,11 +1561,25 @@ class PostHogBackendClient extends PostHogCoreStateless {
2858
1561
  disableGeoip,
2859
1562
  uuid
2860
1563
  } = props;
1564
+ // Run before_send if configured
1565
+ const eventMessage = this._runBeforeSend({
1566
+ distinctId,
1567
+ event,
1568
+ properties,
1569
+ groups,
1570
+ sendFeatureFlags,
1571
+ timestamp,
1572
+ disableGeoip,
1573
+ uuid
1574
+ });
1575
+ if (!eventMessage) {
1576
+ return;
1577
+ }
2861
1578
  const _capture = props => {
2862
- super.captureStateless(distinctId, event, props, {
2863
- timestamp,
2864
- disableGeoip,
2865
- uuid
1579
+ super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
1580
+ timestamp: eventMessage.timestamp,
1581
+ disableGeoip: eventMessage.disableGeoip,
1582
+ uuid: eventMessage.uuid
2866
1583
  });
2867
1584
  };
2868
1585
  // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
@@ -2897,8 +1614,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
2897
1614
  // No matter what - capture the event
2898
1615
  _capture({
2899
1616
  ...additionalProperties,
2900
- ...properties,
2901
- $groups: groups
1617
+ ...(eventMessage.properties || {}),
1618
+ $groups: eventMessage.groups || groups
2902
1619
  });
2903
1620
  });
2904
1621
  this.addPendingPromise(capturePromise);
@@ -2917,11 +1634,25 @@ class PostHogBackendClient extends PostHogCoreStateless {
2917
1634
  disableGeoip,
2918
1635
  uuid
2919
1636
  } = props;
1637
+ // Run before_send if configured
1638
+ const eventMessage = this._runBeforeSend({
1639
+ distinctId,
1640
+ event,
1641
+ properties,
1642
+ groups,
1643
+ sendFeatureFlags,
1644
+ timestamp,
1645
+ disableGeoip,
1646
+ uuid
1647
+ });
1648
+ if (!eventMessage) {
1649
+ return;
1650
+ }
2920
1651
  const _capture = props => {
2921
- return super.captureStatelessImmediate(distinctId, event, props, {
2922
- timestamp,
2923
- disableGeoip,
2924
- uuid
1652
+ return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
1653
+ timestamp: eventMessage.timestamp,
1654
+ disableGeoip: eventMessage.disableGeoip,
1655
+ uuid: eventMessage.uuid
2925
1656
  });
2926
1657
  };
2927
1658
  const capturePromise = Promise.resolve().then(async () => {
@@ -2955,8 +1686,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
2955
1686
  // No matter what - capture the event
2956
1687
  _capture({
2957
1688
  ...additionalProperties,
2958
- ...properties,
2959
- $groups: groups
1689
+ ...(eventMessage.properties || {}),
1690
+ $groups: eventMessage.groups || groups
2960
1691
  });
2961
1692
  });
2962
1693
  await capturePromise;
@@ -3059,7 +1790,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3059
1790
  return undefined;
3060
1791
  }
3061
1792
  flagDetail = remoteResponse.response;
3062
- response = getFeatureFlagValue(flagDetail);
1793
+ response = core.getFeatureFlagValue(flagDetail);
3063
1794
  requestId = remoteResponse?.requestId;
3064
1795
  }
3065
1796
  const featureFlagReportedKey = `${key}_${response}`;
@@ -3108,19 +1839,17 @@ class PostHogBackendClient extends PostHogCoreStateless {
3108
1839
  let response = undefined;
3109
1840
  const localEvaluationEnabled = this.featureFlagsPoller !== undefined;
3110
1841
  if (localEvaluationEnabled) {
3111
- // Try to get match value locally if not provided
3112
- if (!matchValue) {
3113
- matchValue = await this.getFeatureFlag(key, distinctId, {
3114
- ...options,
3115
- onlyEvaluateLocally: true,
3116
- sendFeatureFlagEvents: false
3117
- });
3118
- }
3119
- if (matchValue) {
3120
- response = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue);
1842
+ // Ensure flags are loaded before checking for the specific flag
1843
+ await this.featureFlagsPoller?.loadFeatureFlags();
1844
+ const flag = this.featureFlagsPoller?.featureFlagsByKey[key];
1845
+ if (flag) {
1846
+ const result = await this.featureFlagsPoller?.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties, matchValue);
1847
+ if (result) {
1848
+ matchValue = result.value;
1849
+ response = result.payload;
1850
+ }
3121
1851
  }
3122
1852
  }
3123
- //}
3124
1853
  // set defaults
3125
1854
  if (onlyEvaluateLocally == undefined) {
3126
1855
  onlyEvaluateLocally = false;
@@ -3128,10 +1857,6 @@ class PostHogBackendClient extends PostHogCoreStateless {
3128
1857
  if (sendFeatureFlagEvents == undefined) {
3129
1858
  sendFeatureFlagEvents = true;
3130
1859
  }
3131
- // set defaults
3132
- if (onlyEvaluateLocally == undefined) {
3133
- onlyEvaluateLocally = false;
3134
- }
3135
1860
  const payloadWasLocallyEvaluated = response !== undefined;
3136
1861
  if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
3137
1862
  response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
@@ -3175,7 +1900,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
3175
1900
  async getAllFlagsAndPayloads(distinctId, options) {
3176
1901
  const {
3177
1902
  groups,
3178
- disableGeoip
1903
+ disableGeoip,
1904
+ flagKeys
3179
1905
  } = options || {};
3180
1906
  let {
3181
1907
  onlyEvaluateLocally,
@@ -3189,7 +1915,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3189
1915
  if (onlyEvaluateLocally == undefined) {
3190
1916
  onlyEvaluateLocally = false;
3191
1917
  }
3192
- const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties);
1918
+ const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties, flagKeys);
3193
1919
  let featureFlags = {};
3194
1920
  let featureFlagPayloads = {};
3195
1921
  let fallbackToFlags = true;
@@ -3199,7 +1925,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3199
1925
  fallbackToFlags = localEvaluationResult.fallbackToFlags;
3200
1926
  }
3201
1927
  if (fallbackToFlags && !onlyEvaluateLocally) {
3202
- const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip);
1928
+ const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeys);
3203
1929
  featureFlags = {
3204
1930
  ...featureFlags,
3205
1931
  ...(remoteEvaluationResult.flags || {})
@@ -3225,9 +1951,9 @@ class PostHogBackendClient extends PostHogCoreStateless {
3225
1951
  disableGeoip
3226
1952
  }, distinctId);
3227
1953
  }
3228
- /**
3229
- * Reloads the feature flag definitions from the server for local evaluation.
3230
- * This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
1954
+ /**
1955
+ * Reloads the feature flag definitions from the server for local evaluation.
1956
+ * This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
3231
1957
  */
3232
1958
  async reloadFeatureFlags() {
3233
1959
  await this.featureFlagsPoller?.loadFeatureFlags(true);
@@ -3240,7 +1966,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3240
1966
  if (!this.options.personalApiKey) {
3241
1967
  return undefined;
3242
1968
  }
3243
- const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config/`;
1969
+ const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config?token=${encodeURIComponent(this.apiKey)}`;
3244
1970
  const options = {
3245
1971
  method: 'GET',
3246
1972
  headers: {
@@ -3252,7 +1978,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3252
1978
  let abortTimeout = null;
3253
1979
  if (this.options.requestTimeout && typeof this.options.requestTimeout === 'number') {
3254
1980
  const controller = new AbortController();
3255
- abortTimeout = safeSetTimeout(() => {
1981
+ abortTimeout = core.safeSetTimeout(() => {
3256
1982
  controller.abort();
3257
1983
  }, this.options.requestTimeout);
3258
1984
  options.signal = controller.signal;
@@ -3299,6 +2025,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3299
2025
  // Use properties directly from options if they exist
3300
2026
  const finalPersonProperties = sendFeatureFlagsOptions?.personProperties || {};
3301
2027
  const finalGroupProperties = sendFeatureFlagsOptions?.groupProperties || {};
2028
+ const flagKeys = sendFeatureFlagsOptions?.flagKeys;
3302
2029
  // Check if we should only evaluate locally
3303
2030
  const onlyEvaluateLocally = sendFeatureFlagsOptions?.onlyEvaluateLocally ?? false;
3304
2031
  // If onlyEvaluateLocally is true, only use local evaluation
@@ -3313,7 +2040,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
3313
2040
  personProperties: finalPersonProperties,
3314
2041
  groupProperties: finalGroupProperties,
3315
2042
  disableGeoip,
3316
- onlyEvaluateLocally: true
2043
+ onlyEvaluateLocally: true,
2044
+ flagKeys
3317
2045
  });
3318
2046
  } else {
3319
2047
  // If onlyEvaluateLocally is true but we don't have local flags, return empty
@@ -3331,7 +2059,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
3331
2059
  personProperties: finalPersonProperties,
3332
2060
  groupProperties: finalGroupProperties,
3333
2061
  disableGeoip,
3334
- onlyEvaluateLocally: true
2062
+ onlyEvaluateLocally: true,
2063
+ flagKeys
3335
2064
  });
3336
2065
  }
3337
2066
  // Fall back to remote evaluation if local evaluation is not available
@@ -3371,6 +2100,26 @@ class PostHogBackendClient extends PostHogCoreStateless {
3371
2100
  }, distinctId, additionalProperties);
3372
2101
  return await this.captureImmediate(evtMsg);
3373
2102
  }
2103
+ _runBeforeSend(eventMessage) {
2104
+ const beforeSend = this.options.before_send;
2105
+ if (!beforeSend) {
2106
+ return eventMessage;
2107
+ }
2108
+ const fns = Array.isArray(beforeSend) ? beforeSend : [beforeSend];
2109
+ let result = eventMessage;
2110
+ for (const fn of fns) {
2111
+ result = fn(result);
2112
+ if (!result) {
2113
+ this.logMsgIfDebug(() => console.info(`Event '${eventMessage.event}' was rejected in beforeSend function`));
2114
+ return null;
2115
+ }
2116
+ if (!result.properties || Object.keys(result.properties).length === 0) {
2117
+ const message = `Event '${result.event}' has no properties after beforeSend function, this is likely an error.`;
2118
+ this.logMsgIfDebug(() => console.warn(message));
2119
+ }
2120
+ }
2121
+ return result;
2122
+ }
3374
2123
  }
3375
2124
 
3376
2125
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
@@ -3453,7 +2202,7 @@ function node(getModule) {
3453
2202
  }
3454
2203
  return {
3455
2204
  filename: filename ? decodeURI(filename) : undefined,
3456
- module: getModule ? getModule(filename) : undefined,
2205
+ module: undefined,
3457
2206
  function: functionName,
3458
2207
  lineno: _parseIntOrUndefined(lineMatch[3]),
3459
2208
  colno: _parseIntOrUndefined(lineMatch[4]),
@@ -3470,8 +2219,8 @@ function node(getModule) {
3470
2219
  return undefined;
3471
2220
  };
3472
2221
  }
3473
- /**
3474
- * Does this filename look like it's part of the app code?
2222
+ /**
2223
+ * Does this filename look like it's part of the app code?
3475
2224
  */
3476
2225
  function filenameIsInApp(filename, isNative = false) {
3477
2226
  const isInternal = isNative || filename &&
@@ -3492,10 +2241,10 @@ function _parseIntOrUndefined(input) {
3492
2241
  return parseInt(input || '', 10) || undefined;
3493
2242
  }
3494
2243
  function nodeStackLineParser(getModule) {
3495
- return [90, node(getModule)];
2244
+ return [90, node()];
3496
2245
  }
3497
2246
  function createStackParser(getModule) {
3498
- const parsers = [nodeStackLineParser(getModule)];
2247
+ const parsers = [nodeStackLineParser()];
3499
2248
  const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map(p => p[1]);
3500
2249
  return (stack, skipFirstLines = 0) => {
3501
2250
  const frames = [];