posthog-node 4.16.0 → 4.17.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/edge/index.cjs.js +3919 -0
  3. package/lib/edge/index.cjs.js.map +1 -0
  4. package/lib/edge/index.esm.js +3893 -0
  5. package/lib/edge/index.esm.js.map +1 -0
  6. package/lib/index.d.ts +921 -859
  7. package/lib/{index.cjs.js → node/index.cjs.js} +3575 -3594
  8. package/lib/node/index.cjs.js.map +1 -0
  9. package/lib/{index.esm.js → node/index.esm.js} +3576 -3592
  10. package/lib/node/index.esm.js.map +1 -0
  11. package/package.json +31 -4
  12. package/src/{posthog-node.ts → client.ts} +10 -28
  13. package/src/entrypoints/index.edge.ts +15 -0
  14. package/src/entrypoints/index.node.ts +17 -0
  15. package/src/exports.ts +3 -0
  16. package/src/extensions/error-tracking/autocapture.ts +1 -1
  17. package/src/extensions/error-tracking/{context-lines.ts → context-lines.node.ts} +64 -97
  18. package/src/extensions/error-tracking/error-conversion.ts +22 -5
  19. package/src/extensions/error-tracking/get-module.node.ts +57 -0
  20. package/src/{error-tracking.ts → extensions/error-tracking/index.ts} +12 -10
  21. package/src/extensions/error-tracking/{stack-trace.ts → stack-parser.ts} +7 -64
  22. package/src/extensions/error-tracking/types.ts +4 -0
  23. package/src/extensions/express.ts +3 -3
  24. package/src/{crypto-helpers.ts → extensions/feature-flags/crypto-helpers.ts} +1 -1
  25. package/src/{feature-flags.ts → extensions/feature-flags/feature-flags.ts} +7 -6
  26. package/src/extensions/sentry-integration.ts +13 -5
  27. package/src/fetch.ts +2 -2
  28. package/src/storage-memory.ts +13 -0
  29. package/src/types.ts +19 -1
  30. package/test/crypto.spec.ts +2 -2
  31. package/test/extensions/error-conversion.spec.ts +2 -2
  32. package/test/extensions/sentry-integration.spec.ts +1 -2
  33. package/test/feature-flags.decide.spec.ts +2 -1
  34. package/test/feature-flags.spec.ts +7 -4
  35. package/test/lazy.spec.ts +1 -1
  36. package/test/posthog-node.spec.ts +3 -3
  37. package/tsconfig.json +1 -0
  38. package/index.ts +0 -3
  39. package/lib/index.cjs.js.map +0 -1
  40. package/lib/index.esm.js.map +0 -1
  41. package/lib/posthog-core/src/eventemitter.d.ts +0 -8
  42. package/lib/posthog-core/src/featureFlagUtils.d.ts +0 -34
  43. package/lib/posthog-core/src/index.d.ts +0 -259
  44. package/lib/posthog-core/src/lz-string.d.ts +0 -8
  45. package/lib/posthog-core/src/storage-memory.d.ts +0 -6
  46. package/lib/posthog-core/src/types.d.ts +0 -422
  47. package/lib/posthog-core/src/utils.d.ts +0 -20
  48. package/lib/posthog-core/src/vendor/uuidv7.d.ts +0 -179
  49. package/lib/posthog-node/index.d.ts +0 -3
  50. package/lib/posthog-node/src/crypto-helpers.d.ts +0 -3
  51. package/lib/posthog-node/src/crypto.d.ts +0 -2
  52. package/lib/posthog-node/src/error-tracking.d.ts +0 -12
  53. package/lib/posthog-node/src/extensions/error-tracking/autocapture.d.ts +0 -3
  54. package/lib/posthog-node/src/extensions/error-tracking/context-lines.d.ts +0 -6
  55. package/lib/posthog-node/src/extensions/error-tracking/error-conversion.d.ts +0 -2
  56. package/lib/posthog-node/src/extensions/error-tracking/reduceable-cache.d.ts +0 -12
  57. package/lib/posthog-node/src/extensions/error-tracking/stack-trace.d.ts +0 -15
  58. package/lib/posthog-node/src/extensions/error-tracking/type-checking.d.ts +0 -7
  59. package/lib/posthog-node/src/extensions/error-tracking/types.d.ts +0 -57
  60. package/lib/posthog-node/src/extensions/express.d.ts +0 -17
  61. package/lib/posthog-node/src/extensions/sentry-integration.d.ts +0 -51
  62. package/lib/posthog-node/src/feature-flags.d.ts +0 -84
  63. package/lib/posthog-node/src/fetch.d.ts +0 -11
  64. package/lib/posthog-node/src/lazy.d.ts +0 -23
  65. package/lib/posthog-node/src/posthog-node.d.ts +0 -98
  66. package/lib/posthog-node/src/types.d.ts +0 -229
  67. package/lib/posthog-node/test/test-utils.d.ts +0 -18
  68. /package/src/{crypto.ts → extensions/feature-flags/crypto.ts} +0 -0
  69. /package/src/{lazy.ts → extensions/feature-flags/lazy.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posthog-node",
3
- "version": "4.16.0",
3
+ "version": "4.17.0",
4
4
  "description": "PostHog Node.js integration",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,8 +19,8 @@
19
19
  "email": "hey@posthog.com",
20
20
  "url": "https://posthog.com"
21
21
  },
