@goliapkg/sentori-react-native 4.0.0 → 5.0.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 (284) hide show
  1. package/android/build.gradle +21 -0
  2. package/android/src/main/java/com/sentori/SentoriCrashHandler.kt +29 -0
  3. package/android/src/test/java/com/sentori/SentoriCrashHandlerTest.kt +7 -2
  4. package/android/src/test/resources/robolectric.properties +10 -0
  5. package/lib/config.d.ts +16 -47
  6. package/lib/config.d.ts.map +1 -1
  7. package/lib/config.js +3 -0
  8. package/lib/config.js.map +1 -1
  9. package/lib/device.d.ts +5 -0
  10. package/lib/device.d.ts.map +1 -0
  11. package/lib/device.js +29 -0
  12. package/lib/device.js.map +1 -0
  13. package/lib/emit-hooks.d.ts +7 -0
  14. package/lib/emit-hooks.d.ts.map +1 -0
  15. package/lib/emit-hooks.js +24 -0
  16. package/lib/emit-hooks.js.map +1 -0
  17. package/lib/error-boundary.d.ts.map +1 -1
  18. package/lib/error-boundary.js +1 -1
  19. package/lib/error-boundary.js.map +1 -1
  20. package/lib/feature-flags.d.ts.map +1 -1
  21. package/lib/handlers/dev-symbolicate.d.ts +1 -1
  22. package/lib/handlers/dev-symbolicate.d.ts.map +1 -1
  23. package/lib/handlers/dev-symbolicate.js +2 -0
  24. package/lib/handlers/dev-symbolicate.js.map +1 -1
  25. package/lib/handlers/global.js +1 -1
  26. package/lib/handlers/global.js.map +1 -1
  27. package/lib/handlers/lifecycle.js +3 -3
  28. package/lib/handlers/lifecycle.js.map +1 -1
  29. package/lib/handlers/network.d.ts +2 -2
  30. package/lib/handlers/network.d.ts.map +1 -1
  31. package/lib/handlers/network.js +69 -187
  32. package/lib/handlers/network.js.map +1 -1
  33. package/lib/handlers/promise.js +1 -1
  34. package/lib/handlers/promise.js.map +1 -1
  35. package/lib/handlers/screenshot.d.ts.map +1 -1
  36. package/lib/handlers/screenshot.js +3 -12
  37. package/lib/handlers/screenshot.js.map +1 -1
  38. package/lib/index.d.ts +31 -109
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +51 -103
  41. package/lib/index.js.map +1 -1
  42. package/lib/init.d.ts +3 -150
  43. package/lib/init.d.ts.map +1 -1
  44. package/lib/init.js +88 -261
  45. package/lib/init.js.map +1 -1
  46. package/lib/long-task-monitor.d.ts +1 -7
  47. package/lib/long-task-monitor.d.ts.map +1 -1
  48. package/lib/long-task-monitor.js +37 -58
  49. package/lib/long-task-monitor.js.map +1 -1
  50. package/lib/mobile-vitals.d.ts +3 -31
  51. package/lib/mobile-vitals.d.ts.map +1 -1
  52. package/lib/mobile-vitals.js +22 -61
  53. package/lib/mobile-vitals.js.map +1 -1
  54. package/lib/native-pending.d.ts +3 -0
  55. package/lib/native-pending.d.ts.map +1 -0
  56. package/lib/native-pending.js +64 -0
  57. package/lib/native-pending.js.map +1 -0
  58. package/lib/navigation.d.ts +3 -17
  59. package/lib/navigation.d.ts.map +1 -1
  60. package/lib/navigation.js +32 -133
  61. package/lib/navigation.js.map +1 -1
  62. package/lib/netinfo.d.ts +1 -1
  63. package/lib/netinfo.d.ts.map +1 -1
  64. package/lib/netinfo.js +2 -2
  65. package/lib/netinfo.js.map +1 -1
  66. package/lib/push.d.ts.map +1 -1
  67. package/lib/push.js +2 -4
  68. package/lib/push.js.map +1 -1
  69. package/lib/rage-tap.d.ts.map +1 -1
  70. package/lib/rage-tap.js +28 -23
  71. package/lib/rage-tap.js.map +1 -1
  72. package/lib/replay.d.ts.map +1 -1
  73. package/lib/replay.js +3 -24
  74. package/lib/replay.js.map +1 -1
  75. package/lib/scope.d.ts +7 -0
  76. package/lib/scope.d.ts.map +1 -0
  77. package/lib/scope.js +41 -0
  78. package/lib/scope.js.map +1 -0
  79. package/lib/transport.d.ts +19 -92
  80. package/lib/transport.d.ts.map +1 -1
  81. package/lib/transport.js +75 -288
  82. package/lib/transport.js.map +1 -1
  83. package/lib/verbs.d.ts +16 -0
  84. package/lib/verbs.d.ts.map +1 -0
  85. package/lib/verbs.js +153 -0
  86. package/lib/verbs.js.map +1 -0
  87. package/package.json +3 -3
  88. package/src/__tests__/iron-rule.test.ts +128 -0
  89. package/src/__tests__/screenshot.test.ts +0 -2
  90. package/src/__tests__/transport.test.ts +66 -213
  91. package/src/__tests__/verbs.test.ts +91 -0
  92. package/src/config.ts +19 -47
  93. package/src/device.ts +36 -0
  94. package/src/emit-hooks.ts +30 -0
  95. package/src/error-boundary.tsx +1 -1
  96. package/src/handlers/dev-symbolicate.ts +2 -1
  97. package/src/handlers/global.ts +1 -1
  98. package/src/handlers/lifecycle.ts +3 -3
  99. package/src/handlers/network.ts +95 -211
  100. package/src/handlers/promise.ts +1 -1
  101. package/src/handlers/screenshot.ts +3 -11
  102. package/src/index.ts +81 -215
  103. package/src/init.ts +94 -446
  104. package/src/long-task-monitor.ts +42 -64
  105. package/src/mobile-vitals.ts +22 -82
  106. package/src/native-pending.ts +92 -0
  107. package/src/navigation.ts +34 -142
  108. package/src/netinfo.ts +3 -3
  109. package/src/push.ts +2 -5
  110. package/src/rage-tap.tsx +29 -26
  111. package/src/replay.ts +3 -20
  112. package/src/scope.ts +50 -0
  113. package/src/transport.ts +84 -371
  114. package/src/verbs.ts +198 -0
  115. package/lib/breadcrumbs.d.ts +0 -18
  116. package/lib/breadcrumbs.d.ts.map +0 -1
  117. package/lib/breadcrumbs.js +0 -42
  118. package/lib/breadcrumbs.js.map +0 -1
  119. package/lib/bundle-info.d.ts +0 -12
  120. package/lib/bundle-info.d.ts.map +0 -1
  121. package/lib/bundle-info.js +0 -73
  122. package/lib/bundle-info.js.map +0 -1
  123. package/lib/capture.d.ts +0 -140
  124. package/lib/capture.d.ts.map +0 -1
  125. package/lib/capture.js +0 -511
  126. package/lib/capture.js.map +0 -1
  127. package/lib/compat/sentry.d.ts +0 -126
  128. package/lib/compat/sentry.d.ts.map +0 -1
  129. package/lib/compat/sentry.js +0 -332
  130. package/lib/compat/sentry.js.map +0 -1
  131. package/lib/control-channel.d.ts +0 -6
  132. package/lib/control-channel.d.ts.map +0 -1
  133. package/lib/control-channel.js +0 -96
  134. package/lib/control-channel.js.map +0 -1
  135. package/lib/expo-compat.d.ts +0 -270
  136. package/lib/expo-compat.d.ts.map +0 -1
  137. package/lib/expo-compat.js +0 -500
  138. package/lib/expo-compat.js.map +0 -1
  139. package/lib/feedback-widget.d.ts +0 -35
  140. package/lib/feedback-widget.d.ts.map +0 -1
  141. package/lib/feedback-widget.js +0 -193
  142. package/lib/feedback-widget.js.map +0 -1
  143. package/lib/feedback.d.ts +0 -2
  144. package/lib/feedback.d.ts.map +0 -1
  145. package/lib/feedback.js +0 -17
  146. package/lib/feedback.js.map +0 -1
  147. package/lib/heartbeat.d.ts +0 -9
  148. package/lib/heartbeat.d.ts.map +0 -1
  149. package/lib/heartbeat.js +0 -138
  150. package/lib/heartbeat.js.map +0 -1
  151. package/lib/index.js.bak +0 -64
  152. package/lib/install-id.d.ts +0 -17
  153. package/lib/install-id.d.ts.map +0 -1
  154. package/lib/install-id.js +0 -125
  155. package/lib/install-id.js.map +0 -1
  156. package/lib/lifecycle.d.ts +0 -28
  157. package/lib/lifecycle.d.ts.map +0 -1
  158. package/lib/lifecycle.js +0 -73
  159. package/lib/lifecycle.js.map +0 -1
  160. package/lib/mask.d.ts +0 -16
  161. package/lib/mask.d.ts.map +0 -1
  162. package/lib/mask.js +0 -36
  163. package/lib/mask.js.map +0 -1
  164. package/lib/measure.d.ts +0 -4
  165. package/lib/measure.d.ts.map +0 -1
  166. package/lib/measure.js +0 -25
  167. package/lib/measure.js.map +0 -1
  168. package/lib/metrics.d.ts +0 -24
  169. package/lib/metrics.d.ts.map +0 -1
  170. package/lib/metrics.js +0 -81
  171. package/lib/metrics.js.map +0 -1
  172. package/lib/pre-crash-sentinel.d.ts +0 -12
  173. package/lib/pre-crash-sentinel.d.ts.map +0 -1
  174. package/lib/pre-crash-sentinel.js +0 -116
  175. package/lib/pre-crash-sentinel.js.map +0 -1
  176. package/lib/report-security.d.ts +0 -40
  177. package/lib/report-security.d.ts.map +0 -1
  178. package/lib/report-security.js +0 -159
  179. package/lib/report-security.js.map +0 -1
  180. package/lib/runtime-metrics-fps.d.ts +0 -19
  181. package/lib/runtime-metrics-fps.d.ts.map +0 -1
  182. package/lib/runtime-metrics-fps.js +0 -128
  183. package/lib/runtime-metrics-fps.js.map +0 -1
  184. package/lib/runtime-metrics-heap.d.ts +0 -13
  185. package/lib/runtime-metrics-heap.d.ts.map +0 -1
  186. package/lib/runtime-metrics-heap.js +0 -67
  187. package/lib/runtime-metrics-heap.js.map +0 -1
  188. package/lib/runtime-metrics-network.d.ts +0 -23
  189. package/lib/runtime-metrics-network.d.ts.map +0 -1
  190. package/lib/runtime-metrics-network.js +0 -99
  191. package/lib/runtime-metrics-network.js.map +0 -1
  192. package/lib/runtime-metrics.d.ts +0 -43
  193. package/lib/runtime-metrics.d.ts.map +0 -1
  194. package/lib/runtime-metrics.js +0 -115
  195. package/lib/runtime-metrics.js.map +0 -1
  196. package/lib/sample-profiler.d.ts +0 -21
  197. package/lib/sample-profiler.d.ts.map +0 -1
  198. package/lib/sample-profiler.js +0 -152
  199. package/lib/sample-profiler.js.map +0 -1
  200. package/lib/sentinel-context.d.ts +0 -4
  201. package/lib/sentinel-context.d.ts.map +0 -1
  202. package/lib/sentinel-context.js +0 -52
  203. package/lib/sentinel-context.js.map +0 -1
  204. package/lib/session-tracker.d.ts +0 -6
  205. package/lib/session-tracker.d.ts.map +0 -1
  206. package/lib/session-tracker.js +0 -50
  207. package/lib/session-tracker.js.map +0 -1
  208. package/lib/stack.d.ts +0 -3
  209. package/lib/stack.d.ts.map +0 -1
  210. package/lib/stack.js +0 -7
  211. package/lib/stack.js.map +0 -1
  212. package/lib/state-snapshots.d.ts +0 -33
  213. package/lib/state-snapshots.d.ts.map +0 -1
  214. package/lib/state-snapshots.js +0 -109
  215. package/lib/state-snapshots.js.map +0 -1
  216. package/lib/track.d.ts +0 -34
  217. package/lib/track.d.ts.map +0 -1
  218. package/lib/track.js +0 -107
  219. package/lib/track.js.map +0 -1
  220. package/lib/trail.d.ts +0 -12
  221. package/lib/trail.d.ts.map +0 -1
  222. package/lib/trail.js +0 -28
  223. package/lib/trail.js.map +0 -1
  224. package/lib/trust-score.d.ts +0 -20
  225. package/lib/trust-score.d.ts.map +0 -1
  226. package/lib/trust-score.js +0 -151
  227. package/lib/trust-score.js.map +0 -1
  228. package/lib/types.d.ts +0 -2
  229. package/lib/types.d.ts.map +0 -1
  230. package/lib/types.js +0 -4
  231. package/lib/types.js.map +0 -1
  232. package/lib/uuid.d.ts +0 -2
  233. package/lib/uuid.d.ts.map +0 -1
  234. package/lib/uuid.js +0 -3
  235. package/lib/uuid.js.map +0 -1
  236. package/src/__tests__/before-send.test.ts +0 -72
  237. package/src/__tests__/breadcrumbs.test.ts +0 -44
  238. package/src/__tests__/capture-replay.test.ts +0 -150
  239. package/src/__tests__/compat-sentry.test.ts +0 -121
  240. package/src/__tests__/control-channel.test.ts +0 -111
  241. package/src/__tests__/install-id.test.ts +0 -60
  242. package/src/__tests__/long-task-monitor.test.ts +0 -59
  243. package/src/__tests__/measure.test.ts +0 -45
  244. package/src/__tests__/navigation.test.ts +0 -175
  245. package/src/__tests__/push.test.ts +0 -238
  246. package/src/__tests__/report-security.test.ts +0 -106
  247. package/src/__tests__/runtime-metrics-instruments.test.ts +0 -109
  248. package/src/__tests__/runtime-metrics-network.test.ts +0 -78
  249. package/src/__tests__/sample-profiler.test.ts +0 -59
  250. package/src/__tests__/stack.test.ts +0 -45
  251. package/src/__tests__/state-snapshots.test.ts +0 -119
  252. package/src/__tests__/tracing.test.ts +0 -201
  253. package/src/__tests__/track.test.ts +0 -91
  254. package/src/__tests__/uuid.test.ts +0 -41
  255. package/src/breadcrumbs.ts +0 -57
  256. package/src/bundle-info.ts +0 -95
  257. package/src/capture.ts +0 -660
  258. package/src/compat/sentry.ts +0 -490
  259. package/src/control-channel.ts +0 -98
  260. package/src/expo-compat.ts +0 -698
  261. package/src/feedback-widget.tsx +0 -316
  262. package/src/feedback.ts +0 -21
  263. package/src/heartbeat.ts +0 -157
  264. package/src/install-id.ts +0 -146
  265. package/src/lifecycle.ts +0 -76
  266. package/src/mask.ts +0 -41
  267. package/src/measure.ts +0 -28
  268. package/src/metrics.ts +0 -94
  269. package/src/pre-crash-sentinel.ts +0 -140
  270. package/src/report-security.ts +0 -165
  271. package/src/runtime-metrics-fps.ts +0 -137
  272. package/src/runtime-metrics-heap.ts +0 -78
  273. package/src/runtime-metrics-network.ts +0 -103
  274. package/src/runtime-metrics.ts +0 -122
  275. package/src/sample-profiler.ts +0 -169
  276. package/src/sentinel-context.ts +0 -60
  277. package/src/session-tracker.ts +0 -53
  278. package/src/stack.ts +0 -10
  279. package/src/state-snapshots.ts +0 -133
  280. package/src/track.ts +0 -123
  281. package/src/trail.ts +0 -32
  282. package/src/trust-score.ts +0 -176
  283. package/src/types.ts +0 -21
  284. package/src/uuid.ts +0 -2
