posthog-node 5.6.0 → 5.8.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,51 +1,32 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var path = require('path');
6
4
  var node_fs = require('node:fs');
7
5
  var node_readline = require('node:readline');
6
+ var core = require('@posthog/core');
8
7
 
9
- function _interopNamespace(e) {
10
- if (e && e.__esModule) return e;
11
- var n = Object.create(null);
12
- if (e) {
13
- Object.keys(e).forEach(function (k) {
14
- if (k !== 'default') {
15
- var d = Object.getOwnPropertyDescriptor(e, k);
16
- Object.defineProperty(n, k, d.get ? d : {
17
- enumerable: true,
18
- get: function () { return e[k]; }
19
- });
20
- }
21
- });
22
- }
23
- n["default"] = e;
24
- return Object.freeze(n);
25
- }
26
-
27
- /**
28
- * @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
8
+ /**
9
+ * @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
29
10
  */
30
- /**
31
- * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
32
- *
33
- * ### Usage
34
- *
35
- * Sentry.init({
36
- * dsn: 'https://example',
37
- * integrations: [
38
- * new PostHogSentryIntegration(posthog)
39
- * ]
40
- * })
41
- *
42
- * Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
43
- *
44
- * @param {Object} [posthog] The posthog object
45
- * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
46
- * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
47
- * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
48
- * @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
11
+ /**
12
+ * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
13
+ *
14
+ * ### Usage
15
+ *
16
+ * Sentry.init({
17
+ * dsn: 'https://example',
18
+ * integrations: [
19
+ * new PostHogSentryIntegration(posthog)
20
+ * ]
21
+ * })
22
+ *
23
+ * Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
24
+ *
25
+ * @param {Object} [posthog] The posthog object
26
+ * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
27
+ * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
28
+ * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
29
+ * @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
49
30
  */
50
31
  const NAME = 'posthog-node';
51
32
  function createEventProcessor(_posthog, {
@@ -139,411 +120,175 @@ class PostHogSentryIntegration {
139
120
  }
140
121
  PostHogSentryIntegration.POSTHOG_ID_TAG = 'posthog_distinct_id';
141
122
 
142
- // vendor from: https://github.com/LiosK/uuidv7/blob/f30b7a7faff73afbce0b27a46c638310f96912ba/src/index.ts
143
- // https://github.com/LiosK/uuidv7#license
144
- /**
145
- * uuidv7: An experimental implementation of the proposed UUID Version 7
146
- *
147
- * @license Apache-2.0
148
- * @copyright 2021-2023 LiosK
149
- * @packageDocumentation
150
- */
151
- const DIGITS = "0123456789abcdef";
152
- /** Represents a UUID as a 16-byte byte array. */
153
- class UUID {
154
- /** @param bytes - The 16-byte byte array representation. */
155
- constructor(bytes) {
156
- this.bytes = bytes;
157
- }
158
- /**
159
- * Creates an object from the internal representation, a 16-byte byte array
160
- * containing the binary UUID representation in the big-endian byte order.
161
- *
162
- * This method does NOT shallow-copy the argument, and thus the created object
163
- * holds the reference to the underlying buffer.
164
- *
165
- * @throws TypeError if the length of the argument is not 16.
166
- */
167
- static ofInner(bytes) {
168
- if (bytes.length !== 16) {
169
- throw new TypeError("not 128-bit length");
170
- }
171
- else {
172
- return new UUID(bytes);
173
- }
174
- }
175
- /**
176
- * Builds a byte array from UUIDv7 field values.
177
- *
178
- * @param unixTsMs - A 48-bit `unix_ts_ms` field value.
179
- * @param randA - A 12-bit `rand_a` field value.
180
- * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value.
181
- * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value.
182
- * @throws RangeError if any field value is out of the specified range.
183
- */
184
- static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
185
- if (!Number.isInteger(unixTsMs) ||
186
- !Number.isInteger(randA) ||
187
- !Number.isInteger(randBHi) ||
188
- !Number.isInteger(randBLo) ||
189
- unixTsMs < 0 ||
190
- randA < 0 ||
191
- randBHi < 0 ||
192
- randBLo < 0 ||
193
- unixTsMs > 281474976710655 ||
194
- randA > 0xfff ||
195
- randBHi > 1073741823 ||
196
- randBLo > 4294967295) {
197
- throw new RangeError("invalid field value");
198
- }
199
- const bytes = new Uint8Array(16);
200
- bytes[0] = unixTsMs / 2 ** 40;
201
- bytes[1] = unixTsMs / 2 ** 32;
202
- bytes[2] = unixTsMs / 2 ** 24;
203
- bytes[3] = unixTsMs / 2 ** 16;
204
- bytes[4] = unixTsMs / 2 ** 8;
205
- bytes[5] = unixTsMs;
206
- bytes[6] = 0x70 | (randA >>> 8);
207
- bytes[7] = randA;
208
- bytes[8] = 0x80 | (randBHi >>> 24);
209
- bytes[9] = randBHi >>> 16;
210
- bytes[10] = randBHi >>> 8;
211
- bytes[11] = randBHi;
212
- bytes[12] = randBLo >>> 24;
213
- bytes[13] = randBLo >>> 16;
214
- bytes[14] = randBLo >>> 8;
215
- bytes[15] = randBLo;
216
- return new UUID(bytes);
217
- }
218
- /**
219
- * Builds a byte array from a string representation.
220
- *
221
- * This method accepts the following formats:
222
- *
223
- * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b`
224
- * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b`
225
- * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}`
226
- * - RFC 4122 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b`
227
- *
228
- * Leading and trailing whitespaces represents an error.
229
- *
230
- * @throws SyntaxError if the argument could not parse as a valid UUID string.
231
- */
232
- static parse(uuid) {
233
- let hex = undefined;
234
- switch (uuid.length) {
235
- case 32:
236
- hex = /^[0-9a-f]{32}$/i.exec(uuid)?.[0];
237
- break;
238
- case 36:
239
- hex =
240
- /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
241
- .exec(uuid)
242
- ?.slice(1, 6)
243
- .join("");
244
- break;
245
- case 38:
246
- hex =
247
- /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i
248
- .exec(uuid)
249
- ?.slice(1, 6)
250
- .join("");
251
- break;
252
- case 45:
253
- hex =
254
- /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
255
- .exec(uuid)
256
- ?.slice(1, 6)
257
- .join("");
258
- break;
259
- }
260
- if (hex) {
261
- const inner = new Uint8Array(16);
262
- for (let i = 0; i < 16; i += 4) {
263
- const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
264
- inner[i + 0] = n >>> 24;
265
- inner[i + 1] = n >>> 16;
266
- inner[i + 2] = n >>> 8;
267
- inner[i + 3] = n;
268
- }
269
- return new UUID(inner);
270
- }
271
- else {
272
- throw new SyntaxError("could not parse UUID string");
273
- }
274
- }
275
- /**
276
- * @returns The 8-4-4-4-12 canonical hexadecimal string representation
277
- * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`).
278
- */
279
- toString() {
280
- let text = "";
281
- for (let i = 0; i < this.bytes.length; i++) {
282
- text += DIGITS.charAt(this.bytes[i] >>> 4);
283
- text += DIGITS.charAt(this.bytes[i] & 0xf);
284
- if (i === 3 || i === 5 || i === 7 || i === 9) {
285
- text += "-";
286
- }
287
- }
288
- return text;
289
- }
290
- /**
291
- * @returns The 32-digit hexadecimal representation without hyphens
292
- * (`0189dcd553117d408db09496a2eef37b`).
293
- */
294
- toHex() {
295
- let text = "";
296
- for (let i = 0; i < this.bytes.length; i++) {
297
- text += DIGITS.charAt(this.bytes[i] >>> 4);
298
- text += DIGITS.charAt(this.bytes[i] & 0xf);
299
- }
300
- return text;
301
- }
302
- /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */
303
- toJSON() {
304
- return this.toString();
305
- }
306
- /**
307
- * Reports the variant field value of the UUID or, if appropriate, "NIL" or
308
- * "MAX".
309
- *
310
- * For convenience, this method reports "NIL" or "MAX" if `this` represents
311
- * the Nil or Max UUID, although the Nil and Max UUIDs are technically
312
- * subsumed under the variants `0b0` and `0b111`, respectively.
313
- */
314
- getVariant() {
315
- const n = this.bytes[8] >>> 4;
316
- if (n < 0) {
317
- throw new Error("unreachable");
318
- }
319
- else if (n <= 0b0111) {
320
- return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0";
321
- }
322
- else if (n <= 0b1011) {
323
- return "VAR_10";
324
- }
325
- else if (n <= 0b1101) {
326
- return "VAR_110";
327
- }
328
- else if (n <= 0b1111) {
329
- return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED";
330
- }
331
- else {
332
- throw new Error("unreachable");
333
- }
334
- }
335
- /**
336
- * Returns the version field value of the UUID or `undefined` if the UUID does
337
- * not have the variant field value of `0b10`.
338
- */
339
- getVersion() {
340
- return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined;
341
- }
342
- /** Creates an object from `this`. */
343
- clone() {
344
- return new UUID(this.bytes.slice(0));
345
- }
346
- /** Returns true if `this` is equivalent to `other`. */
347
- equals(other) {
348
- return this.compareTo(other) === 0;
349
- }
350
- /**
351
- * Returns a negative integer, zero, or positive integer if `this` is less
352
- * than, equal to, or greater than `other`, respectively.
353
- */
354
- compareTo(other) {
355
- for (let i = 0; i < 16; i++) {
356
- const diff = this.bytes[i] - other.bytes[i];
357
- if (diff !== 0) {
358
- return Math.sign(diff);
359
- }
360
- }
361
- return 0;
362
- }
363
- }
364
- /**
365
- * Encapsulates the monotonic counter state.
366
- *
367
- * This class provides APIs to utilize a separate counter state from that of the
368
- * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to
369
- * the default {@link generate} method, this class has {@link generateOrAbort}
370
- * that is useful to absolutely guarantee the monotonically increasing order of
371
- * generated UUIDs. See their respective documentation for details.
372
- */
373
- class V7Generator {
374
- /**
375
- * Creates a generator object with the default random number generator, or
376
- * with the specified one if passed as an argument. The specified random
377
- * number generator should be cryptographically strong and securely seeded.
378
- */
379
- constructor(randomNumberGenerator) {
380
- this.timestamp = 0;
381
- this.counter = 0;
382
- this.random = randomNumberGenerator ?? getDefaultRandom();
383
- }
384
- /**
385
- * Generates a new UUIDv7 object from the current timestamp, or resets the
386
- * generator upon significant timestamp rollback.
387
- *
388
- * This method returns a monotonically increasing UUID by reusing the previous
389
- * timestamp even if the up-to-date timestamp is smaller than the immediately
390
- * preceding UUID's. However, when such a clock rollback is considered
391
- * significant (i.e., by more than ten seconds), this method resets the
392
- * generator and returns a new UUID based on the given timestamp, breaking the
393
- * increasing order of UUIDs.
394
- *
395
- * See {@link generateOrAbort} for the other mode of generation and
396
- * {@link generateOrResetCore} for the low-level primitive.
397
- */
398
- generate() {
399
- return this.generateOrResetCore(Date.now(), 10000);
400
- }
401
- /**
402
- * Generates a new UUIDv7 object from the current timestamp, or returns
403
- * `undefined` upon significant timestamp rollback.
404
- *
405
- * This method returns a monotonically increasing UUID by reusing the previous
406
- * timestamp even if the up-to-date timestamp is smaller than the immediately
407
- * preceding UUID's. However, when such a clock rollback is considered
408
- * significant (i.e., by more than ten seconds), this method aborts and
409
- * returns `undefined` immediately.
410
- *
411
- * See {@link generate} for the other mode of generation and
412
- * {@link generateOrAbortCore} for the low-level primitive.
413
- */
414
- generateOrAbort() {
415
- return this.generateOrAbortCore(Date.now(), 10000);
416
- }
417
- /**
418
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the
419
- * generator upon significant timestamp rollback.
420
- *
421
- * This method is equivalent to {@link generate} except that it takes a custom
422
- * timestamp and clock rollback allowance.
423
- *
424
- * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
425
- * considered significant. A suggested value is `10_000` (milliseconds).
426
- * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
427
- */
428
- generateOrResetCore(unixTsMs, rollbackAllowance) {
429
- let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
430
- if (value === undefined) {
431
- // reset state and resume
432
- this.timestamp = 0;
433
- value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
434
- }
435
- return value;
436
- }
437
- /**
438
- * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns
439
- * `undefined` upon significant timestamp rollback.
440
- *
441
- * This method is equivalent to {@link generateOrAbort} except that it takes a
442
- * custom timestamp and clock rollback allowance.
443
- *
444
- * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
445
- * considered significant. A suggested value is `10_000` (milliseconds).
446
- * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
447
- */
448
- generateOrAbortCore(unixTsMs, rollbackAllowance) {
449
- const MAX_COUNTER = 4398046511103;
450
- if (!Number.isInteger(unixTsMs) ||
451
- unixTsMs < 1 ||
452
- unixTsMs > 281474976710655) {
453
- throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
454
- }
455
- else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) {
456
- throw new RangeError("`rollbackAllowance` out of reasonable range");
457
- }
458
- if (unixTsMs > this.timestamp) {
459
- this.timestamp = unixTsMs;
460
- this.resetCounter();
461
- }
462
- else if (unixTsMs + rollbackAllowance >= this.timestamp) {
463
- // go on with previous timestamp if new one is not much smaller
464
- this.counter++;
465
- if (this.counter > MAX_COUNTER) {
466
- // increment timestamp at counter overflow
467
- this.timestamp++;
468
- this.resetCounter();
469
- }
470
- }
471
- else {
472
- // abort if clock went backwards to unbearable extent
473
- return undefined;
474
- }
475
- return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32());
476
- }
477
- /** Initializes the counter at a 42-bit random integer. */
478
- resetCounter() {
479
- this.counter =
480
- this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff);
481
- }
482
- /**
483
- * Generates a new UUIDv4 object utilizing the random number generator inside.
484
- *
485
- * @internal
486
- */
487
- generateV4() {
488
- const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
489
- bytes[6] = 0x40 | (bytes[6] >>> 4);
490
- bytes[8] = 0x80 | (bytes[8] >>> 2);
491
- return UUID.ofInner(bytes);
492
- }
493
- }
494
- /** A global flag to force use of cryptographically strong RNG. */
495
- // declare const UUIDV7_DENY_WEAK_RNG: boolean;
496
- /** Returns the default random number generator available in the environment. */
497
- const getDefaultRandom = () => {
498
- // fix: crypto isn't available in react-native, always use Math.random
499
- // // detect Web Crypto API
500
- // if (
501
- // typeof crypto !== "undefined" &&
502
- // typeof crypto.getRandomValues !== "undefined"
503
- // ) {
504
- // return new BufferedCryptoRandom();
505
- // } else {
506
- // // fall back on Math.random() unless the flag is set to true
507
- // if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) {
508
- // throw new Error("no cryptographically strong RNG available");
509
- // }
510
- // return {
511
- // nextUint32: (): number =>
512
- // Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +
513
- // Math.trunc(Math.random() * 0x1_0000),
514
- // };
515
- // }
516
- return {
517
- nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 +
518
- Math.trunc(Math.random() * 65536),
519
- };
520
- };
521
- // /**
522
- // * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small
523
- // * buffer by default to avoid both unbearable throughput decline in some
524
- // * environments and the waste of time and space for unused values.
525
- // */
526
- // class BufferedCryptoRandom {
527
- // private readonly buffer = new Uint32Array(8);
528
- // private cursor = 0xffff;
529
- // nextUint32(): number {
530
- // if (this.cursor >= this.buffer.length) {
531
- // crypto.getRandomValues(this.buffer);
532
- // this.cursor = 0;
533
- // }
534
- // return this.buffer[this.cursor++];
535
- // }
536
- // }
537
- let defaultGenerator;
538
- /**
539
- * Generates a UUIDv7 string.
540
- *
541
- * @returns The 8-4-4-4-12 canonical hexadecimal string representation
542
- * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").
543
- */
544
- const uuidv7 = () => uuidv7obj().toString();
545
- /** Generates a UUIDv7 object. */
546
- const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
123
+ /*! For license information please see uuidv7.mjs.LICENSE.txt */
124
+ /**
125
+ * uuidv7: An experimental implementation of the proposed UUID Version 7
126
+ *
127
+ * @license Apache-2.0
128
+ * @copyright 2021-2023 LiosK
129
+ * @packageDocumentation
130
+ */ const DIGITS = "0123456789abcdef";
131
+ class UUID {
132
+ static ofInner(bytes) {
133
+ if (16 === bytes.length) return new UUID(bytes);
134
+ throw new TypeError("not 128-bit length");
135
+ }
136
+ static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
137
+ 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");
138
+ const bytes = new Uint8Array(16);
139
+ bytes[0] = unixTsMs / 2 ** 40;
140
+ bytes[1] = unixTsMs / 2 ** 32;
141
+ bytes[2] = unixTsMs / 2 ** 24;
142
+ bytes[3] = unixTsMs / 2 ** 16;
143
+ bytes[4] = unixTsMs / 256;
144
+ bytes[5] = unixTsMs;
145
+ bytes[6] = 0x70 | randA >>> 8;
146
+ bytes[7] = randA;
147
+ bytes[8] = 0x80 | randBHi >>> 24;
148
+ bytes[9] = randBHi >>> 16;
149
+ bytes[10] = randBHi >>> 8;
150
+ bytes[11] = randBHi;
151
+ bytes[12] = randBLo >>> 24;
152
+ bytes[13] = randBLo >>> 16;
153
+ bytes[14] = randBLo >>> 8;
154
+ bytes[15] = randBLo;
155
+ return new UUID(bytes);
156
+ }
157
+ static parse(uuid) {
158
+ let hex;
159
+ switch(uuid.length){
160
+ case 32:
161
+ var _exec;
162
+ hex = null == (_exec = /^[0-9a-f]{32}$/i.exec(uuid)) ? void 0 : _exec[0];
163
+ break;
164
+ case 36:
165
+ var _exec1;
166
+ 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("");
167
+ break;
168
+ case 38:
169
+ var _exec2;
170
+ 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("");
171
+ break;
172
+ case 45:
173
+ var _exec3;
174
+ 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("");
175
+ break;
176
+ }
177
+ if (hex) {
178
+ const inner = new Uint8Array(16);
179
+ for(let i = 0; i < 16; i += 4){
180
+ const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
181
+ inner[i + 0] = n >>> 24;
182
+ inner[i + 1] = n >>> 16;
183
+ inner[i + 2] = n >>> 8;
184
+ inner[i + 3] = n;
185
+ }
186
+ return new UUID(inner);
187
+ }
188
+ throw new SyntaxError("could not parse UUID string");
189
+ }
190
+ toString() {
191
+ let text = "";
192
+ for(let i = 0; i < this.bytes.length; i++){
193
+ text += DIGITS.charAt(this.bytes[i] >>> 4);
194
+ text += DIGITS.charAt(0xf & this.bytes[i]);
195
+ if (3 === i || 5 === i || 7 === i || 9 === i) text += "-";
196
+ }
197
+ return text;
198
+ }
199
+ toHex() {
200
+ let text = "";
201
+ for(let i = 0; i < this.bytes.length; i++){
202
+ text += DIGITS.charAt(this.bytes[i] >>> 4);
203
+ text += DIGITS.charAt(0xf & this.bytes[i]);
204
+ }
205
+ return text;
206
+ }
207
+ toJSON() {
208
+ return this.toString();
209
+ }
210
+ getVariant() {
211
+ const n = this.bytes[8] >>> 4;
212
+ if (n < 0) throw new Error("unreachable");
213
+ if (n <= 7) return this.bytes.every((e)=>0 === e) ? "NIL" : "VAR_0";
214
+ if (n <= 11) return "VAR_10";
215
+ if (n <= 13) return "VAR_110";
216
+ if (n <= 15) return this.bytes.every((e)=>0xff === e) ? "MAX" : "VAR_RESERVED";
217
+ else throw new Error("unreachable");
218
+ }
219
+ getVersion() {
220
+ return "VAR_10" === this.getVariant() ? this.bytes[6] >>> 4 : void 0;
221
+ }
222
+ clone() {
223
+ return new UUID(this.bytes.slice(0));
224
+ }
225
+ equals(other) {
226
+ return 0 === this.compareTo(other);
227
+ }
228
+ compareTo(other) {
229
+ for(let i = 0; i < 16; i++){
230
+ const diff = this.bytes[i] - other.bytes[i];
231
+ if (0 !== diff) return Math.sign(diff);
232
+ }
233
+ return 0;
234
+ }
235
+ constructor(bytes){
236
+ this.bytes = bytes;
237
+ }
238
+ }
239
+ class V7Generator {
240
+ generate() {
241
+ return this.generateOrResetCore(Date.now(), 10000);
242
+ }
243
+ generateOrAbort() {
244
+ return this.generateOrAbortCore(Date.now(), 10000);
245
+ }
246
+ generateOrResetCore(unixTsMs, rollbackAllowance) {
247
+ let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
248
+ if (void 0 === value) {
249
+ this.timestamp = 0;
250
+ value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
251
+ }
252
+ return value;
253
+ }
254
+ generateOrAbortCore(unixTsMs, rollbackAllowance) {
255
+ const MAX_COUNTER = 0x3ffffffffff;
256
+ if (!Number.isInteger(unixTsMs) || unixTsMs < 1 || unixTsMs > 0xffffffffffff) throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
257
+ if (rollbackAllowance < 0 || rollbackAllowance > 0xffffffffffff) throw new RangeError("`rollbackAllowance` out of reasonable range");
258
+ if (unixTsMs > this.timestamp) {
259
+ this.timestamp = unixTsMs;
260
+ this.resetCounter();
261
+ } else {
262
+ if (!(unixTsMs + rollbackAllowance >= this.timestamp)) return;
263
+ this.counter++;
264
+ if (this.counter > MAX_COUNTER) {
265
+ this.timestamp++;
266
+ this.resetCounter();
267
+ }
268
+ }
269
+ return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & 2 ** 30 - 1, this.random.nextUint32());
270
+ }
271
+ resetCounter() {
272
+ this.counter = 0x400 * this.random.nextUint32() + (0x3ff & this.random.nextUint32());
273
+ }
274
+ generateV4() {
275
+ const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
276
+ bytes[6] = 0x40 | bytes[6] >>> 4;
277
+ bytes[8] = 0x80 | bytes[8] >>> 2;
278
+ return UUID.ofInner(bytes);
279
+ }
280
+ constructor(randomNumberGenerator){
281
+ this.timestamp = 0;
282
+ this.counter = 0;
283
+ this.random = null != randomNumberGenerator ? randomNumberGenerator : getDefaultRandom();
284
+ }
285
+ }
286
+ const getDefaultRandom = ()=>({
287
+ nextUint32: ()=>0x10000 * Math.trunc(0x10000 * Math.random()) + Math.trunc(0x10000 * Math.random())
288
+ });
289
+ let defaultGenerator;
290
+ const uuidv7 = ()=>uuidv7obj().toString();
291
+ const uuidv7obj = ()=>(defaultGenerator || (defaultGenerator = new V7Generator())).generate();
547
292
 
