@goliapkg/sentori-react-native 4.0.0 → 5.1.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 (308) hide show
  1. package/MIGRATION.md +131 -0
  2. package/README.md +40 -30
  3. package/android/build.gradle +21 -0
  4. package/android/src/main/java/com/sentori/SentoriCrashHandler.kt +29 -0
  5. package/android/src/main/java/com/sentori/SentoriModule.kt +8 -0
  6. package/android/src/main/java/com/sentori/SentoriScreenshotCapture.kt +36 -4
  7. package/android/src/test/java/com/sentori/SentoriCrashHandlerTest.kt +7 -2
  8. package/android/src/test/resources/robolectric.properties +10 -0
  9. package/ios/SentoriCrashHandler.swift +48 -4
  10. package/ios/SentoriModule.swift +12 -0
  11. package/ios/SentoriScreenshotCapture.swift +34 -3
  12. package/lib/config.d.ts +17 -47
  13. package/lib/config.d.ts.map +1 -1
  14. package/lib/config.js +3 -0
  15. package/lib/config.js.map +1 -1
  16. package/lib/device.d.ts +5 -0
  17. package/lib/device.d.ts.map +1 -0
  18. package/lib/device.js +29 -0
  19. package/lib/device.js.map +1 -0
  20. package/lib/emit-hooks.d.ts +7 -0
  21. package/lib/emit-hooks.d.ts.map +1 -0
  22. package/lib/emit-hooks.js +24 -0
  23. package/lib/emit-hooks.js.map +1 -0
  24. package/lib/error-boundary.d.ts.map +1 -1
  25. package/lib/error-boundary.js +1 -1
  26. package/lib/error-boundary.js.map +1 -1
  27. package/lib/feature-flags.d.ts.map +1 -1
  28. package/lib/handlers/dev-symbolicate.d.ts +1 -1
  29. package/lib/handlers/dev-symbolicate.d.ts.map +1 -1
  30. package/lib/handlers/dev-symbolicate.js +2 -0
  31. package/lib/handlers/dev-symbolicate.js.map +1 -1
  32. package/lib/handlers/global.js +1 -1
  33. package/lib/handlers/global.js.map +1 -1
  34. package/lib/handlers/lifecycle.js +3 -3
  35. package/lib/handlers/lifecycle.js.map +1 -1
  36. package/lib/handlers/network.d.ts +2 -2
  37. package/lib/handlers/network.d.ts.map +1 -1
  38. package/lib/handlers/network.js +69 -187
  39. package/lib/handlers/network.js.map +1 -1
  40. package/lib/handlers/promise.js +1 -1
  41. package/lib/handlers/promise.js.map +1 -1
  42. package/lib/handlers/screenshot.d.ts.map +1 -1
  43. package/lib/handlers/screenshot.js +3 -12
  44. package/lib/handlers/screenshot.js.map +1 -1
  45. package/lib/index.d.ts +32 -109
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/index.js +54 -103
  48. package/lib/index.js.map +1 -1
  49. package/lib/init.d.ts +3 -150
  50. package/lib/init.d.ts.map +1 -1
  51. package/lib/init.js +100 -261
  52. package/lib/init.js.map +1 -1
  53. package/lib/long-task-monitor.d.ts +1 -7
  54. package/lib/long-task-monitor.d.ts.map +1 -1
  55. package/lib/long-task-monitor.js +37 -58
  56. package/lib/long-task-monitor.js.map +1 -1
  57. package/lib/mask.d.ts +5 -13
  58. package/lib/mask.d.ts.map +1 -1
  59. package/lib/mask.js +29 -30
  60. package/lib/mask.js.map +1 -1
  61. package/lib/mobile-vitals.d.ts +3 -31
  62. package/lib/mobile-vitals.d.ts.map +1 -1
  63. package/lib/mobile-vitals.js +22 -61
  64. package/lib/mobile-vitals.js.map +1 -1
  65. package/lib/native-pending.d.ts +3 -0
  66. package/lib/native-pending.d.ts.map +1 -0
  67. package/lib/native-pending.js +64 -0
  68. package/lib/native-pending.js.map +1 -0
  69. package/lib/native.d.ts +4 -11
  70. package/lib/native.d.ts.map +1 -1
  71. package/lib/native.js +23 -0
  72. package/lib/native.js.map +1 -1
  73. package/lib/navigation.d.ts +3 -17
  74. package/lib/navigation.d.ts.map +1 -1
  75. package/lib/navigation.js +32 -133
  76. package/lib/navigation.js.map +1 -1
  77. package/lib/netinfo.d.ts +1 -1
  78. package/lib/netinfo.d.ts.map +1 -1
  79. package/lib/netinfo.js +2 -2
  80. package/lib/netinfo.js.map +1 -1
  81. package/lib/push.d.ts.map +1 -1
  82. package/lib/push.js +2 -4
  83. package/lib/push.js.map +1 -1
  84. package/lib/rage-tap.d.ts.map +1 -1
  85. package/lib/rage-tap.js +56 -23
  86. package/lib/rage-tap.js.map +1 -1
  87. package/lib/replay-screens.d.ts +15 -0
  88. package/lib/replay-screens.d.ts.map +1 -0
  89. package/lib/replay-screens.js +87 -0
  90. package/lib/replay-screens.js.map +1 -0
  91. package/lib/replay.d.ts.map +1 -1
  92. package/lib/replay.js +3 -24
  93. package/lib/replay.js.map +1 -1
  94. package/lib/responsiveness-detector.d.ts +18 -0
  95. package/lib/responsiveness-detector.d.ts.map +1 -0
  96. package/lib/responsiveness-detector.js +55 -0
  97. package/lib/responsiveness-detector.js.map +1 -0
  98. package/lib/scope.d.ts +7 -0
  99. package/lib/scope.d.ts.map +1 -0
  100. package/lib/scope.js +41 -0
  101. package/lib/scope.js.map +1 -0
  102. package/lib/transport.d.ts +19 -92
  103. package/lib/transport.d.ts.map +1 -1
  104. package/lib/transport.js +75 -288
  105. package/lib/transport.js.map +1 -1
  106. package/lib/verbs.d.ts +16 -0
  107. package/lib/verbs.d.ts.map +1 -0
  108. package/lib/verbs.js +153 -0
  109. package/lib/verbs.js.map +1 -0
  110. package/package.json +4 -3
  111. package/src/__tests__/iron-rule.test.ts +128 -0
  112. package/src/__tests__/replay-screens.test.ts +63 -0
  113. package/src/__tests__/responsiveness-detector.test.ts +74 -0
  114. package/src/__tests__/screenshot.test.ts +0 -2
  115. package/src/__tests__/transport.test.ts +66 -213
  116. package/src/__tests__/verbs.test.ts +91 -0
  117. package/src/config.ts +20 -47
  118. package/src/device.ts +36 -0
  119. package/src/emit-hooks.ts +30 -0
  120. package/src/error-boundary.tsx +1 -1
  121. package/src/handlers/dev-symbolicate.ts +2 -1
  122. package/src/handlers/global.ts +1 -1
  123. package/src/handlers/lifecycle.ts +3 -3
  124. package/src/handlers/network.ts +95 -211
  125. package/src/handlers/promise.ts +1 -1
  126. package/src/handlers/screenshot.ts +3 -11
  127. package/src/index.ts +84 -215
  128. package/src/init.ts +115 -449
  129. package/src/long-task-monitor.ts +42 -64
  130. package/src/mask.ts +29 -31
  131. package/src/mobile-vitals.ts +22 -82
  132. package/src/native-pending.ts +92 -0
  133. package/src/native.ts +33 -0
  134. package/src/navigation.ts +34 -142
  135. package/src/netinfo.ts +3 -3
  136. package/src/push.ts +2 -5
  137. package/src/rage-tap.tsx +72 -25
  138. package/src/replay-screens.ts +95 -0
  139. package/src/replay.ts +3 -20
  140. package/src/responsiveness-detector.ts +66 -0
  141. package/src/scope.ts +50 -0
  142. package/src/transport.ts +84 -371
  143. package/src/verbs.ts +198 -0
  144. package/lib/breadcrumbs.d.ts +0 -18
  145. package/lib/breadcrumbs.d.ts.map +0 -1
  146. package/lib/breadcrumbs.js +0 -42
  147. package/lib/breadcrumbs.js.map +0 -1
  148. package/lib/bundle-info.d.ts +0 -12
  149. package/lib/bundle-info.d.ts.map +0 -1
  150. package/lib/bundle-info.js +0 -73
  151. package/lib/bundle-info.js.map +0 -1
  152. package/lib/capture.d.ts +0 -140
  153. package/lib/capture.d.ts.map +0 -1
  154. package/lib/capture.js +0 -511
  155. package/lib/capture.js.map +0 -1
  156. package/lib/compat/sentry.d.ts +0 -126
  157. package/lib/compat/sentry.d.ts.map +0 -1
  158. package/lib/compat/sentry.js +0 -332
  159. package/lib/compat/sentry.js.map +0 -1
  160. package/lib/control-channel.d.ts +0 -6
  161. package/lib/control-channel.d.ts.map +0 -1
  162. package/lib/control-channel.js +0 -96
  163. package/lib/control-channel.js.map +0 -1
  164. package/lib/expo-compat.d.ts +0 -270
  165. package/lib/expo-compat.d.ts.map +0 -1
  166. package/lib/expo-compat.js +0 -500
  167. package/lib/expo-compat.js.map +0 -1
  168. package/lib/feedback-widget.d.ts +0 -35
  169. package/lib/feedback-widget.d.ts.map +0 -1
  170. package/lib/feedback-widget.js +0 -193
  171. package/lib/feedback-widget.js.map +0 -1
  172. package/lib/feedback.d.ts +0 -2
  173. package/lib/feedback.d.ts.map +0 -1
  174. package/lib/feedback.js +0 -17
  175. package/lib/feedback.js.map +0 -1
  176. package/lib/heartbeat.d.ts +0 -9
  177. package/lib/heartbeat.d.ts.map +0 -1
  178. package/lib/heartbeat.js +0 -138
  179. package/lib/heartbeat.js.map +0 -1
  180. package/lib/index.js.bak +0 -64
  181. package/lib/install-id.d.ts +0 -17
  182. package/lib/install-id.d.ts.map +0 -1
  183. package/lib/install-id.js +0 -125
  184. package/lib/install-id.js.map +0 -1
  185. package/lib/lifecycle.d.ts +0 -28
  186. package/lib/lifecycle.d.ts.map +0 -1
  187. package/lib/lifecycle.js +0 -73
  188. package/lib/lifecycle.js.map +0 -1
  189. package/lib/measure.d.ts +0 -4
  190. package/lib/measure.d.ts.map +0 -1
  191. package/lib/measure.js +0 -25
  192. package/lib/measure.js.map +0 -1
  193. package/lib/metrics.d.ts +0 -24
  194. package/lib/metrics.d.ts.map +0 -1
  195. package/lib/metrics.js +0 -81
  196. package/lib/metrics.js.map +0 -1
  197. package/lib/pre-crash-sentinel.d.ts +0 -12
  198. package/lib/pre-crash-sentinel.d.ts.map +0 -1
  199. package/lib/pre-crash-sentinel.js +0 -116
  200. package/lib/pre-crash-sentinel.js.map +0 -1
  201. package/lib/report-security.d.ts +0 -40
  202. package/lib/report-security.d.ts.map +0 -1
  203. package/lib/report-security.js +0 -159
  204. package/lib/report-security.js.map +0 -1
  205. package/lib/runtime-metrics-fps.d.ts +0 -19
  206. package/lib/runtime-metrics-fps.d.ts.map +0 -1
  207. package/lib/runtime-metrics-fps.js +0 -128
  208. package/lib/runtime-metrics-fps.js.map +0 -1
  209. package/lib/runtime-metrics-heap.d.ts +0 -13
  210. package/lib/runtime-metrics-heap.d.ts.map +0 -1
  211. package/lib/runtime-metrics-heap.js +0 -67
  212. package/lib/runtime-metrics-heap.js.map +0 -1
  213. package/lib/runtime-metrics-network.d.ts +0 -23
  214. package/lib/runtime-metrics-network.d.ts.map +0 -1
  215. package/lib/runtime-metrics-network.js +0 -99
  216. package/lib/runtime-metrics-network.js.map +0 -1
  217. package/lib/runtime-metrics.d.ts +0 -43
  218. package/lib/runtime-metrics.d.ts.map +0 -1
  219. package/lib/runtime-metrics.js +0 -115
  220. package/lib/runtime-metrics.js.map +0 -1
  221. package/lib/sample-profiler.d.ts +0 -21
  222. package/lib/sample-profiler.d.ts.map +0 -1
  223. package/lib/sample-profiler.js +0 -152
  224. package/lib/sample-profiler.js.map +0 -1
  225. package/lib/sentinel-context.d.ts +0 -4
  226. package/lib/sentinel-context.d.ts.map +0 -1
  227. package/lib/sentinel-context.js +0 -52
  228. package/lib/sentinel-context.js.map +0 -1
  229. package/lib/session-tracker.d.ts +0 -6
  230. package/lib/session-tracker.d.ts.map +0 -1
  231. package/lib/session-tracker.js +0 -50
  232. package/lib/session-tracker.js.map +0 -1
  233. package/lib/stack.d.ts +0 -3
  234. package/lib/stack.d.ts.map +0 -1
  235. package/lib/stack.js +0 -7
  236. package/lib/stack.js.map +0 -1
  237. package/lib/state-snapshots.d.ts +0 -33
  238. package/lib/state-snapshots.d.ts.map +0 -1
  239. package/lib/state-snapshots.js +0 -109
  240. package/lib/state-snapshots.js.map +0 -1
  241. package/lib/track.d.ts +0 -34
  242. package/lib/track.d.ts.map +0 -1
  243. package/lib/track.js +0 -107
  244. package/lib/track.js.map +0 -1
  245. package/lib/trail.d.ts +0 -12
  246. package/lib/trail.d.ts.map +0 -1
  247. package/lib/trail.js +0 -28
  248. package/lib/trail.js.map +0 -1
  249. package/lib/trust-score.d.ts +0 -20
  250. package/lib/trust-score.d.ts.map +0 -1
  251. package/lib/trust-score.js +0 -151
  252. package/lib/trust-score.js.map +0 -1
  253. package/lib/types.d.ts +0 -2
  254. package/lib/types.d.ts.map +0 -1
  255. package/lib/types.js +0 -4
  256. package/lib/types.js.map +0 -1
  257. package/lib/uuid.d.ts +0 -2
  258. package/lib/uuid.d.ts.map +0 -1
  259. package/lib/uuid.js +0 -3
  260. package/lib/uuid.js.map +0 -1
  261. package/src/__tests__/before-send.test.ts +0 -72
  262. package/src/__tests__/breadcrumbs.test.ts +0 -44
  263. package/src/__tests__/capture-replay.test.ts +0 -150
  264. package/src/__tests__/compat-sentry.test.ts +0 -121
  265. package/src/__tests__/control-channel.test.ts +0 -111
  266. package/src/__tests__/install-id.test.ts +0 -60
  267. package/src/__tests__/long-task-monitor.test.ts +0 -59
  268. package/src/__tests__/measure.test.ts +0 -45
  269. package/src/__tests__/navigation.test.ts +0 -175
  270. package/src/__tests__/push.test.ts +0 -238
  271. package/src/__tests__/report-security.test.ts +0 -106
  272. package/src/__tests__/runtime-metrics-instruments.test.ts +0 -109
  273. package/src/__tests__/runtime-metrics-network.test.ts +0 -78
  274. package/src/__tests__/sample-profiler.test.ts +0 -59
  275. package/src/__tests__/stack.test.ts +0 -45
  276. package/src/__tests__/state-snapshots.test.ts +0 -119
  277. package/src/__tests__/tracing.test.ts +0 -201
  278. package/src/__tests__/track.test.ts +0 -91
  279. package/src/__tests__/uuid.test.ts +0 -41
  280. package/src/breadcrumbs.ts +0 -57
  281. package/src/bundle-info.ts +0 -95
  282. package/src/capture.ts +0 -660
  283. package/src/compat/sentry.ts +0 -490
  284. package/src/control-channel.ts +0 -98
  285. package/src/expo-compat.ts +0 -698
  286. package/src/feedback-widget.tsx +0 -316
  287. package/src/feedback.ts +0 -21
  288. package/src/heartbeat.ts +0 -157
  289. package/src/install-id.ts +0 -146
  290. package/src/lifecycle.ts +0 -76
  291. package/src/measure.ts +0 -28
  292. package/src/metrics.ts +0 -94
  293. package/src/pre-crash-sentinel.ts +0 -140
  294. package/src/report-security.ts +0 -165
  295. package/src/runtime-metrics-fps.ts +0 -137
  296. package/src/runtime-metrics-heap.ts +0 -78
  297. package/src/runtime-metrics-network.ts +0 -103
  298. package/src/runtime-metrics.ts +0 -122
  299. package/src/sample-profiler.ts +0 -169
  300. package/src/sentinel-context.ts +0 -60
  301. package/src/session-tracker.ts +0 -53
  302. package/src/stack.ts +0 -10
  303. package/src/state-snapshots.ts +0 -133
  304. package/src/track.ts +0 -123
  305. package/src/trail.ts +0 -32
  306. package/src/trust-score.ts +0 -176
  307. package/src/types.ts +0 -21
  308. package/src/uuid.ts +0 -2
