@exodus/error-tracking 3.3.2 → 3.4.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,12 @@
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.4.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/error-tracking@3.3.2...@exodus/error-tracking@3.4.0) (2026-04-03)
7
+
8
+ ### Features
9
+
10
+ - feat: add `unsafe__errorTracking` module (#15400)
11
+
6
12
  ## [3.3.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/error-tracking@3.3.1...@exodus/error-tracking@3.3.2) (2026-02-12)
7
13
 
8
14
  ### Bug Fixes
package/atoms/index.js CHANGED
@@ -18,3 +18,10 @@ export const remoteErrorTrackingEnabledAtomDefinition = {
18
18
  // eslint-disable-next-line @exodus/hydra/in-memory-atom-default-value
19
19
  factory: () => createInMemoryAtom(),
20
20
  }
21
+
22
+ export const unsafeRemoteErrorTrackingEnabledAtomDefinition = {
23
+ id: 'unsafeRemoteErrorTrackingEnabledAtom',
24
+ type: 'atom',
25
+ // eslint-disable-next-line @exodus/hydra/in-memory-atom-default-value
26
+ factory: () => createInMemoryAtom(),
27
+ }
package/index.js CHANGED
@@ -1,9 +1,17 @@
1
1
  import typeforce from '@exodus/typeforce'
2
2
 
3
3
  import { errorTrackingApiDefinition } from './api/index.js'
4
- import { errorsAtomDefinition, remoteErrorTrackingEnabledAtomDefinition } from './atoms/index.js'
4
+ import {
5
+ errorsAtomDefinition,
6
+ remoteErrorTrackingEnabledAtomDefinition,
7
+ unsafeRemoteErrorTrackingEnabledAtomDefinition,
8
+ } from './atoms/index.js'
5
9
  import errorTrackingReportDefinition from './report/index.js'
6
- import { errorTrackingDefinition, remoteErrorTrackingDefinition } from './module/index.js'
10
+ import {
11
+ errorTrackingDefinition,
12
+ remoteErrorTrackingDefinition,
13
+ unsafeErrorTrackingDefinition,
14
+ } from './module/index.js'
7
15
  import errorTrackingPluginDefinition from './plugin/index.js'
8
16
 
9
17
  const defaultConfig = {
@@ -46,6 +54,9 @@ const errorTracking = (config = Object.create(null)) => {
46
54
  {
47
55
  definition: remoteErrorTrackingEnabledAtomDefinition,
48
56
  },
57
+ {
58
+ definition: unsafeRemoteErrorTrackingEnabledAtomDefinition,
59
+ },
49
60
  {
50
61
  definition: errorTrackingApiDefinition,
51
62
  },
@@ -58,6 +69,10 @@ const errorTracking = (config = Object.create(null)) => {
58
69
  definition: remoteErrorTrackingDefinition,
59
70
  config: sentryConfig,
60
71
  },
72
+ {
73
+ if: remoteErrorTrackingAvailable,
74
+ definition: unsafeErrorTrackingDefinition,
75
+ },
61
76
  // deprecated, errors will go to sentry
62
77
  {
63
78
  definition: errorTrackingReportDefinition,
package/module/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { errorTrackingDefinition } from './error-tracking.js'
2
2
  export { remoteErrorTrackingDefinition } from './remote-error-tracking.js'
3
+ export { unsafeErrorTrackingDefinition } from './unsafe-error-tracking.js'
@@ -0,0 +1,34 @@
1
+ import { captureStackTrace } from '@exodus/errors'
2
+
3
+ const createUnsafeErrorTracking = ({
4
+ unsafeRemoteErrorTrackingEnabledAtom,
5
+ remoteErrorTracking,
6
+ logger,
7
+ }) => {
8
+ const track = async ({ error, context }) => {
9
+ if (!remoteErrorTracking) return
10
+
11
+ if (!(error instanceof Error)) {
12
+ logger.error(new TypeError('error must be an instance of Error'))
13
+ return
14
+ }
15
+
16
+ captureStackTrace(error)
17
+ logger.error(error)
18
+ const enabled = await unsafeRemoteErrorTrackingEnabledAtom.get()
19
+ if (!enabled) return
20
+
21
+ await remoteErrorTracking.track({ error, context })
22
+ }
23
+
24
+ return { track }
25
+ }
26
+
27
+ export const unsafeErrorTrackingDefinition = {
28
+ // eslint-disable-next-line @exodus/hydra/dependency-ids
29
+ id: 'unsafe__errorTracking',
30
+ type: 'module',
31
+ factory: createUnsafeErrorTracking,
32
+ dependencies: ['unsafeRemoteErrorTrackingEnabledAtom', 'remoteErrorTracking?', 'logger'],
33
+ public: true,
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/error-tracking",
3
- "version": "3.3.2",
3
+ "version": "3.4.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.",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@exodus/atoms": "^9.0.0",
36
- "@exodus/basic-utils": "^3.0.1",
36
+ "@exodus/basic-utils": "^3.7.3",
37
37
  "@exodus/errors": "^3.7.0",
38
38
  "@exodus/safe-string": "^1.4.0",
39
39
  "@exodus/sentry-client": "^6.2.0",
@@ -41,11 +41,11 @@
41
41
  "@exodus/zod": "^3.24.2"
42
42
  },
43
43
  "devDependencies": {
44
- "@exodus/traceparent": "^2.0.0"
44
+ "@exodus/traceparent": "^3.0.1"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public",
48
48
  "provenance": false
49
49
  },
50
- "gitHead": "fe153ece541b24d322016b462ea1331a11811ed2"
50
+ "gitHead": "d70ddc5076017b6f1b5845c99eff340fc5ef0cdf"
51
51
  }
package/plugin/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { combine, compute } from '@exodus/atoms'
2
2
 
3
- import { whyIsRemoteTrackingDisabled } from './why-is-remote-tracking-disabled.js'
3
+ import {
4
+ whyIsRemoteTrackingDisabled,
5
+ whyIsUnsafeRemoteTrackingDisabled,
6
+ } from './why-is-remote-tracking-disabled.js'
4
7
 
5
8
  function errorTrackingPlugin({
6
9
  abTestingAtom,
@@ -8,6 +11,7 @@ function errorTrackingPlugin({
8
11
  walletCreatedAtAtom,
9
12
  getBuildMetadata,
10
13
  remoteErrorTrackingEnabledAtom,
14
+ unsafeRemoteErrorTrackingEnabledAtom,
11
15
  config: {
12
16
  remoteErrorTrackingABExperimentId,
13
17
  remoteErrorTrackingFundedWalletsABExperimentId,
@@ -18,6 +22,16 @@ function errorTrackingPlugin({
18
22
  }) {
19
23
  const subscriptions = []
20
24
 
25
+ const toEnabled = (reasonDisabled, label) => {
26
+ if (reasonDisabled) {
27
+ logger.debug(`${label} is disabled: ${reasonDisabled}`)
28
+ return false
29
+ }
30
+
31
+ logger.debug(`${label} is enabled`)
32
+ return true
33
+ }
34
+
21
35
  const onAssetsSynced = async () => {
22
36
  if (!abTestingAtom) {
23
37
  logger.debug('remote error tracking is disabled, ab-testing feature not found')
@@ -32,31 +46,43 @@ function errorTrackingPlugin({
32
46
  earliestTxDate: earliestTxDateAtom,
33
47
  walletCreatedAt: walletCreatedAtAtom,
34
48
  }),
35
- selector: async ({ abTesting, earliestTxDate, walletCreatedAt }) => {
36
- const reasonDisabled = whyIsRemoteTrackingDisabled({
37
- remoteErrorTrackingABExperimentId,
38
- remoteErrorTrackingFundedWalletsABExperimentId,
39
- abTesting,
40
- trackWalletsCreatedAfter,
41
- walletCreatedAt,
42
- trackFundedWallets,
43
- earliestTxDate,
44
- buildMetadata: await getBuildMetadata(),
45
- logger,
46
- })
49
+ selector: async ({ abTesting, earliestTxDate, walletCreatedAt }) =>
50
+ toEnabled(
51
+ whyIsRemoteTrackingDisabled({
52
+ remoteErrorTrackingABExperimentId,
53
+ remoteErrorTrackingFundedWalletsABExperimentId,
54
+ abTesting,
55
+ trackWalletsCreatedAfter,
56
+ walletCreatedAt,
57
+ trackFundedWallets,
58
+ earliestTxDate,
59
+ buildMetadata: await getBuildMetadata(),
60
+ logger,
61
+ }),
62
+ 'remote error tracking'
63
+ ),
64
+ })
47
65
 
48
- if (reasonDisabled) {
49
- logger.debug(`remote error tracking is disabled: ${reasonDisabled}`)
50
- return false
51
- }
66
+ subscriptions.push(
67
+ internalRemoteErrorTrackingEnabledAtom.observe(remoteErrorTrackingEnabledAtom.set)
68
+ )
52
69
 
53
- logger.debug('remote error tracking is enabled')
54
- return true
55
- },
70
+ const internalUnsafeRemoteErrorTrackingEnabledAtom = compute({
71
+ atom: abTestingAtom,
72
+ selector: async (abTesting) =>
73
+ toEnabled(
74
+ whyIsUnsafeRemoteTrackingDisabled({
75
+ remoteErrorTrackingABExperimentId,
76
+ abTesting,
77
+ buildMetadata: await getBuildMetadata(),
78
+ logger,
79
+ }),
80
+ 'unsafe remote error tracking'
81
+ ),
56
82
  })
57
83
 
58
84
  subscriptions.push(
59
- internalRemoteErrorTrackingEnabledAtom.observe(remoteErrorTrackingEnabledAtom.set)
85
+ internalUnsafeRemoteErrorTrackingEnabledAtom.observe(unsafeRemoteErrorTrackingEnabledAtom.set)
60
86
  )
61
87
  }
62
88
 
@@ -80,6 +106,7 @@ const errorTrackingPluginDefinition = {
80
106
  'earliestTxDateAtom',
81
107
  'walletCreatedAtAtom',
82
108
  'remoteErrorTrackingEnabledAtom',
109
+ 'unsafeRemoteErrorTrackingEnabledAtom',
83
110
  'getBuildMetadata',
84
111
  'config',
85
112
  'logger',
@@ -59,6 +59,14 @@ export class ErrorTrackingAbExperiment {
59
59
  }
60
60
  }
61
61
 
62
+ function whyIsExperimentDisabled({ experimentId, abTesting, logger, prefix = '' }) {
63
+ const experiment = new ErrorTrackingAbExperiment({ experimentId, abTesting, logger })
64
+
65
+ if (!experiment.hasExperimentId) return `${prefix}missing-ab-experiment-id`
66
+ if (!experiment.enabled) return `${prefix}ab-experiment-disabled`
67
+ if (experiment.variant !== 'enabled') return `${prefix}ab-experiment-variant-disabled`
68
+ }
69
+
62
70
  export function whyIsRemoteTrackingDisabled({
63
71
  remoteErrorTrackingABExperimentId,
64
72
  remoteErrorTrackingFundedWalletsABExperimentId,
@@ -72,20 +80,12 @@ export function whyIsRemoteTrackingDisabled({
72
80
  }) {
73
81
  if (buildMetadata.dev) return 'dev-mode'
74
82
 
75
- const unfundedExperiment = new ErrorTrackingAbExperiment({
83
+ const baseDisabledReason = whyIsExperimentDisabled({
76
84
  experimentId: remoteErrorTrackingABExperimentId,
77
85
  abTesting,
78
86
  logger,
79
87
  })
80
-
81
- if (!unfundedExperiment.hasExperimentId) return 'missing-ab-experiment-id'
82
- if (!unfundedExperiment.enabled) {
83
- return 'ab-experiment-disabled'
84
- }
85
-
86
- if (unfundedExperiment.variant !== 'enabled') {
87
- return 'ab-experiment-variant-disabled'
88
- }
88
+ if (baseDisabledReason) return baseDisabledReason
89
89
 
90
90
  if (
91
91
  trackWalletsCreatedAfter &&
@@ -103,21 +103,26 @@ export function whyIsRemoteTrackingDisabled({
103
103
  }
104
104
 
105
105
  // FUNDED WALLETS: separate AB experiment
106
- const fundedExperiment = new ErrorTrackingAbExperiment({
106
+ const fundedDisabledReason = whyIsExperimentDisabled({
107
107
  experimentId: remoteErrorTrackingFundedWalletsABExperimentId,
108
108
  abTesting,
109
109
  logger,
110
+ prefix: 'funded-wallets-',
110
111
  })
112
+ if (fundedDisabledReason) return fundedDisabledReason
113
+ }
111
114
 
112
- if (!fundedExperiment.hasExperimentId) {
113
- return 'missing-funded-wallets-ab-experiment-id'
114
- }
115
-
116
- if (!fundedExperiment.enabled) {
117
- return 'funded-wallets-ab-experiment-disabled'
118
- }
115
+ export function whyIsUnsafeRemoteTrackingDisabled({
116
+ remoteErrorTrackingABExperimentId,
117
+ abTesting,
118
+ buildMetadata,
119
+ logger,
120
+ }) {
121
+ if (buildMetadata.dev) return 'dev-mode'
119
122
 
120
- if (fundedExperiment.variant !== 'enabled') {
121
- return 'funded-wallets-ab-experiment-variant-disabled'
122
- }
123
+ return whyIsExperimentDisabled({
124
+ experimentId: remoteErrorTrackingABExperimentId,
125
+ abTesting,
126
+ logger,
127
+ })
123
128
  }