@goliapkg/sentori-react-native 3.1.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 +5 -5
  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,201 +0,0 @@
1
- import { clearSpans, drainSpans } from '@goliapkg/sentori-core';
2
- import {
3
- afterEach,
4
- beforeAll,
5
- beforeEach,
6
- describe,
7
- expect,
8
- test,
9
- } from 'bun:test';
10
-
11
- import { installNetworkHandler } from '../handlers/network';
12
-
13
- // network.ts installs ONCE per process. To test reliably we:
14
- // 1. set up a single static recorder on globalThis.fetch
15
- // 2. install the wrapper exactly once (in beforeAll)
16
- // 3. between tests, only mutate the recorder's queue — NEVER
17
- // re-assign globalThis.fetch, because the wrapper captured the
18
- // first reference at install time.
19
-
20
- const recorderCalls: Array<{ init?: RequestInit; url: string }> = [];
21
- let recorderQueue: Array<{ status: number } | Error> = [];
22
-
23
- const recorder = (async (input: Request | string | URL, init?: RequestInit) => {
24
- const url =
25
- typeof input === 'string'
26
- ? input
27
- : input instanceof URL
28
- ? input.toString()
29
- : input.url;
30
- recorderCalls.push({ init, url });
31
- const r = recorderQueue.shift() ?? { status: 200 };
32
- if (r instanceof Error) throw r;
33
- return new Response('', { status: r.status });
34
- }) as unknown as typeof fetch;
35
-
36
- // Fake XMLHttpRequest so installNetworkHandler()'s patchXhr() has a
37
- // prototype to patch. RN's native XHR isn't present in bun:test.
38
- type FakeListener = () => void;
39
- class FakeXHR {
40
- status = 0;
41
- private listeners: Record<string, FakeListener[]> = {};
42
- private requestHeaders: Record<string, string> = {};
43
- private opened = false;
44
-
45
- open(_method: string, _url: string | URL): void {
46
- this.opened = true;
47
- }
48
- setRequestHeader(name: string, value: string): void {
49
- if (!this.opened) throw new Error('setRequestHeader before open');
50
- this.requestHeaders[name.toLowerCase()] = value;
51
- }
52
- send(_body?: unknown): void {}
53
- addEventListener(event: string, fn: FakeListener): void {
54
- (this.listeners[event] ??= []).push(fn);
55
- }
56
- getHeader(name: string): string | undefined {
57
- return this.requestHeaders[name.toLowerCase()];
58
- }
59
- fire(event: string): void {
60
- for (const fn of this.listeners[event] ?? []) fn();
61
- }
62
- }
63
-
64
- beforeAll(() => {
65
- (globalThis as { fetch: typeof fetch }).fetch = recorder;
66
- (globalThis as { XMLHttpRequest: unknown }).XMLHttpRequest = FakeXHR as unknown;
67
- installNetworkHandler(); // patches globalThis.fetch + XMLHttpRequest.prototype
68
- });
69
-
70
- beforeEach(() => {
71
- clearSpans();
72
- recorderCalls.length = 0;
73
- recorderQueue = [{ status: 200 }];
74
- });
75
-
76
- afterEach(() => {
77
- clearSpans();
78
- });
79
-
80
- describe('RN network handler tracing', () => {
81
- test('wrapped fetch emits an http.client span', async () => {
82
- const resp = await fetch('https://api.example.com/v1/users/me', {
83
- method: 'GET',
84
- });
85
- expect(resp.status).toBe(200);
86
-
87
- const spans = drainSpans();
88
- expect(spans).toHaveLength(1);
89
- expect(spans[0]?.op).toBe('http.client');
90
- expect(spans[0]?.name).toBe('GET https://api.example.com/v1/users/me');
91
- expect(spans[0]?.tags).toMatchObject({
92
- 'http.method': 'GET',
93
- 'http.status': '200',
94
- 'http.url': 'https://api.example.com/v1/users/me',
95
- });
96
- expect(spans[0]?.status).toBe('ok');
97
- });
98
-
99
- test('injects W3C traceparent header', async () => {
100
- await fetch('https://api.example.com/x');
101
- expect(recorderCalls).toHaveLength(1);
102
- const headers = new Headers(recorderCalls[0]?.init?.headers);
103
- const tp = headers.get('traceparent');
104
- expect(tp).not.toBeNull();
105
- expect(tp).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/);
106
- });
107
-
108
- test('span name normalizes id-like segments; tag keeps full scrubbed url', async () => {
109
- await fetch('https://api.example.com/users/123/orders/456?token=abc');
110
- const sp = drainSpans()[0]!;
111
- expect(sp.name).toBe('GET https://api.example.com/users/{id}/orders/{id}');
112
- // RN's scrubUrl redacts auth params in the tag, but keeps path + host.
113
- expect(sp.tags['http.url']).toContain('https://api.example.com/users/123/orders/456');
114
- expect(sp.tags['http.url']).not.toContain('token=abc');
115
- });
116
-
117
- test('5xx → span.status = "error"', async () => {
118
- recorderQueue = [{ status: 503 }];
119
- await fetch('https://api.example.com/x');
120
- expect(drainSpans()[0]?.status).toBe('error');
121
- });
122
-
123
- test('throws → status = "error" with error.message tag', async () => {
124
- recorderQueue = [new TypeError('NetworkError: offline')];
125
- await expect(fetch('https://api.example.com/x')).rejects.toThrow('NetworkError');
126
- const sp = drainSpans()[0]!;
127
- expect(sp.status).toBe('error');
128
- expect(sp.tags['error.message']).toContain('offline');
129
- });
130
-
131
- test('AbortError → status = "cancelled"', async () => {
132
- recorderQueue = [Object.assign(new Error('aborted'), { name: 'AbortError' })];
133
- await expect(fetch('https://api.example.com/x')).rejects.toThrow('aborted');
134
- expect(drainSpans()[0]?.status).toBe('cancelled');
135
- });
136
-
137
- test('preserves caller-supplied headers alongside traceparent', async () => {
138
- await fetch('https://api.example.com/x', {
139
- headers: { Authorization: 'Bearer xyz', 'X-Custom': '1' },
140
- });
141
- const h = new Headers(recorderCalls[0]?.init?.headers);
142
- expect(h.get('authorization')).toBe('Bearer xyz');
143
- expect(h.get('x-custom')).toBe('1');
144
- expect(h.get('traceparent')).toBeTruthy();
145
- });
146
- });
147
-
148
- describe('RN XHR tracing (axios goes through XHR on RN)', () => {
149
- test('patched XHR emits an http.client span on loadend', () => {
150
- const x = new FakeXHR();
151
- x.open('POST', 'https://api.example.com/v1/orders');
152
- x.send('{}');
153
- x.status = 201;
154
- x.fire('loadend');
155
-
156
- const sp = drainSpans()[0]!;
157
- expect(sp.op).toBe('http.client');
158
- expect(sp.name).toBe('POST https://api.example.com/v1/orders');
159
- expect(sp.tags).toMatchObject({
160
- 'http.method': 'POST',
161
- 'http.status': '201',
162
- 'http.url': 'https://api.example.com/v1/orders',
163
- });
164
- expect(sp.status).toBe('ok');
165
- });
166
-
167
- test('injects W3C traceparent request header', () => {
168
- const x = new FakeXHR();
169
- x.open('GET', 'https://api.example.com/x');
170
- x.send();
171
- expect(x.getHeader('traceparent')).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/);
172
- x.status = 200;
173
- x.fire('loadend');
174
- });
175
-
176
- test('5xx → span.status = "error"', () => {
177
- const x = new FakeXHR();
178
- x.open('GET', 'https://api.example.com/x');
179
- x.send();
180
- x.status = 502;
181
- x.fire('loadend');
182
- expect(drainSpans()[0]?.status).toBe('error');
183
- });
184
-
185
- test('status 0 → span.status = "error"', () => {
186
- const x = new FakeXHR();
187
- x.open('GET', 'https://api.example.com/x');
188
- x.send();
189
- x.status = 0;
190
- x.fire('loadend');
191
- expect(drainSpans()[0]?.status).toBe('error');
192
- });
193
-
194
- test('abort → span.status = "cancelled"', () => {
195
- const x = new FakeXHR();
196
- x.open('GET', 'https://api.example.com/x');
197
- x.send();
198
- x.fire('abort');
199
- expect(drainSpans()[0]?.status).toBe('cancelled');
200
- });
201
- });
@@ -1,91 +0,0 @@
1
- // v1.1 chunk B — analytics `track` buffer + flush.
2
-
3
- import {
4
- afterEach,
5
- beforeEach,
6
- describe,
7
- expect,
8
- it,
9
- mock,
10
- } from 'bun:test';
11
-
12
- import { __resetForTests as resetConfig, setConfig } from '../config';
13
- import { setUser } from '../capture';
14
- import {
15
- __peekTrackBuffer,
16
- __resetTrackForTests,
17
- flushTrack,
18
- track,
19
- } from '../track';
20
-
21
- const originalFetch = globalThis.fetch;
22
-
23
- describe('track buffer', () => {
24
- beforeEach(() => {
25
- resetConfig();
26
- __resetTrackForTests();
27
- setConfig({
28
- token: 'st_pk_test',
29
- release: 'app@1.0.0+1',
30
- environment: 'test',
31
- ingestUrl: 'http://localhost:8080',
32
- enabled: true,
33
- });
34
- });
35
-
36
- afterEach(() => {
37
- globalThis.fetch = originalFetch;
38
- setUser(null);
39
- });
40
-
41
- it('buffers calls and tags them with release + environment', () => {
42
- track('checkout.started', { cart: 42 });
43
- track('$pageview', undefined, 'Cart');
44
- const buf = __peekTrackBuffer();
45
- expect(buf.length).toBe(2);
46
- expect(buf[0].name).toBe('checkout.started');
47
- expect(buf[0].release).toBe('app@1.0.0+1');
48
- expect(buf[0].environment).toBe('test');
49
- expect(buf[0].props).toEqual({ cart: 42 });
50
- expect(buf[1].name).toBe('$pageview');
51
- expect(buf[1].route).toBe('Cart');
52
- });
53
-
54
- it('attaches the current user id when set', () => {
55
- setUser({ id: 'u_abc' });
56
- track('signed_in');
57
- const buf = __peekTrackBuffer();
58
- expect(buf[0].userId).toBe('u_abc');
59
- });
60
-
61
- it('drops oversized names + over-cap prop bags silently', () => {
62
- track('x'.repeat(201));
63
- const tooManyProps: Record<string, number> = {};
64
- for (let i = 0; i < 41; i += 1) tooManyProps[`k${i}`] = i;
65
- track('big-bag', tooManyProps);
66
- expect(__peekTrackBuffer().length).toBe(0);
67
- });
68
-
69
- it('flushTrack drains the buffer and POSTs the batch envelope', async () => {
70
- const calls: { body: string; url: string }[] = [];
71
- globalThis.fetch = mock(async (url: unknown, init: unknown) => {
72
- calls.push({
73
- body: String((init as { body?: unknown })?.body ?? ''),
74
- url: String(url),
75
- });
76
- return new Response('{}', { status: 202 });
77
- }) as unknown as typeof fetch;
78
-
79
- track('a');
80
- track('b');
81
- await flushTrack();
82
-
83
- expect(calls.length).toBe(1);
84
- expect(calls[0].url).toBe('http://localhost:8080/v1/track:batch');
85
- const parsed = JSON.parse(calls[0].body) as {
86
- events: Array<{ name: string }>;
87
- };
88
- expect(parsed.events.map((e) => e.name)).toEqual(['a', 'b']);
89
- expect(__peekTrackBuffer().length).toBe(0);
90
- });
91
- });
@@ -1,41 +0,0 @@
1
- import { describe, it, expect } from 'bun:test';
2
-
3
- import { uuidV7 } from '../uuid';
4
-
5
- describe('uuidV7', () => {
6
- it('produces a 36-char hyphenated UUID with version 7', () => {
7
- const u = uuidV7();
8
- expect(u).toMatch(
9
- /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
10
- );
11
- });
12
-
13
- it('encodes the current ms timestamp in the leading 48 bits', () => {
14
- const before = Date.now();
15
- const u = uuidV7();
16
- const after = Date.now();
17
- const tsHex = u.replace(/-/g, '').slice(0, 12);
18
- const ts = parseInt(tsHex, 16);
19
- expect(ts).toBeGreaterThanOrEqual(before);
20
- expect(ts).toBeLessThanOrEqual(after);
21
- });
22
-
23
- it('produces unique values across rapid calls', () => {
24
- const seen = new Set<string>();
25
- for (let i = 0; i < 1000; i++) seen.add(uuidV7());
26
- expect(seen.size).toBe(1000);
27
- });
28
-
29
- it('always sets version 7 nibble', () => {
30
- for (let i = 0; i < 100; i++) {
31
- expect(uuidV7().charAt(14)).toBe('7');
32
- }
33
- });
34
-
35
- it('always sets variant to 10xx', () => {
36
- for (let i = 0; i < 100; i++) {
37
- const ch = uuidV7().charAt(19).toLowerCase();
38
- expect('89ab'.includes(ch)).toBe(true);
39
- }
40
- });
41
- });
@@ -1,57 +0,0 @@
1
- // v0.9.8 — RN SDK now owns its own BreadcrumbBuffer instance.
2
- //
3
- // Previous behaviour delegated to sentori-core's module-scoped
4
- // `_global` buffer. That was fine on Node / bun (single module
5
- // instance), but Metro's CJS/ESM interop on react-native can
6
- // instantiate `@goliapkg/sentori-core` twice when the host app and
7
- // the SDK both resolve it — the handler writes to one `_global` and
8
- // `capture.ts` reads from the other, leaving every event with
9
- // `breadcrumbs: []` (Insight 2026-05-16 report).
10
- //
11
- // Keeping the buffer local to the RN SDK is the smallest fix that
12
- // guarantees a single instance regardless of how the bundler
13
- // resolves sentori-core. addBreadcrumb / getBreadcrumbs / clearBreadcrumbs
14
- // are now self-contained.
15
- import { BreadcrumbBuffer, logger } from '@goliapkg/sentori-core'
16
-
17
- import type { Breadcrumb, BreadcrumbType } from './types'
18
-
19
- declare const __DEV__: boolean | undefined
20
-
21
- export type AddBreadcrumbInput = {
22
- data: Record<string, unknown>
23
- timestamp?: string
24
- type: BreadcrumbType
25
- }
26
-
27
- const _local = new BreadcrumbBuffer()
28
-
29
- export const addBreadcrumb = (input: AddBreadcrumbInput): void => {
30
- _local.push(input.type, input.data)
31
- if (input.timestamp) {
32
- // Override the auto-stamped `now()` with the caller's value. Rare
33
- // path; most callers omit timestamp.
34
- const last = _local.snapshot().at(-1)
35
- if (last) last.timestamp = input.timestamp
36
- }
37
- }
38
-
39
- export const getBreadcrumbs = (): Breadcrumb[] => _local.snapshot()
40
-
41
- export const clearBreadcrumbs = (): void => {
42
- _local.clear()
43
- }
44
-
45
- export const __resetForTests = (): void => clearBreadcrumbs()
46
-
47
- /** v0.9.8 — `__DEV__`-gated peek used by `captureException` to log
48
- * diagnostic counts to Metro. Production builds never see this. */
49
- export const __peekBreadcrumbCount = (): number => _local.snapshot().length
50
-
51
- /** Surface-area helper that the SDK uses internally to drop diagnostic
52
- * breadcrumbs without leaking the object-form API. Same target buffer
53
- * as the public `addBreadcrumb`. */
54
- export const addInternalBreadcrumb = (type: BreadcrumbType, data: Record<string, unknown>): void => {
55
- _local.push(type, data)
56
- logger.debug('breadcrumb', type, data)
57
- }
@@ -1,95 +0,0 @@
1
- // v0.9.0 #10 — EAS Update / CodePush awareness.
2
- //
3
- // At capture time we want to know which JS bundle the user is running:
4
- // it's almost always an OTA update rather than the binary `release`,
5
- // and crash spikes correlate to a specific bundle id rather than the
6
- // app version. We try `expo-updates` first (EAS), then `react-native-
7
- // code-push`, then nothing. All access is `require()`-shielded so the
8
- // SDK still works when neither is installed.
9
- //
10
- // Cached at module load — bundle id doesn't change mid-session in
11
- // either Expo or CodePush.
12
-
13
- export type BundleInfo = {
14
- /** Stable identifier — Expo `updateId` or CodePush `label`. */
15
- id: string;
16
- /** When the bundle was published. RFC 3339. */
17
- deployedAt?: string;
18
- /** Which OTA system reported it. */
19
- source: 'codepush' | 'expo';
20
- };
21
-
22
- let _cached: BundleInfo | null | undefined;
23
-
24
- export function getBundleInfo(): BundleInfo | null {
25
- if (_cached !== undefined) return _cached;
26
- _cached = detect();
27
- return _cached;
28
- }
29
-
30
- function detect(): BundleInfo | null {
31
- // Expo Updates first — most modern RN deployments are on EAS Update.
32
- try {
33
- // eslint-disable-next-line @typescript-eslint/no-require-imports
34
- const Updates = require('expo-updates') as {
35
- commitTime?: Date | null;
36
- isEmbeddedLaunch?: boolean;
37
- manifest?: { id?: string; createdAt?: string };
38
- updateId?: null | string;
39
- };
40
- const id = Updates.updateId ?? Updates.manifest?.id;
41
- if (typeof id === 'string' && id.length > 0) {
42
- const deployedAt = pickDeployedAt(Updates);
43
- return { deployedAt, id, source: 'expo' };
44
- }
45
- } catch {
46
- // expo-updates not installed
47
- }
48
- // CodePush fallback.
49
- try {
50
- // eslint-disable-next-line @typescript-eslint/no-require-imports
51
- const cp = require('react-native-code-push') as {
52
- getCurrentPackage?: () => Promise<{
53
- appVersion?: string;
54
- label?: string;
55
- packageHash?: string;
56
- } | null>;
57
- };
58
- // `getCurrentPackage` is async; we don't block init. Schedule a
59
- // background fetch + populate the cache. First-event-after-init
60
- // may miss the bundle id; subsequent events will have it.
61
- if (typeof cp.getCurrentPackage === 'function') {
62
- void cp
63
- .getCurrentPackage()
64
- .then((pkg) => {
65
- if (pkg && (pkg.label || pkg.packageHash)) {
66
- _cached = {
67
- id: pkg.label ?? pkg.packageHash!,
68
- source: 'codepush',
69
- };
70
- }
71
- })
72
- .catch(() => {
73
- // ignore
74
- });
75
- }
76
- } catch {
77
- // not installed
78
- }
79
- return null;
80
- }
81
-
82
- function pickDeployedAt(u: {
83
- commitTime?: Date | null;
84
- manifest?: { createdAt?: string };
85
- }): string | undefined {
86
- if (u.commitTime instanceof Date) return u.commitTime.toISOString();
87
- const ts = u.manifest?.createdAt;
88
- if (typeof ts === 'string') return ts;
89
- return undefined;
90
- }
91
-
92
- /** Test-only — reset cache. */
93
- export function __resetBundleInfoForTests(): void {
94
- _cached = undefined;
95
- }