@posthog/ai 4.3.1 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/anthropic/{index.cjs.js → index.cjs} +1 -1
  3. package/lib/anthropic/index.cjs.map +1 -0
  4. package/lib/anthropic/index.d.ts +4163 -1
  5. package/lib/anthropic/{index.esm.js → index.mjs} +1 -1
  6. package/lib/anthropic/index.mjs.map +1 -0
  7. package/lib/{index.cjs.js → index.cjs} +1 -1
  8. package/lib/index.cjs.map +1 -0
  9. package/lib/index.d.ts +4325 -163
  10. package/lib/{index.esm.js → index.mjs} +1 -1
  11. package/lib/index.mjs.map +1 -0
  12. package/lib/langchain/{index.cjs.js → index.cjs} +1 -1
  13. package/lib/langchain/index.cjs.map +1 -0
  14. package/lib/langchain/index.d.ts +4163 -1
  15. package/lib/langchain/{index.esm.js → index.mjs} +1 -1
  16. package/lib/langchain/index.mjs.map +1 -0
  17. package/lib/openai/{index.cjs.js → index.cjs} +1 -1
  18. package/lib/openai/index.cjs.map +1 -0
  19. package/lib/openai/index.d.ts +4163 -1
  20. package/lib/openai/{index.esm.js → index.mjs} +1 -1
  21. package/lib/openai/index.mjs.map +1 -0
  22. package/lib/vercel/{index.cjs.js → index.cjs} +1 -1
  23. package/lib/vercel/index.cjs.map +1 -0
  24. package/lib/vercel/index.d.ts +4163 -1
  25. package/lib/vercel/{index.esm.js → index.mjs} +1 -1
  26. package/lib/vercel/index.mjs.map +1 -0
  27. package/package.json +14 -13
  28. package/lib/anthropic/index.cjs.js.map +0 -1
  29. package/lib/anthropic/index.esm.js.map +0 -1
  30. package/lib/index.cjs.js.map +0 -1
  31. package/lib/index.esm.js.map +0 -1
  32. package/lib/langchain/index.cjs.js.map +0 -1
  33. package/lib/langchain/index.esm.js.map +0 -1
  34. package/lib/openai/index.cjs.js.map +0 -1
  35. package/lib/openai/index.esm.js.map +0 -1
  36. package/lib/posthog-ai/index.d.ts +0 -1
  37. package/lib/posthog-ai/src/anthropic/index.d.ts +0 -29
  38. package/lib/posthog-ai/src/index.d.ts +0 -10
  39. package/lib/posthog-ai/src/langchain/callbacks.d.ts +0 -65
  40. package/lib/posthog-ai/src/langchain/index.d.ts +0 -1
  41. package/lib/posthog-ai/src/openai/azure.d.ts +0 -32
  42. package/lib/posthog-ai/src/openai/index.d.ts +0 -33
  43. package/lib/posthog-ai/src/utils.d.ts +0 -66
  44. package/lib/posthog-ai/src/vercel/index.d.ts +0 -1
  45. package/lib/posthog-ai/src/vercel/middleware.d.ts +0 -28
  46. package/lib/posthog-ai/tests/openai.test.d.ts +0 -1
  47. package/lib/vercel/index.cjs.js.map +0 -1
  48. package/lib/vercel/index.esm.js.map +0 -1
@@ -1,8 +1,4170 @@
1
1
  import AnthropicOriginal from '@anthropic-ai/sdk';
2
- import { PostHog } from 'posthog-node';
2
+ import { posix, dirname, sep } from 'path';
3
+ import { createReadStream } from 'node:fs';
4
+ import { createInterface } from 'node:readline';
3
5
  import { RequestOptions, APIPromise } from '@anthropic-ai/sdk/core';
4
6
  import { Stream } from '@anthropic-ai/sdk/streaming';
5
7
 