22
- "main": "lib/index.cjs.js",
23
- "module": "lib/index.esm.js",
22
+ "main": "lib/node/index.cjs.js",
23
+ "module": "lib/node/index.esm.js",
24
24
  "types": "lib/index.d.ts",
25
25
  "dependencies": {
26
26
  "axios": "^1.8.2"
@@ -35,5 +35,32 @@
35
35
  "stats",
36
36
  "analysis",
37
37
  "funnels"
38
- ]
38
+ ],
39
+ "exports": {
40
+ ".": {
41
+ "types": "./lib/index.d.ts",
42
+ "edge": {
43
+ "import": "./lib/edge/index.esm.js",
44
+ "require": "./lib/edge/index.cjs.js",
45
+ "default": "./lib/edge/index.js"
46
+ },
47
+ "node": {
48
+ "import": "./lib/node/index.esm.js",
49
+ "require": "./lib/node/index.cjs.js",
50
+ "default": "./lib/node/index.js"
51
+ },
52
+ "edge-light": {
53
+ "import": "./lib/edge/index.esm.js",
54
+ "require": "./lib/edge/index.cjs.js",
55
+ "default": "./lib/edge/index.js"
56
+ },
57
+ "workerd": {
58
+ "import": "./lib/edge/index.esm.js",
59
+ "require": "./lib/edge/index.cjs.js",
60
+ "default": "./lib/edge/index.js"
61
+ },
62
+ "import": "./lib/node/index.esm.js",
63
+ "require": "./lib/node/index.cjs.js"
64
+ }
65
+ }
39
66
  }
@@ -2,43 +2,29 @@ import { version } from '../package.json'
2
2
 
3
3
  import {
4
4
  JsonType,
5
- PostHogCoreOptions,
6
5
  PostHogCoreStateless,
7
6
  PostHogDecideResponse,
8
7
  PostHogFetchOptions,
9
8
  PostHogFetchResponse,
10
9
  PostHogFlagsAndPayloadsResponse,
11
10
  PostHogPersistedProperty,
12
- } from '../../posthog-core/src'
13
- import { PostHogMemoryStorage } from '../../posthog-core/src/storage-memory'
14
- import { EventMessage, GroupIdentifyMessage, IdentifyMessage, PostHogNodeV1 } from './types'
15
- import { FeatureFlagDetail, FeatureFlagValue } from '../../posthog-core/src/types'
16
- import { FeatureFlagsPoller } from './feature-flags'
11
+ } from 'posthog-core'
12
+ import { EventMessage, GroupIdentifyMessage, IdentifyMessage, PostHogNodeV1, PostHogOptions } from './types'
13
+ import { FeatureFlagDetail, FeatureFlagValue } from 'posthog-core'
14
+ import { FeatureFlagsPoller } from './extensions/feature-flags/feature-flags'
17
15
  import fetch from './fetch'
