posthog-node
Version:
PostHog Node.js integration
1 lines • 316 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../../src/extensions/sentry-integration.ts","../../../posthog-core/src/vendor/uuidv7.ts","../../src/extensions/error-tracking/autocapture.ts","../../src/extensions/error-tracking/chunk-ids.ts","../../src/extensions/error-tracking/type-checking.ts","../../src/extensions/error-tracking/error-conversion.ts","../../src/extensions/error-tracking/index.ts","../../src/extensions/express.ts","../../../posthog-core/src/types.ts","../../../posthog-core/src/featureFlagUtils.ts","../../../posthog-core/src/utils.ts","../../../posthog-core/src/gzip.ts","../../../posthog-core/src/eventemitter.ts","../../../posthog-core/src/index.ts","../../src/extensions/feature-flags/lazy.ts","../../src/extensions/feature-flags/crypto-helpers.ts","../../src/extensions/feature-flags/crypto.ts","../../src/extensions/feature-flags/feature-flags.ts","../../src/storage-memory.ts","../../src/client.ts","../../src/extensions/error-tracking/stack-parser.ts","../../src/entrypoints/index.edge.ts"],"sourcesContent":["/**\n * @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.\n */\n/**\n * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.\n *\n * ### Usage\n *\n * Sentry.init({\n * dsn: 'https://example',\n * integrations: [\n * new PostHogSentryIntegration(posthog)\n * ]\n * })\n *\n * Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');\n *\n * @param {Object} [posthog] The posthog object\n * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry\n * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry\n * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)\n * @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])\n */\n\nimport { SeverityLevel } from './error-tracking/types'\nimport { type PostHogBackendClient } from '../client'\n\n// NOTE - we can't import from @sentry/types because it changes frequently and causes clashes\n// We only use a small subset of the types, so we can just define the integration overall and use any for the rest\n\n// import {\n// Event as _SentryEvent,\n// EventProcessor as _SentryEventProcessor,\n// Exception as _SentryException,\n// Hub as _SentryHub,\n// Primitive as _SentryPrimitive,\n// Integration as _SentryIntegration,\n// IntegrationClass as _SentryIntegrationClass,\n// } from '@sentry/types'\n\n// Uncomment the above and comment the below to get type checking for development\n\ntype _SentryEvent = any\ntype _SentryEventProcessor = any\ntype _SentryException = any\ntype _SentryHub = any\ntype _SentryPrimitive = any\n\ninterface _SentryIntegration {\n name: string\n processEvent(event: _SentryEvent): _SentryEvent\n}\n\ninterface _SentryIntegrationClass {\n name: string\n setupOnce(addGlobalEventProcessor: (callback: _SentryEventProcessor) => void, getCurrentHub: () => _SentryHub): void\n}\n\ninterface SentryExceptionProperties {\n $sentry_event_id?: string\n $sentry_exception?: { values?: _SentryException[] }\n $sentry_exception_message?: string\n $sentry_exception_type?: string\n $sentry_tags: { [key: string]: _SentryPrimitive }\n $sentry_url?: string\n}\n\nexport type SentryIntegrationOptions = {\n organization?: string\n projectId?: number\n prefix?: string\n severityAllowList?: SeverityLevel[] | '*'\n}\n\nconst NAME = 'posthog-node'\n\nexport function createEventProcessor(\n _posthog: PostHogBackendClient,\n { organization, projectId, prefix, severityAllowList = ['error'] }: SentryIntegrationOptions = {}\n): (event: _SentryEvent) => _SentryEvent {\n return (event) => {\n const shouldProcessLevel = severityAllowList === '*' || severityAllowList.includes(event.level)\n if (!shouldProcessLevel) {\n return event\n }\n if (!event.tags) {\n event.tags = {}\n }\n\n // Get the PostHog user ID from a specific tag, which users can set on their Sentry scope as they need.\n const userId = event.tags[PostHogSentryIntegration.POSTHOG_ID_TAG]\n if (userId === undefined) {\n // If we can't find a user ID, don't bother linking the event. We won't be able to send anything meaningful to PostHog without it.\n return event\n }\n\n const uiHost = _posthog.options.host ?? 'https://us.i.posthog.com'\n const personUrl = new URL(`/project/${_posthog.apiKey}/person/${userId}`, uiHost).toString()\n\n event.tags['PostHog Person URL'] = personUrl\n\n const exceptions: _SentryException[] = event.exception?.values || []\n\n const exceptionList = exceptions.map((exception) => ({\n ...exception,\n stacktrace: exception.stacktrace\n ? {\n ...exception.stacktrace,\n type: 'raw',\n frames: (exception.stacktrace.frames || []).map((frame: any) => {\n return { ...frame, platform: 'node:javascript' }\n }),\n }\n : undefined,\n }))\n\n const properties: SentryExceptionProperties & {\n // two properties added to match any exception auto-capture\n // added manually to avoid any dependency on the lazily loaded content\n $exception_message: any\n $exception_type: any\n $exception_list: any\n $exception_personURL: string\n $exception_level: SeverityLevel\n } = {\n // PostHog Exception Properties,\n $exception_message: exceptions[0]?.value || event.message,\n $exception_type: exceptions[0]?.type,\n $exception_personURL: personUrl,\n $exception_level: event.level,\n $exception_list: exceptionList,\n // Sentry Exception Properties\n $sentry_event_id: event.event_id,\n $sentry_exception: event.exception,\n $sentry_exception_message: exceptions[0]?.value || event.message,\n $sentry_exception_type: exceptions[0]?.type,\n $sentry_tags: event.tags,\n }\n\n if (organization && projectId) {\n properties['$sentry_url'] =\n (prefix || 'https://sentry.io/organizations/') +\n organization +\n '/issues/?project=' +\n projectId +\n '&query=' +\n event.event_id\n }\n\n _posthog.capture({ event: '$exception', distinctId: userId, properties })\n\n return event\n }\n}\n\n// V8 integration - function based\nexport function sentryIntegration(\n _posthog: PostHogBackendClient,\n options?: SentryIntegrationOptions\n): _SentryIntegration {\n const processor = createEventProcessor(_posthog, options)\n return {\n name: NAME,\n processEvent(event) {\n return processor(event)\n },\n }\n}\n\n// V7 integration - class based\nexport class PostHogSentryIntegration implements _SentryIntegrationClass {\n public readonly name = NAME\n\n public static readonly POSTHOG_ID_TAG = 'posthog_distinct_id'\n\n public setupOnce: (\n addGlobalEventProcessor: (callback: _SentryEventProcessor) => void,\n getCurrentHub: () => _SentryHub\n ) => void\n\n constructor(\n _posthog: PostHogBackendClient,\n organization?: string,\n prefix?: string,\n severityAllowList?: SeverityLevel[] | '*'\n ) {\n // setupOnce gets called by Sentry when it intializes the plugin\n this.name = NAME\n this.setupOnce = function (\n addGlobalEventProcessor: (callback: _SentryEventProcessor) => void,\n getCurrentHub: () => _SentryHub\n ) {\n const projectId = getCurrentHub()?.getClient()?.getDsn()?.projectId\n addGlobalEventProcessor(\n createEventProcessor(_posthog, {\n organization,\n projectId,\n prefix,\n severityAllowList,\n })\n )\n }\n }\n}\n","// vendor from: https://github.com/LiosK/uuidv7/blob/f30b7a7faff73afbce0b27a46c638310f96912ba/src/index.ts\n// https://github.com/LiosK/uuidv7#license\n\n/**\n * uuidv7: An experimental implementation of the proposed UUID Version 7\n *\n * @license Apache-2.0\n * @copyright 2021-2023 LiosK\n * @packageDocumentation\n */\n\nconst DIGITS = \"0123456789abcdef\";\n\n/** Represents a UUID as a 16-byte byte array. */\nexport class UUID {\n /** @param bytes - The 16-byte byte array representation. */\n private constructor(readonly bytes: Readonly<Uint8Array>) {}\n\n /**\n * Creates an object from the internal representation, a 16-byte byte array\n * containing the binary UUID representation in the big-endian byte order.\n *\n * This method does NOT shallow-copy the argument, and thus the created object\n * holds the reference to the underlying buffer.\n *\n * @throws TypeError if the length of the argument is not 16.\n */\n static ofInner(bytes: Readonly<Uint8Array>): UUID {\n if (bytes.length !== 16) {\n throw new TypeError(\"not 128-bit length\");\n } else {\n return new UUID(bytes);\n }\n }\n\n /**\n * Builds a byte array from UUIDv7 field values.\n *\n * @param unixTsMs - A 48-bit `unix_ts_ms` field value.\n * @param randA - A 12-bit `rand_a` field value.\n * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value.\n * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value.\n * @throws RangeError if any field value is out of the specified range.\n */\n static fromFieldsV7(\n unixTsMs: number,\n randA: number,\n randBHi: number,\n randBLo: number,\n ): UUID {\n if (\n !Number.isInteger(unixTsMs) ||\n !Number.isInteger(randA) ||\n !Number.isInteger(randBHi) ||\n !Number.isInteger(randBLo) ||\n unixTsMs < 0 ||\n randA < 0 ||\n randBHi < 0 ||\n randBLo < 0 ||\n unixTsMs > 0xffff_ffff_ffff ||\n randA > 0xfff ||\n randBHi > 0x3fff_ffff ||\n randBLo > 0xffff_ffff\n ) {\n throw new RangeError(\"invalid field value\");\n }\n\n const bytes = new Uint8Array(16);\n bytes[0] = unixTsMs / 2 ** 40;\n bytes[1] = unixTsMs / 2 ** 32;\n bytes[2] = unixTsMs / 2 ** 24;\n bytes[3] = unixTsMs / 2 ** 16;\n bytes[4] = unixTsMs / 2 ** 8;\n bytes[5] = unixTsMs;\n bytes[6] = 0x70 | (randA >>> 8);\n bytes[7] = randA;\n bytes[8] = 0x80 | (randBHi >>> 24);\n bytes[9] = randBHi >>> 16;\n bytes[10] = randBHi >>> 8;\n bytes[11] = randBHi;\n bytes[12] = randBLo >>> 24;\n bytes[13] = randBLo >>> 16;\n bytes[14] = randBLo >>> 8;\n bytes[15] = randBLo;\n return new UUID(bytes);\n }\n\n /**\n * Builds a byte array from a string representation.\n *\n * This method accepts the following formats:\n *\n * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b`\n * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b`\n * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}`\n * - RFC 4122 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b`\n *\n * Leading and trailing whitespaces represents an error.\n *\n * @throws SyntaxError if the argument could not parse as a valid UUID string.\n */\n static parse(uuid: string): UUID {\n let hex: string | undefined = undefined;\n switch (uuid.length) {\n case 32:\n hex = /^[0-9a-f]{32}$/i.exec(uuid)?.[0];\n break;\n case 36:\n hex =\n /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i\n .exec(uuid)\n ?.slice(1, 6)\n .join(\"\");\n break;\n case 38:\n hex =\n /^\\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\\}$/i\n .exec(uuid)\n ?.slice(1, 6)\n .join(\"\");\n break;\n case 45:\n hex =\n /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i\n .exec(uuid)\n ?.slice(1, 6)\n .join(\"\");\n break;\n default:\n break;\n }\n\n if (hex) {\n const inner = new Uint8Array(16);\n for (let i = 0; i < 16; i += 4) {\n const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);\n inner[i + 0] = n >>> 24;\n inner[i + 1] = n >>> 16;\n inner[i + 2] = n >>> 8;\n inner[i + 3] = n;\n }\n return new UUID(inner);\n } else {\n throw new SyntaxError(\"could not parse UUID string\");\n }\n }\n\n /**\n * @returns The 8-4-4-4-12 canonical hexadecimal string representation\n * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`).\n */\n toString(): string {\n let text = \"\";\n for (let i = 0; i < this.bytes.length; i++) {\n text += DIGITS.charAt(this.bytes[i] >>> 4);\n text += DIGITS.charAt(this.bytes[i] & 0xf);\n if (i === 3 || i === 5 || i === 7 || i === 9) {\n text += \"-\";\n }\n }\n return text;\n }\n\n /**\n * @returns The 32-digit hexadecimal representation without hyphens\n * (`0189dcd553117d408db09496a2eef37b`).\n */\n toHex(): string {\n let text = \"\";\n for (let i = 0; i < this.bytes.length; i++) {\n text += DIGITS.charAt(this.bytes[i] >>> 4);\n text += DIGITS.charAt(this.bytes[i] & 0xf);\n }\n return text;\n }\n\n /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */\n toJSON(): string {\n return this.toString();\n }\n\n /**\n * Reports the variant field value of the UUID or, if appropriate, \"NIL\" or\n * \"MAX\".\n *\n * For convenience, this method reports \"NIL\" or \"MAX\" if `this` represents\n * the Nil or Max UUID, although the Nil and Max UUIDs are technically\n * subsumed under the variants `0b0` and `0b111`, respectively.\n */\n getVariant():\n | \"VAR_0\"\n | \"VAR_10\"\n | \"VAR_110\"\n | \"VAR_RESERVED\"\n | \"NIL\"\n | \"MAX\" {\n const n = this.bytes[8] >>> 4;\n if (n < 0) {\n throw new Error(\"unreachable\");\n } else if (n <= 0b0111) {\n return this.bytes.every((e) => e === 0) ? \"NIL\" : \"VAR_0\";\n } else if (n <= 0b1011) {\n return \"VAR_10\";\n } else if (n <= 0b1101) {\n return \"VAR_110\";\n } else if (n <= 0b1111) {\n return this.bytes.every((e) => e === 0xff) ? \"MAX\" : \"VAR_RESERVED\";\n } else {\n throw new Error(\"unreachable\");\n }\n }\n\n /**\n * Returns the version field value of the UUID or `undefined` if the UUID does\n * not have the variant field value of `0b10`.\n */\n getVersion(): number | undefined {\n return this.getVariant() === \"VAR_10\" ? this.bytes[6] >>> 4 : undefined;\n }\n\n /** Creates an object from `this`. */\n clone(): UUID {\n return new UUID(this.bytes.slice(0));\n }\n\n /** Returns true if `this` is equivalent to `other`. */\n equals(other: UUID): boolean {\n return this.compareTo(other) === 0;\n }\n\n /**\n * Returns a negative integer, zero, or positive integer if `this` is less\n * than, equal to, or greater than `other`, respectively.\n */\n compareTo(other: UUID): number {\n for (let i = 0; i < 16; i++) {\n const diff = this.bytes[i] - other.bytes[i];\n if (diff !== 0) {\n return Math.sign(diff);\n }\n }\n return 0;\n }\n}\n\n/**\n * Encapsulates the monotonic counter state.\n *\n * This class provides APIs to utilize a separate counter state from that of the\n * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to\n * the default {@link generate} method, this class has {@link generateOrAbort}\n * that is useful to absolutely guarantee the monotonically increasing order of\n * generated UUIDs. See their respective documentation for details.\n */\nexport class V7Generator {\n private timestamp = 0;\n private counter = 0;\n\n /** The random number generator used by the generator. */\n private readonly random: { nextUint32(): number };\n\n /**\n * Creates a generator object with the default random number generator, or\n * with the specified one if passed as an argument. The specified random\n * number generator should be cryptographically strong and securely seeded.\n */\n constructor(randomNumberGenerator?: {\n /** Returns a 32-bit random unsigned integer. */\n nextUint32(): number;\n }) {\n this.random = randomNumberGenerator ?? getDefaultRandom();\n }\n\n /**\n * Generates a new UUIDv7 object from the current timestamp, or resets the\n * generator upon significant timestamp rollback.\n *\n * This method returns a monotonically increasing UUID by reusing the previous\n * timestamp even if the up-to-date timestamp is smaller than the immediately\n * preceding UUID's. However, when such a clock rollback is considered\n * significant (i.e., by more than ten seconds), this method resets the\n * generator and returns a new UUID based on the given timestamp, breaking the\n * increasing order of UUIDs.\n *\n * See {@link generateOrAbort} for the other mode of generation and\n * {@link generateOrResetCore} for the low-level primitive.\n */\n generate(): UUID {\n return this.generateOrResetCore(Date.now(), 10_000);\n }\n\n /**\n * Generates a new UUIDv7 object from the current timestamp, or returns\n * `undefined` upon significant timestamp rollback.\n *\n * This method returns a monotonically increasing UUID by reusing the previous\n * timestamp even if the up-to-date timestamp is smaller than the immediately\n * preceding UUID's. However, when such a clock rollback is considered\n * significant (i.e., by more than ten seconds), this method aborts and\n * returns `undefined` immediately.\n *\n * See {@link generate} for the other mode of generation and\n * {@link generateOrAbortCore} for the low-level primitive.\n */\n generateOrAbort(): UUID | undefined {\n return this.generateOrAbortCore(Date.now(), 10_000);\n }\n\n /**\n * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the\n * generator upon significant timestamp rollback.\n *\n * This method is equivalent to {@link generate} except that it takes a custom\n * timestamp and clock rollback allowance.\n *\n * @param rollbackAllowance - The amount of `unixTsMs` rollback that is\n * considered significant. A suggested value is `10_000` (milliseconds).\n * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.\n */\n generateOrResetCore(unixTsMs: number, rollbackAllowance: number): UUID {\n let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);\n if (value === undefined) {\n // reset state and resume\n this.timestamp = 0;\n value = this.generateOrAbortCore(unixTsMs, rollbackAllowance)!;\n }\n return value;\n }\n\n /**\n * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns\n * `undefined` upon significant timestamp rollback.\n *\n * This method is equivalent to {@link generateOrAbort} except that it takes a\n * custom timestamp and clock rollback allowance.\n *\n * @param rollbackAllowance - The amount of `unixTsMs` rollback that is\n * considered significant. A suggested value is `10_000` (milliseconds).\n * @throws RangeError if `unixTsMs` is not a 48-bit positive integer.\n */\n generateOrAbortCore(\n unixTsMs: number,\n rollbackAllowance: number,\n ): UUID | undefined {\n const MAX_COUNTER = 0x3ff_ffff_ffff;\n\n if (\n !Number.isInteger(unixTsMs) ||\n unixTsMs < 1 ||\n unixTsMs > 0xffff_ffff_ffff\n ) {\n throw new RangeError(\"`unixTsMs` must be a 48-bit positive integer\");\n } else if (rollbackAllowance < 0 || rollbackAllowance > 0xffff_ffff_ffff) {\n throw new RangeError(\"`rollbackAllowance` out of reasonable range\");\n }\n\n if (unixTsMs > this.timestamp) {\n this.timestamp = unixTsMs;\n this.resetCounter();\n } else if (unixTsMs + rollbackAllowance >= this.timestamp) {\n // go on with previous timestamp if new one is not much smaller\n this.counter++;\n if (this.counter > MAX_COUNTER) {\n // increment timestamp at counter overflow\n this.timestamp++;\n this.resetCounter();\n }\n } else {\n // abort if clock went backwards to unbearable extent\n return undefined;\n }\n\n return UUID.fromFieldsV7(\n this.timestamp,\n Math.trunc(this.counter / 2 ** 30),\n this.counter & (2 ** 30 - 1),\n this.random.nextUint32(),\n );\n }\n\n /** Initializes the counter at a 42-bit random integer. */\n private resetCounter(): void {\n this.counter =\n this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff);\n }\n\n /**\n * Generates a new UUIDv4 object utilizing the random number generator inside.\n *\n * @internal\n */\n generateV4(): UUID {\n const bytes = new Uint8Array(\n Uint32Array.of(\n this.random.nextUint32(),\n this.random.nextUint32(),\n this.random.nextUint32(),\n this.random.nextUint32(),\n ).buffer,\n );\n bytes[6] = 0x40 | (bytes[6] >>> 4);\n bytes[8] = 0x80 | (bytes[8] >>> 2);\n return UUID.ofInner(bytes);\n }\n}\n\n/** A global flag to force use of cryptographically strong RNG. */\n// declare const UUIDV7_DENY_WEAK_RNG: boolean;\n\n/** Returns the default random number generator available in the environment. */\nconst getDefaultRandom = (): { nextUint32(): number } => {\n// fix: crypto isn't available in react-native, always use Math.random\n\n// // detect Web Crypto API\n// if (\n// typeof crypto !== \"undefined\" &&\n// typeof crypto.getRandomValues !== \"undefined\"\n// ) {\n// return new BufferedCryptoRandom();\n// } else {\n// // fall back on Math.random() unless the flag is set to true\n// if (typeof UUIDV7_DENY_WEAK_RNG !== \"undefined\" && UUIDV7_DENY_WEAK_RNG) {\n// throw new Error(\"no cryptographically strong RNG available\");\n// }\n// return {\n// nextUint32: (): number =>\n// Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +\n// Math.trunc(Math.random() * 0x1_0000),\n// };\n// }\n return {\n nextUint32: (): number =>\n Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 +\n Math.trunc(Math.random() * 0x1_0000),\n };\n};\n\n// /**\n// * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small\n// * buffer by default to avoid both unbearable throughput decline in some\n// * environments and the waste of time and space for unused values.\n// */\n// class BufferedCryptoRandom {\n// private readonly buffer = new Uint32Array(8);\n// private cursor = 0xffff;\n// nextUint32(): number {\n// if (this.cursor >= this.buffer.length) {\n// crypto.getRandomValues(this.buffer);\n// this.cursor = 0;\n// }\n// return this.buffer[this.cursor++];\n// }\n// }\n\nlet defaultGenerator: V7Generator | undefined;\n\n/**\n * Generates a UUIDv7 string.\n *\n * @returns The 8-4-4-4-12 canonical hexadecimal string representation\n * (\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\").\n */\nexport const uuidv7 = (): string => uuidv7obj().toString();\n\n/** Generates a UUIDv7 object. */\nexport const uuidv7obj = (): UUID =>\n (defaultGenerator || (defaultGenerator = new V7Generator())).generate();\n\n/**\n * Generates a UUIDv4 string.\n *\n * @returns The 8-4-4-4-12 canonical hexadecimal string representation\n * (\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\").\n */\nexport const uuidv4 = (): string => uuidv4obj().toString();\n\n/** Generates a UUIDv4 object. */\nexport const uuidv4obj = (): UUID =>\n (defaultGenerator || (defaultGenerator = new V7Generator())).generateV4();\n","// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry\n// Licensed under the MIT License\n\nimport { EventHint } from './types'\n\ntype ErrorHandler = { _posthogErrorHandler: boolean } & ((error: Error) => void)\n\nfunction makeUncaughtExceptionHandler(\n captureFn: (exception: Error, hint: EventHint) => void,\n onFatalFn: () => void\n): ErrorHandler {\n let calledFatalError: boolean = false\n\n return Object.assign(\n (error: Error): void => {\n // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not\n // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust\n // exit behaviour of the SDK accordingly:\n // - If other listeners are attached, do not exit.\n // - If the only listener attached is ours, exit.\n const userProvidedListenersCount = global.process.listeners('uncaughtException').filter((listener) => {\n // There are 2 listeners we ignore:\n return (\n // as soon as we're using domains this listener is attached by node itself\n listener.name !== 'domainUncaughtExceptionClear' &&\n // the handler we register in this integration\n (listener as ErrorHandler)._posthogErrorHandler !== true\n )\n }).length\n\n const processWouldExit = userProvidedListenersCount === 0\n\n captureFn(error, {\n mechanism: {\n type: 'onuncaughtexception',\n handled: false,\n },\n })\n\n if (!calledFatalError && processWouldExit) {\n calledFatalError = true\n onFatalFn()\n }\n },\n { _posthogErrorHandler: true }\n )\n}\n\nexport function addUncaughtExceptionListener(\n captureFn: (exception: Error, hint: EventHint) => void,\n onFatalFn: () => void\n): void {\n global.process.on('uncaughtException', makeUncaughtExceptionHandler(captureFn, onFatalFn))\n}\n\nexport function addUnhandledRejectionListener(captureFn: (exception: unknown, hint: EventHint) => void): void {\n global.process.on('unhandledRejection', (reason: unknown) => {\n captureFn(reason, {\n mechanism: {\n type: 'onunhandledrejection',\n handled: false,\n },\n })\n })\n}\n","// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry\n// Licensed under the MIT License\n\nimport type { StackParser } from './types'\n\ntype StackString = string\ntype CachedResult = [string, string]\n\ntype ChunkIdMapType = Record<string, string>\n\nlet parsedStackResults: Record<StackString, CachedResult> | undefined\nlet lastKeysCount: number | undefined\nlet cachedFilenameChunkIds: ChunkIdMapType | undefined\n\nexport function getFilenameToChunkIdMap(stackParser: StackParser): ChunkIdMapType {\n const chunkIdMap = (globalThis as any)._posthogChunkIds as ChunkIdMapType | undefined\n if (!chunkIdMap) {\n console.error('No chunk id map found')\n return {}\n }\n\n const chunkIdKeys = Object.keys(chunkIdMap)\n\n if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) {\n return cachedFilenameChunkIds\n }\n\n lastKeysCount = chunkIdKeys.length\n\n cachedFilenameChunkIds = chunkIdKeys.reduce<Record<string, string>>((acc, stackKey) => {\n if (!parsedStackResults) {\n parsedStackResults = {}\n }\n\n const result = parsedStackResults[stackKey]\n\n if (result) {\n acc[result[0]] = result[1]\n } else {\n const parsedStack = stackParser(stackKey)\n\n for (let i = parsedStack.length - 1; i >= 0; i--) {\n const stackFrame = parsedStack[i]\n const filename = stackFrame?.filename\n const chunkId = chunkIdMap[stackKey]\n\n if (filename && chunkId) {\n acc[filename] = chunkId\n parsedStackResults[stackKey] = [filename, chunkId]\n break\n }\n }\n }\n\n return acc\n }, {})\n\n return cachedFilenameChunkIds\n}\n","// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry\n// Licensed under the MIT License\n\nimport { PolymorphicEvent } from './types'\n\nexport function isEvent(candidate: unknown): candidate is PolymorphicEvent {\n return typeof Event !== 'undefined' && isInstanceOf(candidate, Event)\n}\n\nexport function isPlainObject(candidate: unknown): candidate is Record<string, unknown> {\n return isBuiltin(candidate, 'Object')\n}\n\nexport function isError(candidate: unknown): candidate is Error {\n switch (Object.prototype.toString.call(candidate)) {\n case '[object Error]':\n case '[object Exception]':\n case '[object DOMException]':\n case '[object WebAssembly.Exception]':\n return true\n default:\n return isInstanceOf(candidate, Error)\n }\n}\n\nexport function isInstanceOf(candidate: unknown, base: any): boolean {\n try {\n return candidate instanceof base\n } catch {\n return false\n }\n}\n\nexport function isErrorEvent(event: unknown): boolean {\n return isBuiltin(event, 'ErrorEvent')\n}\n\nexport function isBuiltin(candidate: unknown, className: string): boolean {\n return Object.prototype.toString.call(candidate) === `[object ${className}]`\n}\n","// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry\n// Licensed under the MIT License\n\nimport { getFilenameToChunkIdMap } from './chunk-ids'\nimport { isError, isErrorEvent, isEvent, isPlainObject } from './type-checking'\nimport {\n ErrorProperties,\n EventHint,\n Exception,\n Mechanism,\n StackFrame,\n StackFrameModifierFn,\n StackParser,\n} from './types'\n\nexport async function propertiesFromUnknownInput(\n stackParser: StackParser,\n frameModifiers: StackFrameModifierFn[],\n input: unknown,\n hint?: EventHint\n): Promise<ErrorProperties> {\n const providedMechanism = hint && hint.mechanism\n const mechanism = providedMechanism || {\n handled: true,\n type: 'generic',\n }\n\n const errorList = getErrorList(mechanism, input, hint)\n const exceptionList = await Promise.all(\n errorList.map(async (error) => {\n const exception = await exceptionFromError(stackParser, frameModifiers, error)\n exception.value = exception.value || ''\n exception.type = exception.type || 'Error'\n exception.mechanism = mechanism\n return exception\n })\n )\n\n const properties = { $exception_list: exceptionList }\n return properties\n}\n\n// Flatten error causes into a list of errors\n// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause\nfunction getErrorList(mechanism: Mechanism, input: unknown, hint?: EventHint): Error[] {\n const error = getError(mechanism, input, hint)\n if (error.cause) {\n return [error, ...getErrorList(mechanism, error.cause, hint)]\n }\n return [error]\n}\n\nfunction getError(mechanism: Mechanism, exception: unknown, hint?: EventHint): Error {\n if (isError(exception)) {\n return exception\n }\n\n mechanism.synthetic = true\n\n if (isPlainObject(exception)) {\n const errorFromProp = getErrorPropertyFromObject(exception)\n if (errorFromProp) {\n return errorFromProp\n }\n\n const message = getMessageForObject(exception)\n const ex = hint?.syntheticException || new Error(message)\n ex.message = message\n\n return ex\n }\n\n // This handles when someone does: `throw \"something awesome\";`\n // We use synthesized Error here so we can extract a (rough) stack trace.\n const ex = hint?.syntheticException || new Error(exception as string)\n ex.message = `${exception}`\n\n return ex\n}\n\n/** If a plain object has a property that is an `Error`, return this error. */\nfunction getErrorPropertyFromObject(obj: Record<string, unknown>): Error | undefined {\n for (const prop in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, prop)) {\n const value = obj[prop]\n if (isError(value)) {\n return value\n }\n }\n }\n\n return undefined\n}\n\nfunction getMessageForObject(exception: Record<string, unknown>): string {\n if ('name' in exception && typeof exception.name === 'string') {\n let message = `'${exception.name}' captured as exception`\n\n if ('message' in exception && typeof exception.message === 'string') {\n message += ` with message '${exception.message}'`\n }\n\n return message\n } else if ('message' in exception && typeof exception.message === 'string') {\n return exception.message\n }\n\n const keys = extractExceptionKeysForMessage(exception)\n\n // Some ErrorEvent instances do not have an `error` property, which is why they are not handled before\n // We still want to try to get a decent message for these cases\n if (isErrorEvent(exception)) {\n return `Event \\`ErrorEvent\\` captured as exception with message \\`${exception.message}\\``\n }\n\n const className = getObjectClassName(exception)\n\n return `${className && className !== 'Object' ? `'${className}'` : 'Object'} captured as exception with keys: ${keys}`\n}\n\nfunction getObjectClassName(obj: unknown): string | undefined | void {\n try {\n const prototype: unknown | null = Object.getPrototypeOf(obj)\n return prototype ? prototype.constructor.name : undefined\n } catch (e) {\n // ignore errors here\n }\n}\n\n/**\n * Given any captured exception, extract its keys and create a sorted\n * and truncated list that will be used inside the event message.\n * eg. `Non-error exception captured with keys: foo, bar, baz`\n */\nfunction extractExceptionKeysForMessage(exception: Record<string, unknown>, maxLength: number = 40): string {\n const keys = Object.keys(convertToPlainObject(exception))\n keys.sort()\n\n const firstKey = keys[0]\n\n if (!firstKey) {\n return '[object has no keys]'\n }\n\n if (firstKey.length >= maxLength) {\n return truncate(firstKey, maxLength)\n }\n\n for (let includedKeys = keys.length; includedKeys > 0; includedKeys--) {\n const serialized = keys.slice(0, includedKeys).join(', ')\n if (serialized.length > maxLength) {\n continue\n }\n if (includedKeys === keys.length) {\n return serialized\n }\n return truncate(serialized, maxLength)\n }\n\n return ''\n}\n\nfunction truncate(str: string, max: number = 0): string {\n if (typeof str !== 'string' || max === 0) {\n return str\n }\n return str.length <= max ? str : `${str.slice(0, max)}...`\n}\n\n/**\n * Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their\n * non-enumerable properties attached.\n *\n * @param value Initial source that we have to transform in order for it to be usable by the serializer\n * @returns An Event or Error turned into an object - or the value argument itself, when value is neither an Event nor\n * an Error.\n */\nfunction convertToPlainObject<V>(value: V):\n | {\n [ownProps: string]: unknown\n type: string\n target: string\n currentTarget: string\n detail?: unknown\n }\n | {\n [ownProps: string]: unknown\n message: string\n name: string\n stack?: string\n }\n | V {\n if (isError(value)) {\n return {\n message: value.message,\n name: value.name,\n stack: value.stack,\n ...getOwnProperties(value),\n }\n } else if (isEvent(value)) {\n const newObj: {\n [ownProps: string]: unknown\n type: string\n target: string\n currentTarget: string\n detail?: unknown\n } = {\n type: value.type,\n target: serializeEventTarget(value.target),\n currentTarget: serializeEventTarget(value.currentTarget),\n ...getOwnProperties(value),\n }\n\n // TODO: figure out why this fails typing (I think CustomEvent is only supported in Node 19 onwards)\n // if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {\n // newObj.detail = (value as unknown as CustomEvent).detail\n // }\n\n return newObj\n } else {\n return value\n }\n}\n\n/** Filters out all but an object's own properties */\nfunction getOwnProperties(obj: unknown): { [key: string]: unknown } {\n if (typeof obj === 'object' && obj !== null) {\n const extractedProps: { [key: string]: unknown } = {}\n for (const property in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, property)) {\n extractedProps[property] = (obj as Record<string, unknown>)[property]\n }\n }\n return extractedProps\n } else {\n return {}\n }\n}\n\n/** Creates a string representation of the target of an `Event` object */\nfunction serializeEventTarget(target: unknown): string {\n try {\n return Object.prototype.toString.call(target)\n } catch (_oO) {\n return '<unknown>'\n }\n}\n\n/**\n * Extracts stack frames from the error and builds an Exception\n */\nasync function exceptionFromError(\n stackParser: StackParser,\n frameModifiers: StackFrameModifierFn[],\n error: Error\n): Promise<Exception> {\n const exception: Exception = {\n type: error.name || error.constructor.name,\n value: error.message,\n }\n\n let frames = parseStackFrames(stackParser, error)\n\n for (const modifier of frameModifiers) {\n frames = await modifier(frames)\n }\n\n if (frames.length) {\n exception.stacktrace = { frames, type: 'raw' }\n }\n\n return exception\n}\n\n/**\n * Extracts stack frames from the error.stack string\n */\nfunction parseStackFrames(stackParser: StackParser, error: Error): StackFrame[] {\n return applyChunkIds(stackParser(error.stack || '', 1), stackParser)\n}\n\nexport function applyChunkIds(frames: StackFrame[], parser: StackParser): StackFrame[] {\n const filenameChunkIdMap = getFilenameToChunkIdMap(parser)\n frames.forEach((frame) => {\n if (frame.filename) {\n frame.chunk_id = filenameChunkIdMap[frame.filename]\n }\n })\n\n return frames\n}\n","import { EventHint, StackFrameModifierFn, StackParser } from './types'\nimport { addUncaughtExceptionListener, addUnhandledRejectionListener } from './autocapture'\nimport { PostHogBackendClient } from '../../client'\nimport { uuidv7 } from 'posthog-core/src/vendor/uuidv7'\nimport { propertiesFromUnknownInput } from './error-conversion'\nimport { EventMessage, PostHogOptions } from '../../types'\n\nconst SHUTDOWN_TIMEOUT = 2000\n\nexport default class ErrorTracking {\n private client: PostHogBackendClient\n private _exceptionAutocaptureEnabled: boolean\n\n static stackParser: StackParser\n static frameModifiers: StackFrameModifierFn[]\n\n static async captureException(\n client: PostHogBackendClient,\n error: unknown,\n hint: EventHint,\n distinctId?: string,\n additionalProperties?: Record<string | number, any>\n ): Promise<void> {\n const properties: EventMessage['properties'] = { ...additionalProperties }\n\n // Given stateless nature of Node SDK we capture exceptions using personless processing when no\n // user can be determined because a distinct_id is not provided e.g. exception autocapture\n if (!distinctId) {\n properties.$process_person_profile = false\n }\n\n const exceptionProperties = await propertiesFromUnknownInput(this.stackParser, this.frameModifiers, error, hint)\n\n client.capture({\n event: '$exception',\n distinctId: distinctId || uuidv7(),\n properties: {\n ...exceptionProperties,\n ...properties,\n },\n })\n }\n\n constructor(client: PostHogBackendClient, options: PostHogOptions) {\n this.client = client\n this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false\n\n this.startAutocaptureIfEnabled()\n }\n\n private startAutocaptureIfEnabled(): void {\n if (this.isEnabled()) {\n addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this))\n addUnhandledRejectionListener(this.onException.bind(this))\n }\n }\n\n private onException(exception: unknown, hint: EventHint): void {\n ErrorTracking.captureException(this.client, exception, hint)\n }\n\n private async onFatalError(): Promise<void> {\n await this.client.shutdown(SHUTDOWN_TIMEOUT)\n }\n\n isEnabled(): boolean {\n return !this.client.isDisabled && this._exceptionAutocaptureEnabled\n }\n}\n","import type * as http from 'node:http'\nimport { uuidv7 } from 'posthog-core/src/vendor/uuidv7'\nimport ErrorTracking from './error-tracking'\nimport { PostHogBackendClient } from '../client'\n\ntype ExpressMiddleware = (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => void\n\ntype ExpressErrorMiddleware = (\n error: MiddlewareError,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n next: (error: MiddlewareError) => void\n) => void\n\ninterface MiddlewareError extends Error {\n status?: number | string\n statusCode?: number | string\n status_code?: number | string\n output?: {\n statusCode?: number | string\n }\n}\n\nexport function setupExpressErrorHandler(\n _posthog: PostHogBackendClient,\n app: {\n use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown\n }\n): void {\n app.use((error: MiddlewareError, _, __, next: (error: MiddlewareError) => void): void => {\n const hint = { mechanism: { type: 'middleware', handled: false } }\n // Given stateless nature of Node SDK we capture exceptions using personless processing\n // when no user can be determined e.g. in the case of exception autocapture\n ErrorTracking.captureException(_posthog, error, hint, uuidv7(), { $process_person_profile: false })\n next(error)\n })\n}\n","export type PostHogCoreOptions = {\n /** PostHog API host, usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' */\n host?: string\n /** The number of events to queue before sending to PostHog (flushing) */\n flushAt?: number\n /** The interval in milliseconds between periodic flushes */\n flushInterval?: number\n /** The maximum number of queued messages to be flushed as part of a single batch (must be higher than `flushAt`) */\n maxBatchSize?: number\n /** The maximum number of cached messages either in memory or on the local storage.\n * Defaults to 1000, (must be higher than `flushAt`)\n */\n maxQueueSize?: number\n /** If set to true the SDK is essentially disabled (useful for local environments where you don't want to track anything) */\n disabled?: boolean\n /** If set to false the SDK will not track until the `optIn` function is called. */\n defaultOptIn?: boolean\n /** Whether to track that `getFeatureFlag` was called (used by Experiments) */\n sendFeatureFlagEvent?: boolean\n /** Whether to load feature flags when initialized or not */\n preloadFeatureFlags?: boolean\n /**\n * Whether to load remote config when initialized or not\n * Experimental support\n * Default: false - Remote config is loaded by default\n */\n disableRemoteConfig?: boolean\n /**\n * Whether to load surveys when initialized or not\n * Experimental support\n * Default: false - Surveys are loaded by default, but requires the `PostHogSurveyProvider` to be used\n */\n disableSurveys?: boolean\n /** Option to bootstrap the library with given distinctId and feature flags */\n bootstrap?: {\n distinctId?: string\n isIdentifiedId?: boolean\n featureFlags?: Record<string, FeatureFlagValue>\n featureFlagPayloads?: Record<string, JsonType>\n }\n /** How many times we will retry HTTP requests. Defaults to 3. */\n fetchRetryCount?: number\n /** The delay between HTTP request retries, Defaults to 3 seconds. */\n fetchRetryDelay?: number\n /** Timeout in milliseconds for any calls. Defaults to 10 seconds. */\n requestTimeout?: number\n /** Timeout in milliseconds for feature flag calls. Defaults to 10 seconds for stateful clients, and 3 seconds for stateless. */\n featureFlagsRequestTimeoutMs?: number\n /** Timeout in milliseconds for remote config calls. Defaults to 3 seconds. */\n remoteConfigRequestTimeoutMs?: number\n /** For Session Analysis how long before we expire a session (defaults to 30 mins) */\n sessionExpirationTimeSeconds?: number\n /** Whether to disable GZIP compression */\n disableCompression?: boolean\n disableGeoip?: boolean\n /** Special flag to indicate ingested data is for a historical migration. */\n historicalMigration?: boolean\n}\n\nexport enum PostHogPersistedProperty {\n AnonymousId = 'anonymous_id',\n DistinctId = 'distinct_id',\n Props = 'props',\n FeatureFlagDetails = 'feature_flag_details',\n FeatureFlags = 'feature_flags',\n FeatureFlagPayloads = 'feature_flag_payloads',\n BootstrapFeatureFlagDetails = 'bootstrap_feature_flag_details',\n BootstrapFeatureFlags = 'bootstrap_feature_flags',\n BootstrapFeatureFlagPayloads = 'bootstrap_feature_flag_payloads',\n OverrideFeatureFlags = 'override_feature_flags',\n Queue = 'queue',\n OptedOut = 'opted_out',\n SessionId = 'session_id',\n SessionStartTimestamp = 'session_start_timestamp',\n SessionLastTimestamp = 'session_timestamp',\n PersonProperties = 'person_properties',\n GroupProperties = 'group_properties',\n InstalledAppBuild = 'installed_app_build', // only used by posthog-react-native\n InstalledAppVersion = 'installed_app_version', // only used by posthog-react-native\n SessionReplay = 'session_replay', // only used by posthog-react-native\n SurveyLastSeenDate = 'survey_last_seen_date', // only used by posthog-react-native\n SurveysSeen = 'surveys_seen', // only used by posthog-react-native\n Surveys = 'surveys', // only used by posthog-react-native\n RemoteConfig = 'remote_config',\n FlagsEndpointWasHit = 'flags_endpoint_was_hit', // only used by posthog-react-native\n}\n\nexport type PostHogFetchOptions = {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH'\n mode?: 'no-cors'\n credentials?: 'omit'\n headers: { [key: string]: string }\n body?: string | Blob\n signal?: AbortSignal\n}\n\n// Check out posthog-js for these additional options and try to keep them in sync\nexport type PostHogCaptureOptions = {\n /** If provided overrides the auto-generated event ID */\n uuid?: string\n /** If provided overrides the auto-generated timestamp */\n timestamp?: Date\n disableGeoip?: boolean\n}\n\nexport type PostHogFetchResponse = {\n status: number\n text: () => Promise<string>\n json: () => Promise<any>\n}\n\nexport type PostHogQueueItem = {\n message: any\n callback?: (err: any) => void\n}\n\nexport type PostHogEventProperties = {\n [key: string]: JsonType\n}\n\nexport type PostHogGroupProperties = {\n [type: string]: string | number\n}\n\nexport type PostHogAutocaptureElement = {\n $el_text?: string\n tag_name: string\n href?: string\n nth_child?: number\n nth_of_type?: number\n order?: number\n} & PostHogEventProperties\n// Any key prefixed with `attr__` can be added\n\nexport enum Compression {\n GZipJS = 'gzip-js',\n Base64 = 'base64',\n}\n\nexport type PostHogRemoteConfig = {\n sessionRecording?:\n | boolean\n | {\n [key: string]: JsonType\n }\n\n /**\n * Supported compression algorithms\n */\n supportedCompression?: Compression[]\n\n /**\n * Whether surveys are enabled\n */\n surveys?: boolean | Survey[]\n\n /**\n * Indicates if the team has any flags enabled (if not we don't need to load them)\n */\n hasFeatureFlags?: boolean\n}\n\nexport type FeatureFlagValue = string | boolean\n\nexport type PostHogFlagsResponse = Omit<PostHogRemoteConfig, 'surveys' | 'hasFeatureFlags'> & {\n featureFlags: {\n [key: string]: FeatureFlagValue\n }\n featureFlagPayloads: {\n [key: string]: JsonType\n }\n flags: {\n [key: string]: FeatureFlagDetail\n }\n errorsWhileComputingFlags: boolean\n sessionRecording?:\n | boolean\n | {\n [key: string]: JsonType\n }\n quotaLimited?: string[]\n requestId?: string\n}\n\nexport type PostHogFeatureFlagsResponse = PartialWithRequired<\n PostHogFlagsResponse,\n 'flags' | 'featureFlags' | 'featureFlagPayloads' | 'requestId'\n>\n\n/**\n * Creates a type with all properties of T, but makes only K properties required while the rest remain optional.\n *\n * @template T - The base type containing all properties\n * @template K - Union type of keys from T that should be required\n *\n * @example\n * interface User {\n * id: number;\n * name: string;\n * email?: string;\n * age?: number;\n * }\n *\n * // Makes 'id' and 'name' required, but 'email' and 'age' optional\n * type RequiredUser = PartialWithRequired<User, 'id' | 'name'>;\n *\n * const user: RequiredUser = {\n * id: 1, // Must be provided\n * name: \"John\" // Must be provided\n * // email and age are optional\n * };\n */\nexport type PartialWithRequired<T, K extends keyof T> = {\n [P in K]: T[P] // Required fields\n} & {\n [P in Exclude<keyof T, K>]?: T[P] // Optional fields\n}\n\n/**\n * These are the fields we care about from PostHogFlagsResponse for feature flags.\n */\nexport type PostHogFeatureFlagDetails = PartialWithRequired<\n PostHogFlagsResponse,\n 'flags' | 'featureFlags' | 'featureFlagPayloads' | 'requestId'\n>\n\n/**\n * Models the response from the v1 `/flags` endpoint.\n */\nexport type PostHogV1FlagsResponse = Omit<PostHogFlagsResponse, 'flags'>\n\n/**\n * Models the response from the v2 `/flags` endpoint.\n */\nexport type PostHogV2FlagsResponse = Omit<PostHogFlagsResponse, 'featureFlags' | 'featureFlagPayloads'>\n\n/**\n * The format of the flags object in persisted storage\n *\n * When we pull flags from persistence, we can normalize them to PostHogFeatureFlagDetails\n * so that we can support v1 and v2 of the API.\n */\nexport type PostHogFlagsStorageFormat = Pick<PostHogFeatureFlagDetails, 'flags'>\n\n/**\n * Models legacy flags and payloads return type for many public methods.\n */\nexport type PostHogFlagsAndPayloadsResponse = Partial<\n Pick<PostHogFlagsResponse, 'featureFlags' | 'featureFlagPayloads'>\n>\n\nexport type JsonType = string |