@exodus/error-tracking 3.1.1 → 3.2.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/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [3.2.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/error-tracking@3.1.2...@exodus/error-tracking@3.2.0) (2025-10-14)
7
+
8
+ ### Features
9
+
10
+ - feat: support capturing error context (#13933)
11
+
12
+ ## [3.1.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/error-tracking@3.1.1...@exodus/error-tracking@3.1.2) (2025-09-09)
13
+
14
+ ### Bug Fixes
15
+
16
+ - fix: respect ab-testing variant for sentry [2] (#13783)
17
+
6
18
  ## [3.1.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/error-tracking@3.1.0...@exodus/error-tracking@3.1.1) (2025-08-25)
7
19
 
8
20
  ### Bug Fixes
package/README.md CHANGED
@@ -23,8 +23,13 @@ This feature is designed to be used together with `@exodus/headless`. See [using
23
23
  See [using the sdk](../../docs/development/using-the-sdk.md#setup-the-api-side) for more details on how features plug into the SDK and the API interface in the [type declaration](./api/index.d.ts).
24
24
 
25
25
  ```ts
26
- await exodus.errors.track({ namespace, error, context: {} })
27
- await exodus.errors.trackRemote({ error })
26
+ import type { SafeContextType } from '@exodus/errors'
27
+
28
+ // Track error with optional context (see SafeContextType for available properties).
29
+ const context: SafeContextType = {
30
+ /* ... */
31
+ }
32
+ await exodus.errors.track({ namespace: 'wallet', error, context })
28
33
  ```
29
34
 
30
35
  If you're building a feature and like to use error tracking inside that feature, you can depend on `errorTracking` and will receive the module with a track method that is auto-namespaced to your feature id.
@@ -23,7 +23,6 @@ const createErrorTracking = ({
23
23
  return onError(new TypeError('error must be an instance of Error'))
24
24
  }
25
25
 
26
- // TODO: figure out what to do with `context`
27
26
  // TODO: eventually kill this and only track remote
28
27
  const localPromise = errorsAtom.set(({ errors }) => {
29
28
  return {
@@ -39,7 +38,7 @@ const createErrorTracking = ({
39
38
  const remotePromise = remoteErrorTracking
40
39
  ? remoteErrorTrackingEnabledAtom
41
40
  .get()
42
- .then((enabled) => enabled && remoteErrorTracking.track({ error }))
41
+ .then((enabled) => enabled && remoteErrorTracking.track({ error, context }))
43
42
  : Promise.resolve()
44
43
 
45
44
  let timeoutId
package/module/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ErrorsAtom } from '../atoms/index.js'
2
+ import type { SafeContextType } from '@exodus/errors'
2
3
 
3
4
  export interface ErrorTrackingModule {
4
5
  /**
@@ -12,7 +13,7 @@ export interface ErrorTrackingModule {
12
13
  * })
13
14
  * ```
14
15
  */
15
- track(params: { error: Error; namespace: string; context?: any }): Promise<void>
16
+ track(params: { error: Error; namespace: string; context?: SafeContextType }): Promise<void>
16
17
  }
17
18
 
18
19
  declare const errorTrackingModuleDefinition: {
@@ -1,19 +1,16 @@
1
1
  import createSentryClient from '@exodus/sentry-client'
2
- import createFetchival from '@exodus/fetch/create-fetchival'
3
2
 
4
3
  export const remoteErrorTrackingDefinition = {
5
4
  id: 'remoteErrorTracking',
6
5
  type: 'module',
7
- factory: ({ config, fetch }) => {
8
- const fetchival = createFetchival({ fetch })
6
+ factory: ({ config }) => {
9
7
  const client = createSentryClient({
10
8
  config,
11
- fetchival,
12
9
  })
13
10
 
14
11
  return {
15
- track: ({ error }) => client.captureError({ error }),
12
+ track: ({ error, context }) => client.captureError({ error, context }),
16
13
  }
17
14
  },
18
- dependencies: ['config', 'fetch'],
15
+ dependencies: ['config'],
19
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/error-tracking",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "type": "module",
5
5
  "description": "A simple error tracking package to let any feature collect errors and create the report",
6
6
  "author": "Exodus Movement, Inc.",
@@ -34,14 +34,14 @@
34
34
  "dependencies": {
35
35
  "@exodus/atoms": "^9.0.0",
36
36
  "@exodus/basic-utils": "^3.0.1",
37
- "@exodus/errors": "^3.0.0",
38
- "@exodus/fetch": "^1.3.1",
39
- "@exodus/sentry-client": "^6.0.0",
37
+ "@exodus/errors": "^3.4.0",
38
+ "@exodus/sentry-client": "^6.2.0",
40
39
  "@exodus/typeforce": "^1.19.0",
41
40
  "@exodus/zod": "^3.24.2"
42
41
  },
43
42
  "publishConfig": {
44
- "access": "public"
43
+ "access": "public",
44
+ "provenance": false
45
45
  },
46
- "gitHead": "8d0dc9654666fca43daed44971e4bc52c4d5c9a2"
46
+ "gitHead": "1c7823e1ee4d02280aff05d67169e915ed7909ca"
47
47
  }
package/plugin/index.js CHANGED
@@ -42,6 +42,7 @@ function errorTrackingPlugin({
42
42
  trackFundedWallets,
43
43
  earliestTxDate,
44
44
  buildMetadata: await getBuildMetadata(),
45
+ logger,
45
46
  })
46
47
 
47
48
  if (reasonDisabled) {
@@ -1,3 +1,64 @@
1
+ import typeforce from '@exodus/typeforce'
2
+
3
+ export class ErrorTrackingAbExperiment {
4
+ #experimentId
5
+ #abExperimentSchema = typeforce.maybe({
6
+ enabled: 'Boolean',
7
+ })
8
+
9
+ #abVariantSchema = typeforce.maybe({
10
+ value: 'String',
11
+ type: 'String',
12
+ })
13
+
14
+ #enabled
15
+ #variant
16
+
17
+ constructor({ experimentId, abTesting, logger }) {
18
+ if (!logger) {
19
+ throw new Error('logger is required')
20
+ }
21
+
22
+ this.#experimentId = experimentId
23
+
24
+ if (experimentId) {
25
+ try {
26
+ const experimentParsed = typeforce.parse(
27
+ this.#abExperimentSchema,
28
+ abTesting?.experiments?.[experimentId],
29
+ true
30
+ )
31
+
32
+ const variantParsed = typeforce.parse(
33
+ this.#abVariantSchema,
34
+ abTesting?.variants?.[experimentId],
35
+ true
36
+ )
37
+
38
+ this.#enabled = experimentParsed?.enabled
39
+ this.#variant = variantParsed?.value
40
+ } catch {
41
+ // Attract attention to the issue and keep 'enabled' and 'variant' undefined to ensure remote tracking is disabled.
42
+ logger.debug(
43
+ `ErrorTrackingAbExperiment: failed to parse abTesting for experimentId: ${experimentId}`
44
+ )
45
+ }
46
+ }
47
+ }
48
+
49
+ get hasExperimentId() {
50
+ return !!this.#experimentId
51
+ }
52
+
53
+ get enabled() {
54
+ return this.#enabled
55
+ }
56
+
57
+ get variant() {
58
+ return this.#variant
59
+ }
60
+ }
61
+
1
62
  export function whyIsRemoteTrackingDisabled({
2
63
  remoteErrorTrackingABExperimentId,
3
64
  remoteErrorTrackingFundedWalletsABExperimentId,
@@ -7,15 +68,22 @@ export function whyIsRemoteTrackingDisabled({
7
68
  trackFundedWallets,
8
69
  earliestTxDate,
9
70
  buildMetadata,
71
+ logger,
10
72
  }) {
11
73
  if (buildMetadata.dev) return 'dev-mode'
12
74
 
13
- if (!remoteErrorTrackingABExperimentId) return 'missing-ab-experiment-id'
14
- if (!abTesting.experiments?.[remoteErrorTrackingABExperimentId]?.enabled) {
75
+ const unfundedExperiment = new ErrorTrackingAbExperiment({
76
+ experimentId: remoteErrorTrackingABExperimentId,
77
+ abTesting,
78
+ logger,
79
+ })
80
+
81
+ if (!unfundedExperiment.hasExperimentId) return 'missing-ab-experiment-id'
82
+ if (!unfundedExperiment.enabled) {
15
83
  return 'ab-experiment-disabled'
16
84
  }
17
85
 
18
- if (abTesting.variants?.[remoteErrorTrackingABExperimentId] !== 'enabled') {
86
+ if (unfundedExperiment.variant !== 'enabled') {
19
87
  return 'ab-experiment-variant-disabled'
20
88
  }
21
89
 
@@ -35,15 +103,21 @@ export function whyIsRemoteTrackingDisabled({
35
103
  }
36
104
 
37
105
  // FUNDED WALLETS: separate AB experiment
38
- if (!remoteErrorTrackingFundedWalletsABExperimentId) {
106
+ const fundedExperiment = new ErrorTrackingAbExperiment({
107
+ experimentId: remoteErrorTrackingFundedWalletsABExperimentId,
108
+ abTesting,
109
+ logger,
110
+ })
111
+
112
+ if (!fundedExperiment.hasExperimentId) {
39
113
  return 'missing-funded-wallets-ab-experiment-id'
40
114
  }
41
115
 
42
- if (!abTesting.experiments?.[remoteErrorTrackingFundedWalletsABExperimentId]?.enabled) {
116
+ if (!fundedExperiment.enabled) {
43
117
  return 'funded-wallets-ab-experiment-disabled'
44
118
  }
45
119
 
46
- if (abTesting.variants?.[remoteErrorTrackingFundedWalletsABExperimentId] !== 'enabled') {
120
+ if (fundedExperiment.variant !== 'enabled') {
47
121
  return 'funded-wallets-ab-experiment-variant-disabled'
48
122
  }
49
123
  }