18
- import ErrorTracking from './error-tracking'
19
- import { getFeatureFlagValue } from 'posthog-core/src/featureFlagUtils'
20
-
21
- export type PostHogOptions = PostHogCoreOptions & {
22
- persistence?: 'memory'
23
- personalApiKey?: string
24
- privacyMode?: boolean
25
- enableExceptionAutocapture?: boolean
26
- // The interval in milliseconds between polls for refreshing feature flag definitions. Defaults to 30 seconds.
27
- featureFlagsPollingInterval?: number
28
- // Maximum size of cache that deduplicates $feature_flag_called calls per user.
29
- maxCacheSize?: number
30
- fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>
31
- }
16
+ import ErrorTracking from './extensions/error-tracking'
17
+ import { getFeatureFlagValue } from 'posthog-core'
18
+ import { PostHogMemoryStorage } from './storage-memory'
32
19
 
33
20
  // Standard local evaluation rate limit is 600 per minute (10 per second),
34
21
  // so the fastest a poller should ever be set is 100ms.
35
- export const MINIMUM_POLLING_INTERVAL = 100
36
- export const THIRTY_SECONDS = 30 * 1000
37
- export const SIXTY_SECONDS = 60 * 1000
22
+ const MINIMUM_POLLING_INTERVAL = 100
23
+ const THIRTY_SECONDS = 30 * 1000
38
24
  const MAX_CACHE_SIZE = 50 * 1000
39
25
 
40
26
  // The actual exported Nodejs API.
41
- export class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
27
+ export abstract class PostHogBackendClient extends PostHogCoreStateless implements PostHogNodeV1 {
42
28
  private _memoryStorage = new PostHogMemoryStorage()
43
29
 
44
30
  private featureFlagsPoller?: FeatureFlagsPoller
@@ -97,10 +83,6 @@ export class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
97
83
  fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
98
84
  return this.options.fetch ? this.options.fetch(url, options) : fetch(url, options)
99
85
  }
100
-
101
- getLibraryId(): string {
102
- return 'posthog-node'
103
- }
104
86
  getLibraryVersion(): string {
105
87
  return version
106
88
  }
@@ -0,0 +1,15 @@
1
+ export * from '../exports'
2
+
3
+ import ErrorTracking from '../extensions/error-tracking'
4
+
5
+ import { PostHogBackendClient } from '../client'
6
+ import { createStackParser } from '../extensions/error-tracking/stack-parser'
7
+
8
+ ErrorTracking.stackParser = createStackParser()
9
+ ErrorTracking.frameModifiers = []
10
+
11
+ export class PostHog extends PostHogBackendClient {
12
+ getLibraryId(): string {
13
+ return 'posthog-edge'
14
+ }
15
+ }
@@ -0,0 +1,17 @@
1
+ export * from '../exports'
2
+
3
+ import { createGetModuleFromFilename } from '../extensions/error-tracking/get-module.node'
4
+ import { addSourceContext } from '../extensions/error-tracking/context-lines.node'
5
+ import ErrorTracking from '../extensions/error-tracking'
6
+
7
+ import { PostHogBackendClient } from '../client'
8
+ import { createStackParser } from '../extensions/error-tracking/stack-parser'
9
+
10
+ ErrorTracking.stackParser = createStackParser(createGetModuleFromFilename())
11
+ ErrorTracking.frameModifiers = [addSourceContext]
12
+
13
+ export class PostHog extends PostHogBackendClient {
14
+ getLibraryId(): string {
15
+ return 'posthog-node'
16
+ }
17
+ }
package/src/exports.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './extensions/sentry-integration'
2
+ export * from './extensions/express'
3
+ export * from './types'
@@ -1,7 +1,7 @@
1
1
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
2
2
  // Licensed under the MIT License
3
3
 
4
- import { EventHint } from 'posthog-node/src/extensions/error-tracking/types'
4
+ import { EventHint } from './types'
5
5
 
6
6
  type ErrorHandler = { _posthogErrorHandler: boolean } & ((error: Error) => void)
7
7
 
@@ -3,31 +3,8 @@
3
3
 