8
+ // vendor from: https://github.com/LiosK/uuidv7/blob/f30b7a7faff73afbce0b27a46c638310f96912ba/src/index.ts
9
+ // https://github.com/LiosK/uuidv7#license
10
+ /**
11
+ * uuidv7: An experimental implementation of the proposed UUID Version 7
12
+ *
13
+ * @license Apache-2.0
14
+ * @copyright 2021-2023 LiosK
15
+ * @packageDocumentation
16
+ */
17
+ const DIGITS = "0123456789abcdef";
18
+ /** Represents a UUID as a 16-byte byte array. */
19
+ class UUID {
20
+ /** @param bytes - The 16-byte byte array representation. */
21
+ constructor(bytes) {
22
+ this.bytes = bytes;
23
+ }
24
+ /**
25
+ * Creates an object from the internal representation, a 16-byte byte array
26
+ * containing the binary UUID representation in the big-endian byte order.
27
+ *
28
+ * This method does NOT shallow-copy the argument, and thus the created object
29
+ * holds the reference to the underlying buffer.
30
+ *
31
+ * @throws TypeError if the length of the argument is not 16.
32
+ */
33
+ static ofInner(bytes) {
34
+ if (bytes.length !== 16) {
35
+ throw new TypeError("not 128-bit length");
36
+ }
37
+ else {
38
+ return new UUID(bytes);
39
+ }
40
+ }
41
+ /**
42
+ * Builds a byte array from UUIDv7 field values.
43
+ *
44
+ * @param unixTsMs - A 48-bit `unix_ts_ms` field value.
45
+ * @param randA - A 12-bit `rand_a` field value.
46
+ * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value.
47
+ * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value.
48
+ * @throws RangeError if any field value is out of the specified range.
49
+ */
50
+ static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
51
+ if (!Number.isInteger(unixTsMs) ||
52
+ !Number.isInteger(randA) ||
53
+ !Number.isInteger(randBHi) ||
54
+ !Number.isInteger(randBLo) ||
55
+ unixTsMs < 0 ||
56
+ randA < 0 ||
57
+ randBHi < 0 ||
58
+ randBLo < 0 ||
59
+ unixTsMs > 281474976710655 ||
60
+ randA > 0xfff ||
61
+ randBHi > 1073741823 ||
62
+ randBLo > 4294967295) {
63
+ throw new RangeError("invalid field value");
64
+ }
65
+ const bytes = new Uint8Array(16);
66
+ bytes[0] = unixTsMs / 2 ** 40;
67
+ bytes[1] = unixTsMs / 2 ** 32;
68
+ bytes[2] = unixTsMs / 2 ** 24;
69
+ bytes[3] = unixTsMs / 2 ** 16;
70
+ bytes[4] = unixTsMs / 2 ** 8;
71
+ bytes[5] = unixTsMs;
72
+ bytes[6] = 0x70 | (randA >>> 8);
73
+ bytes[7] = randA;
74
+ bytes[8] = 0x80 | (randBHi >>> 24);
75
+ bytes[9] = randBHi >>> 16;
76
+ bytes[10] = randBHi >>> 8;
77
+ bytes[11] = randBHi;
78
+ bytes[12] = randBLo >>> 24;
79
+ bytes[13] = randBLo >>> 16;
80
+ bytes[14] = randBLo >>> 8;
81
+ bytes[15] = randBLo;
82
+ return new UUID(bytes);
83
+ }
84
+ /**
85
+ * Builds a byte array from a string representation.
86
+ *
87
+ * This method accepts the following formats:
88
+ *
89
+ * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b`
90
+ * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b`
91
+ * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}`
92
+ * - RFC 4122 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b`
93
+ *
94
+ * Leading and trailing whitespaces represents an error.
95
+ *
96
+ * @throws SyntaxError if the argument could not parse as a valid UUID string.
97
+ */
98
+ static parse(uuid) {
99
+ let hex = undefined;
100
+ switch (uuid.length) {
101
+ case 32:
102
+ hex = /^[0-9a-f]{32}$/i.exec(uuid)?.[0];
103
+ break;
104
+ case 36:
105
+ hex =
106
+ /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
107
+ .exec(uuid)
108
+ ?.slice(1, 6)
109
+ .join("");
110
+ break;
111
+ case 38:
112
+ hex =
113
+ /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i
114
+ .exec(uuid)
115
+ ?.slice(1, 6)
116
+ .join("");
117
+ break;
118
+ case 45:
119
+ hex =
120
+ /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i
121
+ .exec(uuid)
122
+ ?.slice(1, 6)
123
+ .join("");
124
+ break;
125
+ }
126
+ if (hex) {
127
+ const inner = new Uint8Array(16);
128
+ for (let i = 0; i < 16; i += 4) {
129
+ const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
130
+ inner[i + 0] = n >>> 24;
131
+ inner[i + 1] = n >>> 16;
132
+ inner[i + 2] = n >>> 8;
133
+ inner[i + 3] = n;
134
+ }
135
+ return new UUID(inner);
136
+ }
137
+ else {
138
+ throw new SyntaxError("could not parse UUID string");
139
+ }
140
+ }
141
+ /**
142
+ * @returns The 8-4-4-4-12 canonical hexadecimal string representation
143
+ * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`).
144
+ */
145
+ toString() {
146
+ let text = "";
147
+ for (let i = 0; i < this.bytes.length; i++) {
148
+ text += DIGITS.charAt(this.bytes[i] >>> 4);
149
+ text += DIGITS.charAt(this.bytes[i] & 0xf);
150
+ if (i === 3 || i === 5 || i === 7 || i === 9) {
151
+ text += "-";
152
+ }
153
+ }
154
+ return text;
155
+ }
156
+ /**
157
+ * @returns The 32-digit hexadecimal representation without hyphens
158
+ * (`0189dcd553117d408db09496a2eef37b`).
159
+ */
160
+ toHex() {
161
+ let text = "";
162
+ for (let i = 0; i < this.bytes.length; i++) {
163
+ text += DIGITS.charAt(this.bytes[i] >>> 4);
164
+ text += DIGITS.charAt(this.bytes[i] & 0xf);
165
+ }
166
+ return text;
167
+ }
168
+ /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */
169
+ toJSON() {
170
+ return this.toString();
171
+ }
172
+ /**
173
+ * Reports the variant field value of the UUID or, if appropriate, "NIL" or
174
+ * "MAX".
175
+ *
176
+ * For convenience, this method reports "NIL" or "MAX" if `this` represents
177
+ * the Nil or Max UUID, although the Nil and Max UUIDs are technically
178
+ * subsumed under the variants `0b0` and `0b111`, respectively.
179
+ */
180
+ getVariant() {
181
+ const n = this.bytes[8] >>> 4;
182
+ if (n < 0) {
183
+ throw new Error("unreachable");
184
+ }
185
+ else if (n <= 0b0111) {
186
+ return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0";
187
+ }
188
+ else if (n <= 0b1011) {
189
+ return "VAR_10";
190
+ }
191
+ else if (n <= 0b1101) {
192
+ return "VAR_110";
193
+ }
194
+ else if (n <= 0b1111) {
195
+ return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED";
196
+ }
197
+ else {
198
+ throw new Error("unreachable");
199
+ }
200
+ }
201
+ /**
202
+ * Returns the version field value of the UUID or `undefined` if the UUID does
203
+ * not have the variant field value of `0b10`.
204
+ */
205
+ getVersion() {
206
+ return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined;
207
+ }
208
+ /** Creates an object from `this`. */
209
+ clone() {
210
+ return new UUID(this.bytes.slice(0));
211
+ }
212
+ /** Returns true if `this` is equivalent to `other`. */
213
+ equals(other) {
214
+ return this.compareTo(other) === 0;
215
+ }
216
+ /**
217
+ * Returns a negative integer, zero, or positive integer if `this` is less
218
+ * than, equal to, or greater than `other`, respectively.
219
+ */
220
+ compareTo(other) {
221
+ for (let i = 0; i < 16; i++) {
222
+ const diff = this.bytes[i] - other.bytes[i];
223
+ if (diff !== 0) {
224
+ return Math.sign(diff);
225
+ }
226
+ }
227
+ return 0;
228
+ }
229
+ }
230
+ /**
231
+ * Encapsulates the monotonic counter state.
232
+ *
233
+ * This class provides APIs to utilize a separate counter state from that of the
234
+ * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to
235
+ * the default {@link generate} method, this class has {@link generateOrAbort}
236
+ * that is useful to absolutely guarantee the monotonically increasing order of
237
+ * generated UUIDs. See their respective documentation for details.
238
+ */
239
+ class V7Generator {
240
+ /**
241
+ * Creates a generator object with the default random number generator, or
242
+ * with the specified one if passed as an argument. The specified random
243
+ * number generator should be cryptographically strong and securely seeded.
244
+ */
245
+ constructor(randomNumberGenerator) {
246
+ this.timestamp = 0;
247
+ this.counter = 0;
248
+ this.random = randomNumberGenerator ?? getDefaultRandom();
249
+ }
250
+ /**
251
+ * Generates a new UUIDv7 object from the current timestamp, or resets the
252
+ * generator upon significant timestamp rollback.
253
+ *
254
+ * This method returns a monotonically increasing UUID by reusing the previous
255
+ * timestamp even if the up-to-date timestamp is smaller than the immediately
256
+ * preceding UUID's. However, when such a clock rollback is considered
257
+ * significant (i.e., by more than ten seconds), this method resets the
258
+ * generator and returns a new UUID based on the given timestamp, breaking the
259
+ * increasing order of UUIDs.
260
+ *
261
+ * See {@link generateOrAbort} for the other mode of generation and
262
+ * {@link generateOrResetCore} for the low-level primitive.
263
+ */
264
+ generate() {
265
+ return this.generateOrResetCore(Date.now(), 10000);
266
+ }
267
+ /**
268
+ * Generates a new UUIDv7 object from the current timestamp, or returns
269
+ * `undefined` upon significant timestamp rollback.
270
+ *
271
+ * This method returns a monotonically increasing UUID by reusing the previous
272
+ * timestamp even if the up-to-date timestamp is smaller than the immediately
273
+ * preceding UUID's. However, when such a clock rollback is considered
274
+ * significant (i.e., by more than ten seconds), this method aborts and
275
+ * returns `undefined` immediately.
276
+ *
277
+ * See {@link generate} for the other mode of generation and
278
+ * {@link generateOrAbortCore} for the low-level primitive.
279
+ */
280
+ generateOrAbort() {
281
+ return this.generateOrAbortCore(Date.now(), 10000);
282
+ }
283
+ /**
284
+ * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the
285
+ * generator upon significant timestamp rollback.
286
+ *
287
+ * This method is equivalent to {@link generate} except that it takes a custom
288
+ * timestamp and clock rollback allowance.
289
+ *
290
+ * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
291
+ * considered significant. A suggested value is `10_000` (milliseconds).
292
+ * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
293
+ */
294
+ generateOrResetCore(unixTsMs, rollbackAllowance) {
295
+ let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
296
+ if (value === undefined) {
297
+ // reset state and resume
298
+ this.timestamp = 0;
299
+ value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
300
+ }
301
+ return value;
302
+ }
303
+ /**
304
+ * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns
305
+ * `undefined` upon significant timestamp rollback.
306
+ *
307
+ * This method is equivalent to {@link generateOrAbort} except that it takes a
308
+ * custom timestamp and clock rollback allowance.
309
+ *
310
+ * @param rollbackAllowance - The amount of `unixTsMs` rollback that is
311
+ * considered significant. A suggested value is `10_000` (milliseconds).
312
+ * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
313
+ */
314
+ generateOrAbortCore(unixTsMs, rollbackAllowance) {
315
+ const MAX_COUNTER = 4398046511103;
316
+ if (!Number.isInteger(unixTsMs) ||
317
+ unixTsMs < 1 ||
318
+ unixTsMs > 281474976710655) {
319
+ throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
320
+ }
321
+ else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) {
322
+ throw new RangeError("`rollbackAllowance` out of reasonable range");
323
+ }
324
+ if (unixTsMs > this.timestamp) {
325
+ this.timestamp = unixTsMs;
326
+ this.resetCounter();
327
+ }
328
+ else if (unixTsMs + rollbackAllowance >= this.timestamp) {
329
+ // go on with previous timestamp if new one is not much smaller
330
+ this.counter++;
331
+ if (this.counter > MAX_COUNTER) {
332
+ // increment timestamp at counter overflow
333
+ this.timestamp++;
334
+ this.resetCounter();
335
+ }
336
+ }
337
+ else {
338
+ // abort if clock went backwards to unbearable extent
339
+ return undefined;
340
+ }
341
+ return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32());
342
+ }
343
+ /** Initializes the counter at a 42-bit random integer. */
344
+ resetCounter() {
345
+ this.counter =
346
+ this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff);
347
+ }
348
+ /**
349
+ * Generates a new UUIDv4 object utilizing the random number generator inside.
350
+ *
351
+ * @internal
352
+ */
353
+ generateV4() {
354
+ const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
355
+ bytes[6] = 0x40 | (bytes[6] >>> 4);
356
+ bytes[8] = 0x80 | (bytes[8] >>> 2);
357
+ return UUID.ofInner(bytes);
358
+ }
359
+ }
360
+ /** A global flag to force use of cryptographically strong RNG. */
361
+ // declare const UUIDV7_DENY_WEAK_RNG: boolean;
362
+ /** Returns the default random number generator available in the environment. */
363
+ const getDefaultRandom = () => {
364
+ // fix: crypto isn't available in react-native, always use Math.random
365
+ // // detect Web Crypto API
366
+ // if (
367
+ // typeof crypto !== "undefined" &&
368
+ // typeof crypto.getRandomValues !== "undefined"
369
+ // ) {
370
+ // return new BufferedCryptoRandom();
371
+ // } else {
372
+ // // fall back on Math.random() unless the flag is set to true
373
+ // if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) {
374
+ // throw new Error("no cryptographically strong RNG available");
375
+ // }
376
+ // return {
377
+ // nextUint32: (): number =>
378
+ // Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +
379
+ // Math.trunc(Math.random() * 0x1_0000),
380
+ // };
381
+ // }
382
+ return {
383
+ nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 +
384
+ Math.trunc(Math.random() * 65536),
385
+ };
386
+ };
387
+ // /**
388
+ // * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small
389
+ // * buffer by default to avoid both unbearable throughput decline in some
390
+ // * environments and the waste of time and space for unused values.
391
+ // */
392
+ // class BufferedCryptoRandom {
393
+ // private readonly buffer = new Uint32Array(8);
394
+ // private cursor = 0xffff;
395
+ // nextUint32(): number {
396
+ // if (this.cursor >= this.buffer.length) {
397
+ // crypto.getRandomValues(this.buffer);
398
+ // this.cursor = 0;
399
+ // }
400
+ // return this.buffer[this.cursor++];
401
+ // }
402
+ // }
403
+ let defaultGenerator;
404
+ /**
405
+ * Generates a UUIDv7 string.
406
+ *
407
+ * @returns The 8-4-4-4-12 canonical hexadecimal string representation
408
+ * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").
409
+ */
410
+ const uuidv7 = () => uuidv7obj().toString();
411
+ /** Generates a UUIDv7 object. */
412
+ const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
413
+
414
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
415
+ // Licensed under the MIT License
416
+ function makeUncaughtExceptionHandler(captureFn, onFatalFn) {
417
+ let calledFatalError = false;
418
+ return Object.assign(error => {
419
+ // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not
420
+ // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust
421
+ // exit behaviour of the SDK accordingly:
422
+ // - If other listeners are attached, do not exit.
423
+ // - If the only listener attached is ours, exit.
424
+ const userProvidedListenersCount = global.process.listeners('uncaughtException').filter(listener => {
425
+ // There are 2 listeners we ignore:
426
+ return (
427
+ // as soon as we're using domains this listener is attached by node itself
428
+ listener.name !== 'domainUncaughtExceptionClear' &&
429
+ // the handler we register in this integration
430
+ listener._posthogErrorHandler !== true
431
+ );
432
+ }).length;
433
+ const processWouldExit = userProvidedListenersCount === 0;
434
+ captureFn(error, {
435
+ mechanism: {
436
+ type: 'onuncaughtexception',
437
+ handled: false
438
+ }
439
+ });
440
+ if (!calledFatalError && processWouldExit) {
441
+ calledFatalError = true;
442
+ onFatalFn();
443
+ }
444
+ }, {
445
+ _posthogErrorHandler: true
446
+ });
447
+ }
448
+ function addUncaughtExceptionListener(captureFn, onFatalFn) {
449
+ global.process.on('uncaughtException', makeUncaughtExceptionHandler(captureFn, onFatalFn));
450
+ }
451
+ function addUnhandledRejectionListener(captureFn) {
452
+ global.process.on('unhandledRejection', reason => {
453
+ captureFn(reason, {
454
+ mechanism: {
455
+ type: 'onunhandledrejection',
456
+ handled: false
457
+ }
458
+ });
459
+ });
460
+ }
461
+
462
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
463
+ // Licensed under the MIT License
464
+ function isEvent(candidate) {
465
+ return typeof Event !== 'undefined' && isInstanceOf(candidate, Event);
466
+ }
467
+ function isPlainObject(candidate) {
468
+ return isBuiltin(candidate, 'Object');
469
+ }
470
+ function isError(candidate) {
471
+ switch (Object.prototype.toString.call(candidate)) {
472
+ case '[object Error]':
473
+ case '[object Exception]':
474
+ case '[object DOMException]':
475
+ case '[object WebAssembly.Exception]':
476
+ return true;
477
+ default:
478
+ return isInstanceOf(candidate, Error);
479
+ }
480
+ }
481
+ function isInstanceOf(candidate, base) {
482
+ try {
483
+ return candidate instanceof base;
484
+ } catch {
485
+ return false;
486
+ }
487
+ }
488
+ function isErrorEvent(event) {
489
+ return isBuiltin(event, 'ErrorEvent');
490
+ }
491
+ function isBuiltin(candidate, className) {
492
+ return Object.prototype.toString.call(candidate) === `[object ${className}]`;
493
+ }
494
+
495
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
496
+ async function propertiesFromUnknownInput(stackParser, frameModifiers, input, hint) {
497
+ const providedMechanism = hint && hint.mechanism;
498
+ const mechanism = providedMechanism || {
499
+ handled: true,
500
+ type: 'generic'
501
+ };
502
+ const errorList = getErrorList(mechanism, input, hint);
503
+ const exceptionList = await Promise.all(errorList.map(async error => {
504
+ const exception = await exceptionFromError(stackParser, frameModifiers, error);
505
+ exception.value = exception.value || '';
506
+ exception.type = exception.type || 'Error';
507
+ exception.mechanism = mechanism;
508
+ return exception;
509
+ }));
510
+ const properties = {
511
+ $exception_list: exceptionList
512
+ };
513
+ return properties;
514
+ }
515
+ // Flatten error causes into a list of errors
516
+ // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
517
+ function getErrorList(mechanism, input, hint) {
518
+ const error = getError(mechanism, input, hint);
519
+ if (error.cause) {
520
+ return [error, ...getErrorList(mechanism, error.cause, hint)];
521
+ }
522
+ return [error];
523
+ }
524
+ function getError(mechanism, exception, hint) {
525
+ if (isError(exception)) {
526
+ return exception;
527
+ }
528
+ mechanism.synthetic = true;
529
+ if (isPlainObject(exception)) {
530
+ const errorFromProp = getErrorPropertyFromObject(exception);
531
+ if (errorFromProp) {
532
+ return errorFromProp;
533
+ }
534
+ const message = getMessageForObject(exception);
535
+ const ex = hint?.syntheticException || new Error(message);
536
+ ex.message = message;
537
+ return ex;
538
+ }
539
+ // This handles when someone does: `throw "something awesome";`
540
+ // We use synthesized Error here so we can extract a (rough) stack trace.
541
+ const ex = hint?.syntheticException || new Error(exception);
542
+ ex.message = `${exception}`;
543
+ return ex;
544
+ }
545
+ /** If a plain object has a property that is an `Error`, return this error. */
546
+ function getErrorPropertyFromObject(obj) {
547
+ for (const prop in obj) {
548
+ if (Object.prototype.hasOwnProperty.call(obj, prop)) {
549
+ const value = obj[prop];
550
+ if (isError(value)) {
551
+ return value;
552
+ }
553
+ }
554
+ }
555
+ return undefined;
556
+ }
557
+ function getMessageForObject(exception) {
558
+ if ('name' in exception && typeof exception.name === 'string') {
559
+ let message = `'${exception.name}' captured as exception`;
560
+ if ('message' in exception && typeof exception.message === 'string') {
561
+ message += ` with message '${exception.message}'`;
562
+ }
563
+ return message;
564
+ } else if ('message' in exception && typeof exception.message === 'string') {
565
+ return exception.message;
566
+ }
567
+ const keys = extractExceptionKeysForMessage(exception);
568
+ // Some ErrorEvent instances do not have an `error` property, which is why they are not handled before
569
+ // We still want to try to get a decent message for these cases
570
+ if (isErrorEvent(exception)) {
571
+ return `Event \`ErrorEvent\` captured as exception with message \`${exception.message}\``;
572
+ }
573
+ const className = getObjectClassName(exception);
574
+ return `${className && className !== 'Object' ? `'${className}'` : 'Object'} captured as exception with keys: ${keys}`;
575
+ }
576
+ function getObjectClassName(obj) {
577
+ try {
578
+ const prototype = Object.getPrototypeOf(obj);
579
+ return prototype ? prototype.constructor.name : undefined;
580
+ } catch (e) {
581
+ // ignore errors here
582
+ }
583
+ }
584
+ /**
585
+ * Given any captured exception, extract its keys and create a sorted
586
+ * and truncated list that will be used inside the event message.
587
+ * eg. `Non-error exception captured with keys: foo, bar, baz`
588
+ */
589
+ function extractExceptionKeysForMessage(exception, maxLength = 40) {
590
+ const keys = Object.keys(convertToPlainObject(exception));
591
+ keys.sort();
592
+ const firstKey = keys[0];
593
+ if (!firstKey) {
594
+ return '[object has no keys]';
595
+ }
596
+ if (firstKey.length >= maxLength) {
597
+ return truncate(firstKey, maxLength);
598
+ }
599
+ for (let includedKeys = keys.length; includedKeys > 0; includedKeys--) {
600
+ const serialized = keys.slice(0, includedKeys).join(', ');
601
+ if (serialized.length > maxLength) {
602
+ continue;
603
+ }
604
+ if (includedKeys === keys.length) {
605
+ return serialized;
606
+ }
607
+ return truncate(serialized, maxLength);
608
+ }
609
+ return '';
610
+ }
611
+ function truncate(str, max = 0) {
612
+ if (typeof str !== 'string' || max === 0) {
613
+ return str;
614
+ }
615
+ return str.length <= max ? str : `${str.slice(0, max)}...`;
616
+ }
617
+ /**
618
+ * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
619
+ * non-enumerable properties attached.
620
+ *
621
+ * @param value Initial source that we have to transform in order for it to be usable by the serializer
622
+ * @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor
623
+ * an Error.
624
+ */
625
+ function convertToPlainObject(value) {
626
+ if (isError(value)) {
627
+ return {
628
+ message: value.message,
629
+ name: value.name,
630
+ stack: value.stack,
631
+ ...getOwnProperties(value)
632
+ };
633
+ } else if (isEvent(value)) {
634
+ const newObj = {
635
+ type: value.type,
636
+ target: serializeEventTarget(value.target),
637
+ currentTarget: serializeEventTarget(value.currentTarget),
638
+ ...getOwnProperties(value)
639
+ };
640
+ // TODO: figure out why this fails typing (I think CustomEvent is only supported in Node 19 onwards)
641
+ // if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {
642
+ // newObj.detail = (value as unknown as CustomEvent).detail
643
+ // }
644
+ return newObj;
645
+ } else {
646
+ return value;
647
+ }
648
+ }
649
+ /** Filters out all but an object's own properties */
650
+ function getOwnProperties(obj) {
651
+ if (typeof obj === 'object' && obj !== null) {
652
+ const extractedProps = {};
653
+ for (const property in obj) {
654
+ if (Object.prototype.hasOwnProperty.call(obj, property)) {
655
+ extractedProps[property] = obj[property];
656
+ }
657
+ }
658
+ return extractedProps;
659
+ } else {
660
+ return {};
661
+ }
662
+ }
663
+ /** Creates a string representation of the target of an `Event` object */
664
+ function serializeEventTarget(target) {
665
+ try {
666
+ return Object.prototype.toString.call(target);
667
+ } catch (_oO) {
668
+ return '<unknown>';
669
+ }
670
+ }
671
+ /**
672
+ * Extracts stack frames from the error and builds an Exception
673
+ */
674
+ async function exceptionFromError(stackParser, frameModifiers, error) {
675
+ const exception = {
676
+ type: error.name || error.constructor.name,
677
+ value: error.message
678
+ };
679
+ let frames = parseStackFrames(stackParser, error);
680
+ for (const modifier of frameModifiers) {
681
+ frames = await modifier(frames);
682
+ }
683
+ if (frames.length) {
684
+ exception.stacktrace = {
685
+ frames,
686
+ type: 'raw'
687
+ };
688
+ }
689
+ return exception;
690
+ }
691
+ /**
692
+ * Extracts stack frames from the error.stack string
693
+ */
694
+ function parseStackFrames(stackParser, error) {
695
+ return stackParser(error.stack || '', 1);
696
+ }
697
+
698
+ const SHUTDOWN_TIMEOUT = 2000;
699
+ class ErrorTracking {
700
+ static async captureException(client, error, hint, distinctId, additionalProperties) {
701
+ const properties = {
702
+ ...additionalProperties
703
+ };
704
+ // Given stateless nature of Node SDK we capture exceptions using personless processing when no
705
+ // user can be determined because a distinct_id is not provided e.g. exception autocapture
706
+ if (!distinctId) {
707
+ properties.$process_person_profile = false;
708
+ }
709
+ const exceptionProperties = await propertiesFromUnknownInput(this.stackParser, this.frameModifiers, error, hint);
710
+ client.capture({
711
+ event: '$exception',
712
+ distinctId: distinctId || uuidv7(),
713
+ properties: {
714
+ ...exceptionProperties,
715
+ ...properties
716
+ }
717
+ });
718
+ }
719
+ constructor(client, options) {
720
+ this.client = client;
721
+ this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
722
+ this.startAutocaptureIfEnabled();
723
+ }
724
+ startAutocaptureIfEnabled() {
725
+ if (this.isEnabled()) {
726
+ addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
727
+ addUnhandledRejectionListener(this.onException.bind(this));
728
+ }
729
+ }
730
+ onException(exception, hint) {
731
+ ErrorTracking.captureException(this.client, exception, hint);
732
+ }
733
+ async onFatalError() {
734
+ await this.client.shutdown(SHUTDOWN_TIMEOUT);
735
+ }
736
+ isEnabled() {
737
+ return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
738
+ }
739
+ }
740
+
741
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
742
+ /** Creates a function that gets the module name from a filename */
743
+ function createGetModuleFromFilename(basePath = process.argv[1] ? dirname(process.argv[1]) : process.cwd(), isWindows = sep === '\\') {
744
+ const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
745
+ return filename => {
746
+ if (!filename) {
747
+ return;
748
+ }
749
+ const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename;
750
+ // eslint-disable-next-line prefer-const
751
+ let {
752
+ dir,
753
+ base: file,
754
+ ext
755
+ } = posix.parse(normalizedFilename);
756
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
757
+ file = file.slice(0, ext.length * -1);
758
+ }
759
+ // The file name might be URI-encoded which we want to decode to
760
+ // the original file name.
761
+ const decodedFile = decodeURIComponent(file);
762
+ if (!dir) {
763
+ // No dirname whatsoever
764
+ dir = '.';
765
+ }
766
+ const n = dir.lastIndexOf('/node_modules');
767
+ if (n > -1) {
768
+ return `${dir.slice(n + 14).replace(/\//g, '.')}:${decodedFile}`;
769
+ }
770
+ // Let's see if it's a part of the main module
771
+ // To be a part of main module, it has to share the same base
772
+ if (dir.startsWith(normalizedBase)) {
773
+ const moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, '.');
774
+ return moduleName ? `${moduleName}:${decodedFile}` : decodedFile;
775
+ }
776
+ return decodedFile;
777
+ };
778
+ }
779
+ /** normalizes Windows paths */
780
+ function normalizeWindowsPath(path) {
781
+ return path.replace(/^[A-Z]:/, '') // remove Windows-style prefix
782
+ .replace(/\\/g, '/'); // replace all `\` instances with `/`
783
+ }
784
+
785
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
786
+ // Licensed under the MIT License
787
+ /** A simple Least Recently Used map */
788
+ class ReduceableCache {
789
+ constructor(_maxSize) {
790
+ this._maxSize = _maxSize;
791
+ this._cache = new Map();
792
+ }
793
+ /** Get an entry or undefined if it was not in the cache. Re-inserts to update the recently used order */
794
+ get(key) {
795
+ const value = this._cache.get(key);
796
+ if (value === undefined) {
797
+ return undefined;
798
+ }
799
+ // Remove and re-insert to update the order
800
+ this._cache.delete(key);
801
+ this._cache.set(key, value);
802
+ return value;
803
+ }
804
+ /** Insert an entry and evict an older entry if we've reached maxSize */
805
+ set(key, value) {
806
+ this._cache.set(key, value);
807
+ }
808
+ /** Remove an entry and return the entry if it was in the cache */
809
+ reduce() {
810
+ while (this._cache.size >= this._maxSize) {
811
+ const value = this._cache.keys().next().value;
812
+ if (value) {
813
+ // keys() returns an iterator in insertion order so keys().next() gives us the oldest key
814
+ this._cache.delete(value);
815
+ }
816
+ }
817
+ }
818
+ }
819
+
820
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
821
+ const LRU_FILE_CONTENTS_CACHE = new ReduceableCache(25);
822
+ const LRU_FILE_CONTENTS_FS_READ_FAILED = new ReduceableCache(20);
823
+ const DEFAULT_LINES_OF_CONTEXT = 7;
824
+ // Determines the upper bound of lineno/colno that we will attempt to read. Large colno values are likely to be
825
+ // minified code while large lineno values are likely to be bundled code.
826
+ // Exported for testing purposes.
827
+ const MAX_CONTEXTLINES_COLNO = 1000;
828
+ const MAX_CONTEXTLINES_LINENO = 10000;
829
+ async function addSourceContext(frames) {
830
+ // keep a lookup map of which files we've already enqueued to read,
831
+ // so we don't enqueue the same file multiple times which would cause multiple i/o reads
832
+ const filesToLines = {};
833
+ // Maps preserve insertion order, so we iterate in reverse, starting at the
834
+ // outermost frame and closer to where the exception has occurred (poor mans priority)
835
+ for (let i = frames.length - 1; i >= 0; i--) {
836
+ const frame = frames[i];
837
+ const filename = frame?.filename;
838
+ if (!frame || typeof filename !== 'string' || typeof frame.lineno !== 'number' || shouldSkipContextLinesForFile(filename) || shouldSkipContextLinesForFrame(frame)) {
839
+ continue;
840
+ }
841
+ const filesToLinesOutput = filesToLines[filename];
842
+ if (!filesToLinesOutput) {
843
+ filesToLines[filename] = [];
844
+ }
845
+ filesToLines[filename].push(frame.lineno);
846
+ }
847
+ const files = Object.keys(filesToLines);
848
+ if (files.length == 0) {
849
+ return frames;
850
+ }
851
+ const readlinePromises = [];
852
+ for (const file of files) {
853
+ // If we failed to read this before, dont try reading it again.
854
+ if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(file)) {
855
+ continue;
856
+ }
857
+ const filesToLineRanges = filesToLines[file];
858
+ if (!filesToLineRanges) {
859
+ continue;
860
+ }
861
+ // Sort ranges so that they are sorted by line increasing order and match how the file is read.
862
+ filesToLineRanges.sort((a, b) => a - b);
863
+ // Check if the contents are already in the cache and if we can avoid reading the file again.
864
+ const ranges = makeLineReaderRanges(filesToLineRanges);
865
+ if (ranges.every(r => rangeExistsInContentCache(file, r))) {
866
+ continue;
867
+ }
868
+ const cache = emplace(LRU_FILE_CONTENTS_CACHE, file, {});
869
+ readlinePromises.push(getContextLinesFromFile(file, ranges, cache));
870
+ }
871
+ // The promise rejections are caught in order to prevent them from short circuiting Promise.all
872
+ await Promise.all(readlinePromises).catch(() => {});
873
+ // Perform the same loop as above, but this time we can assume all files are in the cache
874
+ // and attempt to add source context to frames.
875
+ if (frames && frames.length > 0) {
876
+ addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE);
877
+ }
878
+ // Once we're finished processing an exception reduce the files held in the cache
879
+ // so that we don't indefinetly increase the size of this map
880
+ LRU_FILE_CONTENTS_CACHE.reduce();
881
+ return frames;
882
+ }
883
+ /**
884
+ * Extracts lines from a file and stores them in a cache.
885
+ */
886
+ function getContextLinesFromFile(path, ranges, output) {
887
+ return new Promise(resolve => {
888
+ // It is important *not* to have any async code between createInterface and the 'line' event listener
889
+ // as it will cause the 'line' event to
890
+ // be emitted before the listener is attached.
891
+ const stream = createReadStream(path);
892
+ const lineReaded = createInterface({
893
+ input: stream
894
+ });
895
+ // We need to explicitly destroy the stream to prevent memory leaks,
896
+ // removing the listeners on the readline interface is not enough.
897
+ // See: https://github.com/nodejs/node/issues/9002 and https://github.com/getsentry/sentry-javascript/issues/14892
898
+ function destroyStreamAndResolve() {
899
+ stream.destroy();
900
+ resolve();
901
+ }
902
+ // Init at zero and increment at the start of the loop because lines are 1 indexed.
903
+ let lineNumber = 0;
904
+ let currentRangeIndex = 0;
905
+ const range = ranges[currentRangeIndex];
906
+ if (range === undefined) {
907
+ // We should never reach this point, but if we do, we should resolve the promise to prevent it from hanging.
908
+ destroyStreamAndResolve();
909
+ return;
910
+ }
911
+ let rangeStart = range[0];
912
+ let rangeEnd = range[1];
913
+ // We use this inside Promise.all, so we need to resolve the promise even if there is an error
914
+ // to prevent Promise.all from short circuiting the rest.
915
+ function onStreamError() {
916
+ // Mark file path as failed to read and prevent multiple read attempts.
917
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1);
918
+ lineReaded.close();
919
+ lineReaded.removeAllListeners();
920
+ destroyStreamAndResolve();
921
+ }
922
+ // We need to handle the error event to prevent the process from crashing in < Node 16
923
+ // https://github.com/nodejs/node/pull/31603
924
+ stream.on('error', onStreamError);
925
+ lineReaded.on('error', onStreamError);
926
+ lineReaded.on('close', destroyStreamAndResolve);
927
+ lineReaded.on('line', line => {
928
+ lineNumber++;
929
+ if (lineNumber < rangeStart) {
930
+ return;
931
+ }
932
+ // !Warning: This mutates the cache by storing the snipped line into the cache.
933
+ output[lineNumber] = snipLine(line, 0);
934
+ if (lineNumber >= rangeEnd) {
935
+ if (currentRangeIndex === ranges.length - 1) {
936
+ // We need to close the file stream and remove listeners, else the reader will continue to run our listener;
937
+ lineReaded.close();
938
+ lineReaded.removeAllListeners();
939
+ return;
940
+ }
941
+ currentRangeIndex++;
942
+ const range = ranges[currentRangeIndex];
943
+ if (range === undefined) {
944
+ // This should never happen as it means we have a bug in the context.
945
+ lineReaded.close();
946
+ lineReaded.removeAllListeners();
947
+ return;
948
+ }
949
+ rangeStart = range[0];
950
+ rangeEnd = range[1];
951
+ }
952
+ });
953
+ });
954
+ }
955
+ /** Adds context lines to frames */
956
+ function addSourceContextToFrames(frames, cache) {
957
+ for (const frame of frames) {
958
+ // Only add context if we have a filename and it hasn't already been added
959
+ if (frame.filename && frame.context_line === undefined && typeof frame.lineno === 'number') {
960
+ const contents = cache.get(frame.filename);
961
+ if (contents === undefined) {
962
+ continue;
963
+ }
964
+ addContextToFrame(frame.lineno, frame, contents);
965
+ }
966
+ }
967
+ }
968
+ /**
969
+ * Resolves context lines before and after the given line number and appends them to the frame;
970
+ */
971
+ function addContextToFrame(lineno, frame, contents) {
972
+ // When there is no line number in the frame, attaching context is nonsensical and will even break grouping.
973
+ // We already check for lineno before calling this, but since StackFrame lineno is optional, we check it again.
974
+ if (frame.lineno === undefined || contents === undefined) {
975
+ return;
976
+ }
977
+ frame.pre_context = [];
978
+ for (let i = makeRangeStart(lineno); i < lineno; i++) {
979
+ // We always expect the start context as line numbers cannot be negative. If we dont find a line, then
980
+ // something went wrong somewhere. Clear the context and return without adding any linecontext.
981
+ const line = contents[i];
982
+ if (line === undefined) {
983
+ clearLineContext(frame);
984
+ return;
985
+ }
986
+ frame.pre_context.push(line);
987
+ }
988
+ // We should always have the context line. If we dont, something went wrong, so we clear the context and return
989
+ // without adding any linecontext.
990
+ if (contents[lineno] === undefined) {
991
+ clearLineContext(frame);
992
+ return;
993
+ }
994
+ frame.context_line = contents[lineno];
995
+ const end = makeRangeEnd(lineno);
996
+ frame.post_context = [];
997
+ for (let i = lineno + 1; i <= end; i++) {
998
+ // Since we dont track when the file ends, we cant clear the context if we dont find a line as it could
999
+ // just be that we reached the end of the file.
1000
+ const line = contents[i];
1001
+ if (line === undefined) {
1002
+ break;
1003
+ }
1004
+ frame.post_context.push(line);
1005
+ }
1006
+ }
1007
+ /**
1008
+ * Clears the context lines from a frame, used to reset a frame to its original state
1009
+ * if we fail to resolve all context lines for it.
1010
+ */
1011
+ function clearLineContext(frame) {
1012
+ delete frame.pre_context;
1013
+ delete frame.context_line;
1014
+ delete frame.post_context;
1015
+ }
1016
+ /**
1017
+ * Determines if context lines should be skipped for a file.
1018
+ * - .min.(mjs|cjs|js) files are and not useful since they dont point to the original source
1019
+ * - node: prefixed modules are part of the runtime and cannot be resolved to a file
1020
+ * - data: skip json, wasm and inline js https://nodejs.org/api/esm.html#data-imports
1021
+ */
1022
+ function shouldSkipContextLinesForFile(path) {
1023
+ // Test the most common prefix and extension first. These are the ones we
1024
+ // are most likely to see in user applications and are the ones we can break out of first.
1025
+ return path.startsWith('node:') || path.endsWith('.min.js') || path.endsWith('.min.cjs') || path.endsWith('.min.mjs') || path.startsWith('data:');
1026
+ }
1027
+ /**
1028
+ * Determines if we should skip contextlines based off the max lineno and colno values.
1029
+ */
1030
+ function shouldSkipContextLinesForFrame(frame) {
1031
+ if (frame.lineno !== undefined && frame.lineno > MAX_CONTEXTLINES_LINENO) {
1032
+ return true;
1033
+ }
1034
+ if (frame.colno !== undefined && frame.colno > MAX_CONTEXTLINES_COLNO) {
1035
+ return true;
1036
+ }
1037
+ return false;
1038
+ }
1039
+ /**
1040
+ * Checks if we have all the contents that we need in the cache.
1041
+ */
1042
+ function rangeExistsInContentCache(file, range) {
1043
+ const contents = LRU_FILE_CONTENTS_CACHE.get(file);
1044
+ if (contents === undefined) {
1045
+ return false;
1046
+ }
1047
+ for (let i = range[0]; i <= range[1]; i++) {
1048
+ if (contents[i] === undefined) {
1049
+ return false;
1050
+ }
1051
+ }
1052
+ return true;
1053
+ }
1054
+ /**
1055
+ * Creates contiguous ranges of lines to read from a file. In the case where context lines overlap,
1056
+ * the ranges are merged to create a single range.
1057
+ */
1058
+ function makeLineReaderRanges(lines) {
1059
+ if (!lines.length) {
1060
+ return [];
1061
+ }
1062
+ let i = 0;
1063
+ const line = lines[0];
1064
+ if (typeof line !== 'number') {
1065
+ return [];
1066
+ }
1067
+ let current = makeContextRange(line);
1068
+ const out = [];
1069
+ while (true) {
1070
+ if (i === lines.length - 1) {
1071
+ out.push(current);
1072
+ break;
1073
+ }
1074
+ // If the next line falls into the current range, extend the current range to lineno + linecontext.
1075
+ const next = lines[i + 1];
1076
+ if (typeof next !== 'number') {
1077
+ break;
1078
+ }
1079
+ if (next <= current[1]) {
1080
+ current[1] = next + DEFAULT_LINES_OF_CONTEXT;
1081
+ } else {
1082
+ out.push(current);
1083
+ current = makeContextRange(next);
1084
+ }
1085
+ i++;
1086
+ }
1087
+ return out;
1088
+ }
1089
+ // Determine start and end indices for context range (inclusive);
1090
+ function makeContextRange(line) {
1091
+ return [makeRangeStart(line), makeRangeEnd(line)];
1092
+ }
1093
+ // Compute inclusive end context range
1094
+ function makeRangeStart(line) {
1095
+ return Math.max(1, line - DEFAULT_LINES_OF_CONTEXT);
1096
+ }
1097
+ // Compute inclusive start context range
1098
+ function makeRangeEnd(line) {
1099
+ return line + DEFAULT_LINES_OF_CONTEXT;
1100
+ }
1101
+ /**
1102
+ * Get or init map value
1103
+ */
1104
+ function emplace(map, key, contents) {
1105
+ const value = map.get(key);
1106
+ if (value === undefined) {
1107
+ map.set(key, contents);
1108
+ return contents;
1109
+ }
1110
+ return value;
1111
+ }
1112
+ function snipLine(line, colno) {
1113
+ let newLine = line;
1114
+ const lineLength = newLine.length;
1115
+ if (lineLength <= 150) {
1116
+ return newLine;
1117
+ }
1118
+ if (colno > lineLength) {
1119
+ colno = lineLength;
1120
+ }
1121
+ let start = Math.max(colno - 60, 0);
1122
+ if (start < 5) {
1123
+ start = 0;
1124
+ }
1125
+ let end = Math.min(start + 140, lineLength);
1126
+ if (end > lineLength - 5) {
1127
+ end = lineLength;
1128
+ }
1129
+ if (end === lineLength) {
1130
+ start = Math.max(end - 140, 0);
1131
+ }
1132
+ newLine = newLine.slice(start, end);
1133
+ if (start > 0) {
1134
+ newLine = `...${newLine}`;
1135
+ }
1136
+ if (end < lineLength) {
1137
+ newLine += '...';
1138
+ }
1139
+ return newLine;
1140
+ }
1141
+
1142
+ var version = "4.17.1";
1143
+
1144
+ var PostHogPersistedProperty;
1145
+ (function (PostHogPersistedProperty) {
1146
+ PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
1147
+ PostHogPersistedProperty["DistinctId"] = "distinct_id";
1148
+ PostHogPersistedProperty["Props"] = "props";
1149
+ PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
1150
+ PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
1151
+ PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
1152
+ PostHogPersistedProperty["BootstrapFeatureFlagDetails"] = "bootstrap_feature_flag_details";
1153
+ PostHogPersistedProperty["BootstrapFeatureFlags"] = "bootstrap_feature_flags";
1154
+ PostHogPersistedProperty["BootstrapFeatureFlagPayloads"] = "bootstrap_feature_flag_payloads";
1155
+ PostHogPersistedProperty["OverrideFeatureFlags"] = "override_feature_flags";
1156
+ PostHogPersistedProperty["Queue"] = "queue";
1157
+ PostHogPersistedProperty["OptedOut"] = "opted_out";
1158
+ PostHogPersistedProperty["SessionId"] = "session_id";
1159
+ PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
1160
+ PostHogPersistedProperty["PersonProperties"] = "person_properties";
1161
+ PostHogPersistedProperty["GroupProperties"] = "group_properties";
1162
+ PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
1163
+ PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
1164
+ PostHogPersistedProperty["SessionReplay"] = "session_replay";
1165
+ PostHogPersistedProperty["DecideEndpointWasHit"] = "decide_endpoint_was_hit";
1166
+ PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
1167
+ PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
1168
+ PostHogPersistedProperty["Surveys"] = "surveys";
1169
+ PostHogPersistedProperty["RemoteConfig"] = "remote_config";
1170
+ })(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
1171
+ var SurveyPosition;
1172
+ (function (SurveyPosition) {
1173
+ SurveyPosition["Left"] = "left";
1174
+ SurveyPosition["Right"] = "right";
1175
+ SurveyPosition["Center"] = "center";
1176
+ })(SurveyPosition || (SurveyPosition = {}));
1177
+ var SurveyWidgetType;
1178
+ (function (SurveyWidgetType) {
1179
+ SurveyWidgetType["Button"] = "button";
1180
+ SurveyWidgetType["Tab"] = "tab";
1181
+ SurveyWidgetType["Selector"] = "selector";
1182
+ })(SurveyWidgetType || (SurveyWidgetType = {}));
1183
+ var SurveyType;
1184
+ (function (SurveyType) {
1185
+ SurveyType["Popover"] = "popover";
1186
+ SurveyType["API"] = "api";
1187
+ SurveyType["Widget"] = "widget";
1188
+ })(SurveyType || (SurveyType = {}));
1189
+ var SurveyQuestionDescriptionContentType;
1190
+ (function (SurveyQuestionDescriptionContentType) {
1191
+ SurveyQuestionDescriptionContentType["Html"] = "html";
1192
+ SurveyQuestionDescriptionContentType["Text"] = "text";
1193
+ })(SurveyQuestionDescriptionContentType || (SurveyQuestionDescriptionContentType = {}));
1194
+ var SurveyRatingDisplay;
1195
+ (function (SurveyRatingDisplay) {
1196
+ SurveyRatingDisplay["Number"] = "number";
1197
+ SurveyRatingDisplay["Emoji"] = "emoji";
1198
+ })(SurveyRatingDisplay || (SurveyRatingDisplay = {}));
1199
+ var SurveyQuestionType;
1200
+ (function (SurveyQuestionType) {
1201
+ SurveyQuestionType["Open"] = "open";
1202
+ SurveyQuestionType["MultipleChoice"] = "multiple_choice";
1203
+ SurveyQuestionType["SingleChoice"] = "single_choice";
1204
+ SurveyQuestionType["Rating"] = "rating";
1205
+ SurveyQuestionType["Link"] = "link";
1206
+ })(SurveyQuestionType || (SurveyQuestionType = {}));
1207
+ var SurveyQuestionBranchingType;
1208
+ (function (SurveyQuestionBranchingType) {
1209
+ SurveyQuestionBranchingType["NextQuestion"] = "next_question";
1210
+ SurveyQuestionBranchingType["End"] = "end";
1211
+ SurveyQuestionBranchingType["ResponseBased"] = "response_based";
1212
+ SurveyQuestionBranchingType["SpecificQuestion"] = "specific_question";
1213
+ })(SurveyQuestionBranchingType || (SurveyQuestionBranchingType = {}));
1214
+ var SurveyMatchType;
1215
+ (function (SurveyMatchType) {
1216
+ SurveyMatchType["Regex"] = "regex";
1217
+ SurveyMatchType["NotRegex"] = "not_regex";
1218
+ SurveyMatchType["Exact"] = "exact";
1219
+ SurveyMatchType["IsNot"] = "is_not";
1220
+ SurveyMatchType["Icontains"] = "icontains";
1221
+ SurveyMatchType["NotIcontains"] = "not_icontains";
1222
+ })(SurveyMatchType || (SurveyMatchType = {}));
1223
+ /** Sync with plugin-server/src/types.ts */
1224
+ var ActionStepStringMatching;
1225
+ (function (ActionStepStringMatching) {
1226
+ ActionStepStringMatching["Contains"] = "contains";
1227
+ ActionStepStringMatching["Exact"] = "exact";
1228
+ ActionStepStringMatching["Regex"] = "regex";
1229
+ })(ActionStepStringMatching || (ActionStepStringMatching = {}));
1230
+
1231
+ const normalizeDecideResponse = (decideResponse) => {
1232
+ if ('flags' in decideResponse) {
1233
+ // Convert v4 format to v3 format
1234
+ const featureFlags = getFlagValuesFromFlags(decideResponse.flags);
1235
+ const featureFlagPayloads = getPayloadsFromFlags(decideResponse.flags);
1236
+ return {
1237
+ ...decideResponse,
1238
+ featureFlags,
1239
+ featureFlagPayloads,
1240
+ };
1241
+ }
1242
+ else {
1243
+ // Convert v3 format to v4 format
1244
+ const featureFlags = decideResponse.featureFlags ?? {};
1245
+ const featureFlagPayloads = Object.fromEntries(Object.entries(decideResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
1246
+ const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
1247
+ key,
1248
+ getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
1249
+ ]));
1250
+ return {
1251
+ ...decideResponse,
1252
+ featureFlags,
1253
+ featureFlagPayloads,
1254
+ flags,
1255
+ };
1256
+ }
1257
+ };
1258
+ function getFlagDetailFromFlagAndPayload(key, value, payload) {
1259
+ return {
1260
+ key: key,
1261
+ enabled: typeof value === 'string' ? true : value,
1262
+ variant: typeof value === 'string' ? value : undefined,
1263
+ reason: undefined,
1264
+ metadata: {
1265
+ id: undefined,
1266
+ version: undefined,
1267
+ payload: payload ? JSON.stringify(payload) : undefined,
1268
+ description: undefined,
1269
+ },
1270
+ };
1271
+ }
1272
+ /**
1273
+ * Get the flag values from the flags v4 response.
1274
+ * @param flags - The flags
1275
+ * @returns The flag values
1276
+ */
1277
+ const getFlagValuesFromFlags = (flags) => {
1278
+ return Object.fromEntries(Object.entries(flags ?? {})
1279
+ .map(([key, detail]) => [key, getFeatureFlagValue(detail)])
1280
+ .filter(([, value]) => value !== undefined));
1281
+ };
1282
+ /**
1283
+ * Get the payloads from the flags v4 response.
1284
+ * @param flags - The flags
1285
+ * @returns The payloads
1286
+ */
1287
+ const getPayloadsFromFlags = (flags) => {
1288
+ const safeFlags = flags ?? {};
1289
+ return Object.fromEntries(Object.keys(safeFlags)
1290
+ .filter((flag) => {
1291
+ const details = safeFlags[flag];
1292
+ return details.enabled && details.metadata && details.metadata.payload !== undefined;
1293
+ })
1294
+ .map((flag) => {
1295
+ const payload = safeFlags[flag].metadata?.payload;
1296
+ return [flag, payload ? parsePayload(payload) : undefined];
1297
+ }));
1298
+ };
1299
+ const getFeatureFlagValue = (detail) => {
1300
+ return detail === undefined ? undefined : detail.variant ?? detail.enabled;
1301
+ };
1302
+ const parsePayload = (response) => {
1303
+ if (typeof response !== 'string') {
1304
+ return response;
1305
+ }
1306
+ try {
1307
+ return JSON.parse(response);
1308
+ }
1309
+ catch {
1310
+ return response;
1311
+ }
1312
+ };
1313
+
1314
+ // Rollout constants
1315
+ const NEW_FLAGS_ROLLOUT_PERCENTAGE = 1;
1316
+ // The fnv1a hashes of the tokens that are explicitly excluded from the rollout
1317
+ // see https://github.com/PostHog/posthog-js-lite/blob/main/posthog-core/src/utils.ts#L84
1318
+ // are hashed API tokens from our top 10 for each category supported by this SDK.
1319
+ const NEW_FLAGS_EXCLUDED_HASHES = new Set([
1320
+ // Node
1321
+ '61be3dd8',
1322
+ '96f6df5f',
1323
+ '8cfdba9b',
1324
+ 'bf027177',
1325
+ 'e59430a8',
1326
+ '7fa5500b',
1327
+ '569798e9',
1328
+ '04809ff7',
1329
+ '0ebc61a5',
1330
+ '32de7f98',
1331
+ '3beeb69a',
1332
+ '12d34ad9',
1333
+ '733853ec',
1334
+ '0645bb64',
1335
+ '5dcbee21',
1336
+ 'b1f95fa3',
1337
+ '2189e408',
1338
+ '82b460c2',
1339
+ '3a8cc979',
1340
+ '29ef8843',
1341
+ '2cdbf767',
1342
+ '38084b54',
1343
+ // React Native
1344
+ '50f9f8de',
1345
+ '41d0df91',
1346
+ '5c236689',
1347
+ 'c11aedd3',
1348
+ 'ada46672',
1349
+ 'f4331ee1',
1350
+ '42fed62a',
1351
+ 'c957462c',
1352
+ 'd62f705a',
1353
+ // Web (lots of teams per org, hence lots of API tokens)
1354
+ 'e0162666',
1355
+ '01b3e5cf',
1356
+ '441cef7f',
1357
+ 'bb9cafee',
1358
+ '8f348eb0',
1359
+ 'b2553f3a',
1360
+ '97469d7d',
1361
+ '39f21a76',
1362
+ '03706dcc',
1363
+ '27d50569',
1364
+ '307584a7',
1365
+ '6433e92e',
1366
+ '150c7fbb',
1367
+ '49f57f22',
1368
+ '3772f65b',
1369
+ '01eb8256',
1370
+ '3c9e9234',
1371
+ 'f853c7f7',
1372
+ 'c0ac4b67',
1373
+ 'cd609d40',
1374
+ '10ca9b1a',
1375
+ '8a87f11b',
1376
+ '8e8e5216',
1377
+ '1f6b63b3',
1378
+ 'db7943dd',
1379
+ '79b7164c',
1380
+ '07f78e33',
1381
+ '2d21b6fd',
1382
+ '952db5ee',
1383
+ 'a7d3b43f',
1384
+ '1924dd9c',
1385
+ '84e1b8f6',
1386
+ 'dff631b6',
1387
+ 'c5aa8a79',
1388
+ 'fa133a95',
1389
+ '498a4508',
1390
+ '24748755',
1391
+ '98f3d658',
1392
+ '21bbda67',
1393
+ '7dbfed69',
1394
+ 'be3ec24c',
1395
+ 'fc80b8e2',
1396
+ '75cc0998',
1397
+ ]);
1398
+ const STRING_FORMAT = 'utf8';
1399
+ function assert(truthyValue, message) {
1400
+ if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
1401
+ throw new Error(message);
1402
+ }
1403
+ }
1404
+ function isEmpty(truthyValue) {
1405
+ if (truthyValue.trim().length === 0) {
1406
+ return true;
1407
+ }
1408
+ return false;
1409
+ }
1410
+ function removeTrailingSlash(url) {
1411
+ return url?.replace(/\/+$/, '');
1412
+ }
1413
+ async function retriable(fn, props) {
1414
+ let lastError = null;
1415
+ for (let i = 0; i < props.retryCount + 1; i++) {
1416
+ if (i > 0) {
1417
+ // don't wait when it's the last try
1418
+ await new Promise((r) => setTimeout(r, props.retryDelay));
1419
+ }
1420
+ try {
1421
+ const res = await fn();
1422
+ return res;
1423
+ }
1424
+ catch (e) {
1425
+ lastError = e;
1426
+ if (!props.retryCheck(e)) {
1427
+ throw e;
1428
+ }
1429
+ }
1430
+ }
1431
+ throw lastError;
1432
+ }
1433
+ function currentTimestamp() {
1434
+ return new Date().getTime();
1435
+ }
1436
+ function currentISOTime() {
1437
+ return new Date().toISOString();
1438
+ }
1439
+ function safeSetTimeout(fn, timeout) {
1440
+ // NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``
1441
+ // rather than setImmediate.
1442
+ const t = setTimeout(fn, timeout);
1443
+ // We unref if available to prevent Node.js hanging on exit
1444
+ t?.unref && t?.unref();
1445
+ return t;
1446
+ }
1447
+ function getFetch() {
1448
+ return typeof fetch !== 'undefined' ? fetch : typeof globalThis.fetch !== 'undefined' ? globalThis.fetch : undefined;
1449
+ }
1450
+ // FNV-1a hash function
1451
+ // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
1452
+ // I know, I know, I'm rolling my own hash function, but I didn't want to take on
1453
+ // a crypto dependency and this is just temporary anyway
1454
+ function fnv1a(str) {
1455
+ let hash = 0x811c9dc5; // FNV offset basis
1456
+ for (let i = 0; i < str.length; i++) {
1457
+ hash ^= str.charCodeAt(i);
1458
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
1459
+ }
1460
+ // Convert to hex string, padding to 8 chars
1461
+ return (hash >>> 0).toString(16).padStart(8, '0');
1462
+ }
1463
+ function isTokenInRollout(token, percentage = 0, excludedHashes) {
1464
+ const tokenHash = fnv1a(token);
1465
+ // Check excluded hashes (we're explicitly including these tokens from the rollout)
1466
+ if (excludedHashes?.has(tokenHash)) {
1467
+ return false;
1468
+ }
1469
+ // Convert hash to int and divide by max value to get number between 0-1
1470
+ const hashInt = parseInt(tokenHash, 16);
1471
+ const hashFloat = hashInt / 0xffffffff;
1472
+ return hashFloat < percentage;
1473
+ }
1474
+
1475
+ // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
1476
+ // This work is free. You can redistribute it and/or modify it
1477
+ // under the terms of the WTFPL, Version 2
1478
+ // For more information see LICENSE.txt or http://www.wtfpl.net/
1479
+ //
1480
+ // For more information, the home page:
1481
+ // http://pieroxy.net/blog/pages/lz-string/testing.html
1482
+ //
1483
+ // LZ-based compression algorithm, version 1.4.4
1484
+ // private property
1485
+ const f = String.fromCharCode;
1486
+ const keyStrBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
1487
+ const baseReverseDic = {};
1488
+ function getBaseValue(alphabet, character) {
1489
+ if (!baseReverseDic[alphabet]) {
1490
+ baseReverseDic[alphabet] = {};
1491
+ for (let i = 0; i < alphabet.length; i++) {
1492
+ baseReverseDic[alphabet][alphabet.charAt(i)] = i;
1493
+ }
1494
+ }
1495
+ return baseReverseDic[alphabet][character];
1496
+ }
1497
+ const LZString = {
1498
+ compressToBase64: function (input) {
1499
+ if (input == null) {
1500
+ return '';
1501
+ }
1502
+ const res = LZString._compress(input, 6, function (a) {
1503
+ return keyStrBase64.charAt(a);
1504
+ });
1505
+ switch (res.length % 4 // To produce valid Base64
1506
+ ) {
1507
+ default: // When could this happen ?
1508
+ case 0:
1509
+ return res;
1510
+ case 1:
1511
+ return res + '===';
1512
+ case 2:
1513
+ return res + '==';
1514
+ case 3:
1515
+ return res + '=';
1516
+ }
1517
+ },
1518
+ decompressFromBase64: function (input) {
1519
+ if (input == null) {
1520
+ return '';
1521
+ }
1522
+ if (input == '') {
1523
+ return null;
1524
+ }
1525
+ return LZString._decompress(input.length, 32, function (index) {
1526
+ return getBaseValue(keyStrBase64, input.charAt(index));
1527
+ });
1528
+ },
1529
+ compress: function (uncompressed) {
1530
+ return LZString._compress(uncompressed, 16, function (a) {
1531
+ return f(a);
1532
+ });
1533
+ },
1534
+ _compress: function (uncompressed, bitsPerChar, getCharFromInt) {
1535
+ if (uncompressed == null) {
1536
+ return '';
1537
+ }
1538
+ const context_dictionary = {}, context_dictionaryToCreate = {}, context_data = [];
1539
+ let i, value, context_c = '', context_wc = '', context_w = '', context_enlargeIn = 2, // Compensate for the first entry which should not count
1540
+ context_dictSize = 3, context_numBits = 2, context_data_val = 0, context_data_position = 0, ii;
1541
+ for (ii = 0; ii < uncompressed.length; ii += 1) {
1542
+ context_c = uncompressed.charAt(ii);
1543
+ if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
1544
+ context_dictionary[context_c] = context_dictSize++;
1545
+ context_dictionaryToCreate[context_c] = true;
1546
+ }
1547
+ context_wc = context_w + context_c;
1548
+ if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
1549
+ context_w = context_wc;
1550
+ }
1551
+ else {
1552
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
1553
+ if (context_w.charCodeAt(0) < 256) {
1554
+ for (i = 0; i < context_numBits; i++) {
1555
+ context_data_val = context_data_val << 1;
1556
+ if (context_data_position == bitsPerChar - 1) {
1557
+ context_data_position = 0;
1558
+ context_data.push(getCharFromInt(context_data_val));
1559
+ context_data_val = 0;
1560
+ }
1561
+ else {
1562
+ context_data_position++;
1563
+ }
1564
+ }
1565
+ value = context_w.charCodeAt(0);
1566
+ for (i = 0; i < 8; i++) {
1567
+ context_data_val = (context_data_val << 1) | (value & 1);
1568
+ if (context_data_position == bitsPerChar - 1) {
1569
+ context_data_position = 0;
1570
+ context_data.push(getCharFromInt(context_data_val));
1571
+ context_data_val = 0;
1572
+ }
1573
+ else {
1574
+ context_data_position++;
1575
+ }
1576
+ value = value >> 1;
1577
+ }
1578
+ }
1579
+ else {
1580
+ value = 1;
1581
+ for (i = 0; i < context_numBits; i++) {
1582
+ context_data_val = (context_data_val << 1) | value;
1583
+ if (context_data_position == bitsPerChar - 1) {
1584
+ context_data_position = 0;
1585
+ context_data.push(getCharFromInt(context_data_val));
1586
+ context_data_val = 0;
1587
+ }
1588
+ else {
1589
+ context_data_position++;
1590
+ }
1591
+ value = 0;
1592
+ }
1593
+ value = context_w.charCodeAt(0);
1594
+ for (i = 0; i < 16; i++) {
1595
+ context_data_val = (context_data_val << 1) | (value & 1);
1596
+ if (context_data_position == bitsPerChar - 1) {
1597
+ context_data_position = 0;
1598
+ context_data.push(getCharFromInt(context_data_val));
1599
+ context_data_val = 0;
1600
+ }
1601
+ else {
1602
+ context_data_position++;
1603
+ }
1604
+ value = value >> 1;
1605
+ }
1606
+ }
1607
+ context_enlargeIn--;
1608
+ if (context_enlargeIn == 0) {
1609
+ context_enlargeIn = Math.pow(2, context_numBits);
1610
+ context_numBits++;
1611
+ }
1612
+ delete context_dictionaryToCreate[context_w];
1613
+ }
1614
+ else {
1615
+ value = context_dictionary[context_w];
1616
+ for (i = 0; i < context_numBits; i++) {
1617
+ context_data_val = (context_data_val << 1) | (value & 1);
1618
+ if (context_data_position == bitsPerChar - 1) {
1619
+ context_data_position = 0;
1620
+ context_data.push(getCharFromInt(context_data_val));
1621
+ context_data_val = 0;
1622
+ }
1623
+ else {
1624
+ context_data_position++;
1625
+ }
1626
+ value = value >> 1;
1627
+ }
1628
+ }
1629
+ context_enlargeIn--;
1630
+ if (context_enlargeIn == 0) {
1631
+ context_enlargeIn = Math.pow(2, context_numBits);
1632
+ context_numBits++;
1633
+ }
1634
+ // Add wc to the dictionary.
1635
+ context_dictionary[context_wc] = context_dictSize++;
1636
+ context_w = String(context_c);
1637
+ }
1638
+ }
1639
+ // Output the code for w.
1640
+ if (context_w !== '') {
1641
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
1642
+ if (context_w.charCodeAt(0) < 256) {
1643
+ for (i = 0; i < context_numBits; i++) {
1644
+ context_data_val = context_data_val << 1;
1645
+ if (context_data_position == bitsPerChar - 1) {
1646
+ context_data_position = 0;
1647
+ context_data.push(getCharFromInt(context_data_val));
1648
+ context_data_val = 0;
1649
+ }
1650
+ else {
1651
+ context_data_position++;
1652
+ }
1653
+ }
1654
+ value = context_w.charCodeAt(0);
1655
+ for (i = 0; i < 8; i++) {
1656
+ context_data_val = (context_data_val << 1) | (value & 1);
1657
+ if (context_data_position == bitsPerChar - 1) {
1658
+ context_data_position = 0;
1659
+ context_data.push(getCharFromInt(context_data_val));
1660
+ context_data_val = 0;
1661
+ }
1662
+ else {
1663
+ context_data_position++;
1664
+ }
1665
+ value = value >> 1;
1666
+ }
1667
+ }
1668
+ else {
1669
+ value = 1;
1670
+ for (i = 0; i < context_numBits; i++) {
1671
+ context_data_val = (context_data_val << 1) | value;
1672
+ if (context_data_position == bitsPerChar - 1) {
1673
+ context_data_position = 0;
1674
+ context_data.push(getCharFromInt(context_data_val));
1675
+ context_data_val = 0;
1676
+ }
1677
+ else {
1678
+ context_data_position++;
1679
+ }
1680
+ value = 0;
1681
+ }
1682
+ value = context_w.charCodeAt(0);
1683
+ for (i = 0; i < 16; i++) {
1684
+ context_data_val = (context_data_val << 1) | (value & 1);
1685
+ if (context_data_position == bitsPerChar - 1) {
1686
+ context_data_position = 0;
1687
+ context_data.push(getCharFromInt(context_data_val));
1688
+ context_data_val = 0;
1689
+ }
1690
+ else {
1691
+ context_data_position++;
1692
+ }
1693
+ value = value >> 1;
1694
+ }
1695
+ }
1696
+ context_enlargeIn--;
1697
+ if (context_enlargeIn == 0) {
1698
+ context_enlargeIn = Math.pow(2, context_numBits);
1699
+ context_numBits++;
1700
+ }
1701
+ delete context_dictionaryToCreate[context_w];
1702
+ }
1703
+ else {
1704
+ value = context_dictionary[context_w];
1705
+ for (i = 0; i < context_numBits; i++) {
1706
+ context_data_val = (context_data_val << 1) | (value & 1);
1707
+ if (context_data_position == bitsPerChar - 1) {
1708
+ context_data_position = 0;
1709
+ context_data.push(getCharFromInt(context_data_val));
1710
+ context_data_val = 0;
1711
+ }
1712
+ else {
1713
+ context_data_position++;
1714
+ }
1715
+ value = value >> 1;
1716
+ }
1717
+ }
1718
+ context_enlargeIn--;
1719
+ if (context_enlargeIn == 0) {
1720
+ context_enlargeIn = Math.pow(2, context_numBits);
1721
+ context_numBits++;
1722
+ }
1723
+ }
1724
+ // Mark the end of the stream
1725
+ value = 2;
1726
+ for (i = 0; i < context_numBits; i++) {
1727
+ context_data_val = (context_data_val << 1) | (value & 1);
1728
+ if (context_data_position == bitsPerChar - 1) {
1729
+ context_data_position = 0;
1730
+ context_data.push(getCharFromInt(context_data_val));
1731
+ context_data_val = 0;
1732
+ }
1733
+ else {
1734
+ context_data_position++;
1735
+ }
1736
+ value = value >> 1;
1737
+ }
1738
+ // Flush the last char
1739
+ while (true) {
1740
+ context_data_val = context_data_val << 1;
1741
+ if (context_data_position == bitsPerChar - 1) {
1742
+ context_data.push(getCharFromInt(context_data_val));
1743
+ break;
1744
+ }
1745
+ else {
1746
+ context_data_position++;
1747
+ }
1748
+ }
1749
+ return context_data.join('');
1750
+ },
1751
+ decompress: function (compressed) {
1752
+ if (compressed == null) {
1753
+ return '';
1754
+ }
1755
+ if (compressed == '') {
1756
+ return null;
1757
+ }
1758
+ return LZString._decompress(compressed.length, 32768, function (index) {
1759
+ return compressed.charCodeAt(index);
1760
+ });
1761
+ },
1762
+ _decompress: function (length, resetValue, getNextValue) {
1763
+ const dictionary = [], result = [], data = { val: getNextValue(0), position: resetValue, index: 1 };
1764
+ let enlargeIn = 4, dictSize = 4, numBits = 3, entry = '', i, w, bits, resb, maxpower, power, c;
1765
+ for (i = 0; i < 3; i += 1) {
1766
+ dictionary[i] = i;
1767
+ }
1768
+ bits = 0;
1769
+ maxpower = Math.pow(2, 2);
1770
+ power = 1;
1771
+ while (power != maxpower) {
1772
+ resb = data.val & data.position;
1773
+ data.position >>= 1;
1774
+ if (data.position == 0) {
1775
+ data.position = resetValue;
1776
+ data.val = getNextValue(data.index++);
1777
+ }
1778
+ bits |= (resb > 0 ? 1 : 0) * power;
1779
+ power <<= 1;
1780
+ }
1781
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1782
+ switch ((bits)) {
1783
+ case 0:
1784
+ bits = 0;
1785
+ maxpower = Math.pow(2, 8);
1786
+ power = 1;
1787
+ while (power != maxpower) {
1788
+ resb = data.val & data.position;
1789
+ data.position >>= 1;
1790
+ if (data.position == 0) {
1791
+ data.position = resetValue;
1792
+ data.val = getNextValue(data.index++);
1793
+ }
1794
+ bits |= (resb > 0 ? 1 : 0) * power;
1795
+ power <<= 1;
1796
+ }
1797
+ c = f(bits);
1798
+ break;
1799
+ case 1:
1800
+ bits = 0;
1801
+ maxpower = Math.pow(2, 16);
1802
+ power = 1;
1803
+ while (power != maxpower) {
1804
+ resb = data.val & data.position;
1805
+ data.position >>= 1;
1806
+ if (data.position == 0) {
1807
+ data.position = resetValue;
1808
+ data.val = getNextValue(data.index++);
1809
+ }
1810
+ bits |= (resb > 0 ? 1 : 0) * power;
1811
+ power <<= 1;
1812
+ }
1813
+ c = f(bits);
1814
+ break;
1815
+ case 2:
1816
+ return '';
1817
+ }
1818
+ dictionary[3] = c;
1819
+ w = c;
1820
+ result.push(c);
1821
+ while (true) {
1822
+ if (data.index > length) {
1823
+ return '';
1824
+ }
1825
+ bits = 0;
1826
+ maxpower = Math.pow(2, numBits);
1827
+ power = 1;
1828
+ while (power != maxpower) {
1829
+ resb = data.val & data.position;
1830
+ data.position >>= 1;
1831
+ if (data.position == 0) {
1832
+ data.position = resetValue;
1833
+ data.val = getNextValue(data.index++);
1834
+ }
1835
+ bits |= (resb > 0 ? 1 : 0) * power;
1836
+ power <<= 1;
1837
+ }
1838
+ switch ((c = bits)) {
1839
+ case 0:
1840
+ bits = 0;
1841
+ maxpower = Math.pow(2, 8);
1842
+ power = 1;
1843
+ while (power != maxpower) {
1844
+ resb = data.val & data.position;
1845
+ data.position >>= 1;
1846
+ if (data.position == 0) {
1847
+ data.position = resetValue;
1848
+ data.val = getNextValue(data.index++);
1849
+ }
1850
+ bits |= (resb > 0 ? 1 : 0) * power;
1851
+ power <<= 1;
1852
+ }
1853
+ dictionary[dictSize++] = f(bits);
1854
+ c = dictSize - 1;
1855
+ enlargeIn--;
1856
+ break;
1857
+ case 1:
1858
+ bits = 0;
1859
+ maxpower = Math.pow(2, 16);
1860
+ power = 1;
1861
+ while (power != maxpower) {
1862
+ resb = data.val & data.position;
1863
+ data.position >>= 1;
1864
+ if (data.position == 0) {
1865
+ data.position = resetValue;
1866
+ data.val = getNextValue(data.index++);
1867
+ }
1868
+ bits |= (resb > 0 ? 1 : 0) * power;
1869
+ power <<= 1;
1870
+ }
1871
+ dictionary[dictSize++] = f(bits);
1872
+ c = dictSize - 1;
1873
+ enlargeIn--;
1874
+ break;
1875
+ case 2:
1876
+ return result.join('');
1877
+ }
1878
+ if (enlargeIn == 0) {
1879
+ enlargeIn = Math.pow(2, numBits);
1880
+ numBits++;
1881
+ }
1882
+ if (dictionary[c]) {
1883
+ entry = dictionary[c];
1884
+ }
1885
+ else {
1886
+ if (c === dictSize) {
1887
+ entry = w + w.charAt(0);
1888
+ }
1889
+ else {
1890
+ return null;
1891
+ }
1892
+ }
1893
+ result.push(entry);
1894
+ // Add w+entry[0] to the dictionary.
1895
+ dictionary[dictSize++] = w + entry.charAt(0);
1896
+ enlargeIn--;
1897
+ w = entry;
1898
+ if (enlargeIn == 0) {
1899
+ enlargeIn = Math.pow(2, numBits);
1900
+ numBits++;
1901
+ }
1902
+ }
1903
+ },
1904
+ };
1905
+
1906
+ class SimpleEventEmitter {
1907
+ constructor() {
1908
+ this.events = {};
1909
+ this.events = {};
1910
+ }
1911
+ on(event, listener) {
1912
+ if (!this.events[event]) {
1913
+ this.events[event] = [];
1914
+ }
1915
+ this.events[event].push(listener);
1916
+ return () => {
1917
+ this.events[event] = this.events[event].filter((x) => x !== listener);
1918
+ };
1919
+ }
1920
+ emit(event, payload) {
1921
+ for (const listener of this.events[event] || []) {
1922
+ listener(payload);
1923
+ }
1924
+ for (const listener of this.events['*'] || []) {
1925
+ listener(event, payload);
1926
+ }
1927
+ }
1928
+ }
1929
+
1930
+ class PostHogFetchHttpError extends Error {
1931
+ constructor(response, reqByteLength) {
1932
+ super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength);
1933
+ this.response = response;
1934
+ this.reqByteLength = reqByteLength;
1935
+ this.name = 'PostHogFetchHttpError';
1936
+ }
1937
+ get status() {
1938
+ return this.response.status;
1939
+ }
1940
+ get text() {
1941
+ return this.response.text();
1942
+ }
1943
+ get json() {
1944
+ return this.response.json();
1945
+ }
1946
+ }
1947
+ class PostHogFetchNetworkError extends Error {
1948
+ constructor(error) {
1949
+ // TRICKY: "cause" is a newer property but is just ignored otherwise. Cast to any to ignore the type issue.
1950
+ // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
1951
+ // @ts-ignore
1952
+ super('Network error while fetching PostHog', error instanceof Error ? { cause: error } : {});
1953
+ this.error = error;
1954
+ this.name = 'PostHogFetchNetworkError';
1955
+ }
1956
+ }
1957
+ async function logFlushError(err) {
1958
+ if (err instanceof PostHogFetchHttpError) {
1959
+ let text = '';
1960
+ try {
1961
+ text = await err.text;
1962
+ }
1963
+ catch { }
1964
+ console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
1965
+ }
1966
+ else {
1967
+ console.error('Error while flushing PostHog', err);
1968
+ }
1969
+ return Promise.resolve();
1970
+ }
1971
+ function isPostHogFetchError(err) {
1972
+ return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
1973
+ }
1974
+ function isPostHogFetchContentTooLargeError(err) {
1975
+ return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413;
1976
+ }
1977
+ var QuotaLimitedFeature;
1978
+ (function (QuotaLimitedFeature) {
1979
+ QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
1980
+ QuotaLimitedFeature["Recordings"] = "recordings";
1981
+ })(QuotaLimitedFeature || (QuotaLimitedFeature = {}));
1982
+ class PostHogCoreStateless {
1983
+ constructor(apiKey, options) {
1984
+ this.flushPromise = null;
1985
+ this.shutdownPromise = null;
1986
+ this.pendingPromises = {};
1987
+ // internal
1988
+ this._events = new SimpleEventEmitter();
1989
+ this._isInitialized = false;
1990
+ assert(apiKey, "You must pass your PostHog project's api key.");
1991
+ this.apiKey = apiKey;
1992
+ this.host = removeTrailingSlash(options?.host || 'https://us.i.posthog.com');
1993
+ this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 20;
1994
+ this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
1995
+ this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
1996
+ this.flushInterval = options?.flushInterval ?? 10000;
1997
+ this.captureMode = options?.captureMode || 'json';
1998
+ this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
1999
+ // If enable is explicitly set to false we override the optout
2000
+ this.defaultOptIn = options?.defaultOptIn ?? true;
2001
+ this.disableSurveys = options?.disableSurveys ?? false;
2002
+ this._retryOptions = {
2003
+ retryCount: options?.fetchRetryCount ?? 3,
2004
+ retryDelay: options?.fetchRetryDelay ?? 3000,
2005
+ retryCheck: isPostHogFetchError,
2006
+ };
2007
+ this.requestTimeout = options?.requestTimeout ?? 10000; // 10 seconds
2008
+ this.featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 3000; // 3 seconds
2009
+ this.remoteConfigRequestTimeoutMs = options?.remoteConfigRequestTimeoutMs ?? 3000; // 3 seconds
2010
+ this.disableGeoip = options?.disableGeoip ?? true;
2011
+ this.disabled = options?.disabled ?? false;
2012
+ this.historicalMigration = options?.historicalMigration ?? false;
2013
+ // Init promise allows the derived class to block calls until it is ready
2014
+ this._initPromise = Promise.resolve();
2015
+ this._isInitialized = true;
2016
+ }
2017
+ logMsgIfDebug(fn) {
2018
+ if (this.isDebug) {
2019
+ fn();
2020
+ }
2021
+ }
2022
+ wrap(fn) {
2023
+ if (this.disabled) {
2024
+ this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
2025
+ return;
2026
+ }
2027
+ if (this._isInitialized) {
2028
+ // NOTE: We could also check for the "opt in" status here...
2029
+ return fn();
2030
+ }
2031
+ this._initPromise.then(() => fn());
2032
+ }
2033
+ getCommonEventProperties() {
2034
+ return {
2035
+ $lib: this.getLibraryId(),
2036
+ $lib_version: this.getLibraryVersion(),
2037
+ };
2038
+ }
2039
+ get optedOut() {
2040
+ return this.getPersistedProperty(PostHogPersistedProperty.OptedOut) ?? !this.defaultOptIn;
2041
+ }
2042
+ async optIn() {
2043
+ this.wrap(() => {
2044
+ this.setPersistedProperty(PostHogPersistedProperty.OptedOut, false);
2045
+ });
2046
+ }
2047
+ async optOut() {
2048
+ this.wrap(() => {
2049
+ this.setPersistedProperty(PostHogPersistedProperty.OptedOut, true);
2050
+ });
2051
+ }
2052
+ on(event, cb) {
2053
+ return this._events.on(event, cb);
2054
+ }
2055
+ debug(enabled = true) {
2056
+ this.removeDebugCallback?.();
2057
+ if (enabled) {
2058
+ const removeDebugCallback = this.on('*', (event, payload) => console.log('PostHog Debug', event, payload));
2059
+ this.removeDebugCallback = () => {
2060
+ removeDebugCallback();
2061
+ this.removeDebugCallback = undefined;
2062
+ };
2063
+ }
2064
+ }
2065
+ get isDebug() {
2066
+ return !!this.removeDebugCallback;
2067
+ }
2068
+ get isDisabled() {
2069
+ return this.disabled;
2070
+ }
2071
+ buildPayload(payload) {
2072
+ return {
2073
+ distinct_id: payload.distinct_id,
2074
+ event: payload.event,
2075
+ properties: {
2076
+ ...(payload.properties || {}),
2077
+ ...this.getCommonEventProperties(), // Common PH props
2078
+ },
2079
+ };
2080
+ }
2081
+ addPendingPromise(promise) {
2082
+ const promiseUUID = uuidv7();
2083
+ this.pendingPromises[promiseUUID] = promise;
2084
+ promise
2085
+ .catch(() => { })
2086
+ .finally(() => {
2087
+ delete this.pendingPromises[promiseUUID];
2088
+ });
2089
+ return promise;
2090
+ }
2091
+ /***
2092
+ *** TRACKING
2093
+ ***/
2094
+ identifyStateless(distinctId, properties, options) {
2095
+ this.wrap(() => {
2096
+ // The properties passed to identifyStateless are event properties.
2097
+ // To add person properties, pass in all person properties to the `$set` and `$set_once` keys.
2098
+ const payload = {
2099
+ ...this.buildPayload({
2100
+ distinct_id: distinctId,
2101
+ event: '$identify',
2102
+ properties,
2103
+ }),
2104
+ };
2105
+ this.enqueue('identify', payload, options);
2106
+ });
2107
+ }
2108
+ async identifyStatelessImmediate(distinctId, properties, options) {
2109
+ const payload = {
2110
+ ...this.buildPayload({
2111
+ distinct_id: distinctId,
2112
+ event: '$identify',
2113
+ properties,
2114
+ }),
2115
+ };
2116
+ await this.sendImmediate('identify', payload, options);
2117
+ }
2118
+ captureStateless(distinctId, event, properties, options) {
2119
+ this.wrap(() => {
2120
+ const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
2121
+ this.enqueue('capture', payload, options);
2122
+ });
2123
+ }
2124
+ async captureStatelessImmediate(distinctId, event, properties, options) {
2125
+ const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
2126
+ await this.sendImmediate('capture', payload, options);
2127
+ }
2128
+ aliasStateless(alias, distinctId, properties, options) {
2129
+ this.wrap(() => {
2130
+ const payload = this.buildPayload({
2131
+ event: '$create_alias',
2132
+ distinct_id: distinctId,
2133
+ properties: {
2134
+ ...(properties || {}),
2135
+ distinct_id: distinctId,
2136
+ alias,
2137
+ },
2138
+ });
2139
+ this.enqueue('alias', payload, options);
2140
+ });
2141
+ }
2142
+ async aliasStatelessImmediate(alias, distinctId, properties, options) {
2143
+ const payload = this.buildPayload({
2144
+ event: '$create_alias',
2145
+ distinct_id: distinctId,
2146
+ properties: {
2147
+ ...(properties || {}),
2148
+ distinct_id: distinctId,
2149
+ alias,
2150
+ },
2151
+ });
2152
+ await this.sendImmediate('alias', payload, options);
2153
+ }
2154
+ /***
2155
+ *** GROUPS
2156
+ ***/
2157
+ groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties) {
2158
+ this.wrap(() => {
2159
+ const payload = this.buildPayload({
2160
+ distinct_id: distinctId || `$${groupType}_${groupKey}`,
2161
+ event: '$groupidentify',
2162
+ properties: {
2163
+ $group_type: groupType,
2164
+ $group_key: groupKey,
2165
+ $group_set: groupProperties || {},
2166
+ ...(eventProperties || {}),
2167
+ },
2168
+ });
2169
+ this.enqueue('capture', payload, options);
2170
+ });
2171
+ }
2172
+ async getRemoteConfig() {
2173
+ await this._initPromise;
2174
+ let host = this.host;
2175
+ if (host === 'https://us.i.posthog.com') {
2176
+ host = 'https://us-assets.i.posthog.com';
2177
+ }
2178
+ else if (host === 'https://eu.i.posthog.com') {
2179
+ host = 'https://eu-assets.i.posthog.com';
2180
+ }
2181
+ const url = `${host}/array/${this.apiKey}/config`;
2182
+ const fetchOptions = {
2183
+ method: 'GET',
2184
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
2185
+ };
2186
+ // Don't retry remote config API calls
2187
+ return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.remoteConfigRequestTimeoutMs)
2188
+ .then((response) => response.json())
2189
+ .catch((error) => {
2190
+ this.logMsgIfDebug(() => console.error('Remote config could not be loaded', error));
2191
+ this._events.emit('error', error);
2192
+ return undefined;
2193
+ });
2194
+ }
2195
+ /***
2196
+ *** FEATURE FLAGS
2197
+ ***/
2198
+ async getDecide(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
2199
+ await this._initPromise;
2200
+ // Check if the API token is in the new flags rollout
2201
+ // This is a temporary measure to ensure that we can still use the old flags API
2202
+ // while we migrate to the new flags API
2203
+ const useFlags = isTokenInRollout(this.apiKey, NEW_FLAGS_ROLLOUT_PERCENTAGE, NEW_FLAGS_EXCLUDED_HASHES);
2204
+ const url = useFlags ? `${this.host}/flags/?v=2` : `${this.host}/decide/?v=4`;
2205
+ const fetchOptions = {
2206
+ method: 'POST',
2207
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
2208
+ body: JSON.stringify({
2209
+ token: this.apiKey,
2210
+ distinct_id: distinctId,
2211
+ groups,
2212
+ person_properties: personProperties,
2213
+ group_properties: groupProperties,
2214
+ ...extraPayload,
2215
+ }),
2216
+ };
2217
+ // Don't retry /decide API calls
2218
+ return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
2219
+ .then((response) => response.json())
2220
+ .then((response) => normalizeDecideResponse(response))
2221
+ .catch((error) => {
2222
+ this._events.emit('error', error);
2223
+ return undefined;
2224
+ });
2225
+ }
2226
+ async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
2227
+ await this._initPromise;
2228
+ const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
2229
+ if (flagDetailResponse === undefined) {
2230
+ // If we haven't loaded flags yet, or errored out, we respond with undefined
2231
+ return {
2232
+ response: undefined,
2233
+ requestId: undefined,
2234
+ };
2235
+ }
2236
+ let response = getFeatureFlagValue(flagDetailResponse.response);
2237
+ if (response === undefined) {
2238
+ // For cases where the flag is unknown, return false
2239
+ response = false;
2240
+ }
2241
+ // If we have flags we either return the value (true or string) or false
2242
+ return {
2243
+ response,
2244
+ requestId: flagDetailResponse.requestId,
2245
+ };
2246
+ }
2247
+ async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
2248
+ await this._initPromise;
2249
+ const decideResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
2250
+ if (decideResponse === undefined) {
2251
+ return undefined;
2252
+ }
2253
+ const featureFlags = decideResponse.flags;
2254
+ const flagDetail = featureFlags[key];
2255
+ return {
2256
+ response: flagDetail,
2257
+ requestId: decideResponse.requestId,
2258
+ };
2259
+ }
2260
+ async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
2261
+ await this._initPromise;
2262
+ const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
2263
+ if (!payloads) {
2264
+ return undefined;
2265
+ }
2266
+ const response = payloads[key];
2267
+ // Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
2268
+ if (response === undefined) {
2269
+ return null;
2270
+ }
2271
+ return response;
2272
+ }
2273
+ async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
2274
+ await this._initPromise;
2275
+ const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate)).payloads;
2276
+ return payloads;
2277
+ }
2278
+ async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
2279
+ await this._initPromise;
2280
+ return await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
2281
+ }
2282
+ async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
2283
+ await this._initPromise;
2284
+ const featureFlagDetails = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
2285
+ if (!featureFlagDetails) {
2286
+ return {
2287
+ flags: undefined,
2288
+ payloads: undefined,
2289
+ requestId: undefined,
2290
+ };
2291
+ }
2292
+ return {
2293
+ flags: featureFlagDetails.featureFlags,
2294
+ payloads: featureFlagDetails.featureFlagPayloads,
2295
+ requestId: featureFlagDetails.requestId,
2296
+ };
2297
+ }
2298
+ async getFeatureFlagDetailsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
2299
+ await this._initPromise;
2300
+ const extraPayload = {};
2301
+ if (disableGeoip ?? this.disableGeoip) {
2302
+ extraPayload['geoip_disable'] = true;
2303
+ }
2304
+ if (flagKeysToEvaluate) {
2305
+ extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
2306
+ }
2307
+ const decideResponse = await this.getDecide(distinctId, groups, personProperties, groupProperties, extraPayload);
2308
+ if (decideResponse === undefined) {
2309
+ // We probably errored out, so return undefined
2310
+ return undefined;
2311
+ }
2312
+ // if there's an error on the decideResponse, log a console error, but don't throw an error
2313
+ if (decideResponse.errorsWhileComputingFlags) {
2314
+ 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');
2315
+ }
2316
+ // Add check for quota limitation on feature flags
2317
+ if (decideResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
2318
+ 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');
2319
+ return {
2320
+ flags: {},
2321
+ featureFlags: {},
2322
+ featureFlagPayloads: {},
2323
+ requestId: decideResponse?.requestId,
2324
+ };
2325
+ }
2326
+ return decideResponse;
2327
+ }
2328
+ /***
2329
+ *** SURVEYS
2330
+ ***/
2331
+ async getSurveysStateless() {
2332
+ await this._initPromise;
2333
+ if (this.disableSurveys === true) {
2334
+ this.logMsgIfDebug(() => console.log('Loading surveys is disabled.'));
2335
+ return [];
2336
+ }
2337
+ const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
2338
+ const fetchOptions = {
2339
+ method: 'GET',
2340
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
2341
+ };
2342
+ const response = await this.fetchWithRetry(url, fetchOptions)
2343
+ .then((response) => {
2344
+ if (response.status !== 200 || !response.json) {
2345
+ const msg = `Surveys API could not be loaded: ${response.status}`;
2346
+ const error = new Error(msg);
2347
+ this.logMsgIfDebug(() => console.error(error));
2348
+ this._events.emit('error', new Error(msg));
2349
+ return undefined;
2350
+ }
2351
+ return response.json();
2352
+ })
2353
+ .catch((error) => {
2354
+ this.logMsgIfDebug(() => console.error('Surveys API could not be loaded', error));
2355
+ this._events.emit('error', error);
2356
+ return undefined;
2357
+ });
2358
+ const newSurveys = response?.surveys;
2359
+ if (newSurveys) {
2360
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from API: ', JSON.stringify(newSurveys)));
2361
+ }
2362
+ return newSurveys ?? [];
2363
+ }
2364
+ get props() {
2365
+ if (!this._props) {
2366
+ this._props = this.getPersistedProperty(PostHogPersistedProperty.Props);
2367
+ }
2368
+ return this._props || {};
2369
+ }
2370
+ set props(val) {
2371
+ this._props = val;
2372
+ }
2373
+ async register(properties) {
2374
+ this.wrap(() => {
2375
+ this.props = {
2376
+ ...this.props,
2377
+ ...properties,
2378
+ };
2379
+ this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
2380
+ });
2381
+ }
2382
+ async unregister(property) {
2383
+ this.wrap(() => {
2384
+ delete this.props[property];
2385
+ this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
2386
+ });
2387
+ }
2388
+ /***
2389
+ *** QUEUEING AND FLUSHING
2390
+ ***/
2391
+ enqueue(type, _message, options) {
2392
+ this.wrap(() => {
2393
+ if (this.optedOut) {
2394
+ this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
2395
+ return;
2396
+ }
2397
+ const message = this.prepareMessage(type, _message, options);
2398
+ const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2399
+ if (queue.length >= this.maxQueueSize) {
2400
+ queue.shift();
2401
+ this.logMsgIfDebug(() => console.info('Queue is full, the oldest event is dropped.'));
2402
+ }
2403
+ queue.push({ message });
2404
+ this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
2405
+ this._events.emit(type, message);
2406
+ // Flush queued events if we meet the flushAt length
2407
+ if (queue.length >= this.flushAt) {
2408
+ this.flushBackground();
2409
+ }
2410
+ if (this.flushInterval && !this._flushTimer) {
2411
+ this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
2412
+ }
2413
+ });
2414
+ }
2415
+ async sendImmediate(type, _message, options) {
2416
+ if (this.disabled) {
2417
+ this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
2418
+ return;
2419
+ }
2420
+ if (!this._isInitialized) {
2421
+ await this._initPromise;
2422
+ }
2423
+ if (this.optedOut) {
2424
+ this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
2425
+ return;
2426
+ }
2427
+ const data = {
2428
+ api_key: this.apiKey,
2429
+ batch: [this.prepareMessage(type, _message, options)],
2430
+ sent_at: currentISOTime(),
2431
+ };
2432
+ if (this.historicalMigration) {
2433
+ data.historical_migration = true;
2434
+ }
2435
+ const payload = JSON.stringify(data);
2436
+ const url = this.captureMode === 'form'
2437
+ ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
2438
+ : `${this.host}/batch/`;
2439
+ const fetchOptions = this.captureMode === 'form'
2440
+ ? {
2441
+ method: 'POST',
2442
+ mode: 'no-cors',
2443
+ credentials: 'omit',
2444
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
2445
+ body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
2446
+ }
2447
+ : {
2448
+ method: 'POST',
2449
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
2450
+ body: payload,
2451
+ };
2452
+ try {
2453
+ await this.fetchWithRetry(url, fetchOptions);
2454
+ }
2455
+ catch (err) {
2456
+ this._events.emit('error', err);
2457
+ throw err;
2458
+ }
2459
+ }
2460
+ prepareMessage(type, _message, options) {
2461
+ const message = {
2462
+ ..._message,
2463
+ type: type,
2464
+ library: this.getLibraryId(),
2465
+ library_version: this.getLibraryVersion(),
2466
+ timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
2467
+ uuid: options?.uuid ? options.uuid : uuidv7(),
2468
+ };
2469
+ const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
2470
+ if (addGeoipDisableProperty) {
2471
+ if (!message.properties) {
2472
+ message.properties = {};
2473
+ }
2474
+ message['properties']['$geoip_disable'] = true;
2475
+ }
2476
+ if (message.distinctId) {
2477
+ message.distinct_id = message.distinctId;
2478
+ delete message.distinctId;
2479
+ }
2480
+ return message;
2481
+ }
2482
+ clearFlushTimer() {
2483
+ if (this._flushTimer) {
2484
+ clearTimeout(this._flushTimer);
2485
+ this._flushTimer = undefined;
2486
+ }
2487
+ }
2488
+ /**
2489
+ * Helper for flushing the queue in the background
2490
+ * Avoids unnecessary promise errors
2491
+ */
2492
+ flushBackground() {
2493
+ void this.flush().catch(async (err) => {
2494
+ await logFlushError(err);
2495
+ });
2496
+ }
2497
+ async flush() {
2498
+ // Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
2499
+ // Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
2500
+ const nextFlushPromise = Promise.allSettled([this.flushPromise]).then(() => {
2501
+ return this._flush();
2502
+ });
2503
+ this.flushPromise = nextFlushPromise;
2504
+ void this.addPendingPromise(nextFlushPromise);
2505
+ Promise.allSettled([nextFlushPromise]).then(() => {
2506
+ // If there are no others waiting to flush, clear the promise.
2507
+ // We don't strictly need to do this, but it could make debugging easier
2508
+ if (this.flushPromise === nextFlushPromise) {
2509
+ this.flushPromise = null;
2510
+ }
2511
+ });
2512
+ return nextFlushPromise;
2513
+ }
2514
+ getCustomHeaders() {
2515
+ // Don't set the user agent if we're not on a browser. The latest spec allows
2516
+ // the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
2517
+ // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
2518
+ // but browsers such as Chrome and Safari have not caught up.
2519
+ const customUserAgent = this.getCustomUserAgent();
2520
+ const headers = {};
2521
+ if (customUserAgent && customUserAgent !== '') {
2522
+ headers['User-Agent'] = customUserAgent;
2523
+ }
2524
+ return headers;
2525
+ }
2526
+ async _flush() {
2527
+ this.clearFlushTimer();
2528
+ await this._initPromise;
2529
+ let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2530
+ if (!queue.length) {
2531
+ return [];
2532
+ }
2533
+ const sentMessages = [];
2534
+ const originalQueueLength = queue.length;
2535
+ while (queue.length > 0 && sentMessages.length < originalQueueLength) {
2536
+ const batchItems = queue.slice(0, this.maxBatchSize);
2537
+ const batchMessages = batchItems.map((item) => item.message);
2538
+ const persistQueueChange = () => {
2539
+ const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2540
+ const newQueue = refreshedQueue.slice(batchItems.length);
2541
+ this.setPersistedProperty(PostHogPersistedProperty.Queue, newQueue);
2542
+ queue = newQueue;
2543
+ };
2544
+ const data = {
2545
+ api_key: this.apiKey,
2546
+ batch: batchMessages,
2547
+ sent_at: currentISOTime(),
2548
+ };
2549
+ if (this.historicalMigration) {
2550
+ data.historical_migration = true;
2551
+ }
2552
+ const payload = JSON.stringify(data);
2553
+ const url = this.captureMode === 'form'
2554
+ ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
2555
+ : `${this.host}/batch/`;
2556
+ const fetchOptions = this.captureMode === 'form'
2557
+ ? {
2558
+ method: 'POST',
2559
+ mode: 'no-cors',
2560
+ credentials: 'omit',
2561
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
2562
+ body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
2563
+ }
2564
+ : {
2565
+ method: 'POST',
2566
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
2567
+ body: payload,
2568
+ };
2569
+ const retryOptions = {
2570
+ retryCheck: (err) => {
2571
+ // don't automatically retry on 413 errors, we want to reduce the batch size first
2572
+ if (isPostHogFetchContentTooLargeError(err)) {
2573
+ return false;
2574
+ }
2575
+ // otherwise, retry on network errors
2576
+ return isPostHogFetchError(err);
2577
+ },
2578
+ };
2579
+ try {
2580
+ await this.fetchWithRetry(url, fetchOptions, retryOptions);
2581
+ }
2582
+ catch (err) {
2583
+ if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
2584
+ // if we get a 413 error, we want to reduce the batch size and try again
2585
+ this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
2586
+ this.logMsgIfDebug(() => console.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`));
2587
+ // do not persist the queue change, we want to retry the same batch
2588
+ continue;
2589
+ }
2590
+ // depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
2591
+ // 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
2592
+ if (!(err instanceof PostHogFetchNetworkError)) {
2593
+ persistQueueChange();
2594
+ }
2595
+ this._events.emit('error', err);
2596
+ throw err;
2597
+ }
2598
+ persistQueueChange();
2599
+ sentMessages.push(...batchMessages);
2600
+ }
2601
+ this._events.emit('flush', sentMessages);
2602
+ return sentMessages;
2603
+ }
2604
+ async fetchWithRetry(url, options, retryOptions, requestTimeout) {
2605
+ var _a;
2606
+ (_a = AbortSignal).timeout ?? (_a.timeout = function timeout(ms) {
2607
+ const ctrl = new AbortController();
2608
+ setTimeout(() => ctrl.abort(), ms);
2609
+ return ctrl.signal;
2610
+ });
2611
+ const body = options.body ? options.body : '';
2612
+ const reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
2613
+ return await retriable(async () => {
2614
+ let res = null;
2615
+ try {
2616
+ res = await this.fetch(url, {
2617
+ signal: AbortSignal.timeout(requestTimeout ?? this.requestTimeout),
2618
+ ...options,
2619
+ });
2620
+ }
2621
+ catch (e) {
2622
+ // fetch will only throw on network errors or on timeouts
2623
+ throw new PostHogFetchNetworkError(e);
2624
+ }
2625
+ // If we're in no-cors mode, we can't access the response status
2626
+ // We only throw on HTTP errors if we're not in no-cors mode
2627
+ // https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
2628
+ const isNoCors = options.mode === 'no-cors';
2629
+ if (!isNoCors && (res.status < 200 || res.status >= 400)) {
2630
+ throw new PostHogFetchHttpError(res, reqByteLength);
2631
+ }
2632
+ return res;
2633
+ }, { ...this._retryOptions, ...retryOptions });
2634
+ }
2635
+ async _shutdown(shutdownTimeoutMs = 30000) {
2636
+ // A little tricky - we want to have a max shutdown time and enforce it, even if that means we have some
2637
+ // dangling promises. We'll keep track of the timeout and resolve/reject based on that.
2638
+ await this._initPromise;
2639
+ let hasTimedOut = false;
2640
+ this.clearFlushTimer();
2641
+ const doShutdown = async () => {
2642
+ try {
2643
+ await Promise.all(Object.values(this.pendingPromises));
2644
+ while (true) {
2645
+ const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
2646
+ if (queue.length === 0) {
2647
+ break;
2648
+ }
2649
+ // flush again to make sure we send all events, some of which might've been added
2650
+ // while we were waiting for the pending promises to resolve
2651
+ // For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
2652
+ await this.flush();
2653
+ if (hasTimedOut) {
2654
+ break;
2655
+ }
2656
+ }
2657
+ }
2658
+ catch (e) {
2659
+ if (!isPostHogFetchError(e)) {
2660
+ throw e;
2661
+ }
2662
+ await logFlushError(e);
2663
+ }
2664
+ };
2665
+ return Promise.race([
2666
+ new Promise((_, reject) => {
2667
+ safeSetTimeout(() => {
2668
+ this.logMsgIfDebug(() => console.error('Timed out while shutting down PostHog'));
2669
+ hasTimedOut = true;
2670
+ reject('Timeout while shutting down PostHog. Some events may not have been sent.');
2671
+ }, shutdownTimeoutMs);
2672
+ }),
2673
+ doShutdown(),
2674
+ ]);
2675
+ }
2676
+ /**
2677
+ * Call shutdown() once before the node process exits, so ensure that all events have been sent and all promises
2678
+ * have resolved. Do not use this function if you intend to keep using this PostHog instance after calling it.
2679
+ * @param shutdownTimeoutMs
2680
+ */
2681
+ async shutdown(shutdownTimeoutMs = 30000) {
2682
+ if (this.shutdownPromise) {
2683
+ 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'));
2684
+ }
2685
+ else {
2686
+ this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
2687
+ this.shutdownPromise = null;
2688
+ });
2689
+ }
2690
+ return this.shutdownPromise;
2691
+ }
2692
+ }
2693
+
2694
+ /**
2695
+ * Fetch wrapper
2696
+ *
2697
+ * We want to polyfill fetch when not available with axios but use it when it is.
2698
+ * NOTE: The current version of Axios has an issue when in non-node environments like Clouflare Workers.
2699
+ * This is currently solved by using the global fetch if available instead.
2700
+ * See https://github.com/PostHog/posthog-js-lite/issues/127 for more info
2701
+ */
2702
+ let _fetch = getFetch();
2703
+ if (!_fetch) {
2704
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
2705
+ const axios = require('axios');
2706
+ _fetch = async (url, options) => {
2707
+ const res = await axios.request({
2708
+ url,
2709
+ headers: options.headers,
2710
+ method: options.method.toLowerCase(),
2711
+ data: options.body,
2712
+ signal: options.signal,
2713
+ // fetch only throws on network errors, not on HTTP errors
2714
+ validateStatus: () => true
2715
+ });
2716
+ return {
2717
+ status: res.status,
2718
+ text: async () => res.data,
2719
+ json: async () => res.data
2720
+ };
2721
+ };
2722
+ }
2723
+ // NOTE: We have to export this as default, even though we prefer named exports as we are relying on detecting "fetch" in the global scope
2724
+ var fetch$1 = _fetch;
2725
+
2726
+ /**
2727
+ * A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
2728
+ */
2729
+ class Lazy {
2730
+ constructor(factory) {
2731
+ this.factory = factory;
2732
+ }
2733
+ /**
2734
+ * Gets the value, initializing it if necessary.
2735
+ * Multiple concurrent calls will share the same initialization promise.
2736
+ */
2737
+ async getValue() {
2738
+ if (this.value !== undefined) {
2739
+ return this.value;
2740
+ }
2741
+ if (this.initializationPromise === undefined) {
2742
+ this.initializationPromise = (async () => {
2743
+ try {
2744
+ const result = await this.factory();
2745
+ this.value = result;
2746
+ return result;
2747
+ } finally {
2748
+ // Clear the promise so we can retry if needed
2749
+ this.initializationPromise = undefined;
2750
+ }
2751
+ })();
2752
+ }
2753
+ return this.initializationPromise;
2754
+ }
2755
+ /**
2756
+ * Returns true if the value has been initialized.
2757
+ */
2758
+ isInitialized() {
2759
+ return this.value !== undefined;
2760
+ }
2761
+ /**
2762
+ * Returns a promise that resolves when the value is initialized.
2763
+ * If already initialized, resolves immediately.
2764
+ */
2765
+ async waitForInitialization() {
2766
+ if (this.isInitialized()) {
2767
+ return;
2768
+ }
2769
+ await this.getValue();
2770
+ }
2771
+ }
2772
+
2773
+ /// <reference lib="dom" />
2774
+ const nodeCrypto = new Lazy(async () => {
2775
+ try {
2776
+ return await import('crypto');
2777
+ } catch {
2778
+ return undefined;
2779
+ }
2780
+ });
2781
+ async function getNodeCrypto() {
2782
+ return await nodeCrypto.getValue();
2783
+ }
2784
+ const webCrypto = new Lazy(async () => {
2785
+ if (typeof globalThis.crypto?.subtle !== 'undefined') {
2786
+ return globalThis.crypto.subtle;
2787
+ }
2788
+ try {
2789
+ // Node.js: use built-in webcrypto and assign it if needed
2790
+ const crypto = await nodeCrypto.getValue();
2791
+ if (crypto?.webcrypto?.subtle) {
2792
+ return crypto.webcrypto.subtle;
2793
+ }
2794
+ } catch {
2795
+ // Ignore if not available
2796
+ }
2797
+ return undefined;
2798
+ });
2799
+ async function getWebCrypto() {
2800
+ return await webCrypto.getValue();
2801
+ }
2802
+
2803
+ /// <reference lib="dom" />
2804
+ async function hashSHA1(text) {
2805
+ // Try Node.js crypto first
2806
+ const nodeCrypto = await getNodeCrypto();
2807
+ if (nodeCrypto) {
2808
+ return nodeCrypto.createHash('sha1').update(text).digest('hex');
2809
+ }
2810
+ const webCrypto = await getWebCrypto();
2811
+ // Fall back to Web Crypto API
2812
+ if (webCrypto) {
2813
+ const hashBuffer = await webCrypto.digest('SHA-1', new TextEncoder().encode(text));
2814
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2815
+ return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
2816
+ }
2817
+ throw new Error('No crypto implementation available. Tried Node Crypto API and Web SubtleCrypto API');
2818
+ }
2819
+
2820
+ const SIXTY_SECONDS = 60 * 1000;
2821
+ // eslint-disable-next-line
2822
+ const LONG_SCALE = 0xfffffffffffffff;
2823
+ const NULL_VALUES_ALLOWED_OPERATORS = ['is_not'];
2824
+ class ClientError extends Error {
2825
+ constructor(message) {
2826
+ super();
2827
+ Error.captureStackTrace(this, this.constructor);
2828
+ this.name = 'ClientError';
2829
+ this.message = message;
2830
+ Object.setPrototypeOf(this, ClientError.prototype);
2831
+ }
2832
+ }
2833
+ class InconclusiveMatchError extends Error {
2834
+ constructor(message) {
2835
+ super(message);
2836
+ this.name = this.constructor.name;
2837
+ Error.captureStackTrace(this, this.constructor);
2838
+ // instanceof doesn't work in ES3 or ES5
2839
+ // https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
2840
+ // this is the workaround
2841
+ Object.setPrototypeOf(this, InconclusiveMatchError.prototype);
2842
+ }
2843
+ }
2844
+ class FeatureFlagsPoller {
2845
+ constructor({
2846
+ pollingInterval,
2847
+ personalApiKey,
2848
+ projectApiKey,
2849
+ timeout,
2850
+ host,
2851
+ customHeaders,
2852
+ ...options
2853
+ }) {
2854
+ this.debugMode = false;
2855
+ this.shouldBeginExponentialBackoff = false;
2856
+ this.backOffCount = 0;
2857
+ this.pollingInterval = pollingInterval;
2858
+ this.personalApiKey = personalApiKey;
2859
+ this.featureFlags = [];
2860
+ this.featureFlagsByKey = {};
2861
+ this.groupTypeMapping = {};
2862
+ this.cohorts = {};
2863
+ this.loadedSuccessfullyOnce = false;
2864
+ this.timeout = timeout;
2865
+ this.projectApiKey = projectApiKey;
2866
+ this.host = host;
2867
+ this.poller = undefined;
2868
+ this.fetch = options.fetch || fetch$1;
2869
+ this.onError = options.onError;
2870
+ this.customHeaders = customHeaders;
2871
+ this.onLoad = options.onLoad;
2872
+ void this.loadFeatureFlags();
2873
+ }
2874
+ debug(enabled = true) {
2875
+ this.debugMode = enabled;
2876
+ }
2877
+ logMsgIfDebug(fn) {
2878
+ if (this.debugMode) {
2879
+ fn();
2880
+ }
2881
+ }
2882
+ async getFeatureFlag(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
2883
+ await this.loadFeatureFlags();
2884
+ let response = undefined;
2885
+ let featureFlag = undefined;
2886
+ if (!this.loadedSuccessfullyOnce) {
2887
+ return response;
2888
+ }
2889
+ for (const flag of this.featureFlags) {
2890
+ if (key === flag.key) {
2891
+ featureFlag = flag;
2892
+ break;
2893
+ }
2894
+ }
2895
+ if (featureFlag !== undefined) {
2896
+ try {
2897
+ response = await this.computeFlagLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
2898
+ this.logMsgIfDebug(() => console.debug(`Successfully computed flag locally: ${key} -> ${response}`));
2899
+ } catch (e) {
2900
+ if (e instanceof InconclusiveMatchError) {
2901
+ this.logMsgIfDebug(() => console.debug(`InconclusiveMatchError when computing flag locally: ${key}: ${e}`));
2902
+ } else if (e instanceof Error) {
2903
+ this.onError?.(new Error(`Error computing flag locally: ${key}: ${e}`));
2904
+ }
2905
+ }
2906
+ }
2907
+ return response;
2908
+ }
2909
+ async computeFeatureFlagPayloadLocally(key, matchValue) {
2910
+ await this.loadFeatureFlags();
2911
+ let response = undefined;
2912
+ if (!this.loadedSuccessfullyOnce) {
2913
+ return undefined;
2914
+ }
2915
+ if (typeof matchValue == 'boolean') {
2916
+ response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue.toString()];
2917
+ } else if (typeof matchValue == 'string') {
2918
+ response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue];
2919
+ }
2920
+ // Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
2921
+ if (response === undefined || response === null) {
2922
+ return null;
2923
+ }
2924
+ try {
2925
+ return JSON.parse(response);
2926
+ } catch {
2927
+ return response;
2928
+ }
2929
+ }
2930
+ async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
2931
+ await this.loadFeatureFlags();
2932
+ const response = {};
2933
+ const payloads = {};
2934
+ let fallbackToDecide = this.featureFlags.length == 0;
2935
+ await Promise.all(this.featureFlags.map(async flag => {
2936
+ try {
2937
+ const matchValue = await this.computeFlagLocally(flag, distinctId, groups, personProperties, groupProperties);
2938
+ response[flag.key] = matchValue;
2939
+ const matchPayload = await this.computeFeatureFlagPayloadLocally(flag.key, matchValue);
2940
+ if (matchPayload) {
2941
+ payloads[flag.key] = matchPayload;
2942
+ }
2943
+ } catch (e) {
2944
+ if (e instanceof InconclusiveMatchError) ; else if (e instanceof Error) {
2945
+ this.onError?.(new Error(`Error computing flag locally: ${flag.key}: ${e}`));
2946
+ }
2947
+ fallbackToDecide = true;
2948
+ }
2949
+ }));
2950
+ return {
2951
+ response,
2952
+ payloads,
2953
+ fallbackToDecide
2954
+ };
2955
+ }
2956
+ async computeFlagLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
2957
+ if (flag.ensure_experience_continuity) {
2958
+ throw new InconclusiveMatchError('Flag has experience continuity enabled');
2959
+ }
2960
+ if (!flag.active) {
2961
+ return false;
2962
+ }
2963
+ const flagFilters = flag.filters || {};
2964
+ const aggregation_group_type_index = flagFilters.aggregation_group_type_index;
2965
+ if (aggregation_group_type_index != undefined) {
2966
+ const groupName = this.groupTypeMapping[String(aggregation_group_type_index)];
2967
+ if (!groupName) {
2968
+ this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Unknown group type index ${aggregation_group_type_index} for feature flag ${flag.key}`));
2969
+ throw new InconclusiveMatchError('Flag has unknown group type index');
2970
+ }
2971
+ if (!(groupName in groups)) {
2972
+ this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Can't compute group feature flag: ${flag.key} without group names passed in`));
2973
+ return false;
2974
+ }
2975
+ const focusedGroupProperties = groupProperties[groupName];
2976
+ return await this.matchFeatureFlagProperties(flag, groups[groupName], focusedGroupProperties);
2977
+ } else {
2978
+ return await this.matchFeatureFlagProperties(flag, distinctId, personProperties);
2979
+ }
2980
+ }
2981
+ async matchFeatureFlagProperties(flag, distinctId, properties) {
2982
+ const flagFilters = flag.filters || {};
2983
+ const flagConditions = flagFilters.groups || [];
2984
+ let isInconclusive = false;
2985
+ let result = undefined;
2986
+ // # Stable sort conditions with variant overrides to the top. This ensures that if overrides are present, they are
2987
+ // # evaluated first, and the variant override is applied to the first matching condition.
2988
+ const sortedFlagConditions = [...flagConditions].sort((conditionA, conditionB) => {
2989
+ const AHasVariantOverride = !!conditionA.variant;
2990
+ const BHasVariantOverride = !!conditionB.variant;
2991
+ if (AHasVariantOverride && BHasVariantOverride) {
2992
+ return 0;
2993
+ } else if (AHasVariantOverride) {
2994
+ return -1;
2995
+ } else if (BHasVariantOverride) {
2996
+ return 1;
2997
+ } else {
2998
+ return 0;
2999
+ }
3000
+ });
3001
+ for (const condition of sortedFlagConditions) {
3002
+ try {
3003
+ if (await this.isConditionMatch(flag, distinctId, condition, properties)) {
3004
+ const variantOverride = condition.variant;
3005
+ const flagVariants = flagFilters.multivariate?.variants || [];
3006
+ if (variantOverride && flagVariants.some(variant => variant.key === variantOverride)) {
3007
+ result = variantOverride;
3008
+ } else {
3009
+ result = (await this.getMatchingVariant(flag, distinctId)) || true;
3010
+ }
3011
+ break;
3012
+ }
3013
+ } catch (e) {
3014
+ if (e instanceof InconclusiveMatchError) {
3015
+ isInconclusive = true;
3016
+ } else {
3017
+ throw e;
3018
+ }
3019
+ }
3020
+ }
3021
+ if (result !== undefined) {
3022
+ return result;
3023
+ } else if (isInconclusive) {
3024
+ throw new InconclusiveMatchError("Can't determine if feature flag is enabled or not with given properties");
3025
+ }
3026
+ // We can only return False when all conditions are False
3027
+ return false;
3028
+ }
3029
+ async isConditionMatch(flag, distinctId, condition, properties) {
3030
+ const rolloutPercentage = condition.rollout_percentage;
3031
+ const warnFunction = msg => {
3032
+ this.logMsgIfDebug(() => console.warn(msg));
3033
+ };
3034
+ if ((condition.properties || []).length > 0) {
3035
+ for (const prop of condition.properties) {
3036
+ const propertyType = prop.type;
3037
+ let matches = false;
3038
+ if (propertyType === 'cohort') {
3039
+ matches = matchCohort(prop, properties, this.cohorts, this.debugMode);
3040
+ } else {
3041
+ matches = matchProperty(prop, properties, warnFunction);
3042
+ }
3043
+ if (!matches) {
3044
+ return false;
3045
+ }
3046
+ }
3047
+ if (rolloutPercentage == undefined) {
3048
+ return true;
3049
+ }
3050
+ }
3051
+ if (rolloutPercentage != undefined && (await _hash(flag.key, distinctId)) > rolloutPercentage / 100.0) {
3052
+ return false;
3053
+ }
3054
+ return true;
3055
+ }
3056
+ async getMatchingVariant(flag, distinctId) {
3057
+ const hashValue = await _hash(flag.key, distinctId, 'variant');
3058
+ const matchingVariant = this.variantLookupTable(flag).find(variant => {
3059
+ return hashValue >= variant.valueMin && hashValue < variant.valueMax;
3060
+ });
3061
+ if (matchingVariant) {
3062
+ return matchingVariant.key;
3063
+ }
3064
+ return undefined;
3065
+ }
3066
+ variantLookupTable(flag) {
3067
+ const lookupTable = [];
3068
+ let valueMin = 0;
3069
+ let valueMax = 0;
3070
+ const flagFilters = flag.filters || {};
3071
+ const multivariates = flagFilters.multivariate?.variants || [];
3072
+ multivariates.forEach(variant => {
3073
+ valueMax = valueMin + variant.rollout_percentage / 100.0;
3074
+ lookupTable.push({
3075
+ valueMin,
3076
+ valueMax,
3077
+ key: variant.key
3078
+ });
3079
+ valueMin = valueMax;
3080
+ });
3081
+ return lookupTable;
3082
+ }
3083
+ async loadFeatureFlags(forceReload = false) {
3084
+ if (!this.loadedSuccessfullyOnce || forceReload) {
3085
+ await this._loadFeatureFlags();
3086
+ }
3087
+ }
3088
+ /**
3089
+ * Returns true if the feature flags poller has loaded successfully at least once and has more than 0 feature flags.
3090
+ * This is useful to check if local evaluation is ready before calling getFeatureFlag.
3091
+ */
3092
+ isLocalEvaluationReady() {
3093
+ return (this.loadedSuccessfullyOnce ?? false) && (this.featureFlags?.length ?? 0) > 0;
3094
+ }
3095
+ /**
3096
+ * If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
3097
+ * until a successful request is made, up to a maximum of 60 seconds.
3098
+ *
3099
+ * @returns The polling interval to use for the next request.
3100
+ */
3101
+ getPollingInterval() {
3102
+ if (!this.shouldBeginExponentialBackoff) {
3103
+ return this.pollingInterval;
3104
+ }
3105
+ return Math.min(SIXTY_SECONDS, this.pollingInterval * 2 ** this.backOffCount);
3106
+ }
3107
+ async _loadFeatureFlags() {
3108
+ if (this.poller) {
3109
+ clearTimeout(this.poller);
3110
+ this.poller = undefined;
3111
+ }
3112
+ this.poller = setTimeout(() => this._loadFeatureFlags(), this.getPollingInterval());
3113
+ try {
3114
+ const res = await this._requestFeatureFlagDefinitions();
3115
+ // Handle undefined res case, this shouldn't happen, but it doesn't hurt to handle it anyway
3116
+ if (!res) {
3117
+ // Don't override existing flags when something goes wrong
3118
+ return;
3119
+ }
3120
+ // NB ON ERROR HANDLING & `loadedSuccessfullyOnce`:
3121
+ //
3122
+ // `loadedSuccessfullyOnce` indicates we've successfully loaded a valid set of flags at least once.
3123
+ // If we set it to `true` in an error scenario (e.g. 402 Over Quota, 401 Invalid Key, etc.),
3124
+ // any manual call to `loadFeatureFlags()` (without forceReload) will skip refetching entirely,
3125
+ // leaving us stuck with zero or outdated flags. The poller does keep running, but we also want
3126
+ // manual reloads to be possible as soon as the error condition is resolved.
3127
+ //
3128
+ // Therefore, on error statuses, we do *not* set `loadedSuccessfullyOnce = true`, ensuring that
3129
+ // both the background poller and any subsequent manual calls can keep trying to load flags
3130
+ // once the issue (quota, permission, rate limit, etc.) is resolved.
3131
+ switch (res.status) {
3132
+ case 401:
3133
+ // Invalid API key
3134
+ this.shouldBeginExponentialBackoff = true;
3135
+ this.backOffCount += 1;
3136
+ throw new ClientError(`Your project key or personal API key is invalid. Setting next polling interval to ${this.getPollingInterval()}ms. More information: https://posthog.com/docs/api#rate-limiting`);
3137
+ case 402:
3138
+ // Quota exceeded - clear all flags
3139
+ console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all local flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
3140
+ this.featureFlags = [];
3141
+ this.featureFlagsByKey = {};
3142
+ this.groupTypeMapping = {};
3143
+ this.cohorts = {};
3144
+ return;
3145
+ case 403:
3146
+ // Permissions issue
3147
+ this.shouldBeginExponentialBackoff = true;
3148
+ this.backOffCount += 1;
3149
+ throw new ClientError(`Your personal API key does not have permission to fetch feature flag definitions for local evaluation. Setting next polling interval to ${this.getPollingInterval()}ms. Are you sure you're using the correct personal and Project API key pair? More information: https://posthog.com/docs/api/overview`);
3150
+ case 429:
3151
+ // Rate limited
3152
+ this.shouldBeginExponentialBackoff = true;
3153
+ this.backOffCount += 1;
3154
+ throw new ClientError(`You are being rate limited. Setting next polling interval to ${this.getPollingInterval()}ms. More information: https://posthog.com/docs/api#rate-limiting`);
3155
+ case 200:
3156
+ {
3157
+ // Process successful response
3158
+ const responseJson = await res.json();
3159
+ if (!('flags' in responseJson)) {
3160
+ this.onError?.(new Error(`Invalid response when getting feature flags: ${JSON.stringify(responseJson)}`));
3161
+ return;
3162
+ }
3163
+ this.featureFlags = responseJson.flags || [];
3164
+ this.featureFlagsByKey = this.featureFlags.reduce((acc, curr) => (acc[curr.key] = curr, acc), {});
3165
+ this.groupTypeMapping = responseJson.group_type_mapping || {};
3166
+ this.cohorts = responseJson.cohorts || {};
3167
+ this.loadedSuccessfullyOnce = true;
3168
+ this.shouldBeginExponentialBackoff = false;
3169
+ this.backOffCount = 0;
3170
+ this.onLoad?.(this.featureFlags.length);
3171
+ break;
3172
+ }
3173
+ default:
3174
+ // Something else went wrong, or the server is down.
3175
+ // In this case, don't override existing flags
3176
+ return;
3177
+ }
3178
+ } catch (err) {
3179
+ if (err instanceof ClientError) {
3180
+ this.onError?.(err);
3181
+ }
3182
+ }
3183
+ }
3184
+ getPersonalApiKeyRequestOptions(method = 'GET') {
3185
+ return {
3186
+ method,
3187
+ headers: {
3188
+ ...this.customHeaders,
3189
+ 'Content-Type': 'application/json',
3190
+ Authorization: `Bearer ${this.personalApiKey}`
3191
+ }
3192
+ };
3193
+ }
3194
+ async _requestFeatureFlagDefinitions() {
3195
+ const url = `${this.host}/api/feature_flag/local_evaluation?token=${this.projectApiKey}&send_cohorts`;
3196
+ const options = this.getPersonalApiKeyRequestOptions();
3197
+ let abortTimeout = null;
3198
+ if (this.timeout && typeof this.timeout === 'number') {
3199
+ const controller = new AbortController();
3200
+ abortTimeout = safeSetTimeout(() => {
3201
+ controller.abort();
3202
+ }, this.timeout);
3203
+ options.signal = controller.signal;
3204
+ }
3205
+ try {
3206
+ return await this.fetch(url, options);
3207
+ } finally {
3208
+ clearTimeout(abortTimeout);
3209
+ }
3210
+ }
3211
+ stopPoller() {
3212
+ clearTimeout(this.poller);
3213
+ }
3214
+ _requestRemoteConfigPayload(flagKey) {
3215
+ const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config/`;
3216
+ const options = this.getPersonalApiKeyRequestOptions();
3217
+ let abortTimeout = null;
3218
+ if (this.timeout && typeof this.timeout === 'number') {
3219
+ const controller = new AbortController();
3220
+ abortTimeout = safeSetTimeout(() => {
3221
+ controller.abort();
3222
+ }, this.timeout);
3223
+ options.signal = controller.signal;
3224
+ }
3225
+ try {
3226
+ return this.fetch(url, options);
3227
+ } finally {
3228
+ clearTimeout(abortTimeout);
3229
+ }
3230
+ }
3231
+ }
3232
+ // # This function takes a distinct_id and a feature flag key and returns a float between 0 and 1.
3233
+ // # Given the same distinct_id and key, it'll always return the same float. These floats are
3234
+ // # uniformly distributed between 0 and 1, so if we want to show this feature to 20% of traffic
3235
+ // # we can do _hash(key, distinct_id) < 0.2
3236
+ async function _hash(key, distinctId, salt = '') {
3237
+ const hashString = await hashSHA1(`${key}.${distinctId}${salt}`);
3238
+ return parseInt(hashString.slice(0, 15), 16) / LONG_SCALE;
3239
+ }
3240
+ function matchProperty(property, propertyValues, warnFunction) {
3241
+ const key = property.key;
3242
+ const value = property.value;
3243
+ const operator = property.operator || 'exact';
3244
+ if (!(key in propertyValues)) {
3245
+ throw new InconclusiveMatchError(`Property ${key} not found in propertyValues`);
3246
+ } else if (operator === 'is_not_set') {
3247
+ throw new InconclusiveMatchError(`Operator is_not_set is not supported`);
3248
+ }
3249
+ const overrideValue = propertyValues[key];
3250
+ if (overrideValue == null && !NULL_VALUES_ALLOWED_OPERATORS.includes(operator)) {
3251
+ // if the value is null, just fail the feature flag comparison
3252
+ // this isn't an InconclusiveMatchError because the property value was provided.
3253
+ if (warnFunction) {
3254
+ warnFunction(`Property ${key} cannot have a value of null/undefined with the ${operator} operator`);
3255
+ }
3256
+ return false;
3257
+ }
3258
+ function computeExactMatch(value, overrideValue) {
3259
+ if (Array.isArray(value)) {
3260
+ return value.map(val => String(val).toLowerCase()).includes(String(overrideValue).toLowerCase());
3261
+ }
3262
+ return String(value).toLowerCase() === String(overrideValue).toLowerCase();
3263
+ }
3264
+ function compare(lhs, rhs, operator) {
3265
+ if (operator === 'gt') {
3266
+ return lhs > rhs;
3267
+ } else if (operator === 'gte') {
3268
+ return lhs >= rhs;
3269
+ } else if (operator === 'lt') {
3270
+ return lhs < rhs;
3271
+ } else if (operator === 'lte') {
3272
+ return lhs <= rhs;
3273
+ } else {
3274
+ throw new Error(`Invalid operator: ${operator}`);
3275
+ }
3276
+ }
3277
+ switch (operator) {
3278
+ case 'exact':
3279
+ return computeExactMatch(value, overrideValue);
3280
+ case 'is_not':
3281
+ return !computeExactMatch(value, overrideValue);
3282
+ case 'is_set':
3283
+ return key in propertyValues;
3284
+ case 'icontains':
3285
+ return String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
3286
+ case 'not_icontains':
3287
+ return !String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
3288
+ case 'regex':
3289
+ return isValidRegex(String(value)) && String(overrideValue).match(String(value)) !== null;
3290
+ case 'not_regex':
3291
+ return isValidRegex(String(value)) && String(overrideValue).match(String(value)) === null;
3292
+ case 'gt':
3293
+ case 'gte':
3294
+ case 'lt':
3295
+ case 'lte':
3296
+ {
3297
+ // :TRICKY: We adjust comparison based on the override value passed in,
3298
+ // to make sure we handle both numeric and string comparisons appropriately.
3299
+ let parsedValue = typeof value === 'number' ? value : null;
3300
+ if (typeof value === 'string') {
3301
+ try {
3302
+ parsedValue = parseFloat(value);
3303
+ } catch (err) {
3304
+ // pass
3305
+ }
3306
+ }
3307
+ if (parsedValue != null && overrideValue != null) {
3308
+ // check both null and undefined
3309
+ if (typeof overrideValue === 'string') {
3310
+ return compare(overrideValue, String(value), operator);
3311
+ } else {
3312
+ return compare(overrideValue, parsedValue, operator);
3313
+ }
3314
+ } else {
3315
+ return compare(String(overrideValue), String(value), operator);
3316
+ }
3317
+ }
3318
+ case 'is_date_after':
3319
+ case 'is_date_before':
3320
+ {
3321
+ let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
3322
+ if (parsedDate == null) {
3323
+ parsedDate = convertToDateTime(value);
3324
+ }
3325
+ if (parsedDate == null) {
3326
+ throw new InconclusiveMatchError(`Invalid date: ${value}`);
3327
+ }
3328
+ const overrideDate = convertToDateTime(overrideValue);
3329
+ if (['is_date_before'].includes(operator)) {
3330
+ return overrideDate < parsedDate;
3331
+ }
3332
+ return overrideDate > parsedDate;
3333
+ }
3334
+ default:
3335
+ throw new InconclusiveMatchError(`Unknown operator: ${operator}`);
3336
+ }
3337
+ }
3338
+ function matchCohort(property, propertyValues, cohortProperties, debugMode = false) {
3339
+ const cohortId = String(property.value);
3340
+ if (!(cohortId in cohortProperties)) {
3341
+ throw new InconclusiveMatchError("can't match cohort without a given cohort property value");
3342
+ }
3343
+ const propertyGroup = cohortProperties[cohortId];
3344
+ return matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, debugMode);
3345
+ }
3346
+ function matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, debugMode = false) {
3347
+ if (!propertyGroup) {
3348
+ return true;
3349
+ }
3350
+ const propertyGroupType = propertyGroup.type;
3351
+ const properties = propertyGroup.values;
3352
+ if (!properties || properties.length === 0) {
3353
+ // empty groups are no-ops, always match
3354
+ return true;
3355
+ }
3356
+ let errorMatchingLocally = false;
3357
+ if ('values' in properties[0]) {
3358
+ // a nested property group
3359
+ for (const prop of properties) {
3360
+ try {
3361
+ const matches = matchPropertyGroup(prop, propertyValues, cohortProperties, debugMode);
3362
+ if (propertyGroupType === 'AND') {
3363
+ if (!matches) {
3364
+ return false;
3365
+ }
3366
+ } else {
3367
+ // OR group
3368
+ if (matches) {
3369
+ return true;
3370
+ }
3371
+ }
3372
+ } catch (err) {
3373
+ if (err instanceof InconclusiveMatchError) {
3374
+ if (debugMode) {
3375
+ console.debug(`Failed to compute property ${prop} locally: ${err}`);
3376
+ }
3377
+ errorMatchingLocally = true;
3378
+ } else {
3379
+ throw err;
3380
+ }
3381
+ }
3382
+ }
3383
+ if (errorMatchingLocally) {
3384
+ throw new InconclusiveMatchError("Can't match cohort without a given cohort property value");
3385
+ }
3386
+ // if we get here, all matched in AND case, or none matched in OR case
3387
+ return propertyGroupType === 'AND';
3388
+ } else {
3389
+ for (const prop of properties) {
3390
+ try {
3391
+ let matches;
3392
+ if (prop.type === 'cohort') {
3393
+ matches = matchCohort(prop, propertyValues, cohortProperties, debugMode);
3394
+ } else {
3395
+ matches = matchProperty(prop, propertyValues);
3396
+ }
3397
+ const negation = prop.negation || false;
3398
+ if (propertyGroupType === 'AND') {
3399
+ // if negated property, do the inverse
3400
+ if (!matches && !negation) {
3401
+ return false;
3402
+ }
3403
+ if (matches && negation) {
3404
+ return false;
3405
+ }
3406
+ } else {
3407
+ // OR group
3408
+ if (matches && !negation) {
3409
+ return true;
3410
+ }
3411
+ if (!matches && negation) {
3412
+ return true;
3413
+ }
3414
+ }
3415
+ } catch (err) {
3416
+ if (err instanceof InconclusiveMatchError) {
3417
+ if (debugMode) {
3418
+ console.debug(`Failed to compute property ${prop} locally: ${err}`);
3419
+ }
3420
+ errorMatchingLocally = true;
3421
+ } else {
3422
+ throw err;
3423
+ }
3424
+ }
3425
+ }
3426
+ if (errorMatchingLocally) {
3427
+ throw new InconclusiveMatchError("can't match cohort without a given cohort property value");
3428
+ }
3429
+ // if we get here, all matched in AND case, or none matched in OR case
3430
+ return propertyGroupType === 'AND';
3431
+ }
3432
+ }
3433
+ function isValidRegex(regex) {
3434
+ try {
3435
+ new RegExp(regex);
3436
+ return true;
3437
+ } catch (err) {
3438
+ return false;
3439
+ }
3440
+ }
3441
+ function convertToDateTime(value) {
3442
+ if (value instanceof Date) {
3443
+ return value;
3444
+ } else if (typeof value === 'string' || typeof value === 'number') {
3445
+ const date = new Date(value);
3446
+ if (!isNaN(date.valueOf())) {
3447
+ return date;
3448
+ }
3449
+ throw new InconclusiveMatchError(`${value} is in an invalid date format`);
3450
+ } else {
3451
+ throw new InconclusiveMatchError(`The date provided ${value} must be a string, number, or date object`);
3452
+ }
3453
+ }
3454
+ function relativeDateParseForFeatureFlagMatching(value) {
3455
+ const regex = /^-?(?<number>[0-9]+)(?<interval>[a-z])$/;
3456
+ const match = value.match(regex);
3457
+ const parsedDt = new Date(new Date().toISOString());
3458
+ if (match) {
3459
+ if (!match.groups) {
3460
+ return null;
3461
+ }
3462
+ const number = parseInt(match.groups['number']);
3463
+ if (number >= 10000) {
3464
+ // Guard against overflow, disallow numbers greater than 10_000
3465
+ return null;
3466
+ }
3467
+ const interval = match.groups['interval'];
3468
+ if (interval == 'h') {
3469
+ parsedDt.setUTCHours(parsedDt.getUTCHours() - number);
3470
+ } else if (interval == 'd') {
3471
+ parsedDt.setUTCDate(parsedDt.getUTCDate() - number);
3472
+ } else if (interval == 'w') {
3473
+ parsedDt.setUTCDate(parsedDt.getUTCDate() - number * 7);
3474
+ } else if (interval == 'm') {
3475
+ parsedDt.setUTCMonth(parsedDt.getUTCMonth() - number);
3476
+ } else if (interval == 'y') {
3477
+ parsedDt.setUTCFullYear(parsedDt.getUTCFullYear() - number);
3478
+ } else {
3479
+ return null;
3480
+ }
3481
+ return parsedDt;
3482
+ } else {
3483
+ return null;
3484
+ }
3485
+ }
3486
+
3487
+ class PostHogMemoryStorage {
3488
+ constructor() {
3489
+ this._memoryStorage = {};
3490
+ }
3491
+ getProperty(key) {
3492
+ return this._memoryStorage[key];
3493
+ }
3494
+ setProperty(key, value) {
3495
+ this._memoryStorage[key] = value !== null ? value : undefined;
3496
+ }
3497
+ }
3498
+
3499
+ // Standard local evaluation rate limit is 600 per minute (10 per second),
3500
+ // so the fastest a poller should ever be set is 100ms.
3501
+ const MINIMUM_POLLING_INTERVAL = 100;
3502
+ const THIRTY_SECONDS = 30 * 1000;
3503
+ const MAX_CACHE_SIZE = 50 * 1000;
3504
+ // The actual exported Nodejs API.
3505
+ class PostHogBackendClient extends PostHogCoreStateless {
3506
+ constructor(apiKey, options = {}) {
3507
+ super(apiKey, options);
3508
+ this._memoryStorage = new PostHogMemoryStorage();
3509
+ this.options = options;
3510
+ this.options.featureFlagsPollingInterval = typeof options.featureFlagsPollingInterval === 'number' ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
3511
+ if (options.personalApiKey) {
3512
+ if (options.personalApiKey.includes('phc_')) {
3513
+ throw new Error('Your Personal API key is invalid. These keys are prefixed with "phx_" and can be created in PostHog project settings.');
3514
+ }
3515
+ this.featureFlagsPoller = new FeatureFlagsPoller({
3516
+ pollingInterval: this.options.featureFlagsPollingInterval,
3517
+ personalApiKey: options.personalApiKey,
3518
+ projectApiKey: apiKey,
3519
+ timeout: options.requestTimeout ?? 10000,
3520
+ host: this.host,
3521
+ fetch: options.fetch,
3522
+ onError: err => {
3523
+ this._events.emit('error', err);
3524
+ },
3525
+ onLoad: count => {
3526
+ this._events.emit('localEvaluationFlagsLoaded', count);
3527
+ },
3528
+ customHeaders: this.getCustomHeaders()
3529
+ });
3530
+ }
3531
+ this.errorTracking = new ErrorTracking(this, options);
3532
+ this.distinctIdHasSentFlagCalls = {};
3533
+ this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
3534
+ }
3535
+ getPersistedProperty(key) {
3536
+ return this._memoryStorage.getProperty(key);
3537
+ }
3538
+ setPersistedProperty(key, value) {
3539
+ return this._memoryStorage.setProperty(key, value);
3540
+ }
3541
+ fetch(url, options) {
3542
+ return this.options.fetch ? this.options.fetch(url, options) : fetch$1(url, options);
3543
+ }
3544
+ getLibraryVersion() {
3545
+ return version;
3546
+ }
3547
+ getCustomUserAgent() {
3548
+ return `${this.getLibraryId()}/${this.getLibraryVersion()}`;
3549
+ }
3550
+ enable() {
3551
+ return super.optIn();
3552
+ }
3553
+ disable() {
3554
+ return super.optOut();
3555
+ }
3556
+ debug(enabled = true) {
3557
+ super.debug(enabled);
3558
+ this.featureFlagsPoller?.debug(enabled);
3559
+ }
3560
+ capture(props) {
3561
+ if (typeof props === 'string') {
3562
+ this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
3563
+ }
3564
+ const {
3565
+ distinctId,
3566
+ event,
3567
+ properties,
3568
+ groups,
3569
+ sendFeatureFlags,
3570
+ timestamp,
3571
+ disableGeoip,
3572
+ uuid
3573
+ } = props;
3574
+ const _capture = props => {
3575
+ super.captureStateless(distinctId, event, props, {
3576
+ timestamp,
3577
+ disableGeoip,
3578
+ uuid
3579
+ });
3580
+ };
3581
+ const _getFlags = async (distinctId, groups, disableGeoip) => {
3582
+ return (await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)).flags;
3583
+ };
3584
+ // :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
3585
+ const capturePromise = Promise.resolve().then(async () => {
3586
+ if (sendFeatureFlags) {
3587
+ // If we are sending feature flags, we need to make sure we have the latest flags
3588
+ // return await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)
3589
+ return await _getFlags(distinctId, groups, disableGeoip);
3590
+ }
3591
+ if (event === '$feature_flag_called') {
3592
+ // If we're capturing a $feature_flag_called event, we don't want to enrich the event with cached flags that may be out of date.
3593
+ return {};
3594
+ }
3595
+ if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
3596
+ // Otherwise we may as well check for the flags locally and include them if they are already loaded
3597
+ const groupsWithStringValues = {};
3598
+ for (const [key, value] of Object.entries(groups || {})) {
3599
+ groupsWithStringValues[key] = String(value);
3600
+ }
3601
+ return await this.getAllFlags(distinctId, {
3602
+ groups: groupsWithStringValues,
3603
+ disableGeoip,
3604
+ onlyEvaluateLocally: true
3605
+ });
3606
+ }
3607
+ return {};
3608
+ }).then(flags => {
3609
+ // Derive the relevant flag properties to add
3610
+ const additionalProperties = {};
3611
+ if (flags) {
3612
+ for (const [feature, variant] of Object.entries(flags)) {
3613
+ additionalProperties[`$feature/${feature}`] = variant;
3614
+ }
3615
+ }
3616
+ const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
3617
+ if (activeFlags.length > 0) {
3618
+ additionalProperties['$active_feature_flags'] = activeFlags;
3619
+ }
3620
+ return additionalProperties;
3621
+ }).catch(() => {
3622
+ // Something went wrong getting the flag info - we should capture the event anyways
3623
+ return {};
3624
+ }).then(additionalProperties => {
3625
+ // No matter what - capture the event
3626
+ _capture({
3627
+ ...additionalProperties,
3628
+ ...properties,
3629
+ $groups: groups
3630
+ });
3631
+ });
3632
+ this.addPendingPromise(capturePromise);
3633
+ }
3634
+ async captureImmediate(props) {
3635
+ if (typeof props === 'string') {
3636
+ this.logMsgIfDebug(() => console.warn('Called capture() with a string as the first argument when an object was expected.'));
3637
+ }
3638
+ const {
3639
+ distinctId,
3640
+ event,
3641
+ properties,
3642
+ groups,
3643
+ sendFeatureFlags,
3644
+ timestamp,
3645
+ disableGeoip,
3646
+ uuid
3647
+ } = props;
3648
+ const _capture = props => {
3649
+ return super.captureStatelessImmediate(distinctId, event, props, {
3650
+ timestamp,
3651
+ disableGeoip,
3652
+ uuid
3653
+ });
3654
+ };
3655
+ const _getFlags = async (distinctId, groups, disableGeoip) => {
3656
+ return (await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)).flags;
3657
+ };
3658
+ const capturePromise = Promise.resolve().then(async () => {
3659
+ if (sendFeatureFlags) {
3660
+ // If we are sending feature flags, we need to make sure we have the latest flags
3661
+ // return await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)
3662
+ return await _getFlags(distinctId, groups, disableGeoip);
3663
+ }
3664
+ if (event === '$feature_flag_called') {
3665
+ // If we're capturing a $feature_flag_called event, we don't want to enrich the event with cached flags that may be out of date.
3666
+ return {};
3667
+ }
3668
+ if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
3669
+ // Otherwise we may as well check for the flags locally and include them if they are already loaded
3670
+ const groupsWithStringValues = {};
3671
+ for (const [key, value] of Object.entries(groups || {})) {
3672
+ groupsWithStringValues[key] = String(value);
3673
+ }
3674
+ return await this.getAllFlags(distinctId, {
3675
+ groups: groupsWithStringValues,
3676
+ disableGeoip,
3677
+ onlyEvaluateLocally: true
3678
+ });
3679
+ }
3680
+ return {};
3681
+ }).then(flags => {
3682
+ // Derive the relevant flag properties to add
3683
+ const additionalProperties = {};
3684
+ if (flags) {
3685
+ for (const [feature, variant] of Object.entries(flags)) {
3686
+ additionalProperties[`$feature/${feature}`] = variant;
3687
+ }
3688
+ }
3689
+ const activeFlags = Object.keys(flags || {}).filter(flag => flags?.[flag] !== false).sort();
3690
+ if (activeFlags.length > 0) {
3691
+ additionalProperties['$active_feature_flags'] = activeFlags;
3692
+ }
3693
+ return additionalProperties;
3694
+ }).catch(() => {
3695
+ // Something went wrong getting the flag info - we should capture the event anyways
3696
+ return {};
3697
+ }).then(additionalProperties => {
3698
+ // No matter what - capture the event
3699
+ _capture({
3700
+ ...additionalProperties,
3701
+ ...properties,
3702
+ $groups: groups
3703
+ });
3704
+ });
3705
+ await capturePromise;
3706
+ }
3707
+ identify({
3708
+ distinctId,
3709
+ properties,
3710
+ disableGeoip
3711
+ }) {
3712
+ // Catch properties passed as $set and move them to the top level
3713
+ // promote $set and $set_once to top level
3714
+ const userPropsOnce = properties?.$set_once;
3715
+ delete properties?.$set_once;
3716
+ // if no $set is provided we assume all properties are $set
3717
+ const userProps = properties?.$set || properties;
3718
+ super.identifyStateless(distinctId, {
3719
+ $set: userProps,
3720
+ $set_once: userPropsOnce
3721
+ }, {
3722
+ disableGeoip
3723
+ });
3724
+ }
3725
+ async identifyImmediate({
3726
+ distinctId,
3727
+ properties,
3728
+ disableGeoip
3729
+ }) {
3730
+ // promote $set and $set_once to top level
3731
+ const userPropsOnce = properties?.$set_once;
3732
+ delete properties?.$set_once;
3733
+ // if no $set is provided we assume all properties are $set
3734
+ const userProps = properties?.$set || properties;
3735
+ await super.identifyStatelessImmediate(distinctId, {
3736
+ $set: userProps,
3737
+ $set_once: userPropsOnce
3738
+ }, {
3739
+ disableGeoip
3740
+ });
3741
+ }
3742
+ alias(data) {
3743
+ super.aliasStateless(data.alias, data.distinctId, undefined, {
3744
+ disableGeoip: data.disableGeoip
3745
+ });
3746
+ }
3747
+ async aliasImmediate(data) {
3748
+ await super.aliasStatelessImmediate(data.alias, data.distinctId, undefined, {
3749
+ disableGeoip: data.disableGeoip
3750
+ });
3751
+ }
3752
+ isLocalEvaluationReady() {
3753
+ return this.featureFlagsPoller?.isLocalEvaluationReady() ?? false;
3754
+ }
3755
+ async waitForLocalEvaluationReady(timeoutMs = THIRTY_SECONDS) {
3756
+ if (this.isLocalEvaluationReady()) {
3757
+ return true;
3758
+ }
3759
+ if (this.featureFlagsPoller === undefined) {
3760
+ return false;
3761
+ }
3762
+ return new Promise(resolve => {
3763
+ const timeout = setTimeout(() => {
3764
+ cleanup();
3765
+ resolve(false);
3766
+ }, timeoutMs);
3767
+ const cleanup = this._events.on('localEvaluationFlagsLoaded', count => {
3768
+ clearTimeout(timeout);
3769
+ cleanup();
3770
+ resolve(count > 0);
3771
+ });
3772
+ });
3773
+ }
3774
+ async getFeatureFlag(key, distinctId, options) {
3775
+ const {
3776
+ groups,
3777
+ disableGeoip
3778
+ } = options || {};
3779
+ let {
3780
+ onlyEvaluateLocally,
3781
+ sendFeatureFlagEvents,
3782
+ personProperties,
3783
+ groupProperties
3784
+ } = options || {};
3785
+ const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
3786
+ personProperties = adjustedProperties.allPersonProperties;
3787
+ groupProperties = adjustedProperties.allGroupProperties;
3788
+ // set defaults
3789
+ if (onlyEvaluateLocally == undefined) {
3790
+ onlyEvaluateLocally = false;
3791
+ }
3792
+ if (sendFeatureFlagEvents == undefined) {
3793
+ sendFeatureFlagEvents = true;
3794
+ }
3795
+ let response = await this.featureFlagsPoller?.getFeatureFlag(key, distinctId, groups, personProperties, groupProperties);
3796
+ const flagWasLocallyEvaluated = response !== undefined;
3797
+ let requestId = undefined;
3798
+ let flagDetail = undefined;
3799
+ if (!flagWasLocallyEvaluated && !onlyEvaluateLocally) {
3800
+ const remoteResponse = await super.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
3801
+ if (remoteResponse === undefined) {
3802
+ return undefined;
3803
+ }
3804
+ flagDetail = remoteResponse.response;
3805
+ response = getFeatureFlagValue(flagDetail);
3806
+ requestId = remoteResponse?.requestId;
3807
+ }
3808
+ const featureFlagReportedKey = `${key}_${response}`;
3809
+ if (sendFeatureFlagEvents && (!(distinctId in this.distinctIdHasSentFlagCalls) || !this.distinctIdHasSentFlagCalls[distinctId].includes(featureFlagReportedKey))) {
3810
+ if (Object.keys(this.distinctIdHasSentFlagCalls).length >= this.maxCacheSize) {
3811
+ this.distinctIdHasSentFlagCalls = {};
3812
+ }
3813
+ if (Array.isArray(this.distinctIdHasSentFlagCalls[distinctId])) {
3814
+ this.distinctIdHasSentFlagCalls[distinctId].push(featureFlagReportedKey);
3815
+ } else {
3816
+ this.distinctIdHasSentFlagCalls[distinctId] = [featureFlagReportedKey];
3817
+ }
3818
+ this.capture({
3819
+ distinctId,
3820
+ event: '$feature_flag_called',
3821
+ properties: {
3822
+ $feature_flag: key,
3823
+ $feature_flag_response: response,
3824
+ $feature_flag_id: flagDetail?.metadata?.id,
3825
+ $feature_flag_version: flagDetail?.metadata?.version,
3826
+ $feature_flag_reason: flagDetail?.reason?.description ?? flagDetail?.reason?.code,
3827
+ locally_evaluated: flagWasLocallyEvaluated,
3828
+ [`$feature/${key}`]: response,
3829
+ $feature_flag_request_id: requestId
3830
+ },
3831
+ groups,
3832
+ disableGeoip
3833
+ });
3834
+ }
3835
+ return response;
3836
+ }
3837
+ async getFeatureFlagPayload(key, distinctId, matchValue, options) {
3838
+ const {
3839
+ groups,
3840
+ disableGeoip
3841
+ } = options || {};
3842
+ let {
3843
+ onlyEvaluateLocally,
3844
+ sendFeatureFlagEvents,
3845
+ personProperties,
3846
+ groupProperties
3847
+ } = options || {};
3848
+ const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
3849
+ personProperties = adjustedProperties.allPersonProperties;
3850
+ groupProperties = adjustedProperties.allGroupProperties;
3851
+ let response = undefined;
3852
+ const localEvaluationEnabled = this.featureFlagsPoller !== undefined;
3853
+ if (localEvaluationEnabled) {
3854
+ // Try to get match value locally if not provided
3855
+ if (!matchValue) {
3856
+ matchValue = await this.getFeatureFlag(key, distinctId, {
3857
+ ...options,
3858
+ onlyEvaluateLocally: true,
3859
+ sendFeatureFlagEvents: false
3860
+ });
3861
+ }
3862
+ if (matchValue) {
3863
+ response = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue);
3864
+ }
3865
+ }
3866
+ //}
3867
+ // set defaults
3868
+ if (onlyEvaluateLocally == undefined) {
3869
+ onlyEvaluateLocally = false;
3870
+ }
3871
+ if (sendFeatureFlagEvents == undefined) {
3872
+ sendFeatureFlagEvents = true;
3873
+ }
3874
+ // set defaults
3875
+ if (onlyEvaluateLocally == undefined) {
3876
+ onlyEvaluateLocally = false;
3877
+ }
3878
+ const payloadWasLocallyEvaluated = response !== undefined;
3879
+ if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
3880
+ response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
3881
+ }
3882
+ return response;
3883
+ }
3884
+ async getRemoteConfigPayload(flagKey) {
3885
+ return (await this.featureFlagsPoller?._requestRemoteConfigPayload(flagKey))?.json();
3886
+ }
3887
+ async isFeatureEnabled(key, distinctId, options) {
3888
+ const feat = await this.getFeatureFlag(key, distinctId, options);
3889
+ if (feat === undefined) {
3890
+ return undefined;
3891
+ }
3892
+ return !!feat || false;
3893
+ }
3894
+ async getAllFlags(distinctId, options) {
3895
+ const response = await this.getAllFlagsAndPayloads(distinctId, options);
3896
+ return response.featureFlags || {};
3897
+ }
3898
+ async getAllFlagsAndPayloads(distinctId, options) {
3899
+ const {
3900
+ groups,
3901
+ disableGeoip
3902
+ } = options || {};
3903
+ let {
3904
+ onlyEvaluateLocally,
3905
+ personProperties,
3906
+ groupProperties
3907
+ } = options || {};
3908
+ const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
3909
+ personProperties = adjustedProperties.allPersonProperties;
3910
+ groupProperties = adjustedProperties.allGroupProperties;
3911
+ // set defaults
3912
+ if (onlyEvaluateLocally == undefined) {
3913
+ onlyEvaluateLocally = false;
3914
+ }
3915
+ const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties);
3916
+ let featureFlags = {};
3917
+ let featureFlagPayloads = {};
3918
+ let fallbackToDecide = true;
3919
+ if (localEvaluationResult) {
3920
+ featureFlags = localEvaluationResult.response;
3921
+ featureFlagPayloads = localEvaluationResult.payloads;
3922
+ fallbackToDecide = localEvaluationResult.fallbackToDecide;
3923
+ }
3924
+ if (fallbackToDecide && !onlyEvaluateLocally) {
3925
+ const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip);
3926
+ featureFlags = {
3927
+ ...featureFlags,
3928
+ ...(remoteEvaluationResult.flags || {})
3929
+ };
3930
+ featureFlagPayloads = {
3931
+ ...featureFlagPayloads,
3932
+ ...(remoteEvaluationResult.payloads || {})
3933
+ };
3934
+ }
3935
+ return {
3936
+ featureFlags,
3937
+ featureFlagPayloads
3938
+ };
3939
+ }
3940
+ groupIdentify({
3941
+ groupType,
3942
+ groupKey,
3943
+ properties,
3944
+ distinctId,
3945
+ disableGeoip
3946
+ }) {
3947
+ super.groupIdentifyStateless(groupType, groupKey, properties, {
3948
+ disableGeoip
3949
+ }, distinctId);
3950
+ }
3951
+ /**
3952
+ * Reloads the feature flag definitions from the server for local evaluation.
3953
+ * This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
3954
+ */
3955
+ async reloadFeatureFlags() {
3956
+ await this.featureFlagsPoller?.loadFeatureFlags(true);
3957
+ }
3958
+ async _shutdown(shutdownTimeoutMs) {
3959
+ this.featureFlagsPoller?.stopPoller();
3960
+ return super._shutdown(shutdownTimeoutMs);
3961
+ }
3962
+ addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties) {
3963
+ const allPersonProperties = {
3964
+ distinct_id: distinctId,
3965
+ ...(personProperties || {})
3966
+ };
3967
+ const allGroupProperties = {};
3968
+ if (groups) {
3969
+ for (const groupName of Object.keys(groups)) {
3970
+ allGroupProperties[groupName] = {
3971
+ $group_key: groups[groupName],
3972
+ ...(groupProperties?.[groupName] || {})
3973
+ };
3974
+ }
3975
+ }
3976
+ return {
3977
+ allPersonProperties,
3978
+ allGroupProperties
3979
+ };
3980
+ }
3981
+ captureException(error, distinctId, additionalProperties) {
3982
+ const syntheticException = new Error('PostHog syntheticException');
3983
+ ErrorTracking.captureException(this, error, {
3984
+ syntheticException
3985
+ }, distinctId, additionalProperties);
3986
+ }
3987
+ }
3988
+
3989
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
3990
+ // Licensed under the MIT License
3991
+ // This was originally forked from https://github.com/csnover/TraceKit, and was largely
3992
+ // re-written as part of raven - js.
3993
+ //
3994
+ // This code was later copied to the JavaScript mono - repo and further modified and
3995
+ // refactored over the years.
3996
+ // Copyright (c) 2013 Onur Can Cakmak onur.cakmak@gmail.com and all TraceKit contributors.
3997
+ //
3998
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this
3999
+ // software and associated documentation files(the 'Software'), to deal in the Software
4000
+ // without restriction, including without limitation the rights to use, copy, modify,
4001
+ // merge, publish, distribute, sublicense, and / or sell copies of the Software, and to
4002
+ // permit persons to whom the Software is furnished to do so, subject to the following
4003
+ // conditions:
4004
+ //
4005
+ // The above copyright notice and this permission notice shall be included in all copies
4006
+ // or substantial portions of the Software.
4007
+ //
4008
+ // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
4009
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
4010
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
4011
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
4012
+ // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
4013
+ // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4014
+ const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/;
4015
+ const STACKTRACE_FRAME_LIMIT = 50;
4016
+ const UNKNOWN_FUNCTION = '?';
4017
+ /** Node Stack line parser */
4018
+ function node(getModule) {
4019
+ const FILENAME_MATCH = /^\s*[-]{4,}$/;
4020
+ const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/;
4021
+ return line => {
4022
+ const lineMatch = line.match(FULL_MATCH);
4023
+ if (lineMatch) {
4024
+ let object;
4025
+ let method;
4026
+ let functionName;
4027
+ let typeName;
4028
+ let methodName;
4029
+ if (lineMatch[1]) {
4030
+ functionName = lineMatch[1];
4031
+ let methodStart = functionName.lastIndexOf('.');
4032
+ if (functionName[methodStart - 1] === '.') {
4033
+ methodStart--;
4034
+ }
4035
+ if (methodStart > 0) {
4036
+ object = functionName.slice(0, methodStart);
4037
+ method = functionName.slice(methodStart + 1);
4038
+ const objectEnd = object.indexOf('.Module');
4039
+ if (objectEnd > 0) {
4040
+ functionName = functionName.slice(objectEnd + 1);
4041
+ object = object.slice(0, objectEnd);
4042
+ }
4043
+ }
4044
+ typeName = undefined;
4045
+ }
4046
+ if (method) {
4047
+ typeName = object;
4048
+ methodName = method;
4049
+ }
4050
+ if (method === '<anonymous>') {
4051
+ methodName = undefined;
4052
+ functionName = undefined;
4053
+ }
4054
+ if (functionName === undefined) {
4055
+ methodName = methodName || UNKNOWN_FUNCTION;
4056
+ functionName = typeName ? `${typeName}.${methodName}` : methodName;
4057
+ }
4058
+ let filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].slice(7) : lineMatch[2];
4059
+ const isNative = lineMatch[5] === 'native';
4060
+ // If it's a Windows path, trim the leading slash so that `/C:/foo` becomes `C:/foo`
4061
+ if (filename?.match(/\/[A-Z]:/)) {
4062
+ filename = filename.slice(1);
4063
+ }
4064
+ if (!filename && lineMatch[5] && !isNative) {
4065
+ filename = lineMatch[5];
4066
+ }
4067
+ return {
4068
+ filename: filename ? decodeURI(filename) : undefined,
4069
+ module: getModule ? getModule(filename) : undefined,
4070
+ function: functionName,
4071
+ lineno: _parseIntOrUndefined(lineMatch[3]),
4072
+ colno: _parseIntOrUndefined(lineMatch[4]),
4073
+ in_app: filenameIsInApp(filename || '', isNative),
4074
+ platform: 'node:javascript'
4075
+ };
4076
+ }
4077
+ if (line.match(FILENAME_MATCH)) {
4078
+ return {
4079
+ filename: line,
4080
+ platform: 'node:javascript'
4081
+ };
4082
+ }
4083
+ return undefined;
4084
+ };
4085
+ }
4086
+ /**
4087
+ * Does this filename look like it's part of the app code?
4088
+ */
4089
+ function filenameIsInApp(filename, isNative = false) {
4090
+ const isInternal = isNative || filename &&
4091
+ // It's not internal if it's an absolute linux path
4092
+ !filename.startsWith('/') &&
4093
+ // It's not internal if it's an absolute windows path
4094
+ !filename.match(/^[A-Z]:/) &&
4095
+ // It's not internal if the path is starting with a dot
4096
+ !filename.startsWith('.') &&
4097
+ // It's not internal if the frame has a protocol. In node, this is usually the case if the file got pre-processed with a bundler like webpack
4098
+ !filename.match(/^[a-zA-Z]([a-zA-Z0-9.\-+])*:\/\//); // Schema from: https://stackoverflow.com/a/3641782
4099
+ // in_app is all that's not an internal Node function or a module within node_modules
4100
+ // note that isNative appears to return true even for node core libraries
4101
+ // see https://github.com/getsentry/raven-node/issues/176
4102
+ return !isInternal && filename !== undefined && !filename.includes('node_modules/');
4103
+ }
4104
+ function _parseIntOrUndefined(input) {
4105
+ return parseInt(input || '', 10) || undefined;
4106
+ }
4107
+ function nodeStackLineParser(getModule) {
4108
+ return [90, node(getModule)];
4109
+ }
4110
+ function createStackParser(getModule) {
4111
+ const parsers = [nodeStackLineParser(getModule)];
4112
+ const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map(p => p[1]);
4113
+ return (stack, skipFirstLines = 0) => {
4114
+ const frames = [];
4115
+ const lines = stack.split('\n');
4116
+ for (let i = skipFirstLines; i < lines.length; i++) {
4117
+ const line = lines[i];
4118
+ // Ignore lines over 1kb as they are unlikely to be stack frames.
4119
+ if (line.length > 1024) {
4120
+ continue;
4121
+ }
4122
+ // https://github.com/getsentry/sentry-javascript/issues/5459
4123
+ // Remove webpack (error: *) wrappers
4124
+ const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, '$1') : line;
4125
+ // https://github.com/getsentry/sentry-javascript/issues/7813
4126
+ // Skip Error: lines
4127
+ if (cleanedLine.match(/\S*Error: /)) {
4128
+ continue;
4129
+ }
4130
+ for (const parser of sortedParsers) {
4131
+ const frame = parser(cleanedLine);
4132
+ if (frame) {
4133
+ frames.push(frame);
4134
+ break;
4135
+ }
4136
+ }
4137
+ if (frames.length >= STACKTRACE_FRAME_LIMIT) {
4138
+ break;
4139
+ }
4140
+ }
4141
+ return reverseAndStripFrames(frames);
4142
+ };
4143
+ }
4144
+ function reverseAndStripFrames(stack) {
4145
+ if (!stack.length) {
4146
+ return [];
4147
+ }
4148
+ const localStack = Array.from(stack);
4149
+ localStack.reverse();
4150
+ return localStack.slice(0, STACKTRACE_FRAME_LIMIT).map(frame => ({
4151
+ ...frame,
4152
+ filename: frame.filename || getLastStackFrame(localStack).filename,
4153
+ function: frame.function || UNKNOWN_FUNCTION
4154
+ }));
4155
+ }
4156
+ function getLastStackFrame(arr) {
4157
+ return arr[arr.length - 1] || {};
4158
+ }
4159
+
4160
+ ErrorTracking.stackParser = createStackParser(createGetModuleFromFilename());
4161
+ ErrorTracking.frameModifiers = [addSourceContext];
4162
+ class PostHog extends PostHogBackendClient {
4163
+ getLibraryId() {
4164
+ return 'posthog-node';
4165
+ }
4166
+ }
4167
+
6
4168
  interface MonitoringParams {
7
4169
  posthogDistinctId?: string;
8
4170
  posthogTraceId?: string;