@@ -1,490 +0,0 @@
1
- /**
2
- * v2.3 W6.3 — Sentry-compatible API surface.
3
- *
4
- * Drop-in for code (or LLM-generated code) written against
5
- * `@sentry/react-native`. Every Sentry call maps to exactly one
6
- * Sentori-native call internally. Translation differences (e.g.
7
- * `Sentry.setUser({ ip_address })` — Sentori never stores IP) fire
8
- * a one-shot console hint at `info` level, deduplicated per
9
- * (api, dropped_field).
10
- *
11
- * Why this exists: LLMs have seen a LOT of Sentry code; letting
12
- * them write the same syntax against Sentori is one less thing
13
- * Sentori asks the host to think about. Combined with the v2.3
14
- * "free bonus" stance — host adds Sentori without unlearning
15
- * anything.
16
- *
17
- * Usage:
18
- *
19
- * import * as Sentry from '@goliapkg/sentori-react-native/compat'
20
- *
21
- * Sentry.init({ dsn: 'https://<token>@<host>/<projectId>', ... })
22
- * Sentry.captureException(err)
23
- * Sentry.setUser({ id, email }) // email → linkBy.email (hashed)
24
- *
25
- * The compat layer holds NO state of its own — it's a thin shim
26
- * over the same Sentori internals. Mixing `Sentry.*` and
27
- * `sentori.*` calls in the same app works fine.
28
- *
29
- * See `docs/design/sdk-v2.3-redesign.md` §4 for the full
30
- * translation table + design rationale.
31
- */
32
-
33
- import { logger } from '@goliapkg/sentori-core'
34
-
35
- import { addBreadcrumb as nativeAddBreadcrumb } from '../breadcrumbs'
36
- import {
37
- captureException as nativeCaptureException,
38
- captureMessage as nativeCaptureMessage,
39
- setTag as nativeSetTag,
40
- setTags as nativeSetTags,
41
- setUser as nativeSetUser,
42
- } from '../capture'
43
- import { type InitOptions, init as nativeInit } from '../init'
44
-
45
- // ── one-shot warn dedup ───────────────────────────────────────────────────
46
-
47
- const _warnedOnce = new Set<string>()
48
- function warnOnce(key: string, msg: string): void {
49
- if (_warnedOnce.has(key)) return
50
- _warnedOnce.add(key)
51
- logger.info('compat', msg)
52
- }
53
-
54
- // ── DSN parsing ───────────────────────────────────────────────────────────
55
-
56
- type SentryInitOpts = {
57
- dsn: string
58
- environment?: string
59
- release?: string
60
- tracesSampleRate?: number
61
- sampleRate?: number
62
- attachStacktrace?: boolean
63
- autoSessionTracking?: boolean
64
- /** Sentry's `debug: true` ≈ Sentori's `logLevel: 'debug'`. */
65
- debug?: boolean
66
- /** Catch-all for fields Sentori either ignores or doesn't map yet. */
67
- [other: string]: unknown
68
- }
69
-
70
- /** Exported for tests only — direct callers should use `Sentry.init`,
71
- * which threads the parsed DSN through the rest of the init machinery. */
72
- export const __parseDsnForTests = parseDsn
73
-
74
- function parseDsn(dsn: string): { token: string; ingestUrl: string } {
75
- // Sentry DSN shape: `https://<key>@<host>[:port][/<projectId>]`
76
- // Sentori cares about `<key>` (must be `st_pk_…`) and `<host>`.
77
- let url: URL
78
- try {
79
- url = new URL(dsn)
80
- } catch {
81
- throw new Error(`Sentory compat: dsn is not a valid URL: ${dsn}`)
82
- }
83
- const key = url.username
84
- if (!key) {
85
- throw new Error(`Sentory compat: dsn missing token in user-info component`)
86
- }
87
- if (!key.startsWith('st_pk_')) {
88
- throw new Error(
89
- `Sentory compat: dsn token must start with 'st_pk_' (got prefix '${key.slice(0, 8)}…'). ` +
90
- `Sentori does not parse Sentry-issued tokens — generate a Sentori project token via the dashboard.`,
91
- )
92
- }
93
- // strip user-info to reconstruct the ingest origin
94
- const ingestUrl = `${url.protocol}//${url.host}`
95
- return { token: key, ingestUrl }
96
- }
97
-
98
- // ── Sentry.init ───────────────────────────────────────────────────────────
99
-
100
- export function init(opts: SentryInitOpts): void {
101
- const { token, ingestUrl } = parseDsn(opts.dsn)
102
-
103
- const sentoriOpts: InitOptions = {
104
- token,
105
- release: opts.release ?? '',
106
- ingestUrl,
107
- ...(opts.environment ? { environment: opts.environment } : {}),
108
- sample: {
109
- ...(opts.tracesSampleRate !== undefined ? { traces: opts.tracesSampleRate } : {}),
110
- ...(opts.sampleRate !== undefined ? { errors: opts.sampleRate } : {}),
111
- },
112
- ...(opts.debug ? { logLevel: 'debug' as const } : {}),
113
- }
114
-
115
- // Empty release is the most common Sentry-init mistake; warn but
116
- // continue.
117
- if (!sentoriOpts.release) {
118
- warnOnce(
119
- 'init:no-release',
120
- 'Sentry.init() with no `release` — Sentori requires release for grouping + drop-down menus to make sense. Set `release: "myapp@1.2.3"` for production cuts.',
121
- )
122
- // Sentori's init throws when release is empty; provide a
123
- // reasonable fallback so the rest of the code path works in dev.
124
- sentoriOpts.release = `unspecified@${Date.now()}`
125
- }
126
-
127
- // Pass-through informational hints for ignored fields.
128
- for (const ignored of [
129
- 'attachStacktrace',
130
- 'autoSessionTracking',
131
- 'integrations',
132
- 'beforeSend',
133
- 'beforeBreadcrumb',
134
- 'maxBreadcrumbs',
135
- ]) {
136
- if (ignored in opts) {
137
- warnOnce(
138
- `init:ignored:${ignored}`,
139
- `Sentry.init({ ${ignored} }) ignored. ` +
140
- `${getIgnoredHint(ignored)}`,
141
- )
142
- }
143
- }
144
-
145
- nativeInit(sentoriOpts)
146
- }
147
-
148
- function getIgnoredHint(field: string): string {
149
- switch (field) {
150
- case 'attachStacktrace':
151
- return 'Sentori always sends stack traces — no toggle.'
152
- case 'autoSessionTracking':
153
- return "Sentori sessions are on by default; toggle via `init({ capture: { sessions: true|false } })`."
154
- case 'integrations':
155
- return 'Sentori uses `init({ capture: {...} })` toggles instead of Integration classes — see the docs.'
156
- case 'beforeSend':
157
- case 'beforeBreadcrumb':
158
- return 'Sentori does not support an arbitrary beforeSend hook today. Server-side PII scrubbing is automatic.'
159
- case 'maxBreadcrumbs':
160
- return 'Sentori uses a fixed 100-slot ring buffer.'
161
- default:
162
- return ''
163
- }
164
- }
165
-
166
- // ── Severity / level enum ─────────────────────────────────────────────────
167
-
168
- /** Sentry's severity values, surfaced as strings (Sentori only
169
- * uses 5 levels). `Log` and `Critical` collapse to `'info'` and
170
- * `'fatal'` respectively. */
171
- export const Severity = {
172
- Fatal: 'fatal' as const,
173
- Critical: 'fatal' as const,
174
- Error: 'error' as const,
175
- Warning: 'warning' as const,
176
- Log: 'info' as const,
177
- Info: 'info' as const,
178
- Debug: 'debug' as const,
179
- }
180
-
181
- type SentryLevelString =
182
- | 'critical'
183
- | 'debug'
184
- | 'error'
185
- | 'fatal'
186
- | 'info'
187
- | 'log'
188
- | 'warning'
189
-
190
- type SentoriLevel = 'debug' | 'error' | 'fatal' | 'info' | 'warning'
191
-
192
- function mapLevel(level: SentryLevelString | undefined): SentoriLevel | undefined {
193
- if (!level) return undefined
194
- switch (level) {
195
- case 'critical':
196
- warnOnce(
197
- 'severity:critical',
198
- "Sentry.Severity.Critical → mapped to 'fatal' (Sentori's 5-level syslog-style scale).",
199
- )
200
- return 'fatal'
201
- case 'log':
202
- warnOnce(
203
- 'severity:log',
204
- "Sentry.Severity.Log → mapped to 'info' (Sentori's 5-level scale has no separate Log).",
205
- )
206
- return 'info'
207
- default:
208
- return level as SentoriLevel
209
- }
210
- }
211
-
212
- // ── captureException ──────────────────────────────────────────────────────
213
-
214
- type SentryCaptureContext = {
215
- tags?: Record<string, string>
216
- extra?: Record<string, unknown>
217
- level?: SentryLevelString
218
- fingerprint?: string[]
219
- user?: SentrySetUserInput
220
- }
221
-
222
- export function captureException(
223
- err: unknown,
224
- hint?: { captureContext?: SentryCaptureContext } | SentryCaptureContext,
225
- ): void {
226
- // Sentry v8+ takes the context inline (Hint); earlier versions
227
- // wrapped it in `{ captureContext: {...} }`. Accept both.
228
- const ctx: SentryCaptureContext | undefined = (() => {
229
- if (!hint) return undefined
230
- if ('captureContext' in (hint as { captureContext?: unknown })) {
231
- return (hint as { captureContext?: SentryCaptureContext }).captureContext
232
- }
233
- return hint as SentryCaptureContext
234
- })()
235
-
236
- if (ctx?.extra) {
237
- warnOnce(
238
- 'captureException:extra',
239
- 'Sentry.captureException(err, { extra }) → `extra` mapped to `tags` (Sentori does not have a separate extra namespace).',
240
- )
241
- }
242
- if (ctx?.user) {
243
- // Apply the per-call user via setUser (Sentori takes the
244
- // current scope user automatically).
245
- setUser(ctx.user)
246
- }
247
-
248
- const mergedTags = {
249
- ...(ctx?.tags ?? {}),
250
- ...(ctx?.extra
251
- ? Object.fromEntries(
252
- Object.entries(ctx.extra).map(([k, v]) => [k, String(v)]),
253
- )
254
- : {}),
255
- }
256
-
257
- nativeCaptureException(err as Error, {
258
- ...(Object.keys(mergedTags).length > 0 ? { tags: mergedTags } : {}),
259
- ...(ctx?.fingerprint ? { fingerprint: ctx.fingerprint } : {}),
260
- })
261
- }
262
-
263
- // ── captureMessage ────────────────────────────────────────────────────────
264
-
265
- export function captureMessage(
266
- msg: string,
267
- levelOrCtx?: SentryCaptureContext | SentryLevelString,
268
- ): void {
269
- let level: SentoriLevel | undefined
270
- let tags: Record<string, string> | undefined
271
- if (typeof levelOrCtx === 'string') {
272
- level = mapLevel(levelOrCtx)
273
- } else if (levelOrCtx) {
274
- level = mapLevel(levelOrCtx.level)
275
- tags = levelOrCtx.tags
276
- }
277
- nativeCaptureMessage(msg, {
278
- ...(level ? { level } : {}),
279
- ...(tags ? { tags } : {}),
280
- })
281
- }
282
-
283
- // ── setUser ───────────────────────────────────────────────────────────────
284
-
285
- type SentrySetUserInput = {
286
- id?: string
287
- email?: string
288
- username?: string
289
- ip_address?: string
290
- segment?: string
291
- [other: string]: unknown
292
- } | null
293
-
294
- export function setUser(user: SentrySetUserInput): void {
295
- if (user == null) {
296
- nativeSetUser(null)
297
- return
298
- }
299
- const { id, email, username, ip_address, segment, ...rest } = user
300
-
301
- if (ip_address !== undefined) {
302
- warnOnce(
303
- 'setUser:ip_address',
304
- 'Sentry.setUser({ ip_address }) → dropped. Sentori never stores IP (privacy by design).',
305
- )
306
- }
307
- if (segment !== undefined) {
308
- warnOnce(
309
- 'setUser:segment',
310
- 'Sentry.setUser({ segment }) → mapped to tag `user.segment`. Set via setTag for clarity.',
311
- )
312
- if (typeof segment === 'string') nativeSetTag('user.segment', segment)
313
- }
314
-
315
- const linkBy: Record<string, string> = {}
316
- if (email) linkBy.email = email
317
- if (username) linkBy.username = username
318
-
319
- // Surface any other fields the host bolted on (Sentry historically
320
- // accepted arbitrary keys) — they pass through as tags.
321
- for (const [k, v] of Object.entries(rest)) {
322
- if (v !== undefined && v !== null) nativeSetTag(`user.${k}`, String(v))
323
- }
324
-
325
- nativeSetUser({
326
- ...(id ? { id } : {}),
327
- ...(Object.keys(linkBy).length > 0 ? { linkBy } : {}),
328
- })
329
- }
330
-
331
- // ── setTag / setTags ──────────────────────────────────────────────────────
332
-
333
- // Re-export Sentori-native semantics; identical signatures.
334
- export const setTag = nativeSetTag
335
- export const setTags = nativeSetTags
336
-
337
- // ── addBreadcrumb ─────────────────────────────────────────────────────────
338
-
339
- type SentryBreadcrumb = {
340
- category?: string
341
- message?: string
342
- level?: SentryLevelString
343
- type?: 'default' | 'error' | 'http' | 'info' | 'navigation' | 'query' | 'user' | string
344
- data?: Record<string, unknown>
345
- timestamp?: number
346
- }
347
-
348
- type SentoriBreadcrumbType = 'custom' | 'log' | 'nav' | 'net' | 'user'
349
-
350
- /** Exported for tests only. */
351
- export const __mapCategoryToTypeForTests = mapCategoryToType
352
- export const __mapLevelForTests = mapLevel
353
-
354
- function mapCategoryToType(category: string | undefined): SentoriBreadcrumbType | undefined {
355
- if (!category) return undefined
356
- if (['auth', 'click', 'gesture', 'input', 'touch', 'ui'].includes(category)) return 'user'
357
- if (['fetch', 'http', 'xhr'].includes(category)) return 'net'
358
- if (['nav', 'navigation', 'route'].includes(category)) return 'nav'
359
- if (['console', 'log', 'sentry'].includes(category)) return 'log'
360
- return 'custom'
361
- }
362
-
363
- function mapSentryType(t: string | undefined): SentoriBreadcrumbType | undefined {
364
- if (!t) return undefined
365
- switch (t) {
366
- case 'http':
367
- return 'net'
368
- case 'navigation':
369
- return 'nav'
370
- case 'user':
371
- case 'log':
372
- case 'custom':
373
- return t
374
- default:
375
- return 'custom'
376
- }
377
- }
378
-
379
- export function addBreadcrumb(crumb: SentryBreadcrumb): void {
380
- if (!crumb.message) {
381
- crumb.message = crumb.category ?? crumb.type ?? '(no message)'
382
- }
383
- if (crumb.category && !crumb.type) {
384
- warnOnce(
385
- 'breadcrumb:category',
386
- 'Sentry.addBreadcrumb({ category }) → mapped to `type` via well-known table; the category string itself is preserved under `data.category`.',
387
- )
388
- }
389
- // RN SDK shape: { type, data }. No top-level `message` —
390
- // Sentry's message goes into data.message.
391
- nativeAddBreadcrumb({
392
- type: mapSentryType(crumb.type) ?? mapCategoryToType(crumb.category) ?? 'custom',
393
- data: {
394
- message: crumb.message,
395
- ...(crumb.data ?? {}),
396
- ...(crumb.category ? { category: crumb.category } : {}),
397
- ...(crumb.level ? { level: mapLevel(crumb.level) } : {}),
398
- },
399
- })
400
- }
401
-
402
- // ── flush / close ─────────────────────────────────────────────────────────
403
-
404
- // Re-export Sentori native flush + close — same signatures.
405
- export { close, flush } from '../lifecycle'
406
-
407
- // ── startTransaction / startSpan / withScope ──────────────────────────────
408
-
409
- // Trace mapping is non-trivial (Sentry's transaction object exposes
410
- // startChild, etc.). v2.3 ships a minimum-viable Sentry trace
411
- // surface that supports startTransaction returning a Sentori Span
412
- // object with a partial Sentry-style API (.startChild, .finish).
413
- // Anything beyond that throws a clear error directing to the native
414
- // `sentori.startSpan` / `sentori.withScopedSpan`.
415
-
416
- import { startSpan } from '@goliapkg/sentori-core'
417
-
418
- type SentrySpanOpts = {
419
- op?: string
420
- name?: string
421
- description?: string
422
- tags?: Record<string, string>
423
- }
424
-
425
- export function startTransaction(opts: SentrySpanOpts): {
426
- finish: (status?: 'cancelled' | 'error' | 'ok') => void
427
- setStatus: (status: 'cancelled' | 'error' | 'ok') => void
428
- setTag: (k: string, v: string) => void
429
- startChild: (childOpts: SentrySpanOpts) => unknown
430
- } {
431
- warnOnce(
432
- 'startTransaction',
433
- 'Sentry.startTransaction() → mapped to sentori.startSpan() with op as name. Native equivalent: sentori.startTrace(name) or sentori.startSpan({ name }).',
434
- )
435
- const name = opts.name ?? opts.op ?? 'transaction'
436
- const span = startSpan(name, {
437
- parent: null,
438
- tags: opts.tags,
439
- })
440
- return {
441
- finish: (status) => span.finish({ status: status === 'ok' ? 'ok' : 'error' }),
442
- setStatus: (status) => { span.setTag('status', status) },
443
- setTag: (k, v) => { span.setTag(k, v) },
444
- startChild: (childOpts) => {
445
- return startSpan(childOpts.name ?? childOpts.op ?? 'child', {
446
- tags: childOpts.tags,
447
- })
448
- },
449
- }
450
- }
451
-
452
- // ── withScope / configureScope (no-op scoping; same state as native) ─────
453
-
454
- type ScopeProxy = {
455
- setTag: (k: string, v: string) => void
456
- setTags: (rec: Record<string, string>) => void
457
- setUser: (u: SentrySetUserInput) => void
458
- setExtra: (k: string, v: unknown) => void
459
- setLevel: (l: SentryLevelString) => void
460
- }
461
-
462
- function scopeProxy(): ScopeProxy {
463
- return {
464
- setTag: nativeSetTag,
465
- setTags: nativeSetTags,
466
- setUser,
467
- setExtra: (k, v) => nativeSetTag(`extra.${k}`, String(v)),
468
- setLevel: () => {
469
- warnOnce(
470
- 'scope:setLevel',
471
- 'Sentry.withScope(s => s.setLevel(…)) → not supported. Sentori levels travel on capture call, not on scope.',
472
- )
473
- },
474
- }
475
- }
476
-
477
- export function withScope<T>(fn: (scope: ScopeProxy) => T): T {
478
- // Sentori has no Hub; tags set inside `fn` persist (best-effort
479
- // approximation of Sentry semantics). For most callers this is
480
- // fine; tighter isolation needs an actual Hub which we don't ship.
481
- warnOnce(
482
- 'withScope',
483
- 'Sentry.withScope() → tag mutations are NOT auto-reverted on scope exit. Use sentori.setTag/clearTags explicitly for tight isolation.',
484
- )
485
- return fn(scopeProxy())
486
- }
487
-
488
- export function configureScope(fn: (scope: ScopeProxy) => void): void {
489
- fn(scopeProxy())
490
- }
@@ -1,98 +0,0 @@
1
- // v1.1 +S7 升级 — SDK control channel for sub-second live debug.
2
- //
3
- // Every 30 s the SDK polls `/v1/control/poll?userId=<current>` and
4
- // reads back `{ liveMode: boolean, ttlMs: number }`. While live mode
5
- // is on, the transport flushes events immediately (BATCH_SIZE = 1,
6
- // FLUSH_INTERVAL = 0) so dashboard's live-debug viewer sees each
7
- // event with sub-second latency instead of waiting for the 5 s
8
- // batch tick.
9
- //
10
- // When live mode goes off the SDK reverts to its normal batching —
11
- // no permanent overhead, no state to clean up.
12
- //
13
- // Implementation note: the control channel is purely advisory.
14
- // transport.ts reads `isLiveMode()` before each enqueue and decides
15
- // whether to wait for batch or flush right now.
16
-
17
- import { getConfig } from './config';
18
- import { getCurrentUserId } from './capture';
19
-
20
- const POLL_INTERVAL_MS = 30_000;
21
-
22
- /** Cap on the live-mode window even if the server reports a longer
23
- * TTL. Defense-in-depth: an unbounded `ttlMs` (server bug, clock
24
- * skew, malicious server) would otherwise pin the SDK into flush-
25
- * every-event mode indefinitely, which is expensive on slow
26
- * networks. 15 min matches the dashboard's default arm length so
27
- * normal operation stays uncapped. */
28
- const MAX_LIVE_MODE_TTL_MS = 15 * 60_000;
29
-
30
- let _liveMode = false;
31
- let _liveModeUntil = 0;
32
- let _timer: ReturnType<typeof setInterval> | null = null;
33
-
34
- export function isLiveMode(): boolean {
35
- if (!_liveMode) return false;
36
- // Self-expire if the server-side TTL has passed without a refresh.
37
- if (Date.now() > _liveModeUntil) {
38
- _liveMode = false;
39
- return false;
40
- }
41
- return true;
42
- }
43
-
44
- export function startControlChannel(): void {
45
- if (_timer !== null) return;
46
- // Fire one poll right away so init → setUser → quick captureException
47
- // can already pick up an existing live-debug session.
48
- void pollOnce();
49
- _timer = setInterval(() => {
50
- void pollOnce();
51
- }, POLL_INTERVAL_MS);
52
- (_timer as unknown as { unref?: () => void }).unref?.();
53
- }
54
-
55
- export function stopControlChannel(): void {
56
- if (_timer !== null) {
57
- clearInterval(_timer);
58
- _timer = null;
59
- }
60
- _liveMode = false;
61
- _liveModeUntil = 0;
62
- }
63
-
64
- async function pollOnce(): Promise<void> {
65
- const config = getConfig();
66
- if (!config) return;
67
- const userId = getCurrentUserId();
68
- if (!userId) {
69
- // Without setUser we have no key. Live mode is per-user-id.
70
- _liveMode = false;
71
- _liveModeUntil = 0;
72
- return;
73
- }
74
- try {
75
- const url = `${config.ingestUrl}/v1/control/poll?userId=${encodeURIComponent(userId)}`;
76
- const resp = await fetch(url, {
77
- headers: { Authorization: `Bearer ${config.token}` },
78
- method: 'GET',
79
- });
80
- if (!resp.ok) return;
81
- const body = (await resp.json()) as { liveMode?: boolean; ttlMs?: number };
82
- if (body.liveMode === true) {
83
- _liveMode = true;
84
- _liveModeUntil = Date.now() + Math.max(0, Math.min(body.ttlMs ?? 0, MAX_LIVE_MODE_TTL_MS));
85
- } else {
86
- _liveMode = false;
87
- _liveModeUntil = 0;
88
- }
89
- } catch {
90
- // Network failure → leave the previous state. Self-expiring TTL
91
- // handles "didn't reach server" gracefully.
92
- }
93
- }
94
-
95
- /** Test-only. */
96
- export function __resetControlChannelForTests(): void {
97
- stopControlChannel();
98
- }