4
4
  import { StackFrame } from './types'
5
5
  import { ReduceableCache } from './reduceable-cache'
6
- import { Lazy } from 'posthog-node/src/lazy'
7
-
8
- const nodeFs = new Lazy(async () => {
9
- try {
10
- return await import('fs')
11
- } catch {
12
- return undefined
13
- }
14
- })
15
-
16
- export async function getNodeFs(): Promise<typeof import('fs') | undefined> {
17
- return await nodeFs.getValue()
18
- }
19
-
20
- const nodeReadline = new Lazy(async () => {
21
- try {
22
- return await import('readline')
23
- } catch {
24
- return undefined
25
- }
26
- })
27
-
28
- export async function getNodeReadline(): Promise<typeof import('readline') | undefined> {
29
- return await nodeReadline.getValue()
30
- }
6
+ import { createReadStream } from 'node:fs'
7
+ import { createInterface } from 'node:readline'
31
8
 
32
9
  const LRU_FILE_CONTENTS_CACHE = new ReduceableCache<string, Record<number, string>>(25)
33
10
  const LRU_FILE_CONTENTS_FS_READ_FAILED = new ReduceableCache<string, 1>(20)
@@ -118,87 +95,77 @@ export async function addSourceContext(frames: StackFrame[]): Promise<StackFrame
118
95
  */