548
293
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
549
294
  // Licensed under the MIT License
@@ -668,6 +413,7 @@ function isBuiltin(candidate, className) {
668
413
  }
669
414
 
670
415
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
416
+ // Licensed under the MIT License
671
417
  async function propertiesFromUnknownInput(stackParser, frameModifiers, input, hint) {
672
418
  const providedMechanism = hint && hint.mechanism;
673
419
  const mechanism = providedMechanism || {
@@ -756,10 +502,10 @@ function getObjectClassName(obj) {
756
502
  // ignore errors here
757
503
  }
758
504
  }
759
- /**
760
- * Given any captured exception, extract its keys and create a sorted
761
- * and truncated list that will be used inside the event message.
762
- * eg. `Non-error exception captured with keys: foo, bar, baz`
505
+ /**
506
+ * Given any captured exception, extract its keys and create a sorted
507
+ * and truncated list that will be used inside the event message.
508
+ * eg. `Non-error exception captured with keys: foo, bar, baz`
763
509
  */
764
510
  function extractExceptionKeysForMessage(exception, maxLength = 40) {
765
511
  const keys = Object.keys(convertToPlainObject(exception));
@@ -789,13 +535,13 @@ function truncate(str, max = 0) {
789
535
  }
790
536
  return str.length <= max ? str : `${str.slice(0, max)}...`;
791
537
  }
792
- /**
793
- * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
794
- * non-enumerable properties attached.
795
- *
796
- * @param value Initial source that we have to transform in order for it to be usable by the serializer
797
- * @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor
798
- * an Error.
538
+ /**
539
+ * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
540
+ * non-enumerable properties attached.
541
+ *
542
+ * @param value Initial source that we have to transform in order for it to be usable by the serializer
543
+ * @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor
544
+ * an Error.
799
545
  */
800
546
  function convertToPlainObject(value) {
801
547
  if (isError(value)) {
@@ -843,8 +589,8 @@ function serializeEventTarget(target) {
843
589
  return '<unknown>';
844
590
  }
845
591
  }
846
- /**
847
- * Extracts stack frames from the error and builds an Exception
592
+ /**
593
+ * Extracts stack frames from the error and builds an Exception
848
594
  */
849
595
  async function exceptionFromError(stackParser, frameModifiers, error) {
850
596
  const exception = {
@@ -863,8 +609,8 @@ async function exceptionFromError(stackParser, frameModifiers, error) {
863
609
  }
864
610
  return exception;
865
611
  }
866
- /**
867
- * Extracts stack frames from the error.stack string
612
+ /**
613
+ * Extracts stack frames from the error.stack string
868
614
  */
869
615
  function parseStackFrames(stackParser, error) {
870
616
  return applyChunkIds(stackParser(error.stack || '', 1), stackParser);
@@ -879,6 +625,71 @@ function applyChunkIds(frames, parser) {
879
625
  return frames;
880
626
  }
881
627
 
628
+ const ObjProto = Object.prototype;
629
+ const type_utils_toString = ObjProto.toString;
630
+ const isNumber = (x)=>'[object Number]' == type_utils_toString.call(x);
631
+
632
+ function clampToRange(value, min, max, logger, fallbackValue) {
633
+ if (min > max) {
634
+ logger.warn('min cannot be greater than max.');
635
+ min = max;
636
+ }
637
+ if (isNumber(value)) if (value > max) {
638
+ logger.warn(' cannot be greater than max: ' + max + '. Using max value instead.');
639
+ return max;
640
+ } else {
641
+ if (!(value < min)) return value;
642
+ logger.warn(' cannot be less than min: ' + min + '. Using min value instead.');
643
+ return min;
644
+ }
645
+ logger.warn(' must be a number. using max or fallback. max: ' + max + ', fallback: ' + fallbackValue);
646
+ return clampToRange(max, min, max, logger);
647
+ }
648
+
649
+ class BucketedRateLimiter {
650
+ constructor(_options){
651
+ this._options = _options;
652
+ this._buckets = {};
653
+ this._refillBuckets = ()=>{
654
+ Object.keys(this._buckets).forEach((key)=>{
655
+ const newTokens = this._getBucket(key) + this._refillRate;
656
+ if (newTokens >= this._bucketSize) delete this._buckets[key];
657
+ else this._setBucket(key, newTokens);
658
+ });
659
+ };
660
+ this._getBucket = (key)=>this._buckets[String(key)];
661
+ this._setBucket = (key, value)=>{
662
+ this._buckets[String(key)] = value;
663
+ };
664
+ this.consumeRateLimit = (key)=>{
665
+ var _this__getBucket;
666
+ let tokens = null != (_this__getBucket = this._getBucket(key)) ? _this__getBucket : this._bucketSize;
667
+ tokens = Math.max(tokens - 1, 0);
668
+ if (0 === tokens) return true;
669
+ this._setBucket(key, tokens);
670
+ const hasReachedZero = 0 === tokens;
671
+ if (hasReachedZero) {
672
+ var _this__onBucketRateLimited, _this;
673
+ null == (_this__onBucketRateLimited = (_this = this)._onBucketRateLimited) || _this__onBucketRateLimited.call(_this, key);
674
+ }
675
+ return hasReachedZero;
676
+ };
677
+ this._onBucketRateLimited = this._options._onBucketRateLimited;
678
+ this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger);
679
+ this._refillRate = clampToRange(this._options.refillRate, 0, this._bucketSize, this._options._logger);
680
+ this._refillInterval = clampToRange(this._options.refillInterval, 0, 86400000, this._options._logger);
681
+ setInterval(()=>{
682
+ this._refillBuckets();
683
+ }, this._refillInterval);
684
+ }
685
+ }
686
+
687
+ function safeSetTimeout(fn, timeout) {
688
+ const t = setTimeout(fn, timeout);
689
+ (null == t ? void 0 : t.unref) && (null == t || t.unref());
690
+ return t;
691
+ }
692
+
882
693
  const SHUTDOWN_TIMEOUT = 2000;
883
694
  class ErrorTracking {
884
695
  static async buildEventMessage(error, hint, distinctId, additionalProperties) {
@@ -900,9 +711,20 @@ class ErrorTracking {
900
711
  }
901
712
  };
902
713
  }
903
- constructor(client, options) {
714
+ constructor(client, options, _logger) {
904
715
  this.client = client;
905
716
  this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
717
+ this._logger = _logger;
718
+ // by default captures ten exceptions before rate limiting by exception type
719
+ // refills at a rate of one token / 10 second period
720
+ // e.g. will capture 1 exception rate limited exception every 10 seconds until burst ends
721
+ this._rateLimiter = new BucketedRateLimiter({
722
+ refillRate: 1,
723
+ bucketSize: 10,
724
+ refillInterval: 10000,
725
+ // ten seconds in milliseconds
726
+ _logger: this._logger
727
+ });
906
728
  this.startAutocaptureIfEnabled();
907
729
  }
908
730
  startAutocaptureIfEnabled() {
@@ -912,7 +734,16 @@ class ErrorTracking {
912
734
  }
913
735
  }
914
736
  onException(exception, hint) {
915
- void ErrorTracking.buildEventMessage(exception, hint).then(msg => {
737
+ return ErrorTracking.buildEventMessage(exception, hint).then(msg => {
738
+ const exceptionProperties = msg.properties;
739
+ const exceptionType = exceptionProperties?.$exception_list[0].type ?? 'Exception';
740
+ const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
741
+ if (isRateLimited) {
742
+ this._logger.info('Skipping exception capture because of client rate limiting.', {
743
+ exception: exceptionType
744
+ });
745
+ return;
746
+ }
916
747
  this.client.capture(msg);
917
748
  });
918
749
  }
@@ -942,6 +773,7 @@ function setupExpressErrorHandler(_posthog, app) {
942
773
  }
943
774
 
944
775
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
776
+ // Licensed under the MIT License
945
777
  /** Creates a function that gets the module name from a filename */
946
778
  function createGetModuleFromFilename(basePath = process.argv[1] ? path.dirname(process.argv[1]) : process.cwd(), isWindows = path.sep === '\\') {
947
779
  const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
@@ -1021,6 +853,7 @@ class ReduceableCache {
1021
853
  }
1022
854
 
1023
855
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
856
+ // Licensed under the MIT License
1024
857
  const LRU_FILE_CONTENTS_CACHE = new ReduceableCache(25);
1025
858
  const LRU_FILE_CONTENTS_FS_READ_FAILED = new ReduceableCache(20);
1026
859
  const DEFAULT_LINES_OF_CONTEXT = 7;
@@ -1083,8 +916,8 @@ async function addSourceContext(frames) {
1083
916
  LRU_FILE_CONTENTS_CACHE.reduce();
1084
917
  return frames;
1085
918
  }
1086
- /**
1087
- * Extracts lines from a file and stores them in a cache.
919
+ /**
920
+ * Extracts lines from a file and stores them in a cache.
1088
921
  */
1089
922
  function getContextLinesFromFile(path, ranges, output) {
1090
923
  return new Promise(resolve => {
@@ -1168,8 +1001,8 @@ function addSourceContextToFrames(frames, cache) {
1168
1001
  }
1169
1002
  }
1170
1003
  }
1171
- /**
1172
- * Resolves context lines before and after the given line number and appends them to the frame;
1004
+ /**
1005
+ * Resolves context lines before and after the given line number and appends them to the frame;
1173
1006
  */
1174
1007
  function addContextToFrame(lineno, frame, contents) {
1175
1008
  // When there is no line number in the frame, attaching context is nonsensical and will even break grouping.
@@ -1207,28 +1040,28 @@ function addContextToFrame(lineno, frame, contents) {
1207
1040
  frame.post_context.push(line);
1208
1041
  }
1209
1042
  }
1210
- /**
1211
- * Clears the context lines from a frame, used to reset a frame to its original state
1212
- * if we fail to resolve all context lines for it.
1043
+ /**
1044
+ * Clears the context lines from a frame, used to reset a frame to its original state
1045
+ * if we fail to resolve all context lines for it.
1213
1046
  */
1214
1047
  function clearLineContext(frame) {
1215
1048
  delete frame.pre_context;
1216
1049
  delete frame.context_line;
1217
1050
  delete frame.post_context;
1218
1051
  }
1219
- /**
1220
- * Determines if context lines should be skipped for a file.
1221
- * - .min.(mjs|cjs|js) files are and not useful since they dont point to the original source
1222
- * - node: prefixed modules are part of the runtime and cannot be resolved to a file
1223
- * - data: skip json, wasm and inline js https://nodejs.org/api/esm.html#data-imports
1052
+ /**
1053
+ * Determines if context lines should be skipped for a file.
1054
+ * - .min.(mjs|cjs|js) files are and not useful since they dont point to the original source
1055
+ * - node: prefixed modules are part of the runtime and cannot be resolved to a file
1056
+ * - data: skip json, wasm and inline js https://nodejs.org/api/esm.html#data-imports
1224
1057
  */
1225
1058
  function shouldSkipContextLinesForFile(path) {
1226
1059
  // Test the most common prefix and extension first. These are the ones we
1227
1060
  // are most likely to see in user applications and are the ones we can break out of first.
1228
1061
  return path.startsWith('node:') || path.endsWith('.min.js') || path.endsWith('.min.cjs') || path.endsWith('.min.mjs') || path.startsWith('data:');
1229
1062
  }
1230
- /**
1231
- * Determines if we should skip contextlines based off the max lineno and colno values.
1063
+ /**
1064
+ * Determines if we should skip contextlines based off the max lineno and colno values.
1232
1065
  */
1233
1066
  function shouldSkipContextLinesForFrame(frame) {
1234
1067
  if (frame.lineno !== undefined && frame.lineno > MAX_CONTEXTLINES_LINENO) {
@@ -1239,8 +1072,8 @@ function shouldSkipContextLinesForFrame(frame) {
1239
1072
  }
1240
1073
  return false;
1241
1074
  }
1242
- /**
1243
- * Checks if we have all the contents that we need in the cache.
1075
+ /**
1076
+ * Checks if we have all the contents that we need in the cache.
1244
1077
  */
1245
1078
  function rangeExistsInContentCache(file, range) {
1246
1079
  const contents = LRU_FILE_CONTENTS_CACHE.get(file);
@@ -1254,9 +1087,9 @@ function rangeExistsInContentCache(file, range) {
1254
1087
  }
1255
1088
  return true;
1256
1089
  }
1257
- /**
1258
- * Creates contiguous ranges of lines to read from a file. In the case where context lines overlap,
1259
- * the ranges are merged to create a single range.
1090
+ /**
1091
+ * Creates contiguous ranges of lines to read from a file. In the case where context lines overlap,
1092
+ * the ranges are merged to create a single range.
1260
1093
  */
1261
1094
  function makeLineReaderRanges(lines) {
1262
1095
  if (!lines.length) {
@@ -1301,8 +1134,8 @@ function makeRangeStart(line) {
1301
1134
  function makeRangeEnd(line) {
1302
1135
  return line + DEFAULT_LINES_OF_CONTEXT;
1303
1136
  }
1304
- /**
1305
- * Get or init map value
1137
+ /**
1138
+ * Get or init map value
1306
1139
  */
1307
1140
  function emplace(map, key, contents) {
1308
1141
  const value = map.get(key);
@@ -1342,1092 +1175,18 @@ function snipLine(line, colno) {
1342
1175
  return newLine;
1343
1176
  }
1344
1177
 
1345
- var version = "5.6.0";
1178
+ var version = "5.8.0";
1346
1179
 
1347
- var PostHogPersistedProperty;
1348
- (function (PostHogPersistedProperty) {
1349
- PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
1350
- PostHogPersistedProperty["DistinctId"] = "distinct_id";
1351
- PostHogPersistedProperty["Props"] = "props";
1352
- PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
1353
- PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
1354
- PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
1355
- PostHogPersistedProperty["BootstrapFeatureFlagDetails"] = "bootstrap_feature_flag_details";
1356
- PostHogPersistedProperty["BootstrapFeatureFlags"] = "bootstrap_feature_flags";
1357
- PostHogPersistedProperty["BootstrapFeatureFlagPayloads"] = "bootstrap_feature_flag_payloads";
1358
- PostHogPersistedProperty["OverrideFeatureFlags"] = "override_feature_flags";
1359
- PostHogPersistedProperty["Queue"] = "queue";
1360
- PostHogPersistedProperty["OptedOut"] = "opted_out";
1361
- PostHogPersistedProperty["SessionId"] = "session_id";
1362
- PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
1363
- PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
1364
- PostHogPersistedProperty["PersonProperties"] = "person_properties";
1365
- PostHogPersistedProperty["GroupProperties"] = "group_properties";
1366
- PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
1367
- PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
1368
- PostHogPersistedProperty["SessionReplay"] = "session_replay";
1369
- PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
1370
- PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
1371
- PostHogPersistedProperty["Surveys"] = "surveys";
1372
- PostHogPersistedProperty["RemoteConfig"] = "remote_config";
1373
- PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
1374
- })(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
1375
- // Any key prefixed with `attr__` can be added
1376
- var Compression;
1377
- (function (Compression) {
1378
- Compression["GZipJS"] = "gzip-js";
1379
- Compression["Base64"] = "base64";
1380
- })(Compression || (Compression = {}));
1381
- var SurveyPosition;
1382
- (function (SurveyPosition) {
1383
- SurveyPosition["TopLeft"] = "top_left";
1384
- SurveyPosition["TopCenter"] = "top_center";
1385
- SurveyPosition["TopRight"] = "top_right";
1386
- SurveyPosition["MiddleLeft"] = "middle_left";
1387
- SurveyPosition["MiddleCenter"] = "middle_center";
1388
- SurveyPosition["MiddleRight"] = "middle_right";
1389
- SurveyPosition["Left"] = "left";
1390
- SurveyPosition["Right"] = "right";
1391
- SurveyPosition["Center"] = "center";
1392
- })(SurveyPosition || (SurveyPosition = {}));
1393
- var SurveyWidgetType;
1394
- (function (SurveyWidgetType) {
1395
- SurveyWidgetType["Button"] = "button";
1396
- SurveyWidgetType["Tab"] = "tab";
1397
- SurveyWidgetType["Selector"] = "selector";
1398
- })(SurveyWidgetType || (SurveyWidgetType = {}));
1399
- var SurveyType;
1400
- (function (SurveyType) {
1401
- SurveyType["Popover"] = "popover";
1402
- SurveyType["API"] = "api";
1403
- SurveyType["Widget"] = "widget";
1404
- })(SurveyType || (SurveyType = {}));
1405
- var SurveyQuestionDescriptionContentType;
1406
- (function (SurveyQuestionDescriptionContentType) {
1407
- SurveyQuestionDescriptionContentType["Html"] = "html";
1408
- SurveyQuestionDescriptionContentType["Text"] = "text";
1409
- })(SurveyQuestionDescriptionContentType || (SurveyQuestionDescriptionContentType = {}));
1410
- var SurveyRatingDisplay;
1411
- (function (SurveyRatingDisplay) {
1412
- SurveyRatingDisplay["Number"] = "number";
1413
- SurveyRatingDisplay["Emoji"] = "emoji";
1414
- })(SurveyRatingDisplay || (SurveyRatingDisplay = {}));
1415
- var SurveyQuestionType;
1416
- (function (SurveyQuestionType) {
1417
- SurveyQuestionType["Open"] = "open";
1418
- SurveyQuestionType["MultipleChoice"] = "multiple_choice";
1419
- SurveyQuestionType["SingleChoice"] = "single_choice";
1420
- SurveyQuestionType["Rating"] = "rating";
1421
- SurveyQuestionType["Link"] = "link";
1422
- })(SurveyQuestionType || (SurveyQuestionType = {}));
1423
- var SurveyQuestionBranchingType;
1424
- (function (SurveyQuestionBranchingType) {
1425
- SurveyQuestionBranchingType["NextQuestion"] = "next_question";
1426
- SurveyQuestionBranchingType["End"] = "end";
1427
- SurveyQuestionBranchingType["ResponseBased"] = "response_based";
1428
- SurveyQuestionBranchingType["SpecificQuestion"] = "specific_question";
1429
- })(SurveyQuestionBranchingType || (SurveyQuestionBranchingType = {}));
1430
- var SurveyMatchType;
1431
- (function (SurveyMatchType) {
1432
- SurveyMatchType["Regex"] = "regex";
1433
- SurveyMatchType["NotRegex"] = "not_regex";
1434
- SurveyMatchType["Exact"] = "exact";
1435
- SurveyMatchType["IsNot"] = "is_not";
1436
- SurveyMatchType["Icontains"] = "icontains";
1437
- SurveyMatchType["NotIcontains"] = "not_icontains";
1438
- })(SurveyMatchType || (SurveyMatchType = {}));
1439
- /** Sync with plugin-server/src/types.ts */
1440
- var ActionStepStringMatching;
1441
- (function (ActionStepStringMatching) {
1442
- ActionStepStringMatching["Contains"] = "contains";
1443
- ActionStepStringMatching["Exact"] = "exact";
1444
- ActionStepStringMatching["Regex"] = "regex";
1445
- })(ActionStepStringMatching || (ActionStepStringMatching = {}));
1446
-
1447
- const normalizeFlagsResponse = (flagsResponse) => {
1448
- if ('flags' in flagsResponse) {
1449
- // Convert v2 format to v1 format
1450
- const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
1451
- const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
1452
- return {
1453
- ...flagsResponse,
1454
- featureFlags,
1455
- featureFlagPayloads,
1456
- };
1457
- }
1458
- else {
1459
- // Convert v1 format to v2 format
1460
- const featureFlags = flagsResponse.featureFlags ?? {};
1461
- const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
1462
- const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
1463
- key,
1464
- getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
1465
- ]));
1466
- return {
1467
- ...flagsResponse,
1468
- featureFlags,
1469
- featureFlagPayloads,
1470
- flags,
1471
- };
1472
- }
1473
- };
1474
- function getFlagDetailFromFlagAndPayload(key, value, payload) {
1475
- return {
1476
- key: key,
1477
- enabled: typeof value === 'string' ? true : value,
1478
- variant: typeof value === 'string' ? value : undefined,
1479
- reason: undefined,
1480
- metadata: {
1481
- id: undefined,
1482
- version: undefined,
1483
- payload: payload ? JSON.stringify(payload) : undefined,
1484
- description: undefined,
1485
- },
1486
- };
1487
- }
1488
- /**
1489
- * Get the flag values from the flags v4 response.
1490
- * @param flags - The flags
1491
- * @returns The flag values
1492
- */
1493
- const getFlagValuesFromFlags = (flags) => {
1494
- return Object.fromEntries(Object.entries(flags ?? {})
1495
- .map(([key, detail]) => [key, getFeatureFlagValue(detail)])
1496
- .filter(([, value]) => value !== undefined));
1497
- };
1498
- /**
1499
- * Get the payloads from the flags v4 response.
1500
- * @param flags - The flags
1501
- * @returns The payloads
1502
- */
1503
- const getPayloadsFromFlags = (flags) => {
1504
- const safeFlags = flags ?? {};
1505
- return Object.fromEntries(Object.keys(safeFlags)
1506
- .filter((flag) => {
1507
- const details = safeFlags[flag];
1508
- return details.enabled && details.metadata && details.metadata.payload !== undefined;
1509
- })
1510
- .map((flag) => {
1511
- const payload = safeFlags[flag].metadata?.payload;
1512
- return [flag, payload ? parsePayload(payload) : undefined];
1513
- }));
1514
- };
1515
- const getFeatureFlagValue = (detail) => {
1516
- return detail === undefined ? undefined : detail.variant ?? detail.enabled;
1517
- };
1518
- const parsePayload = (response) => {
1519
- if (typeof response !== 'string') {
1520
- return response;
1521
- }
1522
- try {
1523
- return JSON.parse(response);
1524
- }
1525
- catch {
1526
- return response;
1527
- }
1528
- };
1529
-
1530
- const STRING_FORMAT = 'utf8';
1531
- function assert(truthyValue, message) {
1532
- if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
1533
- throw new Error(message);
1534
- }
1535
- }
1536
- function isEmpty(truthyValue) {
1537
- if (truthyValue.trim().length === 0) {
1538
- return true;
1539
- }
1540
- return false;
1541
- }
1542
- function removeTrailingSlash(url) {
1543
- return url?.replace(/\/+$/, '');
1544
- }
1545
- async function retriable(fn, props) {
1546
- let lastError = null;
1547
- for (let i = 0; i < props.retryCount + 1; i++) {
1548
- if (i > 0) {
1549
- // don't wait when it's the last try
1550
- await new Promise((r) => setTimeout(r, props.retryDelay));
1551
- }
1552
- try {
1553
- const res = await fn();
1554
- return res;
1555
- }
1556
- catch (e) {
1557
- lastError = e;
1558
- if (!props.retryCheck(e)) {
1559
- throw e;
1560
- }
1561
- }
1562
- }
1563
- throw lastError;
1564
- }
1565
- function currentISOTime() {
1566
- return new Date().toISOString();
1567
- }
1568
- function safeSetTimeout(fn, timeout) {
1569
- // NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``
1570
- // rather than setImmediate.
1571
- const t = setTimeout(fn, timeout);
1572
- // We unref if available to prevent Node.js hanging on exit
1573
- t?.unref && t?.unref();
1574
- return t;
1575
- }
1576
- function allSettled(promises) {
1577
- return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
1578
- }
1579
-
1580
- /**
1581
- * Older browsers and some runtimes don't support this yet
1582
- * This API (as of 2025-05-07) is not available on React Native.
1583
- */
1584
- function isGzipSupported() {
1585
- return 'CompressionStream' in globalThis;
1586
- }
1587
- /**
1588
- * Gzip a string using Compression Streams API if it's available
1589
- */
1590
- async function gzipCompress(input, isDebug = true) {
1591
- try {
1592
- // Turn the string into a stream using a Blob, and then compress it
1593
- const dataStream = new Blob([input], {
1594
- type: 'text/plain',
1595
- }).stream();
1596
- const compressedStream = dataStream.pipeThrough(new CompressionStream('gzip'));
1597
- // Using a Response to easily extract the readablestream value. Decoding into a string for fetch
1598
- return await new Response(compressedStream).blob();
1599
- }
1600
- catch (error) {
1601
- if (isDebug) {
1602
- console.error('Failed to gzip compress data', error);
1603
- }
1604
- return null;
1605
- }
1606
- }
1607
-
1608
- class SimpleEventEmitter {
1609
- constructor() {
1610
- this.events = {};
1611
- this.events = {};
1612
- }
1613
- on(event, listener) {
1614
- if (!this.events[event]) {
1615
- this.events[event] = [];
1616
- }
1617
- this.events[event].push(listener);
1618
- return () => {
1619
- this.events[event] = this.events[event].filter((x) => x !== listener);
1620
- };
1621
- }
1622
- emit(event, payload) {
1623
- for (const listener of this.events[event] || []) {
1624
- listener(payload);
1625
- }
1626
- for (const listener of this.events['*'] || []) {
1627
- listener(event, payload);
1628
- }
1629
- }
1630
- }
1631
-
1632
- class PostHogFetchHttpError extends Error {
1633
- constructor(response, reqByteLength) {
1634
- super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength);
1635
- this.response = response;
1636
- this.reqByteLength = reqByteLength;
1637
- this.name = 'PostHogFetchHttpError';
1638
- }
1639
- get status() {
1640
- return this.response.status;
1641
- }
1642
- get text() {
1643
- return this.response.text();
1644
- }
1645
- get json() {
1646
- return this.response.json();
1647
- }
1648
- }
1649
- class PostHogFetchNetworkError extends Error {
1650
- constructor(error) {
1651
- // TRICKY: "cause" is a newer property but is just ignored otherwise. Cast to any to ignore the type issue.
1652
- // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
1653
- // @ts-ignore
1654
- super('Network error while fetching PostHog', error instanceof Error ? { cause: error } : {});
1655
- this.error = error;
1656
- this.name = 'PostHogFetchNetworkError';
1657
- }
1658
- }
1659
- async function logFlushError(err) {
1660
- if (err instanceof PostHogFetchHttpError) {
1661
- let text = '';
1662
- try {
1663
- text = await err.text;
1664
- }
1665
- catch { }
1666
- console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
1667
- }
1668
- else {
1669
- console.error('Error while flushing PostHog', err);
1670
- }
1671
- return Promise.resolve();
1672
- }
1673
- function isPostHogFetchError(err) {
1674
- return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
1675
- }
1676
- function isPostHogFetchContentTooLargeError(err) {
1677
- return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413;
1678
- }
1679
- var QuotaLimitedFeature;
1680
- (function (QuotaLimitedFeature) {
1681
- QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
1682
- QuotaLimitedFeature["Recordings"] = "recordings";
1683
- })(QuotaLimitedFeature || (QuotaLimitedFeature = {}));
1684
- class PostHogCoreStateless {
1685
- constructor(apiKey, options) {
1686
- this.flushPromise = null;
1687
- this.shutdownPromise = null;
1688
- this.pendingPromises = {};
1689
- // internal
1690
- this._events = new SimpleEventEmitter();
1691
- this._isInitialized = false;
1692
- assert(apiKey, "You must pass your PostHog project's api key.");
1693
- this.apiKey = apiKey;
1694
- this.host = removeTrailingSlash(options?.host || 'https://us.i.posthog.com');
1695
- this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 20;
1696
- this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
1697
- this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
1698
- this.flushInterval = options?.flushInterval ?? 10000;
1699
- this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
1700
- // If enable is explicitly set to false we override the optout
1701
- this.defaultOptIn = options?.defaultOptIn ?? true;
1702
- this.disableSurveys = options?.disableSurveys ?? false;
1703
- this._retryOptions = {
1704
- retryCount: options?.fetchRetryCount ?? 3,
1705
- retryDelay: options?.fetchRetryDelay ?? 3000,
1706
- retryCheck: isPostHogFetchError,
1707
- };
1708
- this.requestTimeout = options?.requestTimeout ?? 10000; // 10 seconds
1709
- this.featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 3000; // 3 seconds
1710
- this.remoteConfigRequestTimeoutMs = options?.remoteConfigRequestTimeoutMs ?? 3000; // 3 seconds
1711
- this.disableGeoip = options?.disableGeoip ?? true;
1712
- this.disabled = options?.disabled ?? false;
1713
- this.historicalMigration = options?.historicalMigration ?? false;
1714
- // Init promise allows the derived class to block calls until it is ready
1715
- this._initPromise = Promise.resolve();
1716
- this._isInitialized = true;
1717
- this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
1718
- }
1719
- logMsgIfDebug(fn) {
1720
- if (this.isDebug) {
1721
- fn();
1722
- }
1723
- }
1724
- wrap(fn) {
1725
- if (this.disabled) {
1726
- this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
1727
- return;
1728
- }
1729
- if (this._isInitialized) {
1730
- // NOTE: We could also check for the "opt in" status here...
1731
- return fn();
1732
- }
1733
- this._initPromise.then(() => fn());
1734
- }
1735
- getCommonEventProperties() {
1736
- return {
1737
- $lib: this.getLibraryId(),
1738
- $lib_version: this.getLibraryVersion(),
1739
- };
1740
- }
1741
- get optedOut() {
1742
- return this.getPersistedProperty(PostHogPersistedProperty.OptedOut) ?? !this.defaultOptIn;
1743
- }
1744
- async optIn() {
1745
- this.wrap(() => {
1746
- this.setPersistedProperty(PostHogPersistedProperty.OptedOut, false);
1747
- });
1748
- }
1749
- async optOut() {
1750
- this.wrap(() => {
1751
- this.setPersistedProperty(PostHogPersistedProperty.OptedOut, true);
1752
- });
1753
- }
1754
- on(event, cb) {
1755
- return this._events.on(event, cb);
1756
- }
1757
- debug(enabled = true) {
1758
- this.removeDebugCallback?.();
1759
- if (enabled) {
1760
- const removeDebugCallback = this.on('*', (event, payload) => console.log('PostHog Debug', event, payload));
1761
- this.removeDebugCallback = () => {
1762
- removeDebugCallback();
1763
- this.removeDebugCallback = undefined;
1764
- };
1765
- }
1766
- }
1767
- get isDebug() {
1768
- return !!this.removeDebugCallback;
1769
- }
1770
- get isDisabled() {
1771
- return this.disabled;
1772
- }
1773
- buildPayload(payload) {
1774
- return {
1775
- distinct_id: payload.distinct_id,
1776
- event: payload.event,
1777
- properties: {
1778
- ...(payload.properties || {}),
1779
- ...this.getCommonEventProperties(), // Common PH props
1780
- },
1781
- };
1782
- }
1783
- addPendingPromise(promise) {
1784
- const promiseUUID = uuidv7();
1785
- this.pendingPromises[promiseUUID] = promise;
1786
- promise
1787
- .catch(() => { })
1788
- .finally(() => {
1789
- delete this.pendingPromises[promiseUUID];
1790
- });
1791
- return promise;
1792
- }
1793
- /***
1794
- *** TRACKING
1795
- ***/
1796
- identifyStateless(distinctId, properties, options) {
1797
- this.wrap(() => {
1798
- // The properties passed to identifyStateless are event properties.
1799
- // To add person properties, pass in all person properties to the `$set` and `$set_once` keys.
1800
- const payload = {
1801
- ...this.buildPayload({
1802
- distinct_id: distinctId,
1803
- event: '$identify',
1804
- properties,
1805
- }),
1806
- };
1807
- this.enqueue('identify', payload, options);
1808
- });
1809
- }
1810
- async identifyStatelessImmediate(distinctId, properties, options) {
1811
- const payload = {
1812
- ...this.buildPayload({
1813
- distinct_id: distinctId,
1814
- event: '$identify',
1815
- properties,
1816
- }),
1817
- };
1818
- await this.sendImmediate('identify', payload, options);
1819
- }
1820
- captureStateless(distinctId, event, properties, options) {
1821
- this.wrap(() => {
1822
- const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
1823
- this.enqueue('capture', payload, options);
1824
- });
1825
- }
1826
- async captureStatelessImmediate(distinctId, event, properties, options) {
1827
- const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
1828
- await this.sendImmediate('capture', payload, options);
1829
- }
1830
- aliasStateless(alias, distinctId, properties, options) {
1831
- this.wrap(() => {
1832
- const payload = this.buildPayload({
1833
- event: '$create_alias',
1834
- distinct_id: distinctId,
1835
- properties: {
1836
- ...(properties || {}),
1837
- distinct_id: distinctId,
1838
- alias,
1839
- },
1840
- });
1841
- this.enqueue('alias', payload, options);
1842
- });
1843
- }
1844
- async aliasStatelessImmediate(alias, distinctId, properties, options) {
1845
- const payload = this.buildPayload({
1846
- event: '$create_alias',
1847
- distinct_id: distinctId,
1848
- properties: {
1849
- ...(properties || {}),
1850
- distinct_id: distinctId,
1851
- alias,
1852
- },
1853
- });
1854
- await this.sendImmediate('alias', payload, options);
1855
- }
1856
- /***
1857
- *** GROUPS
1858
- ***/
1859
- groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties) {
1860
- this.wrap(() => {
1861
- const payload = this.buildPayload({
1862
- distinct_id: distinctId || `$${groupType}_${groupKey}`,
1863
- event: '$groupidentify',
1864
- properties: {
1865
- $group_type: groupType,
1866
- $group_key: groupKey,
1867
- $group_set: groupProperties || {},
1868
- ...(eventProperties || {}),
1869
- },
1870
- });
1871
- this.enqueue('capture', payload, options);
1872
- });
1873
- }
1874
- async getRemoteConfig() {
1875
- await this._initPromise;
1876
- let host = this.host;
1877
- if (host === 'https://us.i.posthog.com') {
1878
- host = 'https://us-assets.i.posthog.com';
1879
- }
1880
- else if (host === 'https://eu.i.posthog.com') {
1881
- host = 'https://eu-assets.i.posthog.com';
1882
- }
1883
- const url = `${host}/array/${this.apiKey}/config`;
1884
- const fetchOptions = {
1885
- method: 'GET',
1886
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1887
- };
1888
- // Don't retry remote config API calls
1889
- return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.remoteConfigRequestTimeoutMs)
1890
- .then((response) => response.json())
1891
- .catch((error) => {
1892
- this.logMsgIfDebug(() => console.error('Remote config could not be loaded', error));
1893
- this._events.emit('error', error);
1894
- return undefined;
1895
- });
1896
- }
1897
- /***
1898
- *** FEATURE FLAGS
1899
- ***/
1900
- async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
1901
- await this._initPromise;
1902
- const url = `${this.host}/flags/?v=2&config=true`;
1903
- const fetchOptions = {
1904
- method: 'POST',
1905
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1906
- body: JSON.stringify({
1907
- token: this.apiKey,
1908
- distinct_id: distinctId,
1909
- groups,
1910
- person_properties: personProperties,
1911
- group_properties: groupProperties,
1912
- ...extraPayload,
1913
- }),
1914
- };
1915
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Flags URL', url));
1916
- // Don't retry /flags API calls
1917
- return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
1918
- .then((response) => response.json())
1919
- .then((response) => normalizeFlagsResponse(response))
1920
- .catch((error) => {
1921
- this._events.emit('error', error);
1922
- return undefined;
1923
- });
1924
- }
1925
- async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1926
- await this._initPromise;
1927
- const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
1928
- if (flagDetailResponse === undefined) {
1929
- // If we haven't loaded flags yet, or errored out, we respond with undefined
1930
- return {
1931
- response: undefined,
1932
- requestId: undefined,
1933
- };
1934
- }
1935
- let response = getFeatureFlagValue(flagDetailResponse.response);
1936
- if (response === undefined) {
1937
- // For cases where the flag is unknown, return false
1938
- response = false;
1939
- }
1940
- // If we have flags we either return the value (true or string) or false
1941
- return {
1942
- response,
1943
- requestId: flagDetailResponse.requestId,
1944
- };
1945
- }
1946
- async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1947
- await this._initPromise;
1948
- const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1949
- if (flagsResponse === undefined) {
1950
- return undefined;
1951
- }
1952
- const featureFlags = flagsResponse.flags;
1953
- const flagDetail = featureFlags[key];
1954
- return {
1955
- response: flagDetail,
1956
- requestId: flagsResponse.requestId,
1957
- };
1958
- }
1959
- async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1960
- await this._initPromise;
1961
- const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1962
- if (!payloads) {
1963
- return undefined;
1964
- }
1965
- const response = payloads[key];
1966
- // Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
1967
- if (response === undefined) {
1968
- return null;
1969
- }
1970
- return response;
1971
- }
1972
- async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1973
- await this._initPromise;
1974
- const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate)).payloads;
1975
- return payloads;
1976
- }
1977
- async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1978
- await this._initPromise;
1979
- return await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1980
- }
1981
- async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1982
- await this._initPromise;
1983
- const featureFlagDetails = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1984
- if (!featureFlagDetails) {
1985
- return {
1986
- flags: undefined,
1987
- payloads: undefined,
1988
- requestId: undefined,
1989
- };
1990
- }
1991
- return {
1992
- flags: featureFlagDetails.featureFlags,
1993
- payloads: featureFlagDetails.featureFlagPayloads,
1994
- requestId: featureFlagDetails.requestId,
1995
- };
1996
- }
1997
- async getFeatureFlagDetailsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1998
- await this._initPromise;
1999
- const extraPayload = {};
2000
- if (disableGeoip ?? this.disableGeoip) {
2001
- extraPayload['geoip_disable'] = true;
2002
- }
2003
- if (flagKeysToEvaluate) {
2004
- extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
2005
- }
2006
- const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
2007
- if (flagsResponse === undefined) {
2008
- // We probably errored out, so return undefined
2009
- return undefined;
2010
- }
2011
- // if there's an error on the flagsResponse, log a console error, but don't throw an error
2012
- if (flagsResponse.errorsWhileComputingFlags) {
2013
- 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');
2014
- }
2015
- // Add check for quota limitation on feature flags
2016
- if (flagsResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
2017
- 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');
2018
- return {
2019
- flags: {},
2020
- featureFlags: {},
2021
- featureFlagPayloads: {},
2022
- requestId: flagsResponse?.requestId,
2023
- };
2024
- }
2025
- return flagsResponse;
2026
- }
2027
- /***
2028
- *** SURVEYS
2029
- ***/
2030
- async getSurveysStateless() {
2031
- await this._initPromise;
2032
- if (this.disableSurveys === true) {
2033
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Loading surveys is disabled.'));
2034
- return [];
2035
- }
2036
- const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
2037
- const fetchOptions = {
2038
- method: 'GET',
2039
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
2040
- };
2041
- const response = await this.fetchWithRetry(url, fetchOptions)
2042
- .then((response) => {
2043
- if (response.status !== 200 || !response.json) {
2044
- const msg = `Surveys API could not be loaded: ${response.status}`;
2045
- const error = new Error(msg);
2046
- this.logMsgIfDebug(() => console.error(error));
2047
- this._events.emit('error', new Error(msg));
2048
- return undefined;
2049
- }
2050
- return response.json();
2051
- })
2052
- .catch((error) => {
2053
- this.logMsgIfDebug(() => console.error('Surveys API could not be loaded', error));
2054
- this._events.emit('error', error);
2055
- return undefined;
2056
- });
2057
- const newSurveys = response?.surveys;
2058
- if (newSurveys) {
2059
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from API: ', JSON.stringify(newSurveys)));
2060
- }
2061
- return newSurveys ?? [];
2062
- }
2063
- get props() {
2064
- if (!this._props) {
2065
- this._props = this.getPersistedProperty(PostHogPersistedProperty.Props);
2066
- }
2067
- return this._props || {};
2068
- }
2069
- set props(val) {
2070
- this._props = val;
2071
- }
2072
- async register(properties) {
2073
- this.wrap(() => {
2074
- this.props = {
2075
- ...this.props,
2076
- ...properties,
2077
- };
2078
- this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
2079
- });
2080
- }
2081
- async unregister(property) {
2082
- this.wrap(() => {
2083
- delete this.props[property];
2084
- this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
2085
- });
2086
- }
2087
- /***
2088
- *** QUEUEING AND FLUSHING
2089
- ***/
2090
- enqueue(type, _message, options) {
2091
- this.wrap(() => {
2092
- if (this.optedOut) {
2093
- this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
2094
- return;
2095
- }
2096
- const message = this.prepareMessage(type, _message, options);
2097
- const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2098
- if (queue.length >= this.maxQueueSize) {
2099
- queue.shift();
2100
- this.logMsgIfDebug(() => console.info('Queue is full, the oldest event is dropped.'));
2101
- }
2102
- queue.push({ message });
2103
- this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
2104
- this._events.emit(type, message);
2105
- // Flush queued events if we meet the flushAt length
2106
- if (queue.length >= this.flushAt) {
2107
- this.flushBackground();
2108
- }
2109
- if (this.flushInterval && !this._flushTimer) {
2110
- this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
2111
- }
2112
- });
2113
- }
2114
- async sendImmediate(type, _message, options) {
2115
- if (this.disabled) {
2116
- this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
2117
- return;
2118
- }
2119
- if (!this._isInitialized) {
2120
- await this._initPromise;
2121
- }
2122
- if (this.optedOut) {
2123
- this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
2124
- return;
2125
- }
2126
- const data = {
2127
- api_key: this.apiKey,
2128
- batch: [this.prepareMessage(type, _message, options)],
2129
- sent_at: currentISOTime(),
2130
- };
2131
- if (this.historicalMigration) {
2132
- data.historical_migration = true;
2133
- }
2134
- const payload = JSON.stringify(data);
2135
- const url = `${this.host}/batch/`;
2136
- const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
2137
- const fetchOptions = {
2138
- method: 'POST',
2139
- headers: {
2140
- ...this.getCustomHeaders(),
2141
- 'Content-Type': 'application/json',
2142
- ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
2143
- },
2144
- body: gzippedPayload || payload,
2145
- };
2146
- try {
2147
- await this.fetchWithRetry(url, fetchOptions);
2148
- }
2149
- catch (err) {
2150
- this._events.emit('error', err);
2151
- }
2152
- }
2153
- prepareMessage(type, _message, options) {
2154
- const message = {
2155
- ..._message,
2156
- type: type,
2157
- library: this.getLibraryId(),
2158
- library_version: this.getLibraryVersion(),
2159
- timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
2160
- uuid: options?.uuid ? options.uuid : uuidv7(),
2161
- };
2162
- const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
2163
- if (addGeoipDisableProperty) {
2164
- if (!message.properties) {
2165
- message.properties = {};
2166
- }
2167
- message['properties']['$geoip_disable'] = true;
2168
- }
2169
- if (message.distinctId) {
2170
- message.distinct_id = message.distinctId;
2171
- delete message.distinctId;
2172
- }
2173
- return message;
2174
- }
2175
- clearFlushTimer() {
2176
- if (this._flushTimer) {
2177
- clearTimeout(this._flushTimer);
2178
- this._flushTimer = undefined;
2179
- }
2180
- }
2181
- /**
2182
- * Helper for flushing the queue in the background
2183
- * Avoids unnecessary promise errors
2184
- */
2185
- flushBackground() {
2186
- void this.flush().catch(async (err) => {
2187
- await logFlushError(err);
2188
- });
2189
- }
2190
- /**
2191
- * Flushes the queue
2192
- *
2193
- * This function will return a promise that will resolve when the flush is complete,
2194
- * or reject if there was an error (for example if the server or network is down).
2195
- *
2196
- * If there is already a flush in progress, this function will wait for that flush to complete.
2197
- *
2198
- * It's recommended to do error handling in the callback of the promise.
2199
- *
2200
- * @example
2201
- * posthog.flush().then(() => {
2202
- * console.log('Flush complete')
2203
- * }).catch((err) => {
2204
- * console.error('Flush failed', err)
2205
- * })
2206
- *
2207
- *
2208
- * @throws PostHogFetchHttpError
2209
- * @throws PostHogFetchNetworkError
2210
- * @throws Error
2211
- */
2212
- async flush() {
2213
- // Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
2214
- // Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
2215
- // Use a custom allSettled implementation to avoid issues with patching Promise on RN
2216
- const nextFlushPromise = allSettled([this.flushPromise]).then(() => {
2217
- return this._flush();
2218
- });
2219
- this.flushPromise = nextFlushPromise;
2220
- void this.addPendingPromise(nextFlushPromise);
2221
- allSettled([nextFlushPromise]).then(() => {
2222
- // If there are no others waiting to flush, clear the promise.
2223
- // We don't strictly need to do this, but it could make debugging easier
2224
- if (this.flushPromise === nextFlushPromise) {
2225
- this.flushPromise = null;
2226
- }
2227
- });
2228
- return nextFlushPromise;
2229
- }
2230
- getCustomHeaders() {
2231
- // Don't set the user agent if we're not on a browser. The latest spec allows
2232
- // the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
2233
- // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
2234
- // but browsers such as Chrome and Safari have not caught up.
2235
- const customUserAgent = this.getCustomUserAgent();
2236
- const headers = {};
2237
- if (customUserAgent && customUserAgent !== '') {
2238
- headers['User-Agent'] = customUserAgent;
2239
- }
2240
- return headers;
2241
- }
2242
- async _flush() {
2243
- this.clearFlushTimer();
2244
- await this._initPromise;
2245
- let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2246
- if (!queue.length) {
2247
- return;
2248
- }
2249
- const sentMessages = [];
2250
- const originalQueueLength = queue.length;
2251
- while (queue.length > 0 && sentMessages.length < originalQueueLength) {
2252
- const batchItems = queue.slice(0, this.maxBatchSize);
2253
- const batchMessages = batchItems.map((item) => item.message);
2254
- const persistQueueChange = () => {
2255
- const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2256
- const newQueue = refreshedQueue.slice(batchItems.length);
2257
- this.setPersistedProperty(PostHogPersistedProperty.Queue, newQueue);
2258
- queue = newQueue;
2259
- };
2260
- const data = {
2261
- api_key: this.apiKey,
2262
- batch: batchMessages,
2263
- sent_at: currentISOTime(),
2264
- };
2265
- if (this.historicalMigration) {
2266
- data.historical_migration = true;
2267
- }
2268
- const payload = JSON.stringify(data);
2269
- const url = `${this.host}/batch/`;
2270
- const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
2271
- const fetchOptions = {
2272
- method: 'POST',
2273
- headers: {
2274
- ...this.getCustomHeaders(),
2275
- 'Content-Type': 'application/json',
2276
- ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
2277
- },
2278
- body: gzippedPayload || payload,
2279
- };
2280
- const retryOptions = {
2281
- retryCheck: (err) => {
2282
- // don't automatically retry on 413 errors, we want to reduce the batch size first
2283
- if (isPostHogFetchContentTooLargeError(err)) {
2284
- return false;
2285
- }
2286
- // otherwise, retry on network errors
2287
- return isPostHogFetchError(err);
2288
- },
2289
- };
2290
- try {
2291
- await this.fetchWithRetry(url, fetchOptions, retryOptions);
2292
- }
2293
- catch (err) {
2294
- if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
2295
- // if we get a 413 error, we want to reduce the batch size and try again
2296
- this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
2297
- this.logMsgIfDebug(() => console.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`));
2298
- // do not persist the queue change, we want to retry the same batch
2299
- continue;
2300
- }
2301
- // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
2302
- // 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
2303
- if (!(err instanceof PostHogFetchNetworkError)) {
2304
- persistQueueChange();
2305
- }
2306
- this._events.emit('error', err);
2307
- throw err;
2308
- }
2309
- persistQueueChange();
2310
- sentMessages.push(...batchMessages);
2311
- }
2312
- this._events.emit('flush', sentMessages);
2313
- }
2314
- async fetchWithRetry(url, options, retryOptions, requestTimeout) {
2315
- var _a;
2316
- (_a = AbortSignal).timeout ?? (_a.timeout = function timeout(ms) {
2317
- const ctrl = new AbortController();
2318
- setTimeout(() => ctrl.abort(), ms);
2319
- return ctrl.signal;
2320
- });
2321
- const body = options.body ? options.body : '';
2322
- let reqByteLength = -1;
2323
- try {
2324
- if (body instanceof Blob) {
2325
- reqByteLength = body.size;
2326
- }
2327
- else {
2328
- reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
2329
- }
2330
- }
2331
- catch {
2332
- if (body instanceof Blob) {
2333
- reqByteLength = body.size;
2334
- }
2335
- else {
2336
- const encoded = new TextEncoder().encode(body);
2337
- reqByteLength = encoded.length;
2338
- }
2339
- }
2340
- return await retriable(async () => {
2341
- let res = null;
2342
- try {
2343
- res = await this.fetch(url, {
2344
- signal: AbortSignal.timeout(requestTimeout ?? this.requestTimeout),
2345
- ...options,
2346
- });
2347
- }
2348
- catch (e) {
2349
- // fetch will only throw on network errors or on timeouts
2350
- throw new PostHogFetchNetworkError(e);
2351
- }
2352
- // If we're in no-cors mode, we can't access the response status
2353
- // We only throw on HTTP errors if we're not in no-cors mode
2354
- // https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
2355
- const isNoCors = options.mode === 'no-cors';
2356
- if (!isNoCors && (res.status < 200 || res.status >= 400)) {
2357
- throw new PostHogFetchHttpError(res, reqByteLength);
2358
- }
2359
- return res;
2360
- }, { ...this._retryOptions, ...retryOptions });
2361
- }
2362
- async _shutdown(shutdownTimeoutMs = 30000) {
2363
- // A little tricky - we want to have a max shutdown time and enforce it, even if that means we have some
2364
- // dangling promises. We'll keep track of the timeout and resolve/reject based on that.
2365
- await this._initPromise;
2366
- let hasTimedOut = false;
2367
- this.clearFlushTimer();
2368
- const doShutdown = async () => {
2369
- try {
2370
- await Promise.all(Object.values(this.pendingPromises));
2371
- while (true) {
2372
- const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2373
- if (queue.length === 0) {
2374
- break;
2375
- }
2376
- // flush again to make sure we send all events, some of which might've been added
2377
- // while we were waiting for the pending promises to resolve
2378
- // For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
2379
- await this.flush();
2380
- if (hasTimedOut) {
2381
- break;
2382
- }
2383
- }
2384
- }
2385
- catch (e) {
2386
- if (!isPostHogFetchError(e)) {
2387
- throw e;
2388
- }
2389
- await logFlushError(e);
2390
- }
2391
- };
2392
- return Promise.race([
2393
- new Promise((_, reject) => {
2394
- safeSetTimeout(() => {
2395
- this.logMsgIfDebug(() => console.error('Timed out while shutting down PostHog'));
2396
- hasTimedOut = true;
2397
- reject('Timeout while shutting down PostHog. Some events may not have been sent.');
2398
- }, shutdownTimeoutMs);
2399
- }),
2400
- doShutdown(),
2401
- ]);
2402
- }
2403
- /**
2404
- * Call shutdown() once before the node process exits, so ensure that all events have been sent and all promises
2405
- * have resolved. Do not use this function if you intend to keep using this PostHog instance after calling it.
2406
- * @param shutdownTimeoutMs
2407
- */
2408
- async shutdown(shutdownTimeoutMs = 30000) {
2409
- if (this.shutdownPromise) {
2410
- 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'));
2411
- }
2412
- else {
2413
- this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
2414
- this.shutdownPromise = null;
2415
- });
2416
- }
2417
- return this.shutdownPromise;
2418
- }
2419
- }
2420
-
2421
- /**
2422
- * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
1180
+ /**
1181
+ * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
2423
1182
  */
2424
1183
  class Lazy {
2425
1184
  constructor(factory) {
2426
1185
  this.factory = factory;
2427
1186
  }
2428
- /**
2429
- * Gets the value, initializing it if necessary.
2430
- * Multiple concurrent calls will share the same initialization promise.
1187
+ /**
1188
+ * Gets the value, initializing it if necessary.
1189
+ * Multiple concurrent calls will share the same initialization promise.
2431
1190
  */
2432
1191
  async getValue() {
2433
1192
  if (this.value !== undefined) {
@@ -2447,15 +1206,15 @@ class Lazy {
2447
1206
  }
2448
1207
  return this.initializationPromise;
2449
1208
  }
2450
- /**
2451
- * Returns true if the value has been initialized.
1209
+ /**
1210
+ * Returns true if the value has been initialized.
2452
1211
  */
2453
1212
  isInitialized() {
2454
1213
  return this.value !== undefined;
2455
1214
  }
2456
- /**
2457
- * Returns a promise that resolves when the value is initialized.
2458
- * If already initialized, resolves immediately.
1215
+ /**
1216
+ * Returns a promise that resolves when the value is initialized.
1217
+ * If already initialized, resolves immediately.
2459
1218
  */
2460
1219
  async waitForInitialization() {
2461
1220
  if (this.isInitialized()) {
@@ -2468,7 +1227,7 @@ class Lazy {
2468
1227
  /// <reference lib="dom" />
2469
1228
  const nodeCrypto = new Lazy(async () => {
2470
1229
  try {
2471
- return await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('crypto')); });
1230
+ return await import('crypto');
2472
1231
  } catch {
2473
1232
  return undefined;
2474
1233
  }
@@ -2584,7 +1343,8 @@ class FeatureFlagsPoller {
2584
1343
  featureFlag = this.featureFlagsByKey[key];
2585
1344
  if (featureFlag !== undefined) {
2586
1345
  try {
2587
- response = await this.computeFlagLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
1346
+ const result = await this.computeFlagAndPayloadLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
1347
+ response = result.value;
2588
1348
  this.logMsgIfDebug(() => console.debug(`Successfully computed flag locally: ${key} -> ${response}`));
2589
1349
  } catch (e) {
2590
1350
  if (e instanceof InconclusiveMatchError) {
@@ -2596,27 +1356,6 @@ class FeatureFlagsPoller {
2596
1356
  }
2597
1357
  return response;
2598
1358
  }
2599
- async computeFeatureFlagPayloadLocally(key, matchValue) {
2600
- await this.loadFeatureFlags();
2601
- let response = undefined;
2602
- if (!this.loadedSuccessfullyOnce) {
2603
- return undefined;
2604
- }
2605
- if (typeof matchValue == 'boolean') {
2606
- response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue.toString()];
2607
- } else if (typeof matchValue == 'string') {
2608
- response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue];
2609
- }
2610
- // Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
2611
- if (response === undefined || response === null) {
2612
- return null;
2613
- }
2614
- try {
2615
- return JSON.parse(response);
2616
- } catch {
2617
- return response;
2618
- }
2619
- }
2620
1359
  async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}, flagKeysToExplicitlyEvaluate) {
2621
1360
  await this.loadFeatureFlags();
2622
1361
  const response = {};
@@ -2625,9 +1364,11 @@ class FeatureFlagsPoller {
2625
1364
  const flagsToEvaluate = flagKeysToExplicitlyEvaluate ? flagKeysToExplicitlyEvaluate.map(key => this.featureFlagsByKey[key]).filter(Boolean) : this.featureFlags;
2626
1365
  await Promise.all(flagsToEvaluate.map(async flag => {
2627
1366
  try {
2628
- const matchValue = await this.computeFlagLocally(flag, distinctId, groups, personProperties, groupProperties);
1367
+ const {
1368
+ value: matchValue,
1369
+ payload: matchPayload
1370
+ } = await this.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties);
2629
1371
  response[flag.key] = matchValue;
2630
- const matchPayload = await this.computeFeatureFlagPayloadLocally(flag.key, matchValue);
2631
1372
  if (matchPayload) {
2632
1373
  payloads[flag.key] = matchPayload;
2633
1374
  }
@@ -2646,7 +1387,30 @@ class FeatureFlagsPoller {
2646
1387
  fallbackToFlags
2647
1388
  };
2648
1389
  }
2649
- async computeFlagLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
1390
+ async computeFlagAndPayloadLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}, matchValue) {
1391
+ // Always ensure flags are loaded for payload computation
1392
+ await this.loadFeatureFlags();
1393
+ if (!this.loadedSuccessfullyOnce) {
1394
+ return {
1395
+ value: false,
1396
+ payload: null
1397
+ };
1398
+ }
1399
+ let flagValue;
1400
+ // If matchValue is provided, use it directly; otherwise evaluate the flag
1401
+ if (matchValue !== undefined) {
1402
+ flagValue = matchValue;
1403
+ } else {
1404
+ flagValue = await this.computeFlagValueLocally(flag, distinctId, groups, personProperties, groupProperties);
1405
+ }
1406
+ // Always compute payload based on the final flagValue (whether provided or computed)
1407
+ const payload = this.getFeatureFlagPayload(flag.key, flagValue);
1408
+ return {
1409
+ value: flagValue,
1410
+ payload
1411
+ };
1412
+ }
1413
+ async computeFlagValueLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
2650
1414
  if (flag.ensure_experience_continuity) {
2651
1415
  throw new InconclusiveMatchError('Flag has experience continuity enabled');
2652
1416
  }
@@ -2671,7 +1435,98 @@ class FeatureFlagsPoller {
2671
1435
  return await this.matchFeatureFlagProperties(flag, distinctId, personProperties);
2672
1436
  }
2673
1437
  }
2674
- async matchFeatureFlagProperties(flag, distinctId, properties) {
1438
+ getFeatureFlagPayload(key, flagValue) {
1439
+ let payload = null;
1440
+ if (flagValue !== false && flagValue !== null && flagValue !== undefined) {
1441
+ if (typeof flagValue == 'boolean') {
1442
+ payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue.toString()] || null;
1443
+ } else if (typeof flagValue == 'string') {
1444
+ payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue] || null;
1445
+ }
1446
+ if (payload !== null && payload !== undefined) {
1447
+ // If payload is already an object, return it directly
1448
+ if (typeof payload === 'object') {
1449
+ return payload;
1450
+ }
1451
+ // If payload is a string, try to parse it as JSON
1452
+ if (typeof payload === 'string') {
1453
+ try {
1454
+ return JSON.parse(payload);
1455
+ } catch {
1456
+ // If parsing fails, return the string as is
1457
+ return payload;
1458
+ }
1459
+ }
1460
+ // For other types, return as is
1461
+ return payload;
1462
+ }
1463
+ }
1464
+ return null;
1465
+ }
1466
+ async evaluateFlagDependency(property, distinctId, properties, evaluationCache) {
1467
+ const targetFlagKey = property.key;
1468
+ if (!this.featureFlagsByKey) {
1469
+ throw new InconclusiveMatchError('Feature flags not available for dependency evaluation');
1470
+ }
1471
+ // Check if dependency_chain is present - it should always be provided for flag dependencies
1472
+ if (!('dependency_chain' in property)) {
1473
+ throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' is missing required 'dependency_chain' field`);
1474
+ }
1475
+ const dependencyChain = property.dependency_chain;
1476
+ // Check for missing or invalid dependency chain (This should never happen, but being defensive)
1477
+ if (!Array.isArray(dependencyChain)) {
1478
+ throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' has an invalid 'dependency_chain' (expected array, got ${typeof dependencyChain})`);
1479
+ }
1480
+ // Handle circular dependency (empty chain means circular) (This should never happen, but being defensive)
1481
+ if (dependencyChain.length === 0) {
1482
+ throw new InconclusiveMatchError(`Circular dependency detected for flag '${targetFlagKey}' (empty dependency chain)`);
1483
+ }
1484
+ // Evaluate all dependencies in the chain order
1485
+ for (const depFlagKey of dependencyChain) {
1486
+ if (!(depFlagKey in evaluationCache)) {
1487
+ // Need to evaluate this dependency first
1488
+ const depFlag = this.featureFlagsByKey[depFlagKey];
1489
+ if (!depFlag) {
1490
+ // Missing flag dependency - cannot evaluate locally
1491
+ throw new InconclusiveMatchError(`Missing flag dependency '${depFlagKey}' for flag '${targetFlagKey}'`);
1492
+ } else if (!depFlag.active) {
1493
+ // Inactive flag evaluates to false
1494
+ evaluationCache[depFlagKey] = false;
1495
+ } else {
1496
+ // Recursively evaluate the dependency
1497
+ try {
1498
+ const depResult = await this.matchFeatureFlagProperties(depFlag, distinctId, properties, evaluationCache);
1499
+ evaluationCache[depFlagKey] = depResult;
1500
+ } catch (error) {
1501
+ // If we can't evaluate a dependency, store throw InconclusiveMatchError(`Missing flag dependency '${depFlagKey}' for flag '${targetFlagKey}'`)
1502
+ throw new InconclusiveMatchError(`Error evaluating flag dependency '${depFlagKey}' for flag '${targetFlagKey}': ${error}`);
1503
+ }
1504
+ }
1505
+ }
1506
+ // Check if dependency evaluation was inconclusive
1507
+ const cachedResult = evaluationCache[depFlagKey];
1508
+ if (cachedResult === null || cachedResult === undefined) {
1509
+ throw new InconclusiveMatchError(`Dependency '${depFlagKey}' could not be evaluated`);
1510
+ }
1511
+ }
1512
+ // The target flag is specified in property.key (This should match the last element in the dependency chain)
1513
+ const targetFlagValue = evaluationCache[targetFlagKey];
1514
+ return this.flagEvaluatesToExpectedValue(property.value, targetFlagValue);
1515
+ }
1516
+ flagEvaluatesToExpectedValue(expectedValue, flagValue) {
1517
+ // If the expected value is a boolean, then return true if the flag evaluated to true (or any string variant)
1518
+ // If the expected value is false, then only return true if the flag evaluated to false.
1519
+ if (typeof expectedValue === 'boolean') {
1520
+ return expectedValue === flagValue || typeof flagValue === 'string' && flagValue !== '' && expectedValue === true;
1521
+ }
1522
+ // If the expected value is a string, then return true if and only if the flag evaluated to the expected value.
1523
+ if (typeof expectedValue === 'string') {
1524
+ return flagValue === expectedValue;
1525
+ }
1526
+ // The `flag_evaluates_to` operator is not supported for numbers and arrays.
1527
+ return false;
1528
+ }
1529
+ async matchFeatureFlagProperties(flag, distinctId, properties, evaluationCache = {}) {
2675
1530
  const flagFilters = flag.filters || {};
2676
1531
  const flagConditions = flagFilters.groups || [];
2677
1532
  let isInconclusive = false;
@@ -2693,7 +1548,7 @@ class FeatureFlagsPoller {
2693
1548
  });
2694
1549
  for (const condition of sortedFlagConditions) {
2695
1550
  try {
2696
- if (await this.isConditionMatch(flag, distinctId, condition, properties)) {
1551
+ if (await this.isConditionMatch(flag, distinctId, condition, properties, evaluationCache)) {
2697
1552
  const variantOverride = condition.variant;
2698
1553
  const flagVariants = flagFilters.multivariate?.variants || [];
2699
1554
  if (variantOverride && flagVariants.some(variant => variant.key === variantOverride)) {
@@ -2719,7 +1574,7 @@ class FeatureFlagsPoller {
2719
1574
  // We can only return False when all conditions are False
2720
1575
  return false;
2721
1576
  }
2722
- async isConditionMatch(flag, distinctId, condition, properties) {
1577
+ async isConditionMatch(flag, distinctId, condition, properties, evaluationCache = {}) {
2723
1578
  const rolloutPercentage = condition.rollout_percentage;
2724
1579
  const warnFunction = msg => {
2725
1580
  this.logMsgIfDebug(() => console.warn(msg));
@@ -2731,8 +1586,7 @@ class FeatureFlagsPoller {
2731
1586
  if (propertyType === 'cohort') {
2732
1587
  matches = matchCohort(prop, properties, this.cohorts, this.debugMode);
2733
1588
  } else if (propertyType === 'flag') {
2734
- this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Flag dependency filters are not supported in local evaluation. ` + `Skipping condition for flag '${flag.key}' with dependency on flag '${prop.key || 'unknown'}'`));
2735
- continue;
1589
+ matches = await this.evaluateFlagDependency(prop, distinctId, properties, evaluationCache);
2736
1590
  } else {
2737
1591
  matches = matchProperty(prop, properties, warnFunction);
2738
1592
  }
@@ -2781,18 +1635,18 @@ class FeatureFlagsPoller {
2781
1635
  await this._loadFeatureFlags();
2782
1636
  }
2783
1637
  }
2784
- /**
2785
- * Returns true if the feature flags poller has loaded successfully at least once and has more than 0 feature flags.
2786
- * This is useful to check if local evaluation is ready before calling getFeatureFlag.
1638
+ /**
1639
+ * Returns true if the feature flags poller has loaded successfully at least once and has more than 0 feature flags.
1640
+ * This is useful to check if local evaluation is ready before calling getFeatureFlag.
2787
1641
  */
2788
1642
  isLocalEvaluationReady() {
2789
1643
  return (this.loadedSuccessfullyOnce ?? false) && (this.featureFlags?.length ?? 0) > 0;
2790
1644
  }
2791
- /**
2792
- * If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
2793
- * until a successful request is made, up to a maximum of 60 seconds.
2794
- *
2795
- * @returns The polling interval to use for the next request.
1645
+ /**
1646
+ * If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
1647
+ * until a successful request is made, up to a maximum of 60 seconds.
1648
+ *
1649
+ * @returns The polling interval to use for the next request.
2796
1650
  */
2797
1651
  getPollingInterval() {
2798
1652
  if (!this.shouldBeginExponentialBackoff) {
@@ -2997,6 +1851,10 @@ function matchProperty(property, propertyValues, warnFunction) {
2997
1851
  case 'is_date_after':
2998
1852
  case 'is_date_before':
2999
1853
  {
1854
+ // Boolean values should never be used with date operations
1855
+ if (typeof value === 'boolean') {
1856
+ throw new InconclusiveMatchError(`Date operations cannot be performed on boolean values`);
1857
+ }
3000
1858
  let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
3001
1859
  if (parsedDate == null) {
3002
1860
  parsedDate = convertToDateTime(value);
@@ -3180,17 +2038,49 @@ class PostHogMemoryStorage {
3180
2038
  }
3181
2039
  }
3182
2040
 
2041
+ const _createLogger = (prefix, logMsgIfDebug) => {
2042
+ const logger = {
2043
+ _log: (level, ...args) => {
2044
+ logMsgIfDebug(() => {
2045
+ const consoleLog = console[level];
2046
+ consoleLog(prefix, ...args);
2047
+ });
2048
+ },
2049
+ info: (...args) => {
2050
+ logger._log('log', ...args);
2051
+ },
2052
+ warn: (...args) => {
2053
+ logger._log('warn', ...args);
2054
+ },
2055
+ error: (...args) => {
2056
+ logger._log('error', ...args);
2057
+ },
2058
+ critical: (...args) => {
2059
+ // Critical errors are always logged to the console
2060
+ // eslint-disable-next-line no-console
2061
+ console.error(prefix, ...args);
2062
+ },
2063
+ uninitializedWarning: methodName => {
2064
+ logger.error(`You must initialize PostHog before calling ${methodName}`);
2065
+ },
2066
+ createLogger: additionalPrefix => _createLogger(`${prefix} ${additionalPrefix}`, logMsgIfDebug)
2067
+ };
2068
+ return logger;
2069
+ };
2070
+ const createLogger = logMsgIfDebug => _createLogger('[PostHog.js]', logMsgIfDebug);
2071
+
3183
2072
  // Standard local evaluation rate limit is 600 per minute (10 per second),
3184
2073
  // so the fastest a poller should ever be set is 100ms.
3185
2074
  const MINIMUM_POLLING_INTERVAL = 100;
3186
2075
  const THIRTY_SECONDS = 30 * 1000;
3187
2076
  const MAX_CACHE_SIZE = 50 * 1000;
3188
2077
  // The actual exported Nodejs API.
3189
- class PostHogBackendClient extends PostHogCoreStateless {
2078
+ class PostHogBackendClient extends core.PostHogCoreStateless {
3190
2079
  constructor(apiKey, options = {}) {
3191
2080
  super(apiKey, options);
3192
2081
  this._memoryStorage = new PostHogMemoryStorage();
3193
2082
  this.options = options;
2083
+ this.logger = createLogger(this.logMsgIfDebug.bind(this));
3194
2084
  this.options.featureFlagsPollingInterval = typeof options.featureFlagsPollingInterval === 'number' ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
3195
2085
  if (options.personalApiKey) {
3196
2086
  if (options.personalApiKey.includes('phc_')) {
@@ -3204,6 +2094,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3204
2094
  personalApiKey: options.personalApiKey,
3205
2095
  projectApiKey: apiKey,
3206
2096
  timeout: options.requestTimeout ?? 10000,
2097
+ // 10 seconds
3207
2098
  host: this.host,
3208
2099
  fetch: options.fetch,
3209
2100
  onError: err => {
@@ -3216,7 +2107,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3216
2107
  });
3217
2108
  }
3218
2109
  }
3219
- this.errorTracking = new ErrorTracking(this, options);
2110
+ this.errorTracking = new ErrorTracking(this, options, this.logger);
3220
2111
  this.distinctIdHasSentFlagCalls = {};
3221
2112
  this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
3222
2113
  }
@@ -3259,11 +2150,25 @@ class PostHogBackendClient extends PostHogCoreStateless {
3259
2150
  disableGeoip,
3260
2151
  uuid
3261
2152
  } = props;
2153
+ // Run before_send if configured
2154
+ const eventMessage = this._runBeforeSend({
2155
+ distinctId,
2156
+ event,
2157
+ properties,
2158
+ groups,
2159
+ sendFeatureFlags,
2160
+ timestamp,
2161
+ disableGeoip,
2162
+ uuid
2163
+ });
2164
+ if (!eventMessage) {
2165
+ return;
2166
+ }
3262
2167
  const _capture = props => {
3263
- super.captureStateless(distinctId, event, props, {
3264
- timestamp,
3265
- disableGeoip,
3266
- uuid
2168
+ super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
2169
+ timestamp: eventMessage.timestamp,
2170
+ disableGeoip: eventMessage.disableGeoip,
2171
+ uuid: eventMessage.uuid
3267
2172
  });
3268
2173
  };
3269
2174
  // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
@@ -3298,8 +2203,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
3298
2203
  // No matter what - capture the event
3299
2204
  _capture({
3300
2205
  ...additionalProperties,
3301
- ...properties,
3302
- $groups: groups
2206
+ ...(eventMessage.properties || {}),
2207
+ $groups: eventMessage.groups || groups
3303
2208
  });
3304
2209
  });
3305
2210
  this.addPendingPromise(capturePromise);
@@ -3318,11 +2223,25 @@ class PostHogBackendClient extends PostHogCoreStateless {
3318
2223
  disableGeoip,
3319
2224
  uuid
3320
2225
  } = props;
2226
+ // Run before_send if configured
2227
+ const eventMessage = this._runBeforeSend({
2228
+ distinctId,
2229
+ event,
2230
+ properties,
2231
+ groups,
2232
+ sendFeatureFlags,
2233
+ timestamp,
2234
+ disableGeoip,
2235
+ uuid
2236
+ });
2237
+ if (!eventMessage) {
2238
+ return;
2239
+ }
3321
2240
  const _capture = props => {
3322
- return super.captureStatelessImmediate(distinctId, event, props, {
3323
- timestamp,
3324
- disableGeoip,
3325
- uuid
2241
+ return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
2242
+ timestamp: eventMessage.timestamp,
2243
+ disableGeoip: eventMessage.disableGeoip,
2244
+ uuid: eventMessage.uuid
3326
2245
  });
3327
2246
  };
3328
2247
  const capturePromise = Promise.resolve().then(async () => {
@@ -3356,8 +2275,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
3356
2275
  // No matter what - capture the event
3357
2276
  _capture({
3358
2277
  ...additionalProperties,
3359
- ...properties,
3360
- $groups: groups
2278
+ ...(eventMessage.properties || {}),
2279
+ $groups: eventMessage.groups || groups
3361
2280
  });
3362
2281
  });
3363
2282
  await capturePromise;
@@ -3460,7 +2379,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3460
2379
  return undefined;
3461
2380
  }
3462
2381
  flagDetail = remoteResponse.response;
3463
- response = getFeatureFlagValue(flagDetail);
2382
+ response = core.getFeatureFlagValue(flagDetail);
3464
2383
  requestId = remoteResponse?.requestId;
3465
2384
  }
3466
2385
  const featureFlagReportedKey = `${key}_${response}`;
@@ -3509,19 +2428,17 @@ class PostHogBackendClient extends PostHogCoreStateless {
3509
2428
  let response = undefined;
3510
2429
  const localEvaluationEnabled = this.featureFlagsPoller !== undefined;
3511
2430
  if (localEvaluationEnabled) {
3512
- // Try to get match value locally if not provided
3513
- if (!matchValue) {
3514
- matchValue = await this.getFeatureFlag(key, distinctId, {
3515
- ...options,
3516
- onlyEvaluateLocally: true,
3517
- sendFeatureFlagEvents: false
3518
- });
3519
- }
3520
- if (matchValue) {
3521
- response = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue);
2431
+ // Ensure flags are loaded before checking for the specific flag
2432
+ await this.featureFlagsPoller?.loadFeatureFlags();
2433
+ const flag = this.featureFlagsPoller?.featureFlagsByKey[key];
2434
+ if (flag) {
2435
+ const result = await this.featureFlagsPoller?.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties, matchValue);
2436
+ if (result) {
2437
+ matchValue = result.value;
2438
+ response = result.payload;
2439
+ }
3522
2440
  }
3523
2441
  }
3524
- //}
3525
2442
  // set defaults
3526
2443
  if (onlyEvaluateLocally == undefined) {
3527
2444
  onlyEvaluateLocally = false;
@@ -3529,10 +2446,6 @@ class PostHogBackendClient extends PostHogCoreStateless {
3529
2446
  if (sendFeatureFlagEvents == undefined) {
3530
2447
  sendFeatureFlagEvents = true;
3531
2448
  }
3532
- // set defaults
3533
- if (onlyEvaluateLocally == undefined) {
3534
- onlyEvaluateLocally = false;
3535
- }
3536
2449
  const payloadWasLocallyEvaluated = response !== undefined;
3537
2450
  if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
3538
2451
  response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
@@ -3627,9 +2540,9 @@ class PostHogBackendClient extends PostHogCoreStateless {
3627
2540
  disableGeoip
3628
2541
  }, distinctId);
3629
2542
  }
3630
- /**
3631
- * Reloads the feature flag definitions from the server for local evaluation.
3632
- * This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
2543
+ /**
2544
+ * Reloads the feature flag definitions from the server for local evaluation.
2545
+ * This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
3633
2546
  */
3634
2547
  async reloadFeatureFlags() {
3635
2548
  await this.featureFlagsPoller?.loadFeatureFlags(true);
@@ -3642,7 +2555,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3642
2555
  if (!this.options.personalApiKey) {
3643
2556
  return undefined;
3644
2557
  }
3645
- const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config/`;
2558
+ const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config?token=${encodeURIComponent(this.apiKey)}`;
3646
2559
  const options = {
3647
2560
  method: 'GET',
3648
2561
  headers: {
@@ -3776,6 +2689,26 @@ class PostHogBackendClient extends PostHogCoreStateless {
3776
2689
  }, distinctId, additionalProperties);
3777
2690
  return await this.captureImmediate(evtMsg);
3778
2691
  }
2692
+ _runBeforeSend(eventMessage) {
2693
+ const beforeSend = this.options.before_send;
2694
+ if (!beforeSend) {
2695
+ return eventMessage;
2696
+ }
2697
+ const fns = Array.isArray(beforeSend) ? beforeSend : [beforeSend];
2698
+ let result = eventMessage;
2699
+ for (const fn of fns) {
2700
+ result = fn(result);
2701
+ if (!result) {
2702
+ this.logMsgIfDebug(() => console.info(`Event '${eventMessage.event}' was rejected in beforeSend function`));
2703
+ return null;
2704
+ }
2705
+ if (!result.properties || Object.keys(result.properties).length === 0) {
2706
+ const message = `Event '${result.event}' has no properties after beforeSend function, this is likely an error.`;
2707
+ this.logMsgIfDebug(() => console.warn(message));
2708
+ }
2709
+ }
2710
+ return result;
2711
+ }
3779
2712
  }
3780
2713
 
3781
2714
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
@@ -3875,8 +2808,8 @@ function node(getModule) {
3875
2808
  return undefined;
3876
2809
  };
3877
2810
  }
3878
- /**
3879
- * Does this filename look like it's part of the app code?
2811
+ /**
2812
+ * Does this filename look like it's part of the app code?
3880
2813
  */
3881
2814
  function filenameIsInApp(filename, isNative = false) {
3882
2815
  const isInternal = isNative || filename &&