posthog-node 5.5.1 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +245 -0
- package/{lib → dist}/edge/index.cjs +381 -1632
- package/dist/edge/index.cjs.map +1 -0
- package/{lib → dist}/edge/index.mjs +377 -1608
- package/dist/edge/index.mjs.map +1 -0
- package/dist/index.d.ts +458 -0
- package/{lib → dist}/node/index.cjs +401 -1651
- package/dist/node/index.cjs.map +1 -0
- package/{lib → dist}/node/index.mjs +397 -1627
- package/dist/node/index.mjs.map +1 -0
- package/package.json +41 -26
- package/CHANGELOG.md +0 -481
- package/CONTRIBUTING.md +0 -14
- package/lib/edge/index.cjs.map +0 -1
- package/lib/edge/index.mjs.map +0 -1
- package/lib/index.d.ts +0 -975
- package/lib/node/index.cjs.map +0 -1
- package/lib/node/index.mjs.map +0 -1
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dirname, posix, sep } from 'path';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
|
+
import { safeSetTimeout, PostHogCoreStateless, getFeatureFlagValue } from '@posthog/core';
|
|
4
5
|
|
|
5
|
-
/**
|
|
6
|
-
* @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
|
|
6
|
+
/**
|
|
7
|
+
* @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
|
|
7
8
|
*/
|
|
8
|
-
/**
|
|
9
|
-
* Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
|
|
10
|
-
*
|
|
11
|
-
* ### Usage
|
|
12
|
-
*
|
|
13
|
-
* Sentry.init({
|
|
14
|
-
* dsn: 'https://example',
|
|
15
|
-
* integrations: [
|
|
16
|
-
* new PostHogSentryIntegration(posthog)
|
|
17
|
-
* ]
|
|
18
|
-
* })
|
|
19
|
-
*
|
|
20
|
-
* Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
|
|
21
|
-
*
|
|
22
|
-
* @param {Object} [posthog] The posthog object
|
|
23
|
-
* @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
|
|
24
|
-
* @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
|
|
25
|
-
* @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
|
|
26
|
-
* @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
|
|
9
|
+
/**
|
|
10
|
+
* Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
|
|
11
|
+
*
|
|
12
|
+
* ### Usage
|
|
13
|
+
*
|
|
14
|
+
* Sentry.init({
|
|
15
|
+
* dsn: 'https://example',
|
|
16
|
+
* integrations: [
|
|
17
|
+
* new PostHogSentryIntegration(posthog)
|
|
18
|
+
* ]
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} [posthog] The posthog object
|
|
24
|
+
* @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
|
|
25
|
+
* @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
|
|
26
|
+
* @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
|
|
27
|
+
* @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
|
|
27
28
|
*/
|
|
28
29
|
const NAME = 'posthog-node';
|
|
29
30
|
function createEventProcessor(_posthog, {
|
|
@@ -117,411 +118,175 @@ class PostHogSentryIntegration {
|
|
|
117
118
|
}
|
|
118
119
|
PostHogSentryIntegration.POSTHOG_ID_TAG = 'posthog_distinct_id';
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* @
|
|
126
|
-
* @
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
* the Nil or Max UUID, although the Nil and Max UUIDs are technically
|
|
290
|
-
* subsumed under the variants `0b0` and `0b111`, respectively.
|
|
291
|
-
*/
|
|
292
|
-
getVariant() {
|
|
293
|
-
const n = this.bytes[8] >>> 4;
|
|
294
|
-
if (n < 0) {
|
|
295
|
-
throw new Error("unreachable");
|
|
296
|
-
}
|
|
297
|
-
else if (n <= 0b0111) {
|
|
298
|
-
return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0";
|
|
299
|
-
}
|
|
300
|
-
else if (n <= 0b1011) {
|
|
301
|
-
return "VAR_10";
|
|
302
|
-
}
|
|
303
|
-
else if (n <= 0b1101) {
|
|
304
|
-
return "VAR_110";
|
|
305
|
-
}
|
|
306
|
-
else if (n <= 0b1111) {
|
|
307
|
-
return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED";
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
throw new Error("unreachable");
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Returns the version field value of the UUID or `undefined` if the UUID does
|
|
315
|
-
* not have the variant field value of `0b10`.
|
|
316
|
-
*/
|
|
317
|
-
getVersion() {
|
|
318
|
-
return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined;
|
|
319
|
-
}
|
|
320
|
-
/** Creates an object from `this`. */
|
|
321
|
-
clone() {
|
|
322
|
-
return new UUID(this.bytes.slice(0));
|
|
323
|
-
}
|
|
324
|
-
/** Returns true if `this` is equivalent to `other`. */
|
|
325
|
-
equals(other) {
|
|
326
|
-
return this.compareTo(other) === 0;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Returns a negative integer, zero, or positive integer if `this` is less
|
|
330
|
-
* than, equal to, or greater than `other`, respectively.
|
|
331
|
-
*/
|
|
332
|
-
compareTo(other) {
|
|
333
|
-
for (let i = 0; i < 16; i++) {
|
|
334
|
-
const diff = this.bytes[i] - other.bytes[i];
|
|
335
|
-
if (diff !== 0) {
|
|
336
|
-
return Math.sign(diff);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return 0;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Encapsulates the monotonic counter state.
|
|
344
|
-
*
|
|
345
|
-
* This class provides APIs to utilize a separate counter state from that of the
|
|
346
|
-
* global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to
|
|
347
|
-
* the default {@link generate} method, this class has {@link generateOrAbort}
|
|
348
|
-
* that is useful to absolutely guarantee the monotonically increasing order of
|
|
349
|
-
* generated UUIDs. See their respective documentation for details.
|
|
350
|
-
*/
|
|
351
|
-
class V7Generator {
|
|
352
|
-
/**
|
|
353
|
-
* Creates a generator object with the default random number generator, or
|
|
354
|
-
* with the specified one if passed as an argument. The specified random
|
|
355
|
-
* number generator should be cryptographically strong and securely seeded.
|
|
356
|
-
*/
|
|
357
|
-
constructor(randomNumberGenerator) {
|
|
358
|
-
this.timestamp = 0;
|
|
359
|
-
this.counter = 0;
|
|
360
|
-
this.random = randomNumberGenerator ?? getDefaultRandom();
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Generates a new UUIDv7 object from the current timestamp, or resets the
|
|
364
|
-
* generator upon significant timestamp rollback.
|
|
365
|
-
*
|
|
366
|
-
* This method returns a monotonically increasing UUID by reusing the previous
|
|
367
|
-
* timestamp even if the up-to-date timestamp is smaller than the immediately
|
|
368
|
-
* preceding UUID's. However, when such a clock rollback is considered
|
|
369
|
-
* significant (i.e., by more than ten seconds), this method resets the
|
|
370
|
-
* generator and returns a new UUID based on the given timestamp, breaking the
|
|
371
|
-
* increasing order of UUIDs.
|
|
372
|
-
*
|
|
373
|
-
* See {@link generateOrAbort} for the other mode of generation and
|
|
374
|
-
* {@link generateOrResetCore} for the low-level primitive.
|
|
375
|
-
*/
|
|
376
|
-
generate() {
|
|
377
|
-
return this.generateOrResetCore(Date.now(), 10000);
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Generates a new UUIDv7 object from the current timestamp, or returns
|
|
381
|
-
* `undefined` upon significant timestamp rollback.
|
|
382
|
-
*
|
|
383
|
-
* This method returns a monotonically increasing UUID by reusing the previous
|
|
384
|
-
* timestamp even if the up-to-date timestamp is smaller than the immediately
|
|
385
|
-
* preceding UUID's. However, when such a clock rollback is considered
|
|
386
|
-
* significant (i.e., by more than ten seconds), this method aborts and
|
|
387
|
-
* returns `undefined` immediately.
|
|
388
|
-
*
|
|
389
|
-
* See {@link generate} for the other mode of generation and
|
|
390
|
-
* {@link generateOrAbortCore} for the low-level primitive.
|
|
391
|
-
*/
|
|
392
|
-
generateOrAbort() {
|
|
393
|
-
return this.generateOrAbortCore(Date.now(), 10000);
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the
|
|
397
|
-
* generator upon significant timestamp rollback.
|
|
398
|
-
*
|
|
399
|
-
* This method is equivalent to {@link generate} except that it takes a custom
|
|
400
|
-
* timestamp and clock rollback allowance.
|
|
401
|
-
*
|
|
402
|
-
* @param rollbackAllowance - The amount of `unixTsMs` rollback that is
|
|
403
|
-
* considered significant. A suggested value is `10_000` (milliseconds).
|
|
404
|
-
* @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
|
|
405
|
-
*/
|
|
406
|
-
generateOrResetCore(unixTsMs, rollbackAllowance) {
|
|
407
|
-
let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
|
|
408
|
-
if (value === undefined) {
|
|
409
|
-
// reset state and resume
|
|
410
|
-
this.timestamp = 0;
|
|
411
|
-
value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
|
|
412
|
-
}
|
|
413
|
-
return value;
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Generates a new UUIDv7 object from the `unixTsMs` passed, or returns
|
|
417
|
-
* `undefined` upon significant timestamp rollback.
|
|
418
|
-
*
|
|
419
|
-
* This method is equivalent to {@link generateOrAbort} except that it takes a
|
|
420
|
-
* custom timestamp and clock rollback allowance.
|
|
421
|
-
*
|
|
422
|
-
* @param rollbackAllowance - The amount of `unixTsMs` rollback that is
|
|
423
|
-
* considered significant. A suggested value is `10_000` (milliseconds).
|
|
424
|
-
* @throws RangeError if `unixTsMs` is not a 48-bit positive integer.
|
|
425
|
-
*/
|
|
426
|
-
generateOrAbortCore(unixTsMs, rollbackAllowance) {
|
|
427
|
-
const MAX_COUNTER = 4398046511103;
|
|
428
|
-
if (!Number.isInteger(unixTsMs) ||
|
|
429
|
-
unixTsMs < 1 ||
|
|
430
|
-
unixTsMs > 281474976710655) {
|
|
431
|
-
throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
|
|
432
|
-
}
|
|
433
|
-
else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) {
|
|
434
|
-
throw new RangeError("`rollbackAllowance` out of reasonable range");
|
|
435
|
-
}
|
|
436
|
-
if (unixTsMs > this.timestamp) {
|
|
437
|
-
this.timestamp = unixTsMs;
|
|
438
|
-
this.resetCounter();
|
|
439
|
-
}
|
|
440
|
-
else if (unixTsMs + rollbackAllowance >= this.timestamp) {
|
|
441
|
-
// go on with previous timestamp if new one is not much smaller
|
|
442
|
-
this.counter++;
|
|
443
|
-
if (this.counter > MAX_COUNTER) {
|
|
444
|
-
// increment timestamp at counter overflow
|
|
445
|
-
this.timestamp++;
|
|
446
|
-
this.resetCounter();
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
// abort if clock went backwards to unbearable extent
|
|
451
|
-
return undefined;
|
|
452
|
-
}
|
|
453
|
-
return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32());
|
|
454
|
-
}
|
|
455
|
-
/** Initializes the counter at a 42-bit random integer. */
|
|
456
|
-
resetCounter() {
|
|
457
|
-
this.counter =
|
|
458
|
-
this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff);
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Generates a new UUIDv4 object utilizing the random number generator inside.
|
|
462
|
-
*
|
|
463
|
-
* @internal
|
|
464
|
-
*/
|
|
465
|
-
generateV4() {
|
|
466
|
-
const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
|
|
467
|
-
bytes[6] = 0x40 | (bytes[6] >>> 4);
|
|
468
|
-
bytes[8] = 0x80 | (bytes[8] >>> 2);
|
|
469
|
-
return UUID.ofInner(bytes);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
/** A global flag to force use of cryptographically strong RNG. */
|
|
473
|
-
// declare const UUIDV7_DENY_WEAK_RNG: boolean;
|
|
474
|
-
/** Returns the default random number generator available in the environment. */
|
|
475
|
-
const getDefaultRandom = () => {
|
|
476
|
-
// fix: crypto isn't available in react-native, always use Math.random
|
|
477
|
-
// // detect Web Crypto API
|
|
478
|
-
// if (
|
|
479
|
-
// typeof crypto !== "undefined" &&
|
|
480
|
-
// typeof crypto.getRandomValues !== "undefined"
|
|
481
|
-
// ) {
|
|
482
|
-
// return new BufferedCryptoRandom();
|
|
483
|
-
// } else {
|
|
484
|
-
// // fall back on Math.random() unless the flag is set to true
|
|
485
|
-
// if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) {
|
|
486
|
-
// throw new Error("no cryptographically strong RNG available");
|
|
487
|
-
// }
|
|
488
|
-
// return {
|
|
489
|
-
// nextUint32: (): number =>
|
|
490
|
-
// Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +
|
|
491
|
-
// Math.trunc(Math.random() * 0x1_0000),
|
|
492
|
-
// };
|
|
493
|
-
// }
|
|
494
|
-
return {
|
|
495
|
-
nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 +
|
|
496
|
-
Math.trunc(Math.random() * 65536),
|
|
497
|
-
};
|
|
498
|
-
};
|
|
499
|
-
// /**
|
|
500
|
-
// * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small
|
|
501
|
-
// * buffer by default to avoid both unbearable throughput decline in some
|
|
502
|
-
// * environments and the waste of time and space for unused values.
|
|
503
|
-
// */
|
|
504
|
-
// class BufferedCryptoRandom {
|
|
505
|
-
// private readonly buffer = new Uint32Array(8);
|
|
506
|
-
// private cursor = 0xffff;
|
|
507
|
-
// nextUint32(): number {
|
|
508
|
-
// if (this.cursor >= this.buffer.length) {
|
|
509
|
-
// crypto.getRandomValues(this.buffer);
|
|
510
|
-
// this.cursor = 0;
|
|
511
|
-
// }
|
|
512
|
-
// return this.buffer[this.cursor++];
|
|
513
|
-
// }
|
|
514
|
-
// }
|
|
515
|
-
let defaultGenerator;
|
|
516
|
-
/**
|
|
517
|
-
* Generates a UUIDv7 string.
|
|
518
|
-
*
|
|
519
|
-
* @returns The 8-4-4-4-12 canonical hexadecimal string representation
|
|
520
|
-
* ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").
|
|
521
|
-
*/
|
|
522
|
-
const uuidv7 = () => uuidv7obj().toString();
|
|
523
|
-
/** Generates a UUIDv7 object. */
|
|
524
|
-
const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
|
|
121
|
+
/*! For license information please see uuidv7.mjs.LICENSE.txt */
|
|
122
|
+
/**
|
|
123
|
+
* uuidv7: An experimental implementation of the proposed UUID Version 7
|
|
124
|
+
*
|
|
125
|
+
* @license Apache-2.0
|
|
126
|
+
* @copyright 2021-2023 LiosK
|
|
127
|
+
* @packageDocumentation
|
|
128
|
+
*/ const DIGITS = "0123456789abcdef";
|
|
129
|
+
class UUID {
|
|
130
|
+
static ofInner(bytes) {
|
|
131
|
+
if (16 === bytes.length) return new UUID(bytes);
|
|
132
|
+
throw new TypeError("not 128-bit length");
|
|
133
|
+
}
|
|
134
|
+
static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
|
|
135
|
+
if (!Number.isInteger(unixTsMs) || !Number.isInteger(randA) || !Number.isInteger(randBHi) || !Number.isInteger(randBLo) || unixTsMs < 0 || randA < 0 || randBHi < 0 || randBLo < 0 || unixTsMs > 0xffffffffffff || randA > 0xfff || randBHi > 0x3fffffff || randBLo > 0xffffffff) throw new RangeError("invalid field value");
|
|
136
|
+
const bytes = new Uint8Array(16);
|
|
137
|
+
bytes[0] = unixTsMs / 2 ** 40;
|
|
138
|
+
bytes[1] = unixTsMs / 2 ** 32;
|
|
139
|
+
bytes[2] = unixTsMs / 2 ** 24;
|
|
140
|
+
bytes[3] = unixTsMs / 2 ** 16;
|
|
141
|
+
bytes[4] = unixTsMs / 256;
|
|
142
|
+
bytes[5] = unixTsMs;
|
|
143
|
+
bytes[6] = 0x70 | randA >>> 8;
|
|
144
|
+
bytes[7] = randA;
|
|
145
|
+
bytes[8] = 0x80 | randBHi >>> 24;
|
|
146
|
+
bytes[9] = randBHi >>> 16;
|
|
147
|
+
bytes[10] = randBHi >>> 8;
|
|
148
|
+
bytes[11] = randBHi;
|
|
149
|
+
bytes[12] = randBLo >>> 24;
|
|
150
|
+
bytes[13] = randBLo >>> 16;
|
|
151
|
+
bytes[14] = randBLo >>> 8;
|
|
152
|
+
bytes[15] = randBLo;
|
|
153
|
+
return new UUID(bytes);
|
|
154
|
+
}
|
|
155
|
+
static parse(uuid) {
|
|
156
|
+
let hex;
|
|
157
|
+
switch(uuid.length){
|
|
158
|
+
case 32:
|
|
159
|
+
var _exec;
|
|
160
|
+
hex = null == (_exec = /^[0-9a-f]{32}$/i.exec(uuid)) ? void 0 : _exec[0];
|
|
161
|
+
break;
|
|
162
|
+
case 36:
|
|
163
|
+
var _exec1;
|
|
164
|
+
hex = null == (_exec1 = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)) ? void 0 : _exec1.slice(1, 6).join("");
|
|
165
|
+
break;
|
|
166
|
+
case 38:
|
|
167
|
+
var _exec2;
|
|
168
|
+
hex = null == (_exec2 = /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i.exec(uuid)) ? void 0 : _exec2.slice(1, 6).join("");
|
|
169
|
+
break;
|
|
170
|
+
case 45:
|
|
171
|
+
var _exec3;
|
|
172
|
+
hex = null == (_exec3 = /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)) ? void 0 : _exec3.slice(1, 6).join("");
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
if (hex) {
|
|
176
|
+
const inner = new Uint8Array(16);
|
|
177
|
+
for(let i = 0; i < 16; i += 4){
|
|
178
|
+
const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
|
|
179
|
+
inner[i + 0] = n >>> 24;
|
|
180
|
+
inner[i + 1] = n >>> 16;
|
|
181
|
+
inner[i + 2] = n >>> 8;
|
|
182
|
+
inner[i + 3] = n;
|
|
183
|
+
}
|
|
184
|
+
return new UUID(inner);
|
|
185
|
+
}
|
|
186
|
+
throw new SyntaxError("could not parse UUID string");
|
|
187
|
+
}
|
|
188
|
+
toString() {
|
|
189
|
+
let text = "";
|
|
190
|
+
for(let i = 0; i < this.bytes.length; i++){
|
|
191
|
+
text += DIGITS.charAt(this.bytes[i] >>> 4);
|
|
192
|
+
text += DIGITS.charAt(0xf & this.bytes[i]);
|
|
193
|
+
if (3 === i || 5 === i || 7 === i || 9 === i) text += "-";
|
|
194
|
+
}
|
|
195
|
+
return text;
|
|
196
|
+
}
|
|
197
|
+
toHex() {
|
|
198
|
+
let text = "";
|
|
199
|
+
for(let i = 0; i < this.bytes.length; i++){
|
|
200
|
+
text += DIGITS.charAt(this.bytes[i] >>> 4);
|
|
201
|
+
text += DIGITS.charAt(0xf & this.bytes[i]);
|
|
202
|
+
}
|
|
203
|
+
return text;
|
|
204
|
+
}
|
|
205
|
+
toJSON() {
|
|
206
|
+
return this.toString();
|
|
207
|
+
}
|
|
208
|
+
getVariant() {
|
|
209
|
+
const n = this.bytes[8] >>> 4;
|
|
210
|
+
if (n < 0) throw new Error("unreachable");
|
|
211
|
+
if (n <= 7) return this.bytes.every((e)=>0 === e) ? "NIL" : "VAR_0";
|
|
212
|
+
if (n <= 11) return "VAR_10";
|
|
213
|
+
if (n <= 13) return "VAR_110";
|
|
214
|
+
if (n <= 15) return this.bytes.every((e)=>0xff === e) ? "MAX" : "VAR_RESERVED";
|
|
215
|
+
else throw new Error("unreachable");
|
|
216
|
+
}
|
|
217
|
+
getVersion() {
|
|
218
|
+
return "VAR_10" === this.getVariant() ? this.bytes[6] >>> 4 : void 0;
|
|
219
|
+
}
|
|
220
|
+
clone() {
|
|
221
|
+
return new UUID(this.bytes.slice(0));
|
|
222
|
+
}
|
|
223
|
+
equals(other) {
|
|
224
|
+
return 0 === this.compareTo(other);
|
|
225
|
+
}
|
|
226
|
+
compareTo(other) {
|
|
227
|
+
for(let i = 0; i < 16; i++){
|
|
228
|
+
const diff = this.bytes[i] - other.bytes[i];
|
|
229
|
+
if (0 !== diff) return Math.sign(diff);
|
|
230
|
+
}
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
constructor(bytes){
|
|
234
|
+
this.bytes = bytes;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
class V7Generator {
|
|
238
|
+
generate() {
|
|
239
|
+
return this.generateOrResetCore(Date.now(), 10000);
|
|
240
|
+
}
|
|
241
|
+
generateOrAbort() {
|
|
242
|
+
return this.generateOrAbortCore(Date.now(), 10000);
|
|
243
|
+
}
|
|
244
|
+
generateOrResetCore(unixTsMs, rollbackAllowance) {
|
|
245
|
+
let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
|
|
246
|
+
if (void 0 === value) {
|
|
247
|
+
this.timestamp = 0;
|
|
248
|
+
value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
|
|
249
|
+
}
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
generateOrAbortCore(unixTsMs, rollbackAllowance) {
|
|
253
|
+
const MAX_COUNTER = 0x3ffffffffff;
|
|
254
|
+
if (!Number.isInteger(unixTsMs) || unixTsMs < 1 || unixTsMs > 0xffffffffffff) throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
|
|
255
|
+
if (rollbackAllowance < 0 || rollbackAllowance > 0xffffffffffff) throw new RangeError("`rollbackAllowance` out of reasonable range");
|
|
256
|
+
if (unixTsMs > this.timestamp) {
|
|
257
|
+
this.timestamp = unixTsMs;
|
|
258
|
+
this.resetCounter();
|
|
259
|
+
} else {
|
|
260
|
+
if (!(unixTsMs + rollbackAllowance >= this.timestamp)) return;
|
|
261
|
+
this.counter++;
|
|
262
|
+
if (this.counter > MAX_COUNTER) {
|
|
263
|
+
this.timestamp++;
|
|
264
|
+
this.resetCounter();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & 2 ** 30 - 1, this.random.nextUint32());
|
|
268
|
+
}
|
|
269
|
+
resetCounter() {
|
|
270
|
+
this.counter = 0x400 * this.random.nextUint32() + (0x3ff & this.random.nextUint32());
|
|
271
|
+
}
|
|
272
|
+
generateV4() {
|
|
273
|
+
const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
|
|
274
|
+
bytes[6] = 0x40 | bytes[6] >>> 4;
|
|
275
|
+
bytes[8] = 0x80 | bytes[8] >>> 2;
|
|
276
|
+
return UUID.ofInner(bytes);
|
|
277
|
+
}
|
|
278
|
+
constructor(randomNumberGenerator){
|
|
279
|
+
this.timestamp = 0;
|
|
280
|
+
this.counter = 0;
|
|
281
|
+
this.random = null != randomNumberGenerator ? randomNumberGenerator : getDefaultRandom();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const getDefaultRandom = ()=>({
|
|
285
|
+
nextUint32: ()=>0x10000 * Math.trunc(0x10000 * Math.random()) + Math.trunc(0x10000 * Math.random())
|
|
286
|
+
});
|
|
287
|
+
let defaultGenerator;
|
|
288
|
+
const uuidv7 = ()=>uuidv7obj().toString();
|
|
289
|
+
const uuidv7obj = ()=>(defaultGenerator || (defaultGenerator = new V7Generator())).generate();
|
|
525
290
|
|
|
526
291
|
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
527
292
|
// Licensed under the MIT License
|
|
@@ -646,6 +411,7 @@ function isBuiltin(candidate, className) {
|
|
|
646
411
|
}
|
|
647
412
|
|
|
648
413
|
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
414
|
+
// Licensed under the MIT License
|
|
649
415
|
async function propertiesFromUnknownInput(stackParser, frameModifiers, input, hint) {
|
|
650
416
|
const providedMechanism = hint && hint.mechanism;
|
|
651
417
|
const mechanism = providedMechanism || {
|
|
@@ -734,10 +500,10 @@ function getObjectClassName(obj) {
|
|
|
734
500
|
// ignore errors here
|
|
735
501
|
}
|
|
736
502
|
}
|
|
737
|
-
/**
|
|
738
|
-
* Given any captured exception, extract its keys and create a sorted
|
|
739
|
-
* and truncated list that will be used inside the event message.
|
|
740
|
-
* eg. `Non-error exception captured with keys: foo, bar, baz`
|
|
503
|
+
/**
|
|
504
|
+
* Given any captured exception, extract its keys and create a sorted
|
|
505
|
+
* and truncated list that will be used inside the event message.
|
|
506
|
+
* eg. `Non-error exception captured with keys: foo, bar, baz`
|
|
741
507
|
*/
|
|
742
508
|
function extractExceptionKeysForMessage(exception, maxLength = 40) {
|
|
743
509
|
const keys = Object.keys(convertToPlainObject(exception));
|
|
@@ -767,13 +533,13 @@ function truncate(str, max = 0) {
|
|
|
767
533
|
}
|
|
768
534
|
return str.length <= max ? str : `${str.slice(0, max)}...`;
|
|
769
535
|
}
|
|
770
|
-
/**
|
|
771
|
-
* Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
|
|
772
|
-
* non-enumerable properties attached.
|
|
773
|
-
*
|
|
774
|
-
* @param value Initial source that we have to transform in order for it to be usable by the serializer
|
|
775
|
-
* @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor
|
|
776
|
-
* an Error.
|
|
536
|
+
/**
|
|
537
|
+
* Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
|
|
538
|
+
* non-enumerable properties attached.
|
|
539
|
+
*
|
|
540
|
+
* @param value Initial source that we have to transform in order for it to be usable by the serializer
|
|
541
|
+
* @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor
|
|
542
|
+
* an Error.
|
|
777
543
|
*/
|
|
778
544
|
function convertToPlainObject(value) {
|
|
779
545
|
if (isError(value)) {
|
|
@@ -821,8 +587,8 @@ function serializeEventTarget(target) {
|
|
|
821
587
|
return '<unknown>';
|
|
822
588
|
}
|
|
823
589
|
}
|
|
824
|
-
/**
|
|
825
|
-
* Extracts stack frames from the error and builds an Exception
|
|
590
|
+
/**
|
|
591
|
+
* Extracts stack frames from the error and builds an Exception
|
|
826
592
|
*/
|
|
827
593
|
async function exceptionFromError(stackParser, frameModifiers, error) {
|
|
828
594
|
const exception = {
|
|
@@ -841,8 +607,8 @@ async function exceptionFromError(stackParser, frameModifiers, error) {
|
|
|
841
607
|
}
|
|
842
608
|
return exception;
|
|
843
609
|
}
|
|
844
|
-
/**
|
|
845
|
-
* Extracts stack frames from the error.stack string
|
|
610
|
+
/**
|
|
611
|
+
* Extracts stack frames from the error.stack string
|
|
846
612
|
*/
|
|
847
613
|
function parseStackFrames(stackParser, error) {
|
|
848
614
|
return applyChunkIds(stackParser(error.stack || '', 1), stackParser);
|
|
@@ -920,6 +686,7 @@ function setupExpressErrorHandler(_posthog, app) {
|
|
|
920
686
|
}
|
|
921
687
|
|
|
922
688
|
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
689
|
+
// Licensed under the MIT License
|
|
923
690
|
/** Creates a function that gets the module name from a filename */
|
|
924
691
|
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname(process.argv[1]) : process.cwd(), isWindows = sep === '\\') {
|
|
925
692
|
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
|
|
@@ -999,6 +766,7 @@ class ReduceableCache {
|
|
|
999
766
|
}
|
|
1000
767
|
|
|
1001
768
|
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
769
|
+
// Licensed under the MIT License
|
|
1002
770
|
const LRU_FILE_CONTENTS_CACHE = new ReduceableCache(25);
|
|
1003
771
|
const LRU_FILE_CONTENTS_FS_READ_FAILED = new ReduceableCache(20);
|
|
1004
772
|
const DEFAULT_LINES_OF_CONTEXT = 7;
|
|
@@ -1061,8 +829,8 @@ async function addSourceContext(frames) {
|
|
|
1061
829
|
LRU_FILE_CONTENTS_CACHE.reduce();
|
|
1062
830
|
return frames;
|
|
1063
831
|
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Extracts lines from a file and stores them in a cache.
|
|
832
|
+
/**
|
|
833
|
+
* Extracts lines from a file and stores them in a cache.
|
|
1066
834
|
*/
|
|
1067
835
|
function getContextLinesFromFile(path, ranges, output) {
|
|
1068
836
|
return new Promise(resolve => {
|
|
@@ -1146,8 +914,8 @@ function addSourceContextToFrames(frames, cache) {
|
|
|
1146
914
|
}
|
|
1147
915
|
}
|
|
1148
916
|
}
|
|
1149
|
-
/**
|
|
1150
|
-
* Resolves context lines before and after the given line number and appends them to the frame;
|
|
917
|
+
/**
|
|
918
|
+
* Resolves context lines before and after the given line number and appends them to the frame;
|
|
1151
919
|
*/
|
|
1152
920
|
function addContextToFrame(lineno, frame, contents) {
|
|
1153
921
|
// When there is no line number in the frame, attaching context is nonsensical and will even break grouping.
|
|
@@ -1185,28 +953,28 @@ function addContextToFrame(lineno, frame, contents) {
|
|
|
1185
953
|
frame.post_context.push(line);
|
|
1186
954
|
}
|
|
1187
955
|
}
|
|
1188
|
-
/**
|
|
1189
|
-
* Clears the context lines from a frame, used to reset a frame to its original state
|
|
1190
|
-
* if we fail to resolve all context lines for it.
|
|
956
|
+
/**
|
|
957
|
+
* Clears the context lines from a frame, used to reset a frame to its original state
|
|
958
|
+
* if we fail to resolve all context lines for it.
|
|
1191
959
|
*/
|
|
1192
960
|
function clearLineContext(frame) {
|
|
1193
961
|
delete frame.pre_context;
|
|
1194
962
|
delete frame.context_line;
|
|
1195
963
|
delete frame.post_context;
|
|
1196
964
|
}
|
|
1197
|
-
/**
|
|
1198
|
-
* Determines if context lines should be skipped for a file.
|
|
1199
|
-
* - .min.(mjs|cjs|js) files are and not useful since they dont point to the original source
|
|
1200
|
-
* - node: prefixed modules are part of the runtime and cannot be resolved to a file
|
|
1201
|
-
* - data: skip json, wasm and inline js https://nodejs.org/api/esm.html#data-imports
|
|
965
|
+
/**
|
|
966
|
+
* Determines if context lines should be skipped for a file.
|
|
967
|
+
* - .min.(mjs|cjs|js) files are and not useful since they dont point to the original source
|
|
968
|
+
* - node: prefixed modules are part of the runtime and cannot be resolved to a file
|
|
969
|
+
* - data: skip json, wasm and inline js https://nodejs.org/api/esm.html#data-imports
|
|
1202
970
|
*/
|
|
1203
971
|
function shouldSkipContextLinesForFile(path) {
|
|
1204
972
|
// Test the most common prefix and extension first. These are the ones we
|
|
1205
973
|
// are most likely to see in user applications and are the ones we can break out of first.
|
|
1206
974
|
return path.startsWith('node:') || path.endsWith('.min.js') || path.endsWith('.min.cjs') || path.endsWith('.min.mjs') || path.startsWith('data:');
|
|
1207
975
|
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Determines if we should skip contextlines based off the max lineno and colno values.
|
|
976
|
+
/**
|
|
977
|
+
* Determines if we should skip contextlines based off the max lineno and colno values.
|
|
1210
978
|
*/
|
|
1211
979
|
function shouldSkipContextLinesForFrame(frame) {
|
|
1212
980
|
if (frame.lineno !== undefined && frame.lineno > MAX_CONTEXTLINES_LINENO) {
|
|
@@ -1217,8 +985,8 @@ function shouldSkipContextLinesForFrame(frame) {
|
|
|
1217
985
|
}
|
|
1218
986
|
return false;
|
|
1219
987
|
}
|
|
1220
|
-
/**
|
|
1221
|
-
* Checks if we have all the contents that we need in the cache.
|
|
988
|
+
/**
|
|
989
|
+
* Checks if we have all the contents that we need in the cache.
|
|
1222
990
|
*/
|
|
1223
991
|
function rangeExistsInContentCache(file, range) {
|
|
1224
992
|
const contents = LRU_FILE_CONTENTS_CACHE.get(file);
|
|
@@ -1232,9 +1000,9 @@ function rangeExistsInContentCache(file, range) {
|
|
|
1232
1000
|
}
|
|
1233
1001
|
return true;
|
|
1234
1002
|
}
|
|
1235
|
-
/**
|
|
1236
|
-
* Creates contiguous ranges of lines to read from a file. In the case where context lines overlap,
|
|
1237
|
-
* the ranges are merged to create a single range.
|
|
1003
|
+
/**
|
|
1004
|
+
* Creates contiguous ranges of lines to read from a file. In the case where context lines overlap,
|
|
1005
|
+
* the ranges are merged to create a single range.
|
|
1238
1006
|
*/
|
|
1239
1007
|
function makeLineReaderRanges(lines) {
|
|
1240
1008
|
if (!lines.length) {
|
|
@@ -1279,8 +1047,8 @@ function makeRangeStart(line) {
|
|
|
1279
1047
|
function makeRangeEnd(line) {
|
|
1280
1048
|
return line + DEFAULT_LINES_OF_CONTEXT;
|
|
1281
1049
|
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Get or init map value
|
|
1050
|
+
/**
|
|
1051
|
+
* Get or init map value
|
|
1284
1052
|
*/
|
|
1285
1053
|
function emplace(map, key, contents) {
|
|
1286
1054
|
const value = map.get(key);
|
|
@@ -1320,1092 +1088,18 @@ function snipLine(line, colno) {
|
|
|
1320
1088
|
return newLine;
|
|
1321
1089
|
}
|
|
1322
1090
|
|
|
1323
|
-
var version = "5.
|
|
1324
|
-
|
|
1325
|
-
var PostHogPersistedProperty;
|
|
1326
|
-
(function (PostHogPersistedProperty) {
|
|
1327
|
-
PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
|
|
1328
|
-
PostHogPersistedProperty["DistinctId"] = "distinct_id";
|
|
1329
|
-
PostHogPersistedProperty["Props"] = "props";
|
|
1330
|
-
PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
|
|
1331
|
-
PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
|
|
1332
|
-
PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
|
|
1333
|
-
PostHogPersistedProperty["BootstrapFeatureFlagDetails"] = "bootstrap_feature_flag_details";
|
|
1334
|
-
PostHogPersistedProperty["BootstrapFeatureFlags"] = "bootstrap_feature_flags";
|
|
1335
|
-
PostHogPersistedProperty["BootstrapFeatureFlagPayloads"] = "bootstrap_feature_flag_payloads";
|
|
1336
|
-
PostHogPersistedProperty["OverrideFeatureFlags"] = "override_feature_flags";
|
|
1337
|
-
PostHogPersistedProperty["Queue"] = "queue";
|
|
1338
|
-
PostHogPersistedProperty["OptedOut"] = "opted_out";
|
|
1339
|
-
PostHogPersistedProperty["SessionId"] = "session_id";
|
|
1340
|
-
PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
|
|
1341
|
-
PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
|
|
1342
|
-
PostHogPersistedProperty["PersonProperties"] = "person_properties";
|
|
1343
|
-
PostHogPersistedProperty["GroupProperties"] = "group_properties";
|
|
1344
|
-
PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
|
|
1345
|
-
PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
|
|
1346
|
-
PostHogPersistedProperty["SessionReplay"] = "session_replay";
|
|
1347
|
-
PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
|
|
1348
|
-
PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
|
|
1349
|
-
PostHogPersistedProperty["Surveys"] = "surveys";
|
|
1350
|
-
PostHogPersistedProperty["RemoteConfig"] = "remote_config";
|
|
1351
|
-
PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
|
|
1352
|
-
})(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
|
|
1353
|
-
// Any key prefixed with `attr__` can be added
|
|
1354
|
-
var Compression;
|
|
1355
|
-
(function (Compression) {
|
|
1356
|
-
Compression["GZipJS"] = "gzip-js";
|
|
1357
|
-
Compression["Base64"] = "base64";
|
|
1358
|
-
})(Compression || (Compression = {}));
|
|
1359
|
-
var SurveyPosition;
|
|
1360
|
-
(function (SurveyPosition) {
|
|
1361
|
-
SurveyPosition["TopLeft"] = "top_left";
|
|
1362
|
-
SurveyPosition["TopCenter"] = "top_center";
|
|
1363
|
-
SurveyPosition["TopRight"] = "top_right";
|
|
1364
|
-
SurveyPosition["MiddleLeft"] = "middle_left";
|
|
1365
|
-
SurveyPosition["MiddleCenter"] = "middle_center";
|
|
1366
|
-
SurveyPosition["MiddleRight"] = "middle_right";
|
|
1367
|
-
SurveyPosition["Left"] = "left";
|
|
1368
|
-
SurveyPosition["Right"] = "right";
|
|
1369
|
-
SurveyPosition["Center"] = "center";
|
|
1370
|
-
})(SurveyPosition || (SurveyPosition = {}));
|
|
1371
|
-
var SurveyWidgetType;
|
|
1372
|
-
(function (SurveyWidgetType) {
|
|
1373
|
-
SurveyWidgetType["Button"] = "button";
|
|
1374
|
-
SurveyWidgetType["Tab"] = "tab";
|
|
1375
|
-
SurveyWidgetType["Selector"] = "selector";
|
|
1376
|
-
})(SurveyWidgetType || (SurveyWidgetType = {}));
|
|
1377
|
-
var SurveyType;
|
|
1378
|
-
(function (SurveyType) {
|
|
1379
|
-
SurveyType["Popover"] = "popover";
|
|
1380
|
-
SurveyType["API"] = "api";
|
|
1381
|
-
SurveyType["Widget"] = "widget";
|
|
1382
|
-
})(SurveyType || (SurveyType = {}));
|
|
1383
|
-
var SurveyQuestionDescriptionContentType;
|
|
1384
|
-
(function (SurveyQuestionDescriptionContentType) {
|
|
1385
|
-
SurveyQuestionDescriptionContentType["Html"] = "html";
|
|
1386
|
-
SurveyQuestionDescriptionContentType["Text"] = "text";
|
|
1387
|
-
})(SurveyQuestionDescriptionContentType || (SurveyQuestionDescriptionContentType = {}));
|
|
1388
|
-
var SurveyRatingDisplay;
|
|
1389
|
-
(function (SurveyRatingDisplay) {
|
|
1390
|
-
SurveyRatingDisplay["Number"] = "number";
|
|
1391
|
-
SurveyRatingDisplay["Emoji"] = "emoji";
|
|
1392
|
-
})(SurveyRatingDisplay || (SurveyRatingDisplay = {}));
|
|
1393
|
-
var SurveyQuestionType;
|
|
1394
|
-
(function (SurveyQuestionType) {
|
|
1395
|
-
SurveyQuestionType["Open"] = "open";
|
|
1396
|
-
SurveyQuestionType["MultipleChoice"] = "multiple_choice";
|
|
1397
|
-
SurveyQuestionType["SingleChoice"] = "single_choice";
|
|
1398
|
-
SurveyQuestionType["Rating"] = "rating";
|
|
1399
|
-
SurveyQuestionType["Link"] = "link";
|
|
1400
|
-
})(SurveyQuestionType || (SurveyQuestionType = {}));
|
|
1401
|
-
var SurveyQuestionBranchingType;
|
|
1402
|
-
(function (SurveyQuestionBranchingType) {
|
|
1403
|
-
SurveyQuestionBranchingType["NextQuestion"] = "next_question";
|
|
1404
|
-
SurveyQuestionBranchingType["End"] = "end";
|
|
1405
|
-
SurveyQuestionBranchingType["ResponseBased"] = "response_based";
|
|
1406
|
-
SurveyQuestionBranchingType["SpecificQuestion"] = "specific_question";
|
|
1407
|
-
})(SurveyQuestionBranchingType || (SurveyQuestionBranchingType = {}));
|
|
1408
|
-
var SurveyMatchType;
|
|
1409
|
-
(function (SurveyMatchType) {
|
|
1410
|
-
SurveyMatchType["Regex"] = "regex";
|
|
1411
|
-
SurveyMatchType["NotRegex"] = "not_regex";
|
|
1412
|
-
SurveyMatchType["Exact"] = "exact";
|
|
1413
|
-
SurveyMatchType["IsNot"] = "is_not";
|
|
1414
|
-
SurveyMatchType["Icontains"] = "icontains";
|
|
1415
|
-
SurveyMatchType["NotIcontains"] = "not_icontains";
|
|
1416
|
-
})(SurveyMatchType || (SurveyMatchType = {}));
|
|
1417
|
-
/** Sync with plugin-server/src/types.ts */
|
|
1418
|
-
var ActionStepStringMatching;
|
|
1419
|
-
(function (ActionStepStringMatching) {
|
|
1420
|
-
ActionStepStringMatching["Contains"] = "contains";
|
|
1421
|
-
ActionStepStringMatching["Exact"] = "exact";
|
|
1422
|
-
ActionStepStringMatching["Regex"] = "regex";
|
|
1423
|
-
})(ActionStepStringMatching || (ActionStepStringMatching = {}));
|
|
1424
|
-
|
|
1425
|
-
const normalizeFlagsResponse = (flagsResponse) => {
|
|
1426
|
-
if ('flags' in flagsResponse) {
|
|
1427
|
-
// Convert v2 format to v1 format
|
|
1428
|
-
const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
|
|
1429
|
-
const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
|
|
1430
|
-
return {
|
|
1431
|
-
...flagsResponse,
|
|
1432
|
-
featureFlags,
|
|
1433
|
-
featureFlagPayloads,
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
else {
|
|
1437
|
-
// Convert v1 format to v2 format
|
|
1438
|
-
const featureFlags = flagsResponse.featureFlags ?? {};
|
|
1439
|
-
const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
|
|
1440
|
-
const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
|
|
1441
|
-
key,
|
|
1442
|
-
getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
|
|
1443
|
-
]));
|
|
1444
|
-
return {
|
|
1445
|
-
...flagsResponse,
|
|
1446
|
-
featureFlags,
|
|
1447
|
-
featureFlagPayloads,
|
|
1448
|
-
flags,
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
};
|
|
1452
|
-
function getFlagDetailFromFlagAndPayload(key, value, payload) {
|
|
1453
|
-
return {
|
|
1454
|
-
key: key,
|
|
1455
|
-
enabled: typeof value === 'string' ? true : value,
|
|
1456
|
-
variant: typeof value === 'string' ? value : undefined,
|
|
1457
|
-
reason: undefined,
|
|
1458
|
-
metadata: {
|
|
1459
|
-
id: undefined,
|
|
1460
|
-
version: undefined,
|
|
1461
|
-
payload: payload ? JSON.stringify(payload) : undefined,
|
|
1462
|
-
description: undefined,
|
|
1463
|
-
},
|
|
1464
|
-
};
|
|
1465
|
-
}
|
|
1466
|
-
/**
|
|
1467
|
-
* Get the flag values from the flags v4 response.
|
|
1468
|
-
* @param flags - The flags
|
|
1469
|
-
* @returns The flag values
|
|
1470
|
-
*/
|
|
1471
|
-
const getFlagValuesFromFlags = (flags) => {
|
|
1472
|
-
return Object.fromEntries(Object.entries(flags ?? {})
|
|
1473
|
-
.map(([key, detail]) => [key, getFeatureFlagValue(detail)])
|
|
1474
|
-
.filter(([, value]) => value !== undefined));
|
|
1475
|
-
};
|
|
1476
|
-
/**
|
|
1477
|
-
* Get the payloads from the flags v4 response.
|
|
1478
|
-
* @param flags - The flags
|
|
1479
|
-
* @returns The payloads
|
|
1480
|
-
*/
|
|
1481
|
-
const getPayloadsFromFlags = (flags) => {
|
|
1482
|
-
const safeFlags = flags ?? {};
|
|
1483
|
-
return Object.fromEntries(Object.keys(safeFlags)
|
|
1484
|
-
.filter((flag) => {
|
|
1485
|
-
const details = safeFlags[flag];
|
|
1486
|
-
return details.enabled && details.metadata && details.metadata.payload !== undefined;
|
|
1487
|
-
})
|
|
1488
|
-
.map((flag) => {
|
|
1489
|
-
const payload = safeFlags[flag].metadata?.payload;
|
|
1490
|
-
return [flag, payload ? parsePayload(payload) : undefined];
|
|
1491
|
-
}));
|
|
1492
|
-
};
|
|
1493
|
-
const getFeatureFlagValue = (detail) => {
|
|
1494
|
-
return detail === undefined ? undefined : detail.variant ?? detail.enabled;
|
|
1495
|
-
};
|
|
1496
|
-
const parsePayload = (response) => {
|
|
1497
|
-
if (typeof response !== 'string') {
|
|
1498
|
-
return response;
|
|
1499
|
-
}
|
|
1500
|
-
try {
|
|
1501
|
-
return JSON.parse(response);
|
|
1502
|
-
}
|
|
1503
|
-
catch {
|
|
1504
|
-
return response;
|
|
1505
|
-
}
|
|
1506
|
-
};
|
|
1507
|
-
|
|
1508
|
-
const STRING_FORMAT = 'utf8';
|
|
1509
|
-
function assert(truthyValue, message) {
|
|
1510
|
-
if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
|
|
1511
|
-
throw new Error(message);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
function isEmpty(truthyValue) {
|
|
1515
|
-
if (truthyValue.trim().length === 0) {
|
|
1516
|
-
return true;
|
|
1517
|
-
}
|
|
1518
|
-
return false;
|
|
1519
|
-
}
|
|
1520
|
-
function removeTrailingSlash(url) {
|
|
1521
|
-
return url?.replace(/\/+$/, '');
|
|
1522
|
-
}
|
|
1523
|
-
async function retriable(fn, props) {
|
|
1524
|
-
let lastError = null;
|
|
1525
|
-
for (let i = 0; i < props.retryCount + 1; i++) {
|
|
1526
|
-
if (i > 0) {
|
|
1527
|
-
// don't wait when it's the last try
|
|
1528
|
-
await new Promise((r) => setTimeout(r, props.retryDelay));
|
|
1529
|
-
}
|
|
1530
|
-
try {
|
|
1531
|
-
const res = await fn();
|
|
1532
|
-
return res;
|
|
1533
|
-
}
|
|
1534
|
-
catch (e) {
|
|
1535
|
-
lastError = e;
|
|
1536
|
-
if (!props.retryCheck(e)) {
|
|
1537
|
-
throw e;
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
throw lastError;
|
|
1542
|
-
}
|
|
1543
|
-
function currentISOTime() {
|
|
1544
|
-
return new Date().toISOString();
|
|
1545
|
-
}
|
|
1546
|
-
function safeSetTimeout(fn, timeout) {
|
|
1547
|
-
// NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``
|
|
1548
|
-
// rather than setImmediate.
|
|
1549
|
-
const t = setTimeout(fn, timeout);
|
|
1550
|
-
// We unref if available to prevent Node.js hanging on exit
|
|
1551
|
-
t?.unref && t?.unref();
|
|
1552
|
-
return t;
|
|
1553
|
-
}
|
|
1554
|
-
function allSettled(promises) {
|
|
1555
|
-
return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
|
|
1556
|
-
}
|
|
1091
|
+
var version = "5.7.0";
|
|
1557
1092
|
|
|
1558
|
-
/**
|
|
1559
|
-
*
|
|
1560
|
-
* This API (as of 2025-05-07) is not available on React Native.
|
|
1561
|
-
*/
|
|
1562
|
-
function isGzipSupported() {
|
|
1563
|
-
return 'CompressionStream' in globalThis;
|
|
1564
|
-
}
|
|
1565
|
-
/**
|
|
1566
|
-
* Gzip a string using Compression Streams API if it's available
|
|
1567
|
-
*/
|
|
1568
|
-
async function gzipCompress(input, isDebug = true) {
|
|
1569
|
-
try {
|
|
1570
|
-
// Turn the string into a stream using a Blob, and then compress it
|
|
1571
|
-
const dataStream = new Blob([input], {
|
|
1572
|
-
type: 'text/plain',
|
|
1573
|
-
}).stream();
|
|
1574
|
-
const compressedStream = dataStream.pipeThrough(new CompressionStream('gzip'));
|
|
1575
|
-
// Using a Response to easily extract the readablestream value. Decoding into a string for fetch
|
|
1576
|
-
return await new Response(compressedStream).blob();
|
|
1577
|
-
}
|
|
1578
|
-
catch (error) {
|
|
1579
|
-
if (isDebug) {
|
|
1580
|
-
console.error('Failed to gzip compress data', error);
|
|
1581
|
-
}
|
|
1582
|
-
return null;
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
class SimpleEventEmitter {
|
|
1587
|
-
constructor() {
|
|
1588
|
-
this.events = {};
|
|
1589
|
-
this.events = {};
|
|
1590
|
-
}
|
|
1591
|
-
on(event, listener) {
|
|
1592
|
-
if (!this.events[event]) {
|
|
1593
|
-
this.events[event] = [];
|
|
1594
|
-
}
|
|
1595
|
-
this.events[event].push(listener);
|
|
1596
|
-
return () => {
|
|
1597
|
-
this.events[event] = this.events[event].filter((x) => x !== listener);
|
|
1598
|
-
};
|
|
1599
|
-
}
|
|
1600
|
-
emit(event, payload) {
|
|
1601
|
-
for (const listener of this.events[event] || []) {
|
|
1602
|
-
listener(payload);
|
|
1603
|
-
}
|
|
1604
|
-
for (const listener of this.events['*'] || []) {
|
|
1605
|
-
listener(event, payload);
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
class PostHogFetchHttpError extends Error {
|
|
1611
|
-
constructor(response, reqByteLength) {
|
|
1612
|
-
super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength);
|
|
1613
|
-
this.response = response;
|
|
1614
|
-
this.reqByteLength = reqByteLength;
|
|
1615
|
-
this.name = 'PostHogFetchHttpError';
|
|
1616
|
-
}
|
|
1617
|
-
get status() {
|
|
1618
|
-
return this.response.status;
|
|
1619
|
-
}
|
|
1620
|
-
get text() {
|
|
1621
|
-
return this.response.text();
|
|
1622
|
-
}
|
|
1623
|
-
get json() {
|
|
1624
|
-
return this.response.json();
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
class PostHogFetchNetworkError extends Error {
|
|
1628
|
-
constructor(error) {
|
|
1629
|
-
// TRICKY: "cause" is a newer property but is just ignored otherwise. Cast to any to ignore the type issue.
|
|
1630
|
-
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
1631
|
-
// @ts-ignore
|
|
1632
|
-
super('Network error while fetching PostHog', error instanceof Error ? { cause: error } : {});
|
|
1633
|
-
this.error = error;
|
|
1634
|
-
this.name = 'PostHogFetchNetworkError';
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
async function logFlushError(err) {
|
|
1638
|
-
if (err instanceof PostHogFetchHttpError) {
|
|
1639
|
-
let text = '';
|
|
1640
|
-
try {
|
|
1641
|
-
text = await err.text;
|
|
1642
|
-
}
|
|
1643
|
-
catch { }
|
|
1644
|
-
console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
|
|
1645
|
-
}
|
|
1646
|
-
else {
|
|
1647
|
-
console.error('Error while flushing PostHog', err);
|
|
1648
|
-
}
|
|
1649
|
-
return Promise.resolve();
|
|
1650
|
-
}
|
|
1651
|
-
function isPostHogFetchError(err) {
|
|
1652
|
-
return typeof err === 'object' && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
|
|
1653
|
-
}
|
|
1654
|
-
function isPostHogFetchContentTooLargeError(err) {
|
|
1655
|
-
return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413;
|
|
1656
|
-
}
|
|
1657
|
-
var QuotaLimitedFeature;
|
|
1658
|
-
(function (QuotaLimitedFeature) {
|
|
1659
|
-
QuotaLimitedFeature["FeatureFlags"] = "feature_flags";
|
|
1660
|
-
QuotaLimitedFeature["Recordings"] = "recordings";
|
|
1661
|
-
})(QuotaLimitedFeature || (QuotaLimitedFeature = {}));
|
|
1662
|
-
class PostHogCoreStateless {
|
|
1663
|
-
constructor(apiKey, options) {
|
|
1664
|
-
this.flushPromise = null;
|
|
1665
|
-
this.shutdownPromise = null;
|
|
1666
|
-
this.pendingPromises = {};
|
|
1667
|
-
// internal
|
|
1668
|
-
this._events = new SimpleEventEmitter();
|
|
1669
|
-
this._isInitialized = false;
|
|
1670
|
-
assert(apiKey, "You must pass your PostHog project's api key.");
|
|
1671
|
-
this.apiKey = apiKey;
|
|
1672
|
-
this.host = removeTrailingSlash(options?.host || 'https://us.i.posthog.com');
|
|
1673
|
-
this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 20;
|
|
1674
|
-
this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
|
|
1675
|
-
this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
|
|
1676
|
-
this.flushInterval = options?.flushInterval ?? 10000;
|
|
1677
|
-
this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
|
|
1678
|
-
// If enable is explicitly set to false we override the optout
|
|
1679
|
-
this.defaultOptIn = options?.defaultOptIn ?? true;
|
|
1680
|
-
this.disableSurveys = options?.disableSurveys ?? false;
|
|
1681
|
-
this._retryOptions = {
|
|
1682
|
-
retryCount: options?.fetchRetryCount ?? 3,
|
|
1683
|
-
retryDelay: options?.fetchRetryDelay ?? 3000,
|
|
1684
|
-
retryCheck: isPostHogFetchError,
|
|
1685
|
-
};
|
|
1686
|
-
this.requestTimeout = options?.requestTimeout ?? 10000; // 10 seconds
|
|
1687
|
-
this.featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 3000; // 3 seconds
|
|
1688
|
-
this.remoteConfigRequestTimeoutMs = options?.remoteConfigRequestTimeoutMs ?? 3000; // 3 seconds
|
|
1689
|
-
this.disableGeoip = options?.disableGeoip ?? true;
|
|
1690
|
-
this.disabled = options?.disabled ?? false;
|
|
1691
|
-
this.historicalMigration = options?.historicalMigration ?? false;
|
|
1692
|
-
// Init promise allows the derived class to block calls until it is ready
|
|
1693
|
-
this._initPromise = Promise.resolve();
|
|
1694
|
-
this._isInitialized = true;
|
|
1695
|
-
this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
|
|
1696
|
-
}
|
|
1697
|
-
logMsgIfDebug(fn) {
|
|
1698
|
-
if (this.isDebug) {
|
|
1699
|
-
fn();
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
wrap(fn) {
|
|
1703
|
-
if (this.disabled) {
|
|
1704
|
-
this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
if (this._isInitialized) {
|
|
1708
|
-
// NOTE: We could also check for the "opt in" status here...
|
|
1709
|
-
return fn();
|
|
1710
|
-
}
|
|
1711
|
-
this._initPromise.then(() => fn());
|
|
1712
|
-
}
|
|
1713
|
-
getCommonEventProperties() {
|
|
1714
|
-
return {
|
|
1715
|
-
$lib: this.getLibraryId(),
|
|
1716
|
-
$lib_version: this.getLibraryVersion(),
|
|
1717
|
-
};
|
|
1718
|
-
}
|
|
1719
|
-
get optedOut() {
|
|
1720
|
-
return this.getPersistedProperty(PostHogPersistedProperty.OptedOut) ?? !this.defaultOptIn;
|
|
1721
|
-
}
|
|
1722
|
-
async optIn() {
|
|
1723
|
-
this.wrap(() => {
|
|
1724
|
-
this.setPersistedProperty(PostHogPersistedProperty.OptedOut, false);
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
|
-
async optOut() {
|
|
1728
|
-
this.wrap(() => {
|
|
1729
|
-
this.setPersistedProperty(PostHogPersistedProperty.OptedOut, true);
|
|
1730
|
-
});
|
|
1731
|
-
}
|
|
1732
|
-
on(event, cb) {
|
|
1733
|
-
return this._events.on(event, cb);
|
|
1734
|
-
}
|
|
1735
|
-
debug(enabled = true) {
|
|
1736
|
-
this.removeDebugCallback?.();
|
|
1737
|
-
if (enabled) {
|
|
1738
|
-
const removeDebugCallback = this.on('*', (event, payload) => console.log('PostHog Debug', event, payload));
|
|
1739
|
-
this.removeDebugCallback = () => {
|
|
1740
|
-
removeDebugCallback();
|
|
1741
|
-
this.removeDebugCallback = undefined;
|
|
1742
|
-
};
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
get isDebug() {
|
|
1746
|
-
return !!this.removeDebugCallback;
|
|
1747
|
-
}
|
|
1748
|
-
get isDisabled() {
|
|
1749
|
-
return this.disabled;
|
|
1750
|
-
}
|
|
1751
|
-
buildPayload(payload) {
|
|
1752
|
-
return {
|
|
1753
|
-
distinct_id: payload.distinct_id,
|
|
1754
|
-
event: payload.event,
|
|
1755
|
-
properties: {
|
|
1756
|
-
...(payload.properties || {}),
|
|
1757
|
-
...this.getCommonEventProperties(), // Common PH props
|
|
1758
|
-
},
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
|
-
addPendingPromise(promise) {
|
|
1762
|
-
const promiseUUID = uuidv7();
|
|
1763
|
-
this.pendingPromises[promiseUUID] = promise;
|
|
1764
|
-
promise
|
|
1765
|
-
.catch(() => { })
|
|
1766
|
-
.finally(() => {
|
|
1767
|
-
delete this.pendingPromises[promiseUUID];
|
|
1768
|
-
});
|
|
1769
|
-
return promise;
|
|
1770
|
-
}
|
|
1771
|
-
/***
|
|
1772
|
-
*** TRACKING
|
|
1773
|
-
***/
|
|
1774
|
-
identifyStateless(distinctId, properties, options) {
|
|
1775
|
-
this.wrap(() => {
|
|
1776
|
-
// The properties passed to identifyStateless are event properties.
|
|
1777
|
-
// To add person properties, pass in all person properties to the `$set` and `$set_once` keys.
|
|
1778
|
-
const payload = {
|
|
1779
|
-
...this.buildPayload({
|
|
1780
|
-
distinct_id: distinctId,
|
|
1781
|
-
event: '$identify',
|
|
1782
|
-
properties,
|
|
1783
|
-
}),
|
|
1784
|
-
};
|
|
1785
|
-
this.enqueue('identify', payload, options);
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
async identifyStatelessImmediate(distinctId, properties, options) {
|
|
1789
|
-
const payload = {
|
|
1790
|
-
...this.buildPayload({
|
|
1791
|
-
distinct_id: distinctId,
|
|
1792
|
-
event: '$identify',
|
|
1793
|
-
properties,
|
|
1794
|
-
}),
|
|
1795
|
-
};
|
|
1796
|
-
await this.sendImmediate('identify', payload, options);
|
|
1797
|
-
}
|
|
1798
|
-
captureStateless(distinctId, event, properties, options) {
|
|
1799
|
-
this.wrap(() => {
|
|
1800
|
-
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
1801
|
-
this.enqueue('capture', payload, options);
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
async captureStatelessImmediate(distinctId, event, properties, options) {
|
|
1805
|
-
const payload = this.buildPayload({ distinct_id: distinctId, event, properties });
|
|
1806
|
-
await this.sendImmediate('capture', payload, options);
|
|
1807
|
-
}
|
|
1808
|
-
aliasStateless(alias, distinctId, properties, options) {
|
|
1809
|
-
this.wrap(() => {
|
|
1810
|
-
const payload = this.buildPayload({
|
|
1811
|
-
event: '$create_alias',
|
|
1812
|
-
distinct_id: distinctId,
|
|
1813
|
-
properties: {
|
|
1814
|
-
...(properties || {}),
|
|
1815
|
-
distinct_id: distinctId,
|
|
1816
|
-
alias,
|
|
1817
|
-
},
|
|
1818
|
-
});
|
|
1819
|
-
this.enqueue('alias', payload, options);
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
async aliasStatelessImmediate(alias, distinctId, properties, options) {
|
|
1823
|
-
const payload = this.buildPayload({
|
|
1824
|
-
event: '$create_alias',
|
|
1825
|
-
distinct_id: distinctId,
|
|
1826
|
-
properties: {
|
|
1827
|
-
...(properties || {}),
|
|
1828
|
-
distinct_id: distinctId,
|
|
1829
|
-
alias,
|
|
1830
|
-
},
|
|
1831
|
-
});
|
|
1832
|
-
await this.sendImmediate('alias', payload, options);
|
|
1833
|
-
}
|
|
1834
|
-
/***
|
|
1835
|
-
*** GROUPS
|
|
1836
|
-
***/
|
|
1837
|
-
groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties) {
|
|
1838
|
-
this.wrap(() => {
|
|
1839
|
-
const payload = this.buildPayload({
|
|
1840
|
-
distinct_id: distinctId || `$${groupType}_${groupKey}`,
|
|
1841
|
-
event: '$groupidentify',
|
|
1842
|
-
properties: {
|
|
1843
|
-
$group_type: groupType,
|
|
1844
|
-
$group_key: groupKey,
|
|
1845
|
-
$group_set: groupProperties || {},
|
|
1846
|
-
...(eventProperties || {}),
|
|
1847
|
-
},
|
|
1848
|
-
});
|
|
1849
|
-
this.enqueue('capture', payload, options);
|
|
1850
|
-
});
|
|
1851
|
-
}
|
|
1852
|
-
async getRemoteConfig() {
|
|
1853
|
-
await this._initPromise;
|
|
1854
|
-
let host = this.host;
|
|
1855
|
-
if (host === 'https://us.i.posthog.com') {
|
|
1856
|
-
host = 'https://us-assets.i.posthog.com';
|
|
1857
|
-
}
|
|
1858
|
-
else if (host === 'https://eu.i.posthog.com') {
|
|
1859
|
-
host = 'https://eu-assets.i.posthog.com';
|
|
1860
|
-
}
|
|
1861
|
-
const url = `${host}/array/${this.apiKey}/config`;
|
|
1862
|
-
const fetchOptions = {
|
|
1863
|
-
method: 'GET',
|
|
1864
|
-
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1865
|
-
};
|
|
1866
|
-
// Don't retry remote config API calls
|
|
1867
|
-
return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.remoteConfigRequestTimeoutMs)
|
|
1868
|
-
.then((response) => response.json())
|
|
1869
|
-
.catch((error) => {
|
|
1870
|
-
this.logMsgIfDebug(() => console.error('Remote config could not be loaded', error));
|
|
1871
|
-
this._events.emit('error', error);
|
|
1872
|
-
return undefined;
|
|
1873
|
-
});
|
|
1874
|
-
}
|
|
1875
|
-
/***
|
|
1876
|
-
*** FEATURE FLAGS
|
|
1877
|
-
***/
|
|
1878
|
-
async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
|
|
1879
|
-
await this._initPromise;
|
|
1880
|
-
const url = `${this.host}/flags/?v=2&config=true`;
|
|
1881
|
-
const fetchOptions = {
|
|
1882
|
-
method: 'POST',
|
|
1883
|
-
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
1884
|
-
body: JSON.stringify({
|
|
1885
|
-
token: this.apiKey,
|
|
1886
|
-
distinct_id: distinctId,
|
|
1887
|
-
groups,
|
|
1888
|
-
person_properties: personProperties,
|
|
1889
|
-
group_properties: groupProperties,
|
|
1890
|
-
...extraPayload,
|
|
1891
|
-
}),
|
|
1892
|
-
};
|
|
1893
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Flags URL', url));
|
|
1894
|
-
// Don't retry /flags API calls
|
|
1895
|
-
return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
|
|
1896
|
-
.then((response) => response.json())
|
|
1897
|
-
.then((response) => normalizeFlagsResponse(response))
|
|
1898
|
-
.catch((error) => {
|
|
1899
|
-
this._events.emit('error', error);
|
|
1900
|
-
return undefined;
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1904
|
-
await this._initPromise;
|
|
1905
|
-
const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
1906
|
-
if (flagDetailResponse === undefined) {
|
|
1907
|
-
// If we haven't loaded flags yet, or errored out, we respond with undefined
|
|
1908
|
-
return {
|
|
1909
|
-
response: undefined,
|
|
1910
|
-
requestId: undefined,
|
|
1911
|
-
};
|
|
1912
|
-
}
|
|
1913
|
-
let response = getFeatureFlagValue(flagDetailResponse.response);
|
|
1914
|
-
if (response === undefined) {
|
|
1915
|
-
// For cases where the flag is unknown, return false
|
|
1916
|
-
response = false;
|
|
1917
|
-
}
|
|
1918
|
-
// If we have flags we either return the value (true or string) or false
|
|
1919
|
-
return {
|
|
1920
|
-
response,
|
|
1921
|
-
requestId: flagDetailResponse.requestId,
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
|
-
async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1925
|
-
await this._initPromise;
|
|
1926
|
-
const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
|
|
1927
|
-
if (flagsResponse === undefined) {
|
|
1928
|
-
return undefined;
|
|
1929
|
-
}
|
|
1930
|
-
const featureFlags = flagsResponse.flags;
|
|
1931
|
-
const flagDetail = featureFlags[key];
|
|
1932
|
-
return {
|
|
1933
|
-
response: flagDetail,
|
|
1934
|
-
requestId: flagsResponse.requestId,
|
|
1935
|
-
};
|
|
1936
|
-
}
|
|
1937
|
-
async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1938
|
-
await this._initPromise;
|
|
1939
|
-
const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
|
|
1940
|
-
if (!payloads) {
|
|
1941
|
-
return undefined;
|
|
1942
|
-
}
|
|
1943
|
-
const response = payloads[key];
|
|
1944
|
-
// Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
|
|
1945
|
-
if (response === undefined) {
|
|
1946
|
-
return null;
|
|
1947
|
-
}
|
|
1948
|
-
return response;
|
|
1949
|
-
}
|
|
1950
|
-
async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1951
|
-
await this._initPromise;
|
|
1952
|
-
const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate)).payloads;
|
|
1953
|
-
return payloads;
|
|
1954
|
-
}
|
|
1955
|
-
async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1956
|
-
await this._initPromise;
|
|
1957
|
-
return await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
|
|
1958
|
-
}
|
|
1959
|
-
async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1960
|
-
await this._initPromise;
|
|
1961
|
-
const featureFlagDetails = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
|
|
1962
|
-
if (!featureFlagDetails) {
|
|
1963
|
-
return {
|
|
1964
|
-
flags: undefined,
|
|
1965
|
-
payloads: undefined,
|
|
1966
|
-
requestId: undefined,
|
|
1967
|
-
};
|
|
1968
|
-
}
|
|
1969
|
-
return {
|
|
1970
|
-
flags: featureFlagDetails.featureFlags,
|
|
1971
|
-
payloads: featureFlagDetails.featureFlagPayloads,
|
|
1972
|
-
requestId: featureFlagDetails.requestId,
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
async getFeatureFlagDetailsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
|
|
1976
|
-
await this._initPromise;
|
|
1977
|
-
const extraPayload = {};
|
|
1978
|
-
if (disableGeoip ?? this.disableGeoip) {
|
|
1979
|
-
extraPayload['geoip_disable'] = true;
|
|
1980
|
-
}
|
|
1981
|
-
if (flagKeysToEvaluate) {
|
|
1982
|
-
extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
|
|
1983
|
-
}
|
|
1984
|
-
const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
|
|
1985
|
-
if (flagsResponse === undefined) {
|
|
1986
|
-
// We probably errored out, so return undefined
|
|
1987
|
-
return undefined;
|
|
1988
|
-
}
|
|
1989
|
-
// if there's an error on the flagsResponse, log a console error, but don't throw an error
|
|
1990
|
-
if (flagsResponse.errorsWhileComputingFlags) {
|
|
1991
|
-
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');
|
|
1992
|
-
}
|
|
1993
|
-
// Add check for quota limitation on feature flags
|
|
1994
|
-
if (flagsResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
1995
|
-
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');
|
|
1996
|
-
return {
|
|
1997
|
-
flags: {},
|
|
1998
|
-
featureFlags: {},
|
|
1999
|
-
featureFlagPayloads: {},
|
|
2000
|
-
requestId: flagsResponse?.requestId,
|
|
2001
|
-
};
|
|
2002
|
-
}
|
|
2003
|
-
return flagsResponse;
|
|
2004
|
-
}
|
|
2005
|
-
/***
|
|
2006
|
-
*** SURVEYS
|
|
2007
|
-
***/
|
|
2008
|
-
async getSurveysStateless() {
|
|
2009
|
-
await this._initPromise;
|
|
2010
|
-
if (this.disableSurveys === true) {
|
|
2011
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Loading surveys is disabled.'));
|
|
2012
|
-
return [];
|
|
2013
|
-
}
|
|
2014
|
-
const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
|
|
2015
|
-
const fetchOptions = {
|
|
2016
|
-
method: 'GET',
|
|
2017
|
-
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
2018
|
-
};
|
|
2019
|
-
const response = await this.fetchWithRetry(url, fetchOptions)
|
|
2020
|
-
.then((response) => {
|
|
2021
|
-
if (response.status !== 200 || !response.json) {
|
|
2022
|
-
const msg = `Surveys API could not be loaded: ${response.status}`;
|
|
2023
|
-
const error = new Error(msg);
|
|
2024
|
-
this.logMsgIfDebug(() => console.error(error));
|
|
2025
|
-
this._events.emit('error', new Error(msg));
|
|
2026
|
-
return undefined;
|
|
2027
|
-
}
|
|
2028
|
-
return response.json();
|
|
2029
|
-
})
|
|
2030
|
-
.catch((error) => {
|
|
2031
|
-
this.logMsgIfDebug(() => console.error('Surveys API could not be loaded', error));
|
|
2032
|
-
this._events.emit('error', error);
|
|
2033
|
-
return undefined;
|
|
2034
|
-
});
|
|
2035
|
-
const newSurveys = response?.surveys;
|
|
2036
|
-
if (newSurveys) {
|
|
2037
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from API: ', JSON.stringify(newSurveys)));
|
|
2038
|
-
}
|
|
2039
|
-
return newSurveys ?? [];
|
|
2040
|
-
}
|
|
2041
|
-
get props() {
|
|
2042
|
-
if (!this._props) {
|
|
2043
|
-
this._props = this.getPersistedProperty(PostHogPersistedProperty.Props);
|
|
2044
|
-
}
|
|
2045
|
-
return this._props || {};
|
|
2046
|
-
}
|
|
2047
|
-
set props(val) {
|
|
2048
|
-
this._props = val;
|
|
2049
|
-
}
|
|
2050
|
-
async register(properties) {
|
|
2051
|
-
this.wrap(() => {
|
|
2052
|
-
this.props = {
|
|
2053
|
-
...this.props,
|
|
2054
|
-
...properties,
|
|
2055
|
-
};
|
|
2056
|
-
this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
async unregister(property) {
|
|
2060
|
-
this.wrap(() => {
|
|
2061
|
-
delete this.props[property];
|
|
2062
|
-
this.setPersistedProperty(PostHogPersistedProperty.Props, this.props);
|
|
2063
|
-
});
|
|
2064
|
-
}
|
|
2065
|
-
/***
|
|
2066
|
-
*** QUEUEING AND FLUSHING
|
|
2067
|
-
***/
|
|
2068
|
-
enqueue(type, _message, options) {
|
|
2069
|
-
this.wrap(() => {
|
|
2070
|
-
if (this.optedOut) {
|
|
2071
|
-
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
2072
|
-
return;
|
|
2073
|
-
}
|
|
2074
|
-
const message = this.prepareMessage(type, _message, options);
|
|
2075
|
-
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
2076
|
-
if (queue.length >= this.maxQueueSize) {
|
|
2077
|
-
queue.shift();
|
|
2078
|
-
this.logMsgIfDebug(() => console.info('Queue is full, the oldest event is dropped.'));
|
|
2079
|
-
}
|
|
2080
|
-
queue.push({ message });
|
|
2081
|
-
this.setPersistedProperty(PostHogPersistedProperty.Queue, queue);
|
|
2082
|
-
this._events.emit(type, message);
|
|
2083
|
-
// Flush queued events if we meet the flushAt length
|
|
2084
|
-
if (queue.length >= this.flushAt) {
|
|
2085
|
-
this.flushBackground();
|
|
2086
|
-
}
|
|
2087
|
-
if (this.flushInterval && !this._flushTimer) {
|
|
2088
|
-
this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
|
|
2089
|
-
}
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
|
-
async sendImmediate(type, _message, options) {
|
|
2093
|
-
if (this.disabled) {
|
|
2094
|
-
this.logMsgIfDebug(() => console.warn('[PostHog] The client is disabled'));
|
|
2095
|
-
return;
|
|
2096
|
-
}
|
|
2097
|
-
if (!this._isInitialized) {
|
|
2098
|
-
await this._initPromise;
|
|
2099
|
-
}
|
|
2100
|
-
if (this.optedOut) {
|
|
2101
|
-
this._events.emit(type, `Library is disabled. Not sending event. To re-enable, call posthog.optIn()`);
|
|
2102
|
-
return;
|
|
2103
|
-
}
|
|
2104
|
-
const data = {
|
|
2105
|
-
api_key: this.apiKey,
|
|
2106
|
-
batch: [this.prepareMessage(type, _message, options)],
|
|
2107
|
-
sent_at: currentISOTime(),
|
|
2108
|
-
};
|
|
2109
|
-
if (this.historicalMigration) {
|
|
2110
|
-
data.historical_migration = true;
|
|
2111
|
-
}
|
|
2112
|
-
const payload = JSON.stringify(data);
|
|
2113
|
-
const url = `${this.host}/batch/`;
|
|
2114
|
-
const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
|
|
2115
|
-
const fetchOptions = {
|
|
2116
|
-
method: 'POST',
|
|
2117
|
-
headers: {
|
|
2118
|
-
...this.getCustomHeaders(),
|
|
2119
|
-
'Content-Type': 'application/json',
|
|
2120
|
-
...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
|
|
2121
|
-
},
|
|
2122
|
-
body: gzippedPayload || payload,
|
|
2123
|
-
};
|
|
2124
|
-
try {
|
|
2125
|
-
await this.fetchWithRetry(url, fetchOptions);
|
|
2126
|
-
}
|
|
2127
|
-
catch (err) {
|
|
2128
|
-
this._events.emit('error', err);
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
prepareMessage(type, _message, options) {
|
|
2132
|
-
const message = {
|
|
2133
|
-
..._message,
|
|
2134
|
-
type: type,
|
|
2135
|
-
library: this.getLibraryId(),
|
|
2136
|
-
library_version: this.getLibraryVersion(),
|
|
2137
|
-
timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
|
|
2138
|
-
uuid: options?.uuid ? options.uuid : uuidv7(),
|
|
2139
|
-
};
|
|
2140
|
-
const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
|
|
2141
|
-
if (addGeoipDisableProperty) {
|
|
2142
|
-
if (!message.properties) {
|
|
2143
|
-
message.properties = {};
|
|
2144
|
-
}
|
|
2145
|
-
message['properties']['$geoip_disable'] = true;
|
|
2146
|
-
}
|
|
2147
|
-
if (message.distinctId) {
|
|
2148
|
-
message.distinct_id = message.distinctId;
|
|
2149
|
-
delete message.distinctId;
|
|
2150
|
-
}
|
|
2151
|
-
return message;
|
|
2152
|
-
}
|
|
2153
|
-
clearFlushTimer() {
|
|
2154
|
-
if (this._flushTimer) {
|
|
2155
|
-
clearTimeout(this._flushTimer);
|
|
2156
|
-
this._flushTimer = undefined;
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
/**
|
|
2160
|
-
* Helper for flushing the queue in the background
|
|
2161
|
-
* Avoids unnecessary promise errors
|
|
2162
|
-
*/
|
|
2163
|
-
flushBackground() {
|
|
2164
|
-
void this.flush().catch(async (err) => {
|
|
2165
|
-
await logFlushError(err);
|
|
2166
|
-
});
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* Flushes the queue
|
|
2170
|
-
*
|
|
2171
|
-
* This function will return a promise that will resolve when the flush is complete,
|
|
2172
|
-
* or reject if there was an error (for example if the server or network is down).
|
|
2173
|
-
*
|
|
2174
|
-
* If there is already a flush in progress, this function will wait for that flush to complete.
|
|
2175
|
-
*
|
|
2176
|
-
* It's recommended to do error handling in the callback of the promise.
|
|
2177
|
-
*
|
|
2178
|
-
* @example
|
|
2179
|
-
* posthog.flush().then(() => {
|
|
2180
|
-
* console.log('Flush complete')
|
|
2181
|
-
* }).catch((err) => {
|
|
2182
|
-
* console.error('Flush failed', err)
|
|
2183
|
-
* })
|
|
2184
|
-
*
|
|
2185
|
-
*
|
|
2186
|
-
* @throws PostHogFetchHttpError
|
|
2187
|
-
* @throws PostHogFetchNetworkError
|
|
2188
|
-
* @throws Error
|
|
2189
|
-
*/
|
|
2190
|
-
async flush() {
|
|
2191
|
-
// Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
|
|
2192
|
-
// Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
|
|
2193
|
-
// Use a custom allSettled implementation to avoid issues with patching Promise on RN
|
|
2194
|
-
const nextFlushPromise = allSettled([this.flushPromise]).then(() => {
|
|
2195
|
-
return this._flush();
|
|
2196
|
-
});
|
|
2197
|
-
this.flushPromise = nextFlushPromise;
|
|
2198
|
-
void this.addPendingPromise(nextFlushPromise);
|
|
2199
|
-
allSettled([nextFlushPromise]).then(() => {
|
|
2200
|
-
// If there are no others waiting to flush, clear the promise.
|
|
2201
|
-
// We don't strictly need to do this, but it could make debugging easier
|
|
2202
|
-
if (this.flushPromise === nextFlushPromise) {
|
|
2203
|
-
this.flushPromise = null;
|
|
2204
|
-
}
|
|
2205
|
-
});
|
|
2206
|
-
return nextFlushPromise;
|
|
2207
|
-
}
|
|
2208
|
-
getCustomHeaders() {
|
|
2209
|
-
// Don't set the user agent if we're not on a browser. The latest spec allows
|
|
2210
|
-
// the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
|
|
2211
|
-
// and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
|
|
2212
|
-
// but browsers such as Chrome and Safari have not caught up.
|
|
2213
|
-
const customUserAgent = this.getCustomUserAgent();
|
|
2214
|
-
const headers = {};
|
|
2215
|
-
if (customUserAgent && customUserAgent !== '') {
|
|
2216
|
-
headers['User-Agent'] = customUserAgent;
|
|
2217
|
-
}
|
|
2218
|
-
return headers;
|
|
2219
|
-
}
|
|
2220
|
-
async _flush() {
|
|
2221
|
-
this.clearFlushTimer();
|
|
2222
|
-
await this._initPromise;
|
|
2223
|
-
let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
2224
|
-
if (!queue.length) {
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
const sentMessages = [];
|
|
2228
|
-
const originalQueueLength = queue.length;
|
|
2229
|
-
while (queue.length > 0 && sentMessages.length < originalQueueLength) {
|
|
2230
|
-
const batchItems = queue.slice(0, this.maxBatchSize);
|
|
2231
|
-
const batchMessages = batchItems.map((item) => item.message);
|
|
2232
|
-
const persistQueueChange = () => {
|
|
2233
|
-
const refreshedQueue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
2234
|
-
const newQueue = refreshedQueue.slice(batchItems.length);
|
|
2235
|
-
this.setPersistedProperty(PostHogPersistedProperty.Queue, newQueue);
|
|
2236
|
-
queue = newQueue;
|
|
2237
|
-
};
|
|
2238
|
-
const data = {
|
|
2239
|
-
api_key: this.apiKey,
|
|
2240
|
-
batch: batchMessages,
|
|
2241
|
-
sent_at: currentISOTime(),
|
|
2242
|
-
};
|
|
2243
|
-
if (this.historicalMigration) {
|
|
2244
|
-
data.historical_migration = true;
|
|
2245
|
-
}
|
|
2246
|
-
const payload = JSON.stringify(data);
|
|
2247
|
-
const url = `${this.host}/batch/`;
|
|
2248
|
-
const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
|
|
2249
|
-
const fetchOptions = {
|
|
2250
|
-
method: 'POST',
|
|
2251
|
-
headers: {
|
|
2252
|
-
...this.getCustomHeaders(),
|
|
2253
|
-
'Content-Type': 'application/json',
|
|
2254
|
-
...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
|
|
2255
|
-
},
|
|
2256
|
-
body: gzippedPayload || payload,
|
|
2257
|
-
};
|
|
2258
|
-
const retryOptions = {
|
|
2259
|
-
retryCheck: (err) => {
|
|
2260
|
-
// don't automatically retry on 413 errors, we want to reduce the batch size first
|
|
2261
|
-
if (isPostHogFetchContentTooLargeError(err)) {
|
|
2262
|
-
return false;
|
|
2263
|
-
}
|
|
2264
|
-
// otherwise, retry on network errors
|
|
2265
|
-
return isPostHogFetchError(err);
|
|
2266
|
-
},
|
|
2267
|
-
};
|
|
2268
|
-
try {
|
|
2269
|
-
await this.fetchWithRetry(url, fetchOptions, retryOptions);
|
|
2270
|
-
}
|
|
2271
|
-
catch (err) {
|
|
2272
|
-
if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
|
|
2273
|
-
// if we get a 413 error, we want to reduce the batch size and try again
|
|
2274
|
-
this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
|
|
2275
|
-
this.logMsgIfDebug(() => console.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`));
|
|
2276
|
-
// do not persist the queue change, we want to retry the same batch
|
|
2277
|
-
continue;
|
|
2278
|
-
}
|
|
2279
|
-
// depending on the error type, eg a malformed JSON or broken queue, it'll always return an error
|
|
2280
|
-
// 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
|
|
2281
|
-
if (!(err instanceof PostHogFetchNetworkError)) {
|
|
2282
|
-
persistQueueChange();
|
|
2283
|
-
}
|
|
2284
|
-
this._events.emit('error', err);
|
|
2285
|
-
throw err;
|
|
2286
|
-
}
|
|
2287
|
-
persistQueueChange();
|
|
2288
|
-
sentMessages.push(...batchMessages);
|
|
2289
|
-
}
|
|
2290
|
-
this._events.emit('flush', sentMessages);
|
|
2291
|
-
}
|
|
2292
|
-
async fetchWithRetry(url, options, retryOptions, requestTimeout) {
|
|
2293
|
-
var _a;
|
|
2294
|
-
(_a = AbortSignal).timeout ?? (_a.timeout = function timeout(ms) {
|
|
2295
|
-
const ctrl = new AbortController();
|
|
2296
|
-
setTimeout(() => ctrl.abort(), ms);
|
|
2297
|
-
return ctrl.signal;
|
|
2298
|
-
});
|
|
2299
|
-
const body = options.body ? options.body : '';
|
|
2300
|
-
let reqByteLength = -1;
|
|
2301
|
-
try {
|
|
2302
|
-
if (body instanceof Blob) {
|
|
2303
|
-
reqByteLength = body.size;
|
|
2304
|
-
}
|
|
2305
|
-
else {
|
|
2306
|
-
reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
catch {
|
|
2310
|
-
if (body instanceof Blob) {
|
|
2311
|
-
reqByteLength = body.size;
|
|
2312
|
-
}
|
|
2313
|
-
else {
|
|
2314
|
-
const encoded = new TextEncoder().encode(body);
|
|
2315
|
-
reqByteLength = encoded.length;
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
return await retriable(async () => {
|
|
2319
|
-
let res = null;
|
|
2320
|
-
try {
|
|
2321
|
-
res = await this.fetch(url, {
|
|
2322
|
-
signal: AbortSignal.timeout(requestTimeout ?? this.requestTimeout),
|
|
2323
|
-
...options,
|
|
2324
|
-
});
|
|
2325
|
-
}
|
|
2326
|
-
catch (e) {
|
|
2327
|
-
// fetch will only throw on network errors or on timeouts
|
|
2328
|
-
throw new PostHogFetchNetworkError(e);
|
|
2329
|
-
}
|
|
2330
|
-
// If we're in no-cors mode, we can't access the response status
|
|
2331
|
-
// We only throw on HTTP errors if we're not in no-cors mode
|
|
2332
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#no-cors
|
|
2333
|
-
const isNoCors = options.mode === 'no-cors';
|
|
2334
|
-
if (!isNoCors && (res.status < 200 || res.status >= 400)) {
|
|
2335
|
-
throw new PostHogFetchHttpError(res, reqByteLength);
|
|
2336
|
-
}
|
|
2337
|
-
return res;
|
|
2338
|
-
}, { ...this._retryOptions, ...retryOptions });
|
|
2339
|
-
}
|
|
2340
|
-
async _shutdown(shutdownTimeoutMs = 30000) {
|
|
2341
|
-
// A little tricky - we want to have a max shutdown time and enforce it, even if that means we have some
|
|
2342
|
-
// dangling promises. We'll keep track of the timeout and resolve/reject based on that.
|
|
2343
|
-
await this._initPromise;
|
|
2344
|
-
let hasTimedOut = false;
|
|
2345
|
-
this.clearFlushTimer();
|
|
2346
|
-
const doShutdown = async () => {
|
|
2347
|
-
try {
|
|
2348
|
-
await Promise.all(Object.values(this.pendingPromises));
|
|
2349
|
-
while (true) {
|
|
2350
|
-
const queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
|
|
2351
|
-
if (queue.length === 0) {
|
|
2352
|
-
break;
|
|
2353
|
-
}
|
|
2354
|
-
// flush again to make sure we send all events, some of which might've been added
|
|
2355
|
-
// while we were waiting for the pending promises to resolve
|
|
2356
|
-
// For example, see sendFeatureFlags in posthog-node/src/posthog-node.ts::capture
|
|
2357
|
-
await this.flush();
|
|
2358
|
-
if (hasTimedOut) {
|
|
2359
|
-
break;
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
catch (e) {
|
|
2364
|
-
if (!isPostHogFetchError(e)) {
|
|
2365
|
-
throw e;
|
|
2366
|
-
}
|
|
2367
|
-
await logFlushError(e);
|
|
2368
|
-
}
|
|
2369
|
-
};
|
|
2370
|
-
return Promise.race([
|
|
2371
|
-
new Promise((_, reject) => {
|
|
2372
|
-
safeSetTimeout(() => {
|
|
2373
|
-
this.logMsgIfDebug(() => console.error('Timed out while shutting down PostHog'));
|
|
2374
|
-
hasTimedOut = true;
|
|
2375
|
-
reject('Timeout while shutting down PostHog. Some events may not have been sent.');
|
|
2376
|
-
}, shutdownTimeoutMs);
|
|
2377
|
-
}),
|
|
2378
|
-
doShutdown(),
|
|
2379
|
-
]);
|
|
2380
|
-
}
|
|
2381
|
-
/**
|
|
2382
|
-
* Call shutdown() once before the node process exits, so ensure that all events have been sent and all promises
|
|
2383
|
-
* have resolved. Do not use this function if you intend to keep using this PostHog instance after calling it.
|
|
2384
|
-
* @param shutdownTimeoutMs
|
|
2385
|
-
*/
|
|
2386
|
-
async shutdown(shutdownTimeoutMs = 30000) {
|
|
2387
|
-
if (this.shutdownPromise) {
|
|
2388
|
-
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'));
|
|
2389
|
-
}
|
|
2390
|
-
else {
|
|
2391
|
-
this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
|
|
2392
|
-
this.shutdownPromise = null;
|
|
2393
|
-
});
|
|
2394
|
-
}
|
|
2395
|
-
return this.shutdownPromise;
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
|
|
2399
|
-
/**
|
|
2400
|
-
* A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
|
|
1093
|
+
/**
|
|
1094
|
+
* A lazy value that is only computed when needed. Inspired by C#'s Lazy<T> class.
|
|
2401
1095
|
*/
|
|
2402
1096
|
class Lazy {
|
|
2403
1097
|
constructor(factory) {
|
|
2404
1098
|
this.factory = factory;
|
|
2405
1099
|
}
|
|
2406
|
-
/**
|
|
2407
|
-
* Gets the value, initializing it if necessary.
|
|
2408
|
-
* Multiple concurrent calls will share the same initialization promise.
|
|
1100
|
+
/**
|
|
1101
|
+
* Gets the value, initializing it if necessary.
|
|
1102
|
+
* Multiple concurrent calls will share the same initialization promise.
|
|
2409
1103
|
*/
|
|
2410
1104
|
async getValue() {
|
|
2411
1105
|
if (this.value !== undefined) {
|
|
@@ -2425,15 +1119,15 @@ class Lazy {
|
|
|
2425
1119
|
}
|
|
2426
1120
|
return this.initializationPromise;
|
|
2427
1121
|
}
|
|
2428
|
-
/**
|
|
2429
|
-
* Returns true if the value has been initialized.
|
|
1122
|
+
/**
|
|
1123
|
+
* Returns true if the value has been initialized.
|
|
2430
1124
|
*/
|
|
2431
1125
|
isInitialized() {
|
|
2432
1126
|
return this.value !== undefined;
|
|
2433
1127
|
}
|
|
2434
|
-
/**
|
|
2435
|
-
* Returns a promise that resolves when the value is initialized.
|
|
2436
|
-
* If already initialized, resolves immediately.
|
|
1128
|
+
/**
|
|
1129
|
+
* Returns a promise that resolves when the value is initialized.
|
|
1130
|
+
* If already initialized, resolves immediately.
|
|
2437
1131
|
*/
|
|
2438
1132
|
async waitForInitialization() {
|
|
2439
1133
|
if (this.isInitialized()) {
|
|
@@ -2559,15 +1253,11 @@ class FeatureFlagsPoller {
|
|
|
2559
1253
|
if (!this.loadedSuccessfullyOnce) {
|
|
2560
1254
|
return response;
|
|
2561
1255
|
}
|
|
2562
|
-
|
|
2563
|
-
if (key === flag.key) {
|
|
2564
|
-
featureFlag = flag;
|
|
2565
|
-
break;
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
1256
|
+
featureFlag = this.featureFlagsByKey[key];
|
|
2568
1257
|
if (featureFlag !== undefined) {
|
|
2569
1258
|
try {
|
|
2570
|
-
|
|
1259
|
+
const result = await this.computeFlagAndPayloadLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
|
|
1260
|
+
response = result.value;
|
|
2571
1261
|
this.logMsgIfDebug(() => console.debug(`Successfully computed flag locally: ${key} -> ${response}`));
|
|
2572
1262
|
} catch (e) {
|
|
2573
1263
|
if (e instanceof InconclusiveMatchError) {
|
|
@@ -2579,37 +1269,19 @@ class FeatureFlagsPoller {
|
|
|
2579
1269
|
}
|
|
2580
1270
|
return response;
|
|
2581
1271
|
}
|
|
2582
|
-
async
|
|
2583
|
-
await this.loadFeatureFlags();
|
|
2584
|
-
let response = undefined;
|
|
2585
|
-
if (!this.loadedSuccessfullyOnce) {
|
|
2586
|
-
return undefined;
|
|
2587
|
-
}
|
|
2588
|
-
if (typeof matchValue == 'boolean') {
|
|
2589
|
-
response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue.toString()];
|
|
2590
|
-
} else if (typeof matchValue == 'string') {
|
|
2591
|
-
response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue];
|
|
2592
|
-
}
|
|
2593
|
-
// Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
|
|
2594
|
-
if (response === undefined || response === null) {
|
|
2595
|
-
return null;
|
|
2596
|
-
}
|
|
2597
|
-
try {
|
|
2598
|
-
return JSON.parse(response);
|
|
2599
|
-
} catch {
|
|
2600
|
-
return response;
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
|
|
1272
|
+
async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}, flagKeysToExplicitlyEvaluate) {
|
|
2604
1273
|
await this.loadFeatureFlags();
|
|
2605
1274
|
const response = {};
|
|
2606
1275
|
const payloads = {};
|
|
2607
1276
|
let fallbackToFlags = this.featureFlags.length == 0;
|
|
2608
|
-
|
|
1277
|
+
const flagsToEvaluate = flagKeysToExplicitlyEvaluate ? flagKeysToExplicitlyEvaluate.map(key => this.featureFlagsByKey[key]).filter(Boolean) : this.featureFlags;
|
|
1278
|
+
await Promise.all(flagsToEvaluate.map(async flag => {
|
|
2609
1279
|
try {
|
|
2610
|
-
const
|
|
1280
|
+
const {
|
|
1281
|
+
value: matchValue,
|
|
1282
|
+
payload: matchPayload
|
|
1283
|
+
} = await this.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties);
|
|
2611
1284
|
response[flag.key] = matchValue;
|
|
2612
|
-
const matchPayload = await this.computeFeatureFlagPayloadLocally(flag.key, matchValue);
|
|
2613
1285
|
if (matchPayload) {
|
|
2614
1286
|
payloads[flag.key] = matchPayload;
|
|
2615
1287
|
}
|
|
@@ -2628,7 +1300,30 @@ class FeatureFlagsPoller {
|
|
|
2628
1300
|
fallbackToFlags
|
|
2629
1301
|
};
|
|
2630
1302
|
}
|
|
2631
|
-
async
|
|
1303
|
+
async computeFlagAndPayloadLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}, matchValue) {
|
|
1304
|
+
// Always ensure flags are loaded for payload computation
|
|
1305
|
+
await this.loadFeatureFlags();
|
|
1306
|
+
if (!this.loadedSuccessfullyOnce) {
|
|
1307
|
+
return {
|
|
1308
|
+
value: false,
|
|
1309
|
+
payload: null
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
let flagValue;
|
|
1313
|
+
// If matchValue is provided, use it directly; otherwise evaluate the flag
|
|
1314
|
+
if (matchValue !== undefined) {
|
|
1315
|
+
flagValue = matchValue;
|
|
1316
|
+
} else {
|
|
1317
|
+
flagValue = await this.computeFlagValueLocally(flag, distinctId, groups, personProperties, groupProperties);
|
|
1318
|
+
}
|
|
1319
|
+
// Always compute payload based on the final flagValue (whether provided or computed)
|
|
1320
|
+
const payload = this.getFeatureFlagPayload(flag.key, flagValue);
|
|
1321
|
+
return {
|
|
1322
|
+
value: flagValue,
|
|
1323
|
+
payload
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
async computeFlagValueLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
|
|
2632
1327
|
if (flag.ensure_experience_continuity) {
|
|
2633
1328
|
throw new InconclusiveMatchError('Flag has experience continuity enabled');
|
|
2634
1329
|
}
|
|
@@ -2653,6 +1348,34 @@ class FeatureFlagsPoller {
|
|
|
2653
1348
|
return await this.matchFeatureFlagProperties(flag, distinctId, personProperties);
|
|
2654
1349
|
}
|
|
2655
1350
|
}
|
|
1351
|
+
getFeatureFlagPayload(key, flagValue) {
|
|
1352
|
+
let payload = null;
|
|
1353
|
+
if (flagValue !== false && flagValue !== null && flagValue !== undefined) {
|
|
1354
|
+
if (typeof flagValue == 'boolean') {
|
|
1355
|
+
payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue.toString()] || null;
|
|
1356
|
+
} else if (typeof flagValue == 'string') {
|
|
1357
|
+
payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue] || null;
|
|
1358
|
+
}
|
|
1359
|
+
if (payload !== null && payload !== undefined) {
|
|
1360
|
+
// If payload is already an object, return it directly
|
|
1361
|
+
if (typeof payload === 'object') {
|
|
1362
|
+
return payload;
|
|
1363
|
+
}
|
|
1364
|
+
// If payload is a string, try to parse it as JSON
|
|
1365
|
+
if (typeof payload === 'string') {
|
|
1366
|
+
try {
|
|
1367
|
+
return JSON.parse(payload);
|
|
1368
|
+
} catch {
|
|
1369
|
+
// If parsing fails, return the string as is
|
|
1370
|
+
return payload;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
// For other types, return as is
|
|
1374
|
+
return payload;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
2656
1379
|
async matchFeatureFlagProperties(flag, distinctId, properties) {
|
|
2657
1380
|
const flagFilters = flag.filters || {};
|
|
2658
1381
|
const flagConditions = flagFilters.groups || [];
|
|
@@ -2763,18 +1486,18 @@ class FeatureFlagsPoller {
|
|
|
2763
1486
|
await this._loadFeatureFlags();
|
|
2764
1487
|
}
|
|
2765
1488
|
}
|
|
2766
|
-
/**
|
|
2767
|
-
* Returns true if the feature flags poller has loaded successfully at least once and has more than 0 feature flags.
|
|
2768
|
-
* This is useful to check if local evaluation is ready before calling getFeatureFlag.
|
|
1489
|
+
/**
|
|
1490
|
+
* Returns true if the feature flags poller has loaded successfully at least once and has more than 0 feature flags.
|
|
1491
|
+
* This is useful to check if local evaluation is ready before calling getFeatureFlag.
|
|
2769
1492
|
*/
|
|
2770
1493
|
isLocalEvaluationReady() {
|
|
2771
1494
|
return (this.loadedSuccessfullyOnce ?? false) && (this.featureFlags?.length ?? 0) > 0;
|
|
2772
1495
|
}
|
|
2773
|
-
/**
|
|
2774
|
-
* If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
|
|
2775
|
-
* until a successful request is made, up to a maximum of 60 seconds.
|
|
2776
|
-
*
|
|
2777
|
-
* @returns The polling interval to use for the next request.
|
|
1496
|
+
/**
|
|
1497
|
+
* If a client is misconfigured with an invalid or improper API key, the polling interval is doubled each time
|
|
1498
|
+
* until a successful request is made, up to a maximum of 60 seconds.
|
|
1499
|
+
*
|
|
1500
|
+
* @returns The polling interval to use for the next request.
|
|
2778
1501
|
*/
|
|
2779
1502
|
getPollingInterval() {
|
|
2780
1503
|
if (!this.shouldBeginExponentialBackoff) {
|
|
@@ -3186,6 +1909,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3186
1909
|
personalApiKey: options.personalApiKey,
|
|
3187
1910
|
projectApiKey: apiKey,
|
|
3188
1911
|
timeout: options.requestTimeout ?? 10000,
|
|
1912
|
+
// 10 seconds
|
|
3189
1913
|
host: this.host,
|
|
3190
1914
|
fetch: options.fetch,
|
|
3191
1915
|
onError: err => {
|
|
@@ -3241,11 +1965,25 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3241
1965
|
disableGeoip,
|
|
3242
1966
|
uuid
|
|
3243
1967
|
} = props;
|
|
1968
|
+
// Run before_send if configured
|
|
1969
|
+
const eventMessage = this._runBeforeSend({
|
|
1970
|
+
distinctId,
|
|
1971
|
+
event,
|
|
1972
|
+
properties,
|
|
1973
|
+
groups,
|
|
1974
|
+
sendFeatureFlags,
|
|
1975
|
+
timestamp,
|
|
1976
|
+
disableGeoip,
|
|
1977
|
+
uuid
|
|
1978
|
+
});
|
|
1979
|
+
if (!eventMessage) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
3244
1982
|
const _capture = props => {
|
|
3245
|
-
super.captureStateless(distinctId, event, props, {
|
|
3246
|
-
timestamp,
|
|
3247
|
-
disableGeoip,
|
|
3248
|
-
uuid
|
|
1983
|
+
super.captureStateless(eventMessage.distinctId, eventMessage.event, props, {
|
|
1984
|
+
timestamp: eventMessage.timestamp,
|
|
1985
|
+
disableGeoip: eventMessage.disableGeoip,
|
|
1986
|
+
uuid: eventMessage.uuid
|
|
3249
1987
|
});
|
|
3250
1988
|
};
|
|
3251
1989
|
// :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
|
|
@@ -3280,8 +2018,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3280
2018
|
// No matter what - capture the event
|
|
3281
2019
|
_capture({
|
|
3282
2020
|
...additionalProperties,
|
|
3283
|
-
...properties,
|
|
3284
|
-
$groups: groups
|
|
2021
|
+
...(eventMessage.properties || {}),
|
|
2022
|
+
$groups: eventMessage.groups || groups
|
|
3285
2023
|
});
|
|
3286
2024
|
});
|
|
3287
2025
|
this.addPendingPromise(capturePromise);
|
|
@@ -3300,11 +2038,25 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3300
2038
|
disableGeoip,
|
|
3301
2039
|
uuid
|
|
3302
2040
|
} = props;
|
|
2041
|
+
// Run before_send if configured
|
|
2042
|
+
const eventMessage = this._runBeforeSend({
|
|
2043
|
+
distinctId,
|
|
2044
|
+
event,
|
|
2045
|
+
properties,
|
|
2046
|
+
groups,
|
|
2047
|
+
sendFeatureFlags,
|
|
2048
|
+
timestamp,
|
|
2049
|
+
disableGeoip,
|
|
2050
|
+
uuid
|
|
2051
|
+
});
|
|
2052
|
+
if (!eventMessage) {
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
3303
2055
|
const _capture = props => {
|
|
3304
|
-
return super.captureStatelessImmediate(distinctId, event, props, {
|
|
3305
|
-
timestamp,
|
|
3306
|
-
disableGeoip,
|
|
3307
|
-
uuid
|
|
2056
|
+
return super.captureStatelessImmediate(eventMessage.distinctId, eventMessage.event, props, {
|
|
2057
|
+
timestamp: eventMessage.timestamp,
|
|
2058
|
+
disableGeoip: eventMessage.disableGeoip,
|
|
2059
|
+
uuid: eventMessage.uuid
|
|
3308
2060
|
});
|
|
3309
2061
|
};
|
|
3310
2062
|
const capturePromise = Promise.resolve().then(async () => {
|
|
@@ -3338,8 +2090,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3338
2090
|
// No matter what - capture the event
|
|
3339
2091
|
_capture({
|
|
3340
2092
|
...additionalProperties,
|
|
3341
|
-
...properties,
|
|
3342
|
-
$groups: groups
|
|
2093
|
+
...(eventMessage.properties || {}),
|
|
2094
|
+
$groups: eventMessage.groups || groups
|
|
3343
2095
|
});
|
|
3344
2096
|
});
|
|
3345
2097
|
await capturePromise;
|
|
@@ -3491,19 +2243,17 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3491
2243
|
let response = undefined;
|
|
3492
2244
|
const localEvaluationEnabled = this.featureFlagsPoller !== undefined;
|
|
3493
2245
|
if (localEvaluationEnabled) {
|
|
3494
|
-
//
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
response = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue);
|
|
2246
|
+
// Ensure flags are loaded before checking for the specific flag
|
|
2247
|
+
await this.featureFlagsPoller?.loadFeatureFlags();
|
|
2248
|
+
const flag = this.featureFlagsPoller?.featureFlagsByKey[key];
|
|
2249
|
+
if (flag) {
|
|
2250
|
+
const result = await this.featureFlagsPoller?.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties, matchValue);
|
|
2251
|
+
if (result) {
|
|
2252
|
+
matchValue = result.value;
|
|
2253
|
+
response = result.payload;
|
|
2254
|
+
}
|
|
3504
2255
|
}
|
|
3505
2256
|
}
|
|
3506
|
-
//}
|
|
3507
2257
|
// set defaults
|
|
3508
2258
|
if (onlyEvaluateLocally == undefined) {
|
|
3509
2259
|
onlyEvaluateLocally = false;
|
|
@@ -3511,10 +2261,6 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3511
2261
|
if (sendFeatureFlagEvents == undefined) {
|
|
3512
2262
|
sendFeatureFlagEvents = true;
|
|
3513
2263
|
}
|
|
3514
|
-
// set defaults
|
|
3515
|
-
if (onlyEvaluateLocally == undefined) {
|
|
3516
|
-
onlyEvaluateLocally = false;
|
|
3517
|
-
}
|
|
3518
2264
|
const payloadWasLocallyEvaluated = response !== undefined;
|
|
3519
2265
|
if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
|
|
3520
2266
|
response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
@@ -3558,7 +2304,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3558
2304
|
async getAllFlagsAndPayloads(distinctId, options) {
|
|
3559
2305
|
const {
|
|
3560
2306
|
groups,
|
|
3561
|
-
disableGeoip
|
|
2307
|
+
disableGeoip,
|
|
2308
|
+
flagKeys
|
|
3562
2309
|
} = options || {};
|
|
3563
2310
|
let {
|
|
3564
2311
|
onlyEvaluateLocally,
|
|
@@ -3572,7 +2319,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3572
2319
|
if (onlyEvaluateLocally == undefined) {
|
|
3573
2320
|
onlyEvaluateLocally = false;
|
|
3574
2321
|
}
|
|
3575
|
-
const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties);
|
|
2322
|
+
const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties, flagKeys);
|
|
3576
2323
|
let featureFlags = {};
|
|
3577
2324
|
let featureFlagPayloads = {};
|
|
3578
2325
|
let fallbackToFlags = true;
|
|
@@ -3582,7 +2329,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3582
2329
|
fallbackToFlags = localEvaluationResult.fallbackToFlags;
|
|
3583
2330
|
}
|
|
3584
2331
|
if (fallbackToFlags && !onlyEvaluateLocally) {
|
|
3585
|
-
const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip);
|
|
2332
|
+
const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeys);
|
|
3586
2333
|
featureFlags = {
|
|
3587
2334
|
...featureFlags,
|
|
3588
2335
|
...(remoteEvaluationResult.flags || {})
|
|
@@ -3608,9 +2355,9 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3608
2355
|
disableGeoip
|
|
3609
2356
|
}, distinctId);
|
|
3610
2357
|
}
|
|
3611
|
-
/**
|
|
3612
|
-
* Reloads the feature flag definitions from the server for local evaluation.
|
|
3613
|
-
* This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
|
|
2358
|
+
/**
|
|
2359
|
+
* Reloads the feature flag definitions from the server for local evaluation.
|
|
2360
|
+
* This is useful to call if you want to ensure that the feature flags are up to date before calling getFeatureFlag.
|
|
3614
2361
|
*/
|
|
3615
2362
|
async reloadFeatureFlags() {
|
|
3616
2363
|
await this.featureFlagsPoller?.loadFeatureFlags(true);
|
|
@@ -3623,7 +2370,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3623
2370
|
if (!this.options.personalApiKey) {
|
|
3624
2371
|
return undefined;
|
|
3625
2372
|
}
|
|
3626
|
-
const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config
|
|
2373
|
+
const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config?token=${encodeURIComponent(this.apiKey)}`;
|
|
3627
2374
|
const options = {
|
|
3628
2375
|
method: 'GET',
|
|
3629
2376
|
headers: {
|
|
@@ -3682,6 +2429,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3682
2429
|
// Use properties directly from options if they exist
|
|
3683
2430
|
const finalPersonProperties = sendFeatureFlagsOptions?.personProperties || {};
|
|
3684
2431
|
const finalGroupProperties = sendFeatureFlagsOptions?.groupProperties || {};
|
|
2432
|
+
const flagKeys = sendFeatureFlagsOptions?.flagKeys;
|
|
3685
2433
|
// Check if we should only evaluate locally
|
|
3686
2434
|
const onlyEvaluateLocally = sendFeatureFlagsOptions?.onlyEvaluateLocally ?? false;
|
|
3687
2435
|
// If onlyEvaluateLocally is true, only use local evaluation
|
|
@@ -3696,7 +2444,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3696
2444
|
personProperties: finalPersonProperties,
|
|
3697
2445
|
groupProperties: finalGroupProperties,
|
|
3698
2446
|
disableGeoip,
|
|
3699
|
-
onlyEvaluateLocally: true
|
|
2447
|
+
onlyEvaluateLocally: true,
|
|
2448
|
+
flagKeys
|
|
3700
2449
|
});
|
|
3701
2450
|
} else {
|
|
3702
2451
|
// If onlyEvaluateLocally is true but we don't have local flags, return empty
|
|
@@ -3714,7 +2463,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3714
2463
|
personProperties: finalPersonProperties,
|
|
3715
2464
|
groupProperties: finalGroupProperties,
|
|
3716
2465
|
disableGeoip,
|
|
3717
|
-
onlyEvaluateLocally: true
|
|
2466
|
+
onlyEvaluateLocally: true,
|
|
2467
|
+
flagKeys
|
|
3718
2468
|
});
|
|
3719
2469
|
}
|
|
3720
2470
|
// Fall back to remote evaluation if local evaluation is not available
|
|
@@ -3754,6 +2504,26 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3754
2504
|
}, distinctId, additionalProperties);
|
|
3755
2505
|
return await this.captureImmediate(evtMsg);
|
|
3756
2506
|
}
|
|
2507
|
+
_runBeforeSend(eventMessage) {
|
|
2508
|
+
const beforeSend = this.options.before_send;
|
|
2509
|
+
if (!beforeSend) {
|
|
2510
|
+
return eventMessage;
|
|
2511
|
+
}
|
|
2512
|
+
const fns = Array.isArray(beforeSend) ? beforeSend : [beforeSend];
|
|
2513
|
+
let result = eventMessage;
|
|
2514
|
+
for (const fn of fns) {
|
|
2515
|
+
result = fn(result);
|
|
2516
|
+
if (!result) {
|
|
2517
|
+
this.logMsgIfDebug(() => console.info(`Event '${eventMessage.event}' was rejected in beforeSend function`));
|
|
2518
|
+
return null;
|
|
2519
|
+
}
|
|
2520
|
+
if (!result.properties || Object.keys(result.properties).length === 0) {
|
|
2521
|
+
const message = `Event '${result.event}' has no properties after beforeSend function, this is likely an error.`;
|
|
2522
|
+
this.logMsgIfDebug(() => console.warn(message));
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
return result;
|
|
2526
|
+
}
|
|
3757
2527
|
}
|
|
3758
2528
|
|
|
3759
2529
|
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
@@ -3853,8 +2623,8 @@ function node(getModule) {
|
|
|
3853
2623
|
return undefined;
|
|
3854
2624
|
};
|
|
3855
2625
|
}
|
|
3856
|
-
/**
|
|
3857
|
-
* Does this filename look like it's part of the app code?
|
|
2626
|
+
/**
|
|
2627
|
+
* Does this filename look like it's part of the app code?
|
|
3858
2628
|
*/
|
|
3859
2629
|
function filenameIsInApp(filename, isNative = false) {
|
|
3860
2630
|
const isInternal = isNative || filename &&
|