119
96
  function getContextLinesFromFile(path: string, ranges: ReadlineRange[], output: Record<number, string>): Promise<void> {
120
97
  return new Promise((resolve) => {
121
- // KLUDGE: edge runtimes do not support node:fs or node:readline
122
- // until we have separate packages for each environment this will skip
123
- // trying to access the filesystem when not accessible
124
- Promise.all([getNodeFs(), getNodeReadline()]).then(([nodeFs, nodeReadline]) => {
125
- if (!nodeFs || !nodeReadline) {
126
- resolve()
127
- return
128
- }
98
+ // It is important *not* to have any async code between createInterface and the 'line' event listener
99
+ // as it will cause the 'line' event to
100
+ // be emitted before the listener is attached.
101
+ const stream = createReadStream(path)
102
+ const lineReaded = createInterface({
103
+ input: stream,
104
+ })
129
105
 
130
- // It is important *not* to have any async code between createInterface and the 'line' event listener
131
- // as it will cause the 'line' event to
132
- // be emitted before the listener is attached.
133
- const stream = nodeFs.createReadStream(path)
134
- const lineReaded = nodeReadline.createInterface({
135
- input: stream,
136
- })
137
-
138
- // We need to explicitly destroy the stream to prevent memory leaks,
139
- // removing the listeners on the readline interface is not enough.
140
- // See: https://github.com/nodejs/node/issues/9002 and https://github.com/getsentry/sentry-javascript/issues/14892
141
- function destroyStreamAndResolve(): void {
142
- stream.destroy()
143
- resolve()
144
- }
106
+ // We need to explicitly destroy the stream to prevent memory leaks,
107
+ // removing the listeners on the readline interface is not enough.
108
+ // See: https://github.com/nodejs/node/issues/9002 and https://github.com/getsentry/sentry-javascript/issues/14892
109
+ function destroyStreamAndResolve(): void {
110
+ stream.destroy()
111
+ resolve()
112
+ }
113
+
114
+ // Init at zero and increment at the start of the loop because lines are 1 indexed.
115
+ let lineNumber = 0
116
+ let currentRangeIndex = 0
117
+ const range = ranges[currentRangeIndex]
118
+ if (range === undefined) {
119
+ // We should never reach this point, but if we do, we should resolve the promise to prevent it from hanging.
120
+ destroyStreamAndResolve()
121
+ return
122
+ }
123
+ let rangeStart = range[0]
124
+ let rangeEnd = range[1]
125
+
126
+ // We use this inside Promise.all, so we need to resolve the promise even if there is an error
127
+ // to prevent Promise.all from short circuiting the rest.
128
+ function onStreamError(): void {
129
+ // Mark file path as failed to read and prevent multiple read attempts.
130
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1)
131
+ lineReaded.close()
132
+ lineReaded.removeAllListeners()
133
+ destroyStreamAndResolve()
134
+ }
145
135
 
146
- // Init at zero and increment at the start of the loop because lines are 1 indexed.
147
- let lineNumber = 0
148
- let currentRangeIndex = 0
149
- const range = ranges[currentRangeIndex]
150
- if (range === undefined) {
151
- // We should never reach this point, but if we do, we should resolve the promise to prevent it from hanging.
152
- destroyStreamAndResolve()
136
+ // We need to handle the error event to prevent the process from crashing in < Node 16
137
+ // https://github.com/nodejs/node/pull/31603
138
+ stream.on('error', onStreamError)
139
+ lineReaded.on('error', onStreamError)
140
+ lineReaded.on('close', destroyStreamAndResolve)
141
+
142
+ lineReaded.on('line', (line) => {
143
+ lineNumber++
144
+ if (lineNumber < rangeStart) {
153
145
  return
154
146
  }
155
- let rangeStart = range[0]
156
- let rangeEnd = range[1]
157
-
158
- // We use this inside Promise.all, so we need to resolve the promise even if there is an error
159
- // to prevent Promise.all from short circuiting the rest.
160
- function onStreamError(): void {
161
- // Mark file path as failed to read and prevent multiple read attempts.
162
- LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1)
163
- lineReaded.close()
164
- lineReaded.removeAllListeners()
165
- destroyStreamAndResolve()
166
- }
167
147
 
168
- // We need to handle the error event to prevent the process from crashing in < Node 16
169
- // https://github.com/nodejs/node/pull/31603
170
- stream.on('error', onStreamError)
171
- lineReaded.on('error', onStreamError)
172
- lineReaded.on('close', destroyStreamAndResolve)
148
+ // !Warning: This mutates the cache by storing the snipped line into the cache.
149
+ output[lineNumber] = snipLine(line, 0)
173
150
 
174
- lineReaded.on('line', (line) => {
175
- lineNumber++
176
- if (lineNumber < rangeStart) {
151
+ if (lineNumber >= rangeEnd) {
152
+ if (currentRangeIndex === ranges.length - 1) {
153
+ // We need to close the file stream and remove listeners, else the reader will continue to run our listener;
154
+ lineReaded.close()
155
+ lineReaded.removeAllListeners()
177
156
  return
178
157
  }
179
-
180
- // !Warning: This mutates the cache by storing the snipped line into the cache.
181
- output[lineNumber] = snipLine(line, 0)
182
-
183
- if (lineNumber >= rangeEnd) {
184
- if (currentRangeIndex === ranges.length - 1) {
185
- // We need to close the file stream and remove listeners, else the reader will continue to run our listener;
186
- lineReaded.close()
187
- lineReaded.removeAllListeners()
188
- return
189
- }
190
- currentRangeIndex++
191
- const range = ranges[currentRangeIndex]
192
- if (range === undefined) {
193
- // This should never happen as it means we have a bug in the context.
194
- lineReaded.close()
195
- lineReaded.removeAllListeners()
196
- return
197
- }
198
- rangeStart = range[0]
199
- rangeEnd = range[1]
158
+ currentRangeIndex++
159
+ const range = ranges[currentRangeIndex]
160
+ if (range === undefined) {
161
+ // This should never happen as it means we have a bug in the context.
162
+ lineReaded.close()
163
+ lineReaded.removeAllListeners()
164
+ return
200
165
  }
201
- })
166
+ rangeStart = range[0]
167
+ rangeEnd = range[1]
168
+ }
202
169
  })
203
170
  })
204
171
  }
@@ -2,11 +2,19 @@
2
2
  // Licensed under the MIT License
3
3
 
4
4
  import { isError, isErrorEvent, isEvent, isPlainObject } from './type-checking'
5
- import { ErrorProperties, EventHint, Exception, Mechanism, StackFrame, StackParser } from './types'
6
- import { addSourceContext } from './context-lines'
5
+ import {
6
+ ErrorProperties,
7
+ EventHint,
8
+ Exception,
9
+ Mechanism,
10
+ StackFrame,
11
+ StackFrameModifierFn,
12
+ StackParser,
13
+ } from './types'
7
14
 
8
15
  export async function propertiesFromUnknownInput(
9
16
  stackParser: StackParser,
17
+ frameModifiers: StackFrameModifierFn[],
10
18
  input: unknown,
11
19
  hint?: EventHint
12
20
  ): Promise<ErrorProperties> {
@@ -19,7 +27,7 @@ export async function propertiesFromUnknownInput(
19
27
  const errorList = getErrorList(mechanism, input, hint)
20
28
  const exceptionList = await Promise.all(
21
29
  errorList.map(async (error) => {
22
- const exception = await exceptionFromError(stackParser, error)
30
+ const exception = await exceptionFromError(stackParser, frameModifiers, error)
23
31
  exception.value = exception.value || ''
24
32
  exception.type = exception.type || 'Error'
25
33
  exception.mechanism = mechanism
@@ -240,13 +248,22 @@ function serializeEventTarget(target: unknown): string {
240
248
  /**
241
249
  * Extracts stack frames from the error and builds an Exception
242
250
  */
243
- async function exceptionFromError(stackParser: StackParser, error: Error): Promise<Exception> {
251
+ async function exceptionFromError(
252
+ stackParser: StackParser,
253
+ frameModifiers: StackFrameModifierFn[],
254
+ error: Error
255
+ ): Promise<Exception> {
244
256
  const exception: Exception = {
245
257
  type: error.name || error.constructor.name,
246
258
  value: error.message,
247
259
  }
248
260
 
249
- const frames = await addSourceContext(parseStackFrames(stackParser, error))
261
+ let frames = parseStackFrames(stackParser, error)
262
+
263
+ for (const modifier of frameModifiers) {
264
+ frames = await modifier(frames)
265
+ }
266
+
250
267
  if (frames.length) {
251
268
  exception.stacktrace = { frames, type: 'raw' }
252
269
  }
@@ -0,0 +1,57 @@
1
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
2
+ // Licensed under the MIT License
3
+
4
+ import { posix, sep, dirname } from 'path'
5
+
6
+ /** Creates a function that gets the module name from a filename */
7
+ export function createGetModuleFromFilename(
8
+ basePath: string = process.argv[1] ? dirname(process.argv[1]) : process.cwd(),
9
+ isWindows: boolean = sep === '\\'
10
+ ): (filename: string | undefined) => string | undefined {
11
+ const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath
12
+
13
+ return (filename: string | undefined) => {
14
+ if (!filename) {
15
+ return
16
+ }
17
+
18
+ const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename
19
+
20
+ // eslint-disable-next-line prefer-const
21
+ let { dir, base: file, ext } = posix.parse(normalizedFilename)
22
+
23
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
24
+ file = file.slice(0, ext.length * -1)
25
+ }
26
+
27
+ // The file name might be URI-encoded which we want to decode to
28
+ // the original file name.
29
+ const decodedFile = decodeURIComponent(file)
30
+
31
+ if (!dir) {
32
+ // No dirname whatsoever
33
+ dir = '.'
34
+ }
35
+
36
+ const n = dir.lastIndexOf('/node_modules')
37
+ if (n > -1) {
38
+ return `${dir.slice(n + 14).replace(/\//g, '.')}:${decodedFile}`
39
+ }
40
+
41
+ // Let's see if it's a part of the main module
42
+ // To be a part of main module, it has to share the same base
43
+ if (dir.startsWith(normalizedBase)) {
44
+ const moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, '.')
45
+ return moduleName ? `${moduleName}:${decodedFile}` : decodedFile
46
+ }
47
+
48
+ return decodedFile
49
+ }
50
+ }
51
+
52
+ /** normalizes Windows paths */
53
+ function normalizeWindowsPath(path: string): string {
54
+ return path
55
+ .replace(/^[A-Z]:/, '') // remove Windows-style prefix
56
+ .replace(/\\/g, '/') // replace all `\` instances with `/`
57
+ }
@@ -1,19 +1,21 @@
1
- import { EventHint } from './extensions/error-tracking/types'
2
- import { addUncaughtExceptionListener, addUnhandledRejectionListener } from './extensions/error-tracking/autocapture'
3
- import { PostHog, PostHogOptions } from './posthog-node'
1
+ import { EventHint, StackFrameModifierFn, StackParser } from './types'
2
+ import { addUncaughtExceptionListener, addUnhandledRejectionListener } from './autocapture'
3
+ import { PostHogBackendClient } from '../../client'
4
4
  import { uuidv7 } from 'posthog-core/src/vendor/uuidv7'
5
- import { propertiesFromUnknownInput } from './extensions/error-tracking/error-conversion'
6
- import { EventMessage } from './types'
7
- import { defaultStackParser } from './extensions/error-tracking/stack-trace'
5
+ import { propertiesFromUnknownInput } from './error-conversion'
6
+ import { EventMessage, PostHogOptions } from '../../types'
8
7
 
9
8
  const SHUTDOWN_TIMEOUT = 2000
10
9
 
11
10
  export default class ErrorTracking {
12
- private client: PostHog
11
+ private client: PostHogBackendClient
13
12
  private _exceptionAutocaptureEnabled: boolean
14
13
 
14
+ static stackParser: StackParser
15
+ static frameModifiers: StackFrameModifierFn[]
16
+
15
17
  static async captureException(
16
- client: PostHog,
18
+ client: PostHogBackendClient,
17
19
  error: unknown,
18
20
  hint: EventHint,
19
21
  distinctId?: string,
@@ -27,7 +29,7 @@ export default class ErrorTracking {
27
29
  properties.$process_person_profile = false
28
30
  }
29
31
 
30
- const exceptionProperties = await propertiesFromUnknownInput(defaultStackParser, error, hint)
32
+ const exceptionProperties = await propertiesFromUnknownInput(this.stackParser, this.frameModifiers, error, hint)
31
33
 
32
34
  client.capture({
33
35
  event: '$exception',
@@ -39,7 +41,7 @@ export default class ErrorTracking {
39
41
  })
40
42
  }
41
43
 
42
- constructor(client: PostHog, options: PostHogOptions) {
44
+ constructor(client: PostHogBackendClient, options: PostHogOptions) {
43
45
  this.client = client
44
46
  this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false
45
47
 
@@ -1,10 +1,7 @@
1
1
  // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
2
2
  // Licensed under the MIT License
3
3
 
4
- import { posix, sep, dirname } from 'path'
5
- import { StackFrame, StackLineParser, StackLineParserFn, StackParser } from './types'
6
-
7
- type GetModuleFn = (filename: string | undefined) => string | undefined
4
+ import { GetModuleFn, StackFrame, StackLineParser, StackLineParserFn, StackParser } from './types'
8
5
 
9
6
  // This was originally forked from https://github.com/csnover/TraceKit, and was largely
10
7
  // re-written as part of raven - js.
@@ -37,7 +34,7 @@ const STACKTRACE_FRAME_LIMIT = 50
37
34
  const UNKNOWN_FUNCTION = '?'
38
35
 
39
36
  /** Node Stack line parser */
40
- export function node(getModule?: GetModuleFn): StackLineParserFn {
37
+ function node(getModule?: GetModuleFn): StackLineParserFn {
41
38
  const FILENAME_MATCH = /^\s*[-]{4,}$/
42
39
  const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/
43
40
 
@@ -123,7 +120,7 @@ export function node(getModule?: GetModuleFn): StackLineParserFn {
123
120
  /**
124
121
  * Does this filename look like it's part of the app code?
125
122
  */
126
- export function filenameIsInApp(filename: string, isNative: boolean = false): boolean {
123
+ function filenameIsInApp(filename: string, isNative: boolean = false): boolean {
127
124
  const isInternal =
128
125
  isNative ||
129
126
  (filename &&
@@ -147,66 +144,12 @@ function _parseIntOrUndefined(input: string | undefined): number | undefined {
147
144
  return parseInt(input || '', 10) || undefined
148
145
  }
149
146
 
150
- export function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser {
147
+ function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser {
151
148
  return [90, node(getModule)]
152
149
  }
153
150
 
154
- export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(createGetModuleFromFilename()))
155
-
156
- /** Creates a function that gets the module name from a filename */
157
- export function createGetModuleFromFilename(
158
- basePath: string = process.argv[1] ? dirname(process.argv[1]) : process.cwd(),
159
- isWindows: boolean = sep === '\\'
160
- ): (filename: string | undefined) => string | undefined {
161
- const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath
162
-
163
- return (filename: string | undefined) => {
164
- if (!filename) {
165
- return
166
- }
167
-
168
- const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename
169
-
170
- // eslint-disable-next-line prefer-const
171
- let { dir, base: file, ext } = posix.parse(normalizedFilename)
172
-
173
- if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
174
- file = file.slice(0, ext.length * -1)
175
- }
176
-
177
- // The file name might be URI-encoded which we want to decode to
178
- // the original file name.
179
- const decodedFile = decodeURIComponent(file)
180
-
181
- if (!dir) {
182
- // No dirname whatsoever
183
- dir = '.'
184
- }
185
-
186
- const n = dir.lastIndexOf('/node_modules')
187
- if (n > -1) {
188
- return `${dir.slice(n + 14).replace(/\//g, '.')}:${decodedFile}`
189
- }
190
-
191
- // Let's see if it's a part of the main module
192
- // To be a part of main module, it has to share the same base
193
- if (dir.startsWith(normalizedBase)) {
194
- const moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, '.')
195
- return moduleName ? `${moduleName}:${decodedFile}` : decodedFile
196
- }
197
-
198
- return decodedFile
199
- }
200
- }
201
-
202
- /** normalizes Windows paths */
203
- function normalizeWindowsPath(path: string): string {
204
- return path
205
- .replace(/^[A-Z]:/, '') // remove Windows-style prefix
206
- .replace(/\\/g, '/') // replace all `\` instances with `/`
207
- }
208
-
209
- export function createStackParser(...parsers: StackLineParser[]): StackParser {
151
+ export function createStackParser(getModule?: GetModuleFn): StackParser {
152
+ const parsers = [nodeStackLineParser(getModule)]
210
153
  const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map((p) => p[1])
211
154
 
212
155
  return (stack: string, skipFirstLines: number = 0): StackFrame[] => {
@@ -248,7 +191,7 @@ export function createStackParser(...parsers: StackLineParser[]): StackParser {
248
191
  }
249
192
  }
250
193
 
251
- export function reverseAndStripFrames(stack: ReadonlyArray<StackFrame>): StackFrame[] {
194
+ function reverseAndStripFrames(stack: ReadonlyArray<StackFrame>): StackFrame[] {
252
195
  if (!stack.length) {
253
196
  return []
254
197
  }
@@ -42,10 +42,14 @@ export interface Mechanism {
42
42
  synthetic?: boolean
43
43
  }
44
44
 
45
+ export type GetModuleFn = (filename: string | undefined) => string | undefined
46
+
45
47
  export type StackParser = (stack: string, skipFirstLines?: number) => StackFrame[]
46
48
  export type StackLineParserFn = (line: string) => StackFrame | undefined
47
49
  export type StackLineParser = [number, StackLineParserFn]
48
50
 
51
+ export type StackFrameModifierFn = (frames: StackFrame[]) => Promise<StackFrame[]>
52
+
49
53
  export interface StackFrame {
50
54
  platform: string
51
55
  filename?: string
@@ -1,7 +1,7 @@
1
1
  import type * as http from 'node:http'
2
2
  import { uuidv7 } from 'posthog-core/src/vendor/uuidv7'
3
- import ErrorTracking from '../error-tracking'
4
- import { PostHog } from '../posthog-node'
3
+ import ErrorTracking from './error-tracking'
4
+ import { PostHogBackendClient } from '../client'
5
5
 
6
6
  type ExpressMiddleware = (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => void
7
7
 
@@ -22,7 +22,7 @@ interface MiddlewareError extends Error {
22
22
  }
23
23
 
24
24
  export function setupExpressErrorHandler(
25
- _posthog: PostHog,
25
+ _posthog: PostHogBackendClient,
26
26
  app: {
27
27
  use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown
28
28
  }