@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.
- package/MIGRATION.md +131 -0
- package/README.md +40 -30
- package/android/build.gradle +21 -0
- package/android/src/main/java/com/sentori/SentoriCrashHandler.kt +29 -0
- package/android/src/main/java/com/sentori/SentoriModule.kt +8 -0
- package/android/src/main/java/com/sentori/SentoriScreenshotCapture.kt +36 -4
- package/android/src/test/java/com/sentori/SentoriCrashHandlerTest.kt +7 -2
- package/android/src/test/resources/robolectric.properties +10 -0
- package/ios/SentoriCrashHandler.swift +48 -4
- package/ios/SentoriModule.swift +12 -0
- package/ios/SentoriScreenshotCapture.swift +34 -3
- package/lib/config.d.ts +17 -47
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +3 -0
- package/lib/config.js.map +1 -1
- package/lib/device.d.ts +5 -0
- package/lib/device.d.ts.map +1 -0
- package/lib/device.js +29 -0
- package/lib/device.js.map +1 -0
- package/lib/emit-hooks.d.ts +7 -0
- package/lib/emit-hooks.d.ts.map +1 -0
- package/lib/emit-hooks.js +24 -0
- package/lib/emit-hooks.js.map +1 -0
- package/lib/error-boundary.d.ts.map +1 -1
- package/lib/error-boundary.js +1 -1
- package/lib/error-boundary.js.map +1 -1
- package/lib/feature-flags.d.ts.map +1 -1
- package/lib/handlers/dev-symbolicate.d.ts +1 -1
- package/lib/handlers/dev-symbolicate.d.ts.map +1 -1
- package/lib/handlers/dev-symbolicate.js +2 -0
- package/lib/handlers/dev-symbolicate.js.map +1 -1
- package/lib/handlers/global.js +1 -1
- package/lib/handlers/global.js.map +1 -1
- package/lib/handlers/lifecycle.js +3 -3
- package/lib/handlers/lifecycle.js.map +1 -1
- package/lib/handlers/network.d.ts +2 -2
- package/lib/handlers/network.d.ts.map +1 -1
- package/lib/handlers/network.js +69 -187
- package/lib/handlers/network.js.map +1 -1
- package/lib/handlers/promise.js +1 -1
- package/lib/handlers/promise.js.map +1 -1
- package/lib/handlers/screenshot.d.ts.map +1 -1
- package/lib/handlers/screenshot.js +3 -12
- package/lib/handlers/screenshot.js.map +1 -1
- package/lib/index.d.ts +32 -109
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +54 -103
- package/lib/index.js.map +1 -1
- package/lib/init.d.ts +3 -150
- package/lib/init.d.ts.map +1 -1
- package/lib/init.js +100 -261
- package/lib/init.js.map +1 -1
- package/lib/long-task-monitor.d.ts +1 -7
- package/lib/long-task-monitor.d.ts.map +1 -1
- package/lib/long-task-monitor.js +37 -58
- package/lib/long-task-monitor.js.map +1 -1
- package/lib/mask.d.ts +5 -13
- package/lib/mask.d.ts.map +1 -1
- package/lib/mask.js +29 -30
- package/lib/mask.js.map +1 -1
- package/lib/mobile-vitals.d.ts +3 -31
- package/lib/mobile-vitals.d.ts.map +1 -1
- package/lib/mobile-vitals.js +22 -61
- package/lib/mobile-vitals.js.map +1 -1
- package/lib/native-pending.d.ts +3 -0
- package/lib/native-pending.d.ts.map +1 -0
- package/lib/native-pending.js +64 -0
- package/lib/native-pending.js.map +1 -0
- package/lib/native.d.ts +4 -11
- package/lib/native.d.ts.map +1 -1
- package/lib/native.js +23 -0
- package/lib/native.js.map +1 -1
- package/lib/navigation.d.ts +3 -17
- package/lib/navigation.d.ts.map +1 -1
- package/lib/navigation.js +32 -133
- package/lib/navigation.js.map +1 -1
- package/lib/netinfo.d.ts +1 -1
- package/lib/netinfo.d.ts.map +1 -1
- package/lib/netinfo.js +2 -2
- package/lib/netinfo.js.map +1 -1
- package/lib/push.d.ts.map +1 -1
- package/lib/push.js +2 -4
- package/lib/push.js.map +1 -1
- package/lib/rage-tap.d.ts.map +1 -1
- package/lib/rage-tap.js +56 -23
- package/lib/rage-tap.js.map +1 -1
- package/lib/replay-screens.d.ts +15 -0
- package/lib/replay-screens.d.ts.map +1 -0
- package/lib/replay-screens.js +87 -0
- package/lib/replay-screens.js.map +1 -0
- package/lib/replay.d.ts.map +1 -1
- package/lib/replay.js +3 -24
- package/lib/replay.js.map +1 -1
- package/lib/responsiveness-detector.d.ts +18 -0
- package/lib/responsiveness-detector.d.ts.map +1 -0
- package/lib/responsiveness-detector.js +55 -0
- package/lib/responsiveness-detector.js.map +1 -0
- package/lib/scope.d.ts +7 -0
- package/lib/scope.d.ts.map +1 -0
- package/lib/scope.js +41 -0
- package/lib/scope.js.map +1 -0
- package/lib/transport.d.ts +19 -92
- package/lib/transport.d.ts.map +1 -1
- package/lib/transport.js +75 -288
- package/lib/transport.js.map +1 -1
- package/lib/verbs.d.ts +16 -0
- package/lib/verbs.d.ts.map +1 -0
- package/lib/verbs.js +153 -0
- package/lib/verbs.js.map +1 -0
- package/package.json +4 -3
- package/src/__tests__/iron-rule.test.ts +128 -0
- package/src/__tests__/replay-screens.test.ts +63 -0
- package/src/__tests__/responsiveness-detector.test.ts +74 -0
- package/src/__tests__/screenshot.test.ts +0 -2
- package/src/__tests__/transport.test.ts +66 -213
- package/src/__tests__/verbs.test.ts +91 -0
- package/src/config.ts +20 -47
- package/src/device.ts +36 -0
- package/src/emit-hooks.ts +30 -0
- package/src/error-boundary.tsx +1 -1
- package/src/handlers/dev-symbolicate.ts +2 -1
- package/src/handlers/global.ts +1 -1
- package/src/handlers/lifecycle.ts +3 -3
- package/src/handlers/network.ts +95 -211
- package/src/handlers/promise.ts +1 -1
- package/src/handlers/screenshot.ts +3 -11
- package/src/index.ts +84 -215
- package/src/init.ts +115 -449
- package/src/long-task-monitor.ts +42 -64
- package/src/mask.ts +29 -31
- package/src/mobile-vitals.ts +22 -82
- package/src/native-pending.ts +92 -0
- package/src/native.ts +33 -0
- package/src/navigation.ts +34 -142
- package/src/netinfo.ts +3 -3
- package/src/push.ts +2 -5
- package/src/rage-tap.tsx +72 -25
- package/src/replay-screens.ts +95 -0
- package/src/replay.ts +3 -20
- package/src/responsiveness-detector.ts +66 -0
- package/src/scope.ts +50 -0
- package/src/transport.ts +84 -371
- package/src/verbs.ts +198 -0
- package/lib/breadcrumbs.d.ts +0 -18
- package/lib/breadcrumbs.d.ts.map +0 -1
- package/lib/breadcrumbs.js +0 -42
- package/lib/breadcrumbs.js.map +0 -1
- package/lib/bundle-info.d.ts +0 -12
- package/lib/bundle-info.d.ts.map +0 -1
- package/lib/bundle-info.js +0 -73
- package/lib/bundle-info.js.map +0 -1
- package/lib/capture.d.ts +0 -140
- package/lib/capture.d.ts.map +0 -1
- package/lib/capture.js +0 -511
- package/lib/capture.js.map +0 -1
- package/lib/compat/sentry.d.ts +0 -126
- package/lib/compat/sentry.d.ts.map +0 -1
- package/lib/compat/sentry.js +0 -332
- package/lib/compat/sentry.js.map +0 -1
- package/lib/control-channel.d.ts +0 -6
- package/lib/control-channel.d.ts.map +0 -1
- package/lib/control-channel.js +0 -96
- package/lib/control-channel.js.map +0 -1
- package/lib/expo-compat.d.ts +0 -270
- package/lib/expo-compat.d.ts.map +0 -1
- package/lib/expo-compat.js +0 -500
- package/lib/expo-compat.js.map +0 -1
- package/lib/feedback-widget.d.ts +0 -35
- package/lib/feedback-widget.d.ts.map +0 -1
- package/lib/feedback-widget.js +0 -193
- package/lib/feedback-widget.js.map +0 -1
- package/lib/feedback.d.ts +0 -2
- package/lib/feedback.d.ts.map +0 -1
- package/lib/feedback.js +0 -17
- package/lib/feedback.js.map +0 -1
- package/lib/heartbeat.d.ts +0 -9
- package/lib/heartbeat.d.ts.map +0 -1
- package/lib/heartbeat.js +0 -138
- package/lib/heartbeat.js.map +0 -1
- package/lib/index.js.bak +0 -64
- package/lib/install-id.d.ts +0 -17
- package/lib/install-id.d.ts.map +0 -1
- package/lib/install-id.js +0 -125
- package/lib/install-id.js.map +0 -1
- package/lib/lifecycle.d.ts +0 -28
- package/lib/lifecycle.d.ts.map +0 -1
- package/lib/lifecycle.js +0 -73
- package/lib/lifecycle.js.map +0 -1
- package/lib/measure.d.ts +0 -4
- package/lib/measure.d.ts.map +0 -1
- package/lib/measure.js +0 -25
- package/lib/measure.js.map +0 -1
- package/lib/metrics.d.ts +0 -24
- package/lib/metrics.d.ts.map +0 -1
- package/lib/metrics.js +0 -81
- package/lib/metrics.js.map +0 -1
- package/lib/pre-crash-sentinel.d.ts +0 -12
- package/lib/pre-crash-sentinel.d.ts.map +0 -1
- package/lib/pre-crash-sentinel.js +0 -116
- package/lib/pre-crash-sentinel.js.map +0 -1
- package/lib/report-security.d.ts +0 -40
- package/lib/report-security.d.ts.map +0 -1
- package/lib/report-security.js +0 -159
- package/lib/report-security.js.map +0 -1
- package/lib/runtime-metrics-fps.d.ts +0 -19
- package/lib/runtime-metrics-fps.d.ts.map +0 -1
- package/lib/runtime-metrics-fps.js +0 -128
- package/lib/runtime-metrics-fps.js.map +0 -1
- package/lib/runtime-metrics-heap.d.ts +0 -13
- package/lib/runtime-metrics-heap.d.ts.map +0 -1
- package/lib/runtime-metrics-heap.js +0 -67
- package/lib/runtime-metrics-heap.js.map +0 -1
- package/lib/runtime-metrics-network.d.ts +0 -23
- package/lib/runtime-metrics-network.d.ts.map +0 -1
- package/lib/runtime-metrics-network.js +0 -99
- package/lib/runtime-metrics-network.js.map +0 -1
- package/lib/runtime-metrics.d.ts +0 -43
- package/lib/runtime-metrics.d.ts.map +0 -1
- package/lib/runtime-metrics.js +0 -115
- package/lib/runtime-metrics.js.map +0 -1
- package/lib/sample-profiler.d.ts +0 -21
- package/lib/sample-profiler.d.ts.map +0 -1
- package/lib/sample-profiler.js +0 -152
- package/lib/sample-profiler.js.map +0 -1
- package/lib/sentinel-context.d.ts +0 -4
- package/lib/sentinel-context.d.ts.map +0 -1
- package/lib/sentinel-context.js +0 -52
- package/lib/sentinel-context.js.map +0 -1
- package/lib/session-tracker.d.ts +0 -6
- package/lib/session-tracker.d.ts.map +0 -1
- package/lib/session-tracker.js +0 -50
- package/lib/session-tracker.js.map +0 -1
- package/lib/stack.d.ts +0 -3
- package/lib/stack.d.ts.map +0 -1
- package/lib/stack.js +0 -7
- package/lib/stack.js.map +0 -1
- package/lib/state-snapshots.d.ts +0 -33
- package/lib/state-snapshots.d.ts.map +0 -1
- package/lib/state-snapshots.js +0 -109
- package/lib/state-snapshots.js.map +0 -1
- package/lib/track.d.ts +0 -34
- package/lib/track.d.ts.map +0 -1
- package/lib/track.js +0 -107
- package/lib/track.js.map +0 -1
- package/lib/trail.d.ts +0 -12
- package/lib/trail.d.ts.map +0 -1
- package/lib/trail.js +0 -28
- package/lib/trail.js.map +0 -1
- package/lib/trust-score.d.ts +0 -20
- package/lib/trust-score.d.ts.map +0 -1
- package/lib/trust-score.js +0 -151
- package/lib/trust-score.js.map +0 -1
- package/lib/types.d.ts +0 -2
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -4
- package/lib/types.js.map +0 -1
- package/lib/uuid.d.ts +0 -2
- package/lib/uuid.d.ts.map +0 -1
- package/lib/uuid.js +0 -3
- package/lib/uuid.js.map +0 -1
- package/src/__tests__/before-send.test.ts +0 -72
- package/src/__tests__/breadcrumbs.test.ts +0 -44
- package/src/__tests__/capture-replay.test.ts +0 -150
- package/src/__tests__/compat-sentry.test.ts +0 -121
- package/src/__tests__/control-channel.test.ts +0 -111
- package/src/__tests__/install-id.test.ts +0 -60
- package/src/__tests__/long-task-monitor.test.ts +0 -59
- package/src/__tests__/measure.test.ts +0 -45
- package/src/__tests__/navigation.test.ts +0 -175
- package/src/__tests__/push.test.ts +0 -238
- package/src/__tests__/report-security.test.ts +0 -106
- package/src/__tests__/runtime-metrics-instruments.test.ts +0 -109
- package/src/__tests__/runtime-metrics-network.test.ts +0 -78
- package/src/__tests__/sample-profiler.test.ts +0 -59
- package/src/__tests__/stack.test.ts +0 -45
- package/src/__tests__/state-snapshots.test.ts +0 -119
- package/src/__tests__/tracing.test.ts +0 -201
- package/src/__tests__/track.test.ts +0 -91
- package/src/__tests__/uuid.test.ts +0 -41
- package/src/breadcrumbs.ts +0 -57
- package/src/bundle-info.ts +0 -95
- package/src/capture.ts +0 -660
- package/src/compat/sentry.ts +0 -490
- package/src/control-channel.ts +0 -98
- package/src/expo-compat.ts +0 -698
- package/src/feedback-widget.tsx +0 -316
- package/src/feedback.ts +0 -21
- package/src/heartbeat.ts +0 -157
- package/src/install-id.ts +0 -146
- package/src/lifecycle.ts +0 -76
- package/src/measure.ts +0 -28
- package/src/metrics.ts +0 -94
- package/src/pre-crash-sentinel.ts +0 -140
- package/src/report-security.ts +0 -165
- package/src/runtime-metrics-fps.ts +0 -137
- package/src/runtime-metrics-heap.ts +0 -78
- package/src/runtime-metrics-network.ts +0 -103
- package/src/runtime-metrics.ts +0 -122
- package/src/sample-profiler.ts +0 -169
- package/src/sentinel-context.ts +0 -60
- package/src/session-tracker.ts +0 -53
- package/src/stack.ts +0 -10
- package/src/state-snapshots.ts +0 -133
- package/src/track.ts +0 -123
- package/src/trail.ts +0 -32
- package/src/trust-score.ts +0 -176
- package/src/types.ts +0 -21
- 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
|
-
});
|
package/src/breadcrumbs.ts
DELETED
|
@@ -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
|
-
}
|
package/src/bundle-info.ts
DELETED
|
@@ -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
|
-
}
|