@@ -1,78 +0,0 @@
1
- // v2.1 W2 part 3 — JS heap auto-instrument.
2
- //
3
- // Polls `performance.memory.usedJSHeapSize` every 30 s, emits
4
- // runtime.heap.used_bytes. Web-only metric, RN best-effort —
5
- // the V8/Hermes value depends on which engine RN is wired to:
6
- // • Hermes (RN ≥ 0.70 default): exposes a per-isolate count
7
- // when memoryReporter is enabled (Hermes 0.12+)
8
- // • JSC (legacy / explicit opt-in): no native equivalent
9
- // • Web (sdk-javascript wires this same module): Chromium
10
- // gates it behind a high-resolution-timer flag in some
11
- // contexts; treat missing field as a silent no-op
12
- //
13
- // Cost: one number read + one emit every 30 s. Negligible.
14
- // The timer .unref()s so Node tests / CLI hosts exit cleanly.
15
-
16
- import { emitMetric } from '@goliapkg/sentori-core';
17
-
18
- const TICK_MS = 30_000;
19
-
20
- type MemoryShape = {
21
- usedJSHeapSize?: number;
22
- totalJSHeapSize?: number;
23
- jsHeapSizeLimit?: number;
24
- };
25
-
26
- let _timer: null | ReturnType<typeof setInterval> = null;
27
-
28
- function readHeap(): MemoryShape | null {
29
- const perf = (globalThis as { performance?: { memory?: MemoryShape } }).performance;
30
- return perf?.memory ?? null;
31
- }
32
-
33
- function tickOnce(): void {
34
- const m = readHeap();
35
- if (!m) return;
36
- if (typeof m.usedJSHeapSize === 'number' && Number.isFinite(m.usedJSHeapSize)) {
37
- emitMetric('runtime.heap.used_bytes', m.usedJSHeapSize);
38
- }
39
- // Total + limit help capacity-plan but cost an extra ~16 B
40
- // per row downstream — keep them only when present.
41
- if (typeof m.totalJSHeapSize === 'number' && Number.isFinite(m.totalJSHeapSize)) {
42
- emitMetric('runtime.heap.total_bytes', m.totalJSHeapSize);
43
- }
44
- if (typeof m.jsHeapSizeLimit === 'number' && Number.isFinite(m.jsHeapSizeLimit)) {
45
- emitMetric('runtime.heap.limit_bytes', m.jsHeapSizeLimit);
46
- }
47
- }
48
-
49
- /** Idempotent start. No-op on hosts that don't expose
50
- * performance.memory — checked once at start so we don't pay
51
- * for the detect on every tick. */
52
- export function startHeapInstrument(): void {
53
- if (_timer !== null) return;
54
- if (!readHeap()) return;
55
- // One immediate sample at start so the first dashboard render
56
- // doesn't sit empty for 30 s.
57
- tickOnce();
58
- _timer = setInterval(tickOnce, TICK_MS);
59
- (_timer as unknown as { unref?: () => void }).unref?.();
60
- }
61
-
62
- /** Stop. Idempotent. */
63
- export function stopHeapInstrument(): void {
64
- if (_timer !== null) {
65
- clearInterval(_timer);
66
- _timer = null;
67
- }
68
- }
69
-
70
- /** Test-only force tick (skips the 30 s wait). */
71
- export function __forceHeapTickForTests(): void {
72
- tickOnce();
73
- }
74
-
75
- /** Test-only state. */
76
- export function __peekHeapInstrumentState(): { running: boolean } {
77
- return { running: _timer !== null };
78
- }
@@ -1,103 +0,0 @@
1
- // v2.1 W2 part 4 — network bytes auto-instrument.
2
- //
3
- // Two counters (sent / received bytes) incremented by the existing
4
- // fetch patch on every request/response, drained every 30 s as
5
- // runtime.network.bytes_sent + runtime.network.bytes_received.
6
- //
7
- // Best-effort: not every Response exposes `content-length`
8
- // (chunked encoding, server stripped it, gzipped without a
9
- // pre-decode header). For those we report 0 — undercounts vs.
10
- // over-attributing arbitrary numbers. Request body size is
11
- // estimated only when init.body is a string or has `.byteLength`;
12
- // FormData / Blob / ReadableStream are not measured.
13
- //
14
- // XHR is NOT instrumented here — the existing patchXhr() in
15
- // handlers/network.ts does the trace span work, and XHR usage in
16
- // 2026-era RN apps is rare enough that the engineering cost of
17
- // a second patch isn't justified by the missing data. If a host
18
- // app shows up with significant XHR traffic we add it in a patch
19
- // release.
20
-
21
- import { emitMetric } from '@goliapkg/sentori-core';
22
-
23
- const TICK_MS = 30_000;
24
-
25
- let _bytesSent = 0;
26
- let _bytesReceived = 0;
27
- let _timer: null | ReturnType<typeof setInterval> = null;
28
-
29
- /** Called from handlers/network.ts on every outbound request.
30
- * Cheap — two adds, no allocation. */
31
- export function recordNetworkBytes(sent: number, received: number): void {
32
- if (sent > 0) _bytesSent += sent;
33
- if (received > 0) _bytesReceived += received;
34
- }
35
-
36
- function estimateBodyBytes(body: BodyInit | null | undefined): number {
37
- if (body == null) return 0;
38
- if (typeof body === 'string') return body.length;
39
- // ArrayBuffer / Uint8Array / ArrayBufferView all carry byteLength.
40
- const maybe = body as { byteLength?: number };
41
- if (typeof maybe.byteLength === 'number') return maybe.byteLength;
42
- // FormData / Blob / ReadableStream — not measured.
43
- return 0;
44
- }
45
-
46
- /** Estimate request bytes from a fetch init. Used by the fetch
47
- * patch in handlers/network.ts. */
48
- export function estimateRequestBytes(init?: RequestInit): number {
49
- return estimateBodyBytes(init?.body);
50
- }
51
-
52
- /** Read response bytes from the Content-Length header. Returns
53
- * 0 if missing or unparseable — undercount-safe. */
54
- export function estimateResponseBytes(headers: Headers | undefined | null): number {
55
- if (!headers) return 0;
56
- const v = headers.get?.('content-length');
57
- if (!v) return 0;
58
- const n = parseInt(v, 10);
59
- return Number.isFinite(n) && n > 0 ? n : 0;
60
- }
61
-
62
- function emit(): void {
63
- if (_bytesSent > 0) {
64
- emitMetric('runtime.network.bytes_sent', _bytesSent);
65
- _bytesSent = 0;
66
- }
67
- if (_bytesReceived > 0) {
68
- emitMetric('runtime.network.bytes_received', _bytesReceived);
69
- _bytesReceived = 0;
70
- }
71
- }
72
-
73
- /** Idempotent start — second call is a no-op. */
74
- export function startNetworkBytesInstrument(): void {
75
- if (_timer !== null) return;
76
- _timer = setInterval(emit, TICK_MS);
77
- (_timer as unknown as { unref?: () => void }).unref?.();
78
- }
79
-
80
- /** Stop the periodic emit. Idempotent. */
81
- export function stopNetworkBytesInstrument(): void {
82
- if (_timer !== null) {
83
- clearInterval(_timer);
84
- _timer = null;
85
- }
86
- }
87
-
88
- /** Test-only: force one emit tick + reset counters. */
89
- export function __forceNetworkEmitForTests(): void {
90
- emit();
91
- }
92
-
93
- /** Test-only: peek raw counter state without resetting. */
94
- export function __peekNetworkCountersForTests(): { sent: number; received: number } {
95
- return { sent: _bytesSent, received: _bytesReceived };
96
- }
97
-
98
- /** Test-only: reset for clean test runs. */
99
- export function __resetNetworkBytesForTests(): void {
100
- stopNetworkBytesInstrument();
101
- _bytesSent = 0;
102
- _bytesReceived = 0;
103
- }
@@ -1,122 +0,0 @@
1
- // v2.1 W2 part 2 — RN runtime metrics flusher + cold-start
2
- // instrument.
3
- //
4
- // The buffer + emit primitives live in @goliapkg/sentori-core
5
- // (runtime-metrics.ts). This module owns:
6
- // • the periodic flush timer (30 s, coalesced w/ event flush)
7
- // • the rebuffer-on-failure recovery path
8
- // • the cold-start auto-instrument (one-shot at init)
9
- //
10
- // Other auto-instrument modules (FPS / heap / route-nav / network
11
- // bytes) ship in the W2 part 3+ chunks; each is its own file with
12
- // its own per-tick perf budget test gated as stop-ship in CI.
13
- //
14
- // NEVER rule: every public surface here is wrapped in safeFn
15
- // boundaries by the SDK init layer; the flush timer itself
16
- // catches all rejections + reports via the circuit breaker.
17
-
18
- import {
19
- drainRuntimeMetricsForFlush,
20
- emitMetric,
21
- rebufferRuntimeMetrics,
22
- reportInternal,
23
- } from '@goliapkg/sentori-core';
24
-
25
- import { getConfig, isInitialized } from './config';
26
- import { sendRuntimeMetricsBatch } from './transport';
27
-
28
- const FLUSH_INTERVAL_MS = 30_000;
29
-
30
- let _timer: null | ReturnType<typeof setInterval> = null;
31
- let _coldStartT0: null | number = null;
32
- let _coldStartEmitted = false;
33
-
34
- /**
35
- * Drain core's runtime-metrics ring and POST to
36
- * /v1/runtime-metrics:batch. Rebuffers on failure so the next
37
- * tick retries; sustained outages spill into the ring's drop
38
- * counter which reports to the SDK self-report channel.
39
- *
40
- * Returns when the round-trip settles (success or failure). Per
41
- * the NEVER rule, never throws — failure is logged + rebuffered,
42
- * the resolved promise's value is undefined.
43
- */
44
- export async function flushRuntimeMetrics(): Promise<void> {
45
- if (!isInitialized()) return;
46
- const config = getConfig();
47
- if (!config) return;
48
- const batch = drainRuntimeMetricsForFlush();
49
- if (batch.length === 0) return;
50
- const ok = await sendRuntimeMetricsBatch(config.ingestUrl, config.token, batch);
51
- if (!ok) {
52
- rebufferRuntimeMetrics(batch);
53
- reportInternal('runtime-metrics.flush', new Error('runtime-metrics POST failed'));
54
- }
55
- }
56
-
57
- /**
58
- * Start the 30 s flush timer. Called once from `init()`. Idempotent
59
- * — repeated calls are a no-op so users that call
60
- * `Sentori.init({ metrics: true })` more than once (HMR, fast
61
- * refresh) don't get multiple timers.
62
- */
63
- export function startRuntimeMetricsTimer(): void {
64
- if (_timer !== null) return;
65
- _timer = setInterval(() => {
66
- void flushRuntimeMetrics();
67
- }, FLUSH_INTERVAL_MS);
68
- // Don't keep Node alive solely for this timer. RN's setInterval
69
- // is a NoopRef so this is harmless there; Node + CLI tests
70
- // benefit so the process can exit cleanly.
71
- (_timer as unknown as { unref?: () => void }).unref?.();
72
- }
73
-
74
- /**
75
- * Capture the wall-clock at `init()` so cold-start instrumentation
76
- * has a t0. Called from init() before any other auto-instrument
77
- * fires. Returns the captured t0 in millis (callers that want to
78
- * stash + use it later can hold the value).
79
- */
80
- export function markColdStartT0(): number {
81
- if (_coldStartT0 === null) {
82
- _coldStartT0 = Date.now();
83
- }
84
- return _coldStartT0;
85
- }
86
-
87
- /**
88
- * Emit one `runtime.cold_start_ms` metric point. Idempotent per
89
- * session — the second + later calls are a no-op so route-nav
90
- * instrument (W2 part 3) can call this safely without worrying
91
- * about double-counting.
92
- *
93
- * Hosts that want a more interactive-pixel-perfect cold-start
94
- * boundary (TTI vs. raw init→render) can call
95
- * `markTimeToFullDisplay()` and we emit that separately as
96
- * `runtime.time_to_full_display_ms` (existing v1 instrument).
97
- */
98
- export function emitColdStart(): void {
99
- if (_coldStartEmitted) return;
100
- if (_coldStartT0 === null) return;
101
- const ms = Date.now() - _coldStartT0;
102
- if (ms <= 0 || ms > 60_000) {
103
- // Implausible — likely the host called emit before init or
104
- // we got into a freeze-then-resume situation. Drop silently.
105
- return;
106
- }
107
- emitMetric('runtime.cold_start_ms', ms);
108
- _coldStartEmitted = true;
109
- }
110
-
111
- /**
112
- * Test-only escape hatch: stop the timer + reset cold-start state
113
- * so vitest / bun:test teardown doesn't leak intervals across runs.
114
- */
115
- export function __resetRuntimeMetricsRnForTests(): void {
116
- if (_timer !== null) {
117
- clearInterval(_timer);
118
- _timer = null;
119
- }
120
- _coldStartT0 = null;
121
- _coldStartEmitted = false;
122
- }
@@ -1,169 +0,0 @@
1
- // v1.1 #4 升级 — JS sample profiler (idle-tick sampler).
2
- //
3
- // What it does:
4
- // • setInterval(50ms) — captures `new Error().stack` of the tick
5
- // callback, parses out the top frames, increments per-frame
6
- // counts in a rolling window
7
- // • every 60 s, emits a `sentori.profile` span with the aggregated
8
- // flame-data (frame name → tick count) as span data
9
- //
10
- // What it does NOT do:
11
- // • Sample frames during a busy JS task. JS is single-threaded;
12
- // while the busy code is running, our setInterval can't fire,
13
- // so the busy stack never gets sampled. Long-task monitor (#4
14
- // v0.9.6) catches that case from a different angle (duration).
15
- //
16
- // Idle-tick sampling is still useful: shows which functions appear
17
- // most often between busy moments — typically the render hot path,
18
- // frequent timer callbacks, recurring middleware. Pairs with
19
- // long-task-monitor (catches the 200ms+ outliers).
20
- //
21
- // Real Hermes off-thread sampling profiler (which would catch busy
22
- // frames too) needs RN-internal HermesAPI access, deferred to v1.2.
23
-
24
- import { startSpan } from '@goliapkg/sentori-core';
25
-
26
- const SAMPLE_INTERVAL_MS = 50;
27
- const FLUSH_INTERVAL_MS = 60_000;
28
- const MAX_FRAMES = 200; // safety cap per profile
29
- const MAX_FRAME_NAME_LEN = 120;
30
-
31
- /** Floor on user-configured sample interval. Going below ~20 ms costs
32
- * measurable JS-thread time to `new Error().stack` + regex parse on
33
- * every tick — past that point you'd see the profile drag down the
34
- * thing you're profiling. */
35
- const MIN_SAMPLE_INTERVAL_MS = 20;
36
-
37
- /** Floor on flush interval. Sub-5s windows produce a span every few
38
- * seconds, which is just noise for an aggregator that's looking at
39
- * per-minute hotspot trends. */
40
- const MIN_FLUSH_INTERVAL_MS = 5_000;
41
-
42
- /** How many stack-trace lines to keep per tick (after dropping the
43
- * Error ctor + sampleTick frames). 10 is enough to see the JS-side
44
- * call shape; deeper than that the bottom frames are usually RN
45
- * runtime / event loop and not actionable. */
46
- const FRAMES_PER_TICK = 10;
47
-
48
- let _frameCounts = new Map<string, number>();
49
- let _windowStartedAt = 0;
50
- let _sampleTimer: ReturnType<typeof setInterval> | null = null;
51
- let _flushTimer: ReturnType<typeof setInterval> | null = null;
52
-
53
- export type SampleProfilerOptions = {
54
- enabled: boolean;
55
- /** Sample interval ms. Default 50. Lower → more accurate but more
56
- * JS-thread overhead. */
57
- sampleMs?: number;
58
- /** Flush window ms. Default 60 000 (one minute). */
59
- flushMs?: number;
60
- };
61
-
62
- /**
63
- * Start the idle-tick sample profiler. Idempotent — calling twice
64
- * is a no-op after the first successful start.
65
- *
66
- * Pairs naturally with `longTaskMonitor` (≥200ms outliers): the
67
- * profiler shows the *distribution* of code that runs in idle gaps,
68
- * the long-task monitor catches the few outliers that are blocking
69
- * the thread. Together they cover the JS-side perf signal cheaply.
70
- */
71
- export function startSampleProfiler(opts: SampleProfilerOptions): void {
72
- if (!opts.enabled || _sampleTimer !== null) return;
73
- const sampleMs = Math.max(MIN_SAMPLE_INTERVAL_MS, opts.sampleMs ?? SAMPLE_INTERVAL_MS);
74
- const flushMs = Math.max(MIN_FLUSH_INTERVAL_MS, opts.flushMs ?? FLUSH_INTERVAL_MS);
75
-
76
- _windowStartedAt = Date.now();
77
- _sampleTimer = setInterval(() => sampleTick(), sampleMs);
78
- _flushTimer = setInterval(() => flushWindow(), flushMs);
79
- (_sampleTimer as unknown as { unref?: () => void }).unref?.();
80
- (_flushTimer as unknown as { unref?: () => void }).unref?.();
81
- }
82
-
83
- export function stopSampleProfiler(): void {
84
- if (_sampleTimer !== null) {
85
- clearInterval(_sampleTimer);
86
- _sampleTimer = null;
87
- }
88
- if (_flushTimer !== null) {
89
- clearInterval(_flushTimer);
90
- _flushTimer = null;
91
- }
92
- _frameCounts.clear();
93
- _windowStartedAt = 0;
94
- }
95
-
96
- function sampleTick(): void {
97
- const stack = new Error().stack;
98
- if (!stack) return;
99
- // Skip the first 2 frames — they're our `sampleTick` + `Error
100
- // ctor` which would dominate every sample.
101
- const lines = stack.split('\n').slice(2, 2 + FRAMES_PER_TICK);
102
- for (const line of lines) {
103
- const frame = parseFrameName(line);
104
- if (frame) {
105
- _frameCounts.set(frame, (_frameCounts.get(frame) ?? 0) + 1);
106
- }
107
- }
108
- }
109
-
110
- /** Pull a stable identifier from one stack-trace line. Handles:
111
- *
112
- * at FunctionName (file://path/Foo.js:123:45)
113
- * at file://path/Foo.js:123:45 (anonymous)
114
- * FunctionName@file://path/Foo.js:123:45 (Hermes style)
115
- *
116
- * Drops absolute path noise — keeps `Foo.js:Line` so re-deploys
117
- * with stable code paths bucket together. */
118
- function parseFrameName(line: string): null | string {
119
- const trimmed = line.trim();
120
- if (trimmed.length === 0) return null;
121
- // Hermes / JSC: `FunctionName@file:line:col` or just `@file:line:col`.
122
- // Standard: `at FunctionName (file:line:col)`.
123
- let raw = trimmed.replace(/^at\s+/, '');
124
- raw = raw.replace(/\s*[(\[].*$/, ''); // strip the file part after `(`
125
- // Hermes: split on @
126
- if (raw.includes('@')) {
127
- raw = raw.split('@')[0] ?? raw;
128
- }
129
- raw = raw.trim();
130
- if (raw.length === 0 || raw.length > MAX_FRAME_NAME_LEN) return null;
131
- // Ignore the obvious noise frames.
132
- if (raw === 'Object.<anonymous>' || raw === '<anonymous>') return null;
133
- return raw;
134
- }
135
-
136
- function flushWindow(): void {
137
- if (_frameCounts.size === 0) return;
138
- const windowEndedAt = Date.now();
139
- const durationMs = windowEndedAt - _windowStartedAt;
140
- // Top-N frames so attachment doesn't bloat unboundedly.
141
- const top = Array.from(_frameCounts.entries())
142
- .sort((a, b) => b[1] - a[1])
143
- .slice(0, MAX_FRAMES);
144
-
145
- const span = startSpan('sentori.profile', {
146
- name: 'js.sample-profile',
147
- startNowMs: _windowStartedAt,
148
- tags: {
149
- 'profile.kind': 'sample',
150
- 'profile.sample_count': String(sampleCount(top)),
151
- 'profile.duration_ms': String(durationMs),
152
- },
153
- });
154
- span.setData('flameData', Object.fromEntries(top));
155
- span.finish({ endNowMs: windowEndedAt, status: 'ok' });
156
-
157
- _frameCounts.clear();
158
- _windowStartedAt = windowEndedAt;
159
- }
160
-
161
- function sampleCount(entries: [string, number][]): number {
162
- let total = 0;
163
- for (const [, n] of entries) total += n;
164
- return total;
165
- }
166
-
167
- export function __resetSampleProfilerForTests(): void {
168
- stopSampleProfiler();
169
- }
@@ -1,60 +0,0 @@
1
- // Shared device + app collectors. Used by both capture.ts (errors)
2
- // and pre-crash-sentinel.ts (nearCrash). Same shape so the dashboard
3
- // can render both kinds of events through the same UI components.
4
-
5
- import { getCachedNetworkType } from './netinfo';
6
- import type { App, Device } from './types';
7
-
8
- export const collectDeviceForSentinel = (): Device => {
9
- let os: Device['os'] = 'other';
10
- let osVersion = '0';
11
- let locale: string | undefined;
12
- const networkType = getCachedNetworkType();
13
- try {
14
- // eslint-disable-next-line @typescript-eslint/no-require-imports
15
- const RN = require('react-native') as {
16
- NativeModules: {
17
- I18nManager?: { localeIdentifier?: string };
18
- SettingsManager?: {
19
- settings?: { AppleLanguages?: string[]; AppleLocale?: string };
20
- };
21
- };
22
- Platform: { OS: string; Version: number | string };
23
- };
24
- const rnOS = RN.Platform.OS;
25
- os = rnOS === 'android' || rnOS === 'ios' || rnOS === 'web' ? rnOS : 'other';
26
- osVersion = String(RN.Platform.Version);
27
- if (rnOS === 'ios') {
28
- const s = RN.NativeModules.SettingsManager?.settings;
29
- locale = s?.AppleLocale ?? s?.AppleLanguages?.[0];
30
- } else if (rnOS === 'android') {
31
- locale = RN.NativeModules.I18nManager?.localeIdentifier;
32
- }
33
- } catch {
34
- // not in RN runtime
35
- }
36
- const device: Device = { os, osVersion };
37
- if (locale) device.locale = locale;
38
- if (networkType) device.networkType = networkType;
39
- return device;
40
- };
41
-
42
- export const getAppForSentinel = (release: string): App => {
43
- const m = /^(?:[^@]+@)?([^+]+)(?:\+(.+))?$/.exec(release);
44
- const version = m?.[1] ?? '0.0.0';
45
- const build = m?.[2];
46
-
47
- let rnVersion = 'unknown';
48
- try {
49
- // eslint-disable-next-line @typescript-eslint/no-require-imports
50
- rnVersion = (require('react-native/package.json') as { version: string }).version;
51
- } catch {
52
- // not in RN runtime
53
- }
54
-
55
- return {
56
- build,
57
- framework: { name: 'react-native', version: rnVersion },
58
- version,
59
- };
60
- };
@@ -1,53 +0,0 @@
1
- /**
2
- * Phase 26 sub-B: RN session tracker glue.
3
- *
4
- * Mirrors the JS SDK's session-tracker but sends through the RN
5
- * transport. AppState binding lives in `handlers/lifecycle.ts`; this
6
- * file is just the singleton + the start/end/markErrored/markCrashed
7
- * surface.
8
- */
9
- import { SessionTracker } from '@goliapkg/sentori-core';
10
-
11
- import { getConfig } from './config';
12
- import { getUser } from './capture';
13
- import { sendSessionPing } from './transport';
14
-
15
- let _tracker: null | SessionTracker = null;
16
-
17
- const tracker = (): SessionTracker => {
18
- if (_tracker) return _tracker;
19
- _tracker = new SessionTracker((ping) => {
20
- const cfg = getConfig();
21
- if (!cfg) return;
22
- void sendSessionPing(cfg.ingestUrl, cfg.token, ping);
23
- });
24
- return _tracker;
25
- };
26
-
27
- export const startSession = (): void => {
28
- const cfg = getConfig();
29
- if (!cfg) return;
30
- const user = getUser();
31
- tracker().start({
32
- environment: cfg.environment,
33
- release: cfg.release,
34
- userId: user?.id ?? null,
35
- });
36
- };
37
-
38
- export const endSession = (status?: 'exited'): void => {
39
- if (!_tracker) return;
40
- _tracker.end(status);
41
- };
42
-
43
- export const markSessionErrored = (): void => {
44
- _tracker?.markErrored();
45
- };
46
-
47
- export const markSessionCrashed = (): void => {
48
- _tracker?.markCrashed();
49
- };
50
-
51
- export const __resetSessionForTests = (): void => {
52
- _tracker = null;
53
- };
package/src/stack.ts DELETED
@@ -1,10 +0,0 @@
1
- // Phase 21: regex + parse loop moved to @goliapkg/sentori-core.
2
- // RN keeps the long path (no shortFilenames option) because Hermes
3
- // paths are already short and native symbolication needs the
4
- // absolute form.
5
- import { parseStack as parseStackCore } from '@goliapkg/sentori-core'
6
-
7
- import type { Frame } from './types'
8
-
9
- export const parseStack = (stack: string | undefined): Frame[] =>
10
- parseStackCore(stack)