@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
package/lib/verbs.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// The five event verbs (design.md §4). Everything here is
|
|
2
|
+
// synchronous, never throws, and returns the client-minted event id
|
|
3
|
+
// — the zero-cost iron rule made code.
|
|
4
|
+
//
|
|
5
|
+
// sentori.error(err) 出了什么事?
|
|
6
|
+
// sentori.warn(name) 用户哪里不舒服?
|
|
7
|
+
// sentori.trace(name) 这里发生了什么?
|
|
8
|
+
// sentori.assert(n, ok) 这里应该成立吗?
|
|
9
|
+
// sentori.probe(ref) 那个 bug 回来了吗?
|
|
10
|
+
import { coerceError, parseStack, pushSignal, safeFn, snapshotSignals, uuidV7, } from '@goliapkg/sentori-core';
|
|
11
|
+
import { getConfig } from './config';
|
|
12
|
+
import { collectDevice } from './device';
|
|
13
|
+
import { onEventEmitted } from './emit-hooks';
|
|
14
|
+
import { currentContext, currentUserKey } from './scope';
|
|
15
|
+
import { countAssert, enqueue } from './transport';
|
|
16
|
+
/** Serialize any Error instances found in the data argument — the
|
|
17
|
+
* error-in-data convention (design.md §4): a caught-but-noteworthy
|
|
18
|
+
* exception needs no special API. One level deep is enough; nested
|
|
19
|
+
* containers of errors are an anti-pattern we don't reward. */
|
|
20
|
+
const serializeData = (data) => {
|
|
21
|
+
if (!data)
|
|
22
|
+
return undefined;
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [k, v] of Object.entries(data)) {
|
|
25
|
+
out[k] = v instanceof Error ? toSentoriError(v) : v;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
};
|
|
29
|
+
const toSentoriError = (e) => ({
|
|
30
|
+
type: e.name || 'Error',
|
|
31
|
+
message: e.message,
|
|
32
|
+
stack: parseStack(e.stack),
|
|
33
|
+
cause: e.cause instanceof Error
|
|
34
|
+
? toSentoriError(e.cause)
|
|
35
|
+
: null,
|
|
36
|
+
});
|
|
37
|
+
const platformOf = () => {
|
|
38
|
+
try {
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
|
+
const RN = require('react-native');
|
|
41
|
+
const os = RN.Platform?.OS;
|
|
42
|
+
if (os === 'ios')
|
|
43
|
+
return 'ios';
|
|
44
|
+
if (os === 'android')
|
|
45
|
+
return 'android';
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// headless / test environment
|
|
49
|
+
}
|
|
50
|
+
return 'javascript';
|
|
51
|
+
};
|
|
52
|
+
/** Assemble + enqueue one event. The single funnel every verb uses. */
|
|
53
|
+
const emit = (kind, opts) => {
|
|
54
|
+
const id = uuidV7();
|
|
55
|
+
const config = getConfig();
|
|
56
|
+
if (!config || !config.enabled)
|
|
57
|
+
return id; // no-op before init — iron rule
|
|
58
|
+
const payload = {};
|
|
59
|
+
if (opts.error)
|
|
60
|
+
payload.error = opts.error;
|
|
61
|
+
const data = serializeData(opts.data);
|
|
62
|
+
if (data)
|
|
63
|
+
payload.data = data;
|
|
64
|
+
const ctx = currentContext();
|
|
65
|
+
if (ctx)
|
|
66
|
+
payload.context = ctx;
|
|
67
|
+
if (opts.withSignals) {
|
|
68
|
+
const signals = snapshotSignals();
|
|
69
|
+
if (signals.length > 0)
|
|
70
|
+
payload.signals = signals;
|
|
71
|
+
}
|
|
72
|
+
const device = collectDevice();
|
|
73
|
+
if (device)
|
|
74
|
+
payload.device = device;
|
|
75
|
+
let event = {
|
|
76
|
+
id,
|
|
77
|
+
kind,
|
|
78
|
+
occurredAt: new Date().toISOString(),
|
|
79
|
+
platform: platformOf(),
|
|
80
|
+
release: config.release,
|
|
81
|
+
environment: config.environment,
|
|
82
|
+
name: opts.name,
|
|
83
|
+
surface: opts.surface,
|
|
84
|
+
userKey: currentUserKey(),
|
|
85
|
+
payload,
|
|
86
|
+
};
|
|
87
|
+
if (config.beforeSend) {
|
|
88
|
+
try {
|
|
89
|
+
const out = config.beforeSend(event);
|
|
90
|
+
if (out === null)
|
|
91
|
+
return id; // deliberate drop
|
|
92
|
+
if (out && typeof out === 'object')
|
|
93
|
+
event = out;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Hook broke: the un-mutated event ships. The host's bug must
|
|
97
|
+
// not cost them the crash report.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
enqueue(event);
|
|
101
|
+
onEventEmitted(event);
|
|
102
|
+
return id;
|
|
103
|
+
};
|
|
104
|
+
// ── the verbs ──────────────────────────────────────────────────────
|
|
105
|
+
export const error = safeFn('error', (err, data) => {
|
|
106
|
+
const coerced = coerceError(err);
|
|
107
|
+
return emit('error', {
|
|
108
|
+
error: toSentoriError(coerced),
|
|
109
|
+
data,
|
|
110
|
+
withSignals: true,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
export const warn = safeFn('warn', (name, data) => {
|
|
114
|
+
// A hand-written warn's surface can ride in data.surface; the
|
|
115
|
+
// detected scenarios pass it explicitly via warnDetected.
|
|
116
|
+
const surface = data && typeof data.surface === 'object'
|
|
117
|
+
? data.surface
|
|
118
|
+
: undefined;
|
|
119
|
+
return emit('warn', { name, surface, data, withSignals: true });
|
|
120
|
+
});
|
|
121
|
+
/** SDK-internal: a detected warn scenario with an explicit surface. */
|
|
122
|
+
export const warnDetected = (scenario, surface, data) => emit('warn', { name: scenario, surface, data, withSignals: true });
|
|
123
|
+
export const trace = safeFn('trace', (name, data, opts) => {
|
|
124
|
+
// Every trace is context: it lands in the ring regardless.
|
|
125
|
+
pushSignal('trace', { name, ...(data ?? {}) });
|
|
126
|
+
if (opts?.quiet)
|
|
127
|
+
return uuidV7();
|
|
128
|
+
return emit('trace', { name, data });
|
|
129
|
+
});
|
|
130
|
+
export const assert = safeFn('assert', (name, ok, data) => {
|
|
131
|
+
const config = getConfig();
|
|
132
|
+
if (config)
|
|
133
|
+
countAssert(name, ok, config.release);
|
|
134
|
+
if (ok)
|
|
135
|
+
return uuidV7(); // passes aggregate; they are never events
|
|
136
|
+
return emit('assert', { name, data, withSignals: true });
|
|
137
|
+
});
|
|
138
|
+
export const probe = safeFn('probe', (ref, data) => {
|
|
139
|
+
// A tripwire: reaching this call IS the signal. Never throws,
|
|
140
|
+
// never changes control flow (design.md §4).
|
|
141
|
+
return emit('probe', { name: ref, data });
|
|
142
|
+
});
|
|
143
|
+
// safeFn returns `R | undefined`; the public surface promises a
|
|
144
|
+
// string. An internal failure mints an id that simply never ships —
|
|
145
|
+
// honest enough, and the host never sees a throw.
|
|
146
|
+
export const verbs = {
|
|
147
|
+
error: (err, data) => error(err, data) ?? uuidV7(),
|
|
148
|
+
warn: (name, data) => warn(name, data) ?? uuidV7(),
|
|
149
|
+
trace: (name, data, opts) => trace(name, data, opts) ?? uuidV7(),
|
|
150
|
+
assert: (name, ok, data) => assert(name, ok, data) ?? uuidV7(),
|
|
151
|
+
probe: (ref, data) => probe(ref, data) ?? uuidV7(),
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=verbs.js.map
|
package/lib/verbs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verbs.js","sourceRoot":"","sources":["../src/verbs.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,oEAAoE;AACpE,uCAAuC;AACvC,EAAE;AACF,iCAAiC;AACjC,mCAAmC;AACnC,mCAAmC;AACnC,mCAAmC;AACnC,uCAAuC;AAEvC,OAAO,EACL,WAAW,EACX,UAAU,EACV,UAAU,EACV,MAAM,EACN,eAAe,EACf,MAAM,GACP,MAAM,wBAAwB,CAAC;AAWhC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEnD;;;gEAGgE;AAChE,MAAM,aAAa,GAAG,CAAC,IAAgB,EAAuC,EAAE;IAC9E,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,CAAQ,EAAgB,EAAE,CAAC,CAAC;IAClD,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,OAAO;IACvB,OAAO,EAAE,CAAC,CAAC,OAAO;IAClB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1B,KAAK,EACH,CAAC,CAAC,KAAK,YAAY,KAAK;QACtB,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC;QACzB,CAAC,CAAC,IAAI;CACX,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,GAAqC,EAAE;IACxD,IAAI,CAAC;QACH,iEAAiE;QACjE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAmC,CAAC;QACrE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;QAC3B,IAAI,EAAE,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC/B,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,8BAA8B;IAChC,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AAYF,uEAAuE;AACvE,MAAM,IAAI,GAAG,CAAC,IAAe,EAAE,IAAiB,EAAU,EAAE;IAC1D,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC,CAAC,gCAAgC;IAE3E,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAC3C,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAC9B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,IAAI,GAAG;QAAE,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC;IAC/B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;IACpD,CAAC;IACD,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IAEpC,IAAI,KAAK,GAAc;QACrB,EAAE;QACF,IAAI;QACJ,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,QAAQ,EAAE,UAAU,EAAE;QACtB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,OAAO,EAAE,cAAc,EAAE;QACzB,OAAO;KACR,CAAC;IAEF,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACrC,IAAI,GAAG,KAAK,IAAI;gBAAE,OAAO,EAAE,CAAC,CAAC,kBAAkB;YAC/C,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,KAAK,GAAG,GAAG,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;YAC9D,kCAAkC;QACpC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,CAAC;IACf,cAAc,CAAC,KAAK,CAAC,CAAC;IACtB,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,sEAAsE;AAEtE,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,GAAY,EAAE,IAAgB,EAAU,EAAE;IAC9E,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC,OAAO,EAAE;QACnB,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC;QAC9B,IAAI;QACJ,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CACxB,MAAM,EACN,CAAC,IAAY,EAAE,IAAgB,EAAU,EAAE;IACzC,8DAA8D;IAC9D,0DAA0D;IAC1D,MAAM,OAAO,GACX,IAAI,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;QACtC,CAAC,CAAE,IAAI,CAAC,OAAmB;QAC3B,CAAC,CAAC,SAAS,CAAC;IAChB,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC,CACF,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,QAAgB,EAChB,OAAgB,EAChB,IAAgB,EACR,EAAE,CACV,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AAErE,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CACzB,OAAO,EACP,CAAC,IAAY,EAAE,IAAgB,EAAE,IAAmB,EAAU,EAAE;IAC9D,2DAA2D;IAC3D,UAAU,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,IAAI,IAAI,EAAE,KAAK;QAAE,OAAO,MAAM,EAAE,CAAC;IACjC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACvC,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAC1B,QAAQ,EACR,CAAC,IAAY,EAAE,EAAW,EAAE,IAAgB,EAAU,EAAE;IACtD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,MAAM;QAAE,WAAW,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,EAAE;QAAE,OAAO,MAAM,EAAE,CAAC,CAAC,0CAA0C;IACnE,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AAC3D,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,GAAW,EAAE,IAAgB,EAAU,EAAE;IAC7E,8DAA8D;IAC9D,6CAA6C;IAC7C,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,gEAAgE;AAChE,oEAAoE;AACpE,kDAAkD;AAClD,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK,EAAE,CAAC,GAAY,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE;IAC/E,IAAI,EAAE,CAAC,IAAY,EAAE,IAAgB,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE;IAC9E,KAAK,EAAE,CAAC,IAAY,EAAE,IAAgB,EAAE,IAAmB,EAAU,EAAE,CACrE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE;IACrC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAW,EAAE,IAAgB,EAAU,EAAE,CAC9D,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE;IACpC,KAAK,EAAE,CAAC,GAAW,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE;CAC/E,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-react-native",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Sentori SDK for React Native — JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"author": "GOLIA K.K. <takagi@golia.jp> (https://golia.jp)",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"sentori-rn-upload-source-bundle": "bin/sentori-rn-upload-source-bundle.cjs"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
|
+
"MIGRATION.md",
|
|
49
50
|
"lib/",
|
|
50
51
|
"src/",
|
|
51
52
|
"bin/",
|
|
@@ -90,12 +91,12 @@
|
|
|
90
91
|
"devDependencies": {
|
|
91
92
|
"@types/bun": "latest",
|
|
92
93
|
"@types/react": "^19",
|
|
93
|
-
"typescript": "^
|
|
94
|
+
"typescript": "^7"
|
|
94
95
|
},
|
|
95
96
|
"publishConfig": {
|
|
96
97
|
"access": "public"
|
|
97
98
|
},
|
|
98
99
|
"dependencies": {
|
|
99
|
-
"@goliapkg/sentori-core": "^1.
|
|
100
|
+
"@goliapkg/sentori-core": "^2.1.0"
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The zero-cost iron rule, as executable gates (design.md §6).
|
|
2
|
+
//
|
|
3
|
+
// Dimension 3 (failure isolation): no Sentori failure may ever
|
|
4
|
+
// throw into, block, or alter the host app. These tests inject the
|
|
5
|
+
// failures; a single uncaught throw fails the suite.
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
8
|
+
|
|
9
|
+
import { clearSignals } from '@goliapkg/sentori-core';
|
|
10
|
+
|
|
11
|
+
import { __resetForTests as resetConfig, setConfig } from '../config';
|
|
12
|
+
import { init, __resetForTests as resetInit } from '../init';
|
|
13
|
+
import { __resetForTests as resetScope, patchContext, setUser } from '../scope';
|
|
14
|
+
import {
|
|
15
|
+
__resetForTests as resetTransport,
|
|
16
|
+
flush,
|
|
17
|
+
startTransport,
|
|
18
|
+
} from '../transport';
|
|
19
|
+
import { verbs } from '../verbs';
|
|
20
|
+
|
|
21
|
+
const baseConfig = {
|
|
22
|
+
token: 'st_test',
|
|
23
|
+
ingestUrl: 'http://localhost:18080',
|
|
24
|
+
release: 'app@1.0.0',
|
|
25
|
+
environment: 'test',
|
|
26
|
+
enabled: true,
|
|
27
|
+
detect: { rageTap: true, longFreeze: true, slowColdStart: true, slowApi: false },
|
|
28
|
+
replaySeconds: 30,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const originalFetch = globalThis.fetch;
|
|
32
|
+
|
|
33
|
+
const resetAll = () => {
|
|
34
|
+
resetTransport();
|
|
35
|
+
resetConfig();
|
|
36
|
+
resetScope();
|
|
37
|
+
resetInit();
|
|
38
|
+
clearSignals();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe('iron rule: failure isolation', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
resetAll();
|
|
44
|
+
setConfig(baseConfig);
|
|
45
|
+
startTransport();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
globalThis.fetch = originalFetch;
|
|
49
|
+
resetAll();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('server 500 never reaches the caller', async () => {
|
|
53
|
+
globalThis.fetch = (async () => new Response('boom', { status: 500 })) as typeof fetch;
|
|
54
|
+
verbs.error(new Error('x'));
|
|
55
|
+
await flush(); // retries internally, persists, never rejects
|
|
56
|
+
expect(true).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('network down never reaches the caller', async () => {
|
|
60
|
+
globalThis.fetch = (async () => {
|
|
61
|
+
throw new TypeError('Network request failed');
|
|
62
|
+
}) as typeof fetch;
|
|
63
|
+
verbs.warn('rage_tap');
|
|
64
|
+
await flush();
|
|
65
|
+
expect(true).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('a hostile beforeSend cannot break the verb', () => {
|
|
69
|
+
setConfig({
|
|
70
|
+
...baseConfig,
|
|
71
|
+
beforeSend: () => {
|
|
72
|
+
throw new Error('host bug');
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const id = verbs.error(new Error('x'));
|
|
76
|
+
expect(typeof id).toBe('string');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('fuzz: the verbs accept garbage without throwing', () => {
|
|
80
|
+
const circular: Record<string, unknown> = {};
|
|
81
|
+
circular.self = circular;
|
|
82
|
+
const huge = 'x'.repeat(1_000_000);
|
|
83
|
+
|
|
84
|
+
expect(typeof verbs.error(null)).toBe('string');
|
|
85
|
+
expect(typeof verbs.error(undefined)).toBe('string');
|
|
86
|
+
expect(typeof verbs.error(42)).toBe('string');
|
|
87
|
+
expect(typeof verbs.error(circular)).toBe('string');
|
|
88
|
+
expect(typeof verbs.warn('', circular)).toBe('string');
|
|
89
|
+
expect(typeof verbs.warn(huge)).toBe('string');
|
|
90
|
+
expect(typeof verbs.trace('t', { huge })).toBe('string');
|
|
91
|
+
expect(typeof verbs.assert('a', false, circular)).toBe('string');
|
|
92
|
+
expect(typeof verbs.probe('p', { circular })).toBe('string');
|
|
93
|
+
setUser({ id: huge });
|
|
94
|
+
setUser(null);
|
|
95
|
+
patchContext(circular);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('every verb before init is a silent no-op returning an id', () => {
|
|
99
|
+
resetAll();
|
|
100
|
+
expect(typeof verbs.error(new Error('x'))).toBe('string');
|
|
101
|
+
expect(typeof verbs.warn('w')).toBe('string');
|
|
102
|
+
expect(typeof verbs.trace('t')).toBe('string');
|
|
103
|
+
expect(typeof verbs.assert('a', false)).toBe('string');
|
|
104
|
+
expect(typeof verbs.probe('p')).toBe('string');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('init with a broken config degrades to no-op, never throws', () => {
|
|
108
|
+
resetAll();
|
|
109
|
+
init(undefined as never);
|
|
110
|
+
init({} as never);
|
|
111
|
+
init({ token: '', ingestUrl: '' } as never);
|
|
112
|
+
expect(typeof verbs.error(new Error('x'))).toBe('string');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('iron rule: init timing', () => {
|
|
117
|
+
beforeEach(resetAll);
|
|
118
|
+
afterEach(resetAll);
|
|
119
|
+
|
|
120
|
+
it('init completes inside the 50 ms budget', () => {
|
|
121
|
+
const start = performance.now();
|
|
122
|
+
init(baseConfig);
|
|
123
|
+
const ms = performance.now() - start;
|
|
124
|
+
// The budget is for the synchronous path; native drains and the
|
|
125
|
+
// offline queue are fire-and-forget behind it.
|
|
126
|
+
expect(ms).toBeLessThan(50);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// The screens ring: capacity, drain shape, mask-query isolation,
|
|
2
|
+
// and graceful absence on an old native build.
|
|
3
|
+
|
|
4
|
+
import { afterEach, describe, expect, mock, test } from 'bun:test';
|
|
5
|
+
|
|
6
|
+
import { __resetForTests as resetMask, maskedNativeIds, registerMaskQuery } from '../mask';
|
|
7
|
+
import {
|
|
8
|
+
__resetForTests as resetRing,
|
|
9
|
+
__setCaptureForTests,
|
|
10
|
+
drainScreenReplay,
|
|
11
|
+
startScreenReplay,
|
|
12
|
+
} from '../replay-screens';
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
resetRing();
|
|
16
|
+
resetMask();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('mask query', () => {
|
|
20
|
+
test('unregistered → empty', () => {
|
|
21
|
+
expect(maskedNativeIds()).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('registered query flows through and filters junk', () => {
|
|
25
|
+
registerMaskQuery(() => ['camera-feed', 42 as unknown as string, 'user-email']);
|
|
26
|
+
expect(maskedNativeIds()).toEqual(['camera-feed', 'user-email']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('a throwing query masks nothing and never throws', () => {
|
|
30
|
+
registerMaskQuery(() => {
|
|
31
|
+
throw new Error('boom');
|
|
32
|
+
});
|
|
33
|
+
expect(maskedNativeIds()).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('screens ring', () => {
|
|
38
|
+
test('drain on an empty ring is null (old native builds)', () => {
|
|
39
|
+
startScreenReplay(60);
|
|
40
|
+
expect(drainScreenReplay()).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('drain emits NDJSON with negative relative timestamps', async () => {
|
|
44
|
+
let n = 0;
|
|
45
|
+
__setCaptureForTests(
|
|
46
|
+
mock(async () => ({ base64: `frame${(n += 1)}`, mediaType: 'image/jpeg' })),
|
|
47
|
+
);
|
|
48
|
+
startScreenReplay(60);
|
|
49
|
+
await new Promise((r) => setTimeout(r, 2_600));
|
|
50
|
+
const out = drainScreenReplay();
|
|
51
|
+
expect(out).not.toBeNull();
|
|
52
|
+
const lines = (out ?? '')
|
|
53
|
+
.split('\n')
|
|
54
|
+
.map((l) => JSON.parse(l) as { t: number; base64: string });
|
|
55
|
+
expect(lines.length).toBeGreaterThanOrEqual(1);
|
|
56
|
+
for (const l of lines) {
|
|
57
|
+
expect(l.t).toBeLessThanOrEqual(0);
|
|
58
|
+
expect(l.base64.startsWith('frame')).toBe(true);
|
|
59
|
+
}
|
|
60
|
+
// drain does not clear: a second error still sees the window
|
|
61
|
+
expect(drainScreenReplay()).not.toBeNull();
|
|
62
|
+
}, 10_000);
|
|
63
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// dead_button / sluggish_button verdict logic.
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DEAD_THRESHOLD,
|
|
7
|
+
DEAD_WINDOW_MS,
|
|
8
|
+
RESPONSE_WINDOW_MS,
|
|
9
|
+
SLUGGISH_COOLDOWN_MS,
|
|
10
|
+
SLUGGISH_MS,
|
|
11
|
+
classifyTap,
|
|
12
|
+
recordDeadTap,
|
|
13
|
+
recordSluggish,
|
|
14
|
+
} from '../responsiveness-detector';
|
|
15
|
+
|
|
16
|
+
describe('classifyTap', () => {
|
|
17
|
+
test('a fast reaction is responsive', () => {
|
|
18
|
+
expect(classifyTap(1000, [1300])).toBe('responsive');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('a slow reaction is sluggish', () => {
|
|
22
|
+
expect(classifyTap(1000, [1000 + SLUGGISH_MS + 200])).toBe('sluggish');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('no reaction inside the window is dead', () => {
|
|
26
|
+
expect(classifyTap(1000, [])).toBe('dead');
|
|
27
|
+
expect(classifyTap(1000, [1000 + RESPONSE_WINDOW_MS + 500])).toBe('dead');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('signals from before the tap do not count', () => {
|
|
31
|
+
expect(classifyTap(1000, [900, 500])).toBe('dead');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('the earliest reaction decides', () => {
|
|
35
|
+
expect(classifyTap(1000, [1000 + SLUGGISH_MS + 400, 1200])).toBe('responsive');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('recordDeadTap', () => {
|
|
40
|
+
test('warns only at the threshold, then resets', () => {
|
|
41
|
+
const buckets = new Map<number, number[]>();
|
|
42
|
+
let warned = 0;
|
|
43
|
+
for (let i = 0; i < DEAD_THRESHOLD * 2; i++) {
|
|
44
|
+
if (recordDeadTap(buckets, 7, 1000 + i * 100)) warned += 1;
|
|
45
|
+
}
|
|
46
|
+
expect(warned).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('stale dead taps age out of the window', () => {
|
|
50
|
+
const buckets = new Map<number, number[]>();
|
|
51
|
+
recordDeadTap(buckets, 7, 0);
|
|
52
|
+
recordDeadTap(buckets, 7, 100);
|
|
53
|
+
// Third tap arrives after the window: the old two are gone.
|
|
54
|
+
expect(recordDeadTap(buckets, 7, DEAD_WINDOW_MS + 200)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('targets are independent', () => {
|
|
58
|
+
const buckets = new Map<number, number[]>();
|
|
59
|
+
recordDeadTap(buckets, 1, 0);
|
|
60
|
+
recordDeadTap(buckets, 1, 10);
|
|
61
|
+
expect(recordDeadTap(buckets, 2, 20)).toBe(false);
|
|
62
|
+
expect(recordDeadTap(buckets, 1, 30)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('recordSluggish', () => {
|
|
67
|
+
test('cooldown gates repeat warns per target', () => {
|
|
68
|
+
const warns = new Map<number, number>();
|
|
69
|
+
expect(recordSluggish(warns, 7, 1000)).toBe(true);
|
|
70
|
+
expect(recordSluggish(warns, 7, 2000)).toBe(false);
|
|
71
|
+
expect(recordSluggish(warns, 7, 1000 + SLUGGISH_COOLDOWN_MS + 1)).toBe(true);
|
|
72
|
+
expect(recordSluggish(warns, 8, 2000)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -59,8 +59,6 @@ describe('uploadAttachment', () => {
|
|
|
59
59
|
expect(seen.auth).toBe('Bearer st_pk_test');
|
|
60
60
|
expect(out).not.toBeNull();
|
|
61
61
|
expect(out!.ref).toBe('019e3000-7000-7000-8000-000000000001');
|
|
62
|
-
expect(out!.kind).toBe('screenshot');
|
|
63
|
-
expect(out!.source).toBe('js');
|
|
64
62
|
});
|
|
65
63
|
|
|
66
64
|
test('returns null on a non-201 response', async () => {
|