@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
package/src/init.ts CHANGED
@@ -1,472 +1,120 @@
1
- import { setLogLevel } from '@goliapkg/sentori-core';
1
+ // sentori.init(config) — the single configuration entry point
2
+ // (design.md §4). Synchronous, never throws; a bad config degrades
3
+ // every verb to a no-op with one console.warn, never a crash
4
+ // (failure-isolation iron rule).
2
5
 
3
- import { type ReadyInfo, setConfig } from './config';
6
+ import { safeFn, setLogLevel } from '@goliapkg/sentori-core';
7
+ import type { InitConfig } from '@goliapkg/sentori-core';
8
+
9
+ import { setConfig } from './config';
10
+ import { registerEmitHook } from './emit-hooks';
4
11
  import { installGlobalHandler } from './handlers/global';
5
12
  import { installLifecycleHandler } from './handlers/lifecycle';
6
- import { installPromiseHandler } from './handlers/promise';
7
13
  import { installNetworkHandler } from './handlers/network';
8
- import { getBundleInfo } from './bundle-info';
9
- import {
10
- markLaunchCompleted,
11
- runLaunchCrashGuard,
12
- } from './launch-crash-guard';
13
- import { getInstallId } from './install-id';
14
- import { startMetricsTimer } from './metrics';
15
- import {
16
- emitColdStart,
17
- markColdStartT0,
18
- startRuntimeMetricsTimer,
19
- } from './runtime-metrics';
20
- import { startFpsInstrument } from './runtime-metrics-fps';
21
- import { startHeapInstrument } from './runtime-metrics-heap';
22
- import { startNetworkBytesInstrument } from './runtime-metrics-network';
23
- import { startTrackTimer } from './track';
24
- import { drainNativePending, markNativeJsBridgeReady, setNativeConfig } from './native';
25
- import { getColdStartMs } from './mobile-vitals';
26
- import { startHeartbeat, type HeartbeatOptions } from './heartbeat';
27
- import { startSpan } from '@goliapkg/sentori-core';
28
- import { startControlChannel } from './control-channel';
14
+ import { installPromiseHandler } from './handlers/promise';
29
15
  import { startLongTaskMonitor } from './long-task-monitor';
30
- import { startNetworkTypeWatch } from './netinfo';
31
- import { startPreCrashSentinel, type PreCrashChannel } from './pre-crash-sentinel';
32
- import { startReplay } from './replay';
33
- import { startSampleProfiler } from './sample-profiler';
34
- import { startSession } from './session-tracker';
35
- import {
36
- drainOfflineQueue,
37
- enqueue,
38
- startTransport,
39
- uploadAttachment,
40
- } from './transport';
41
- import type { AttachmentKind, AttachmentMeta, AttachmentSource, Event } from './types';
16
+ import { checkColdStart } from './mobile-vitals';
17
+ import { markNativeJsBridgeReady, setNativeConfig } from './native';
18
+ import { shipNativePending } from './native-pending';
19
+ import { drainReplay, startReplay } from './replay';
20
+ import { drainOfflineQueue, startTransport, uploadAttachment } from './transport';
42
21
 
43
- declare const __DEV__: boolean | undefined;
22
+ let _initialized = false;
44
23
 
45
- export type InitOptions = {
46
- /** Project token starting with `st_pk_`. Required. */
47
- token: string;
48
- /** Release identifier, e.g. `myapp@1.2.3+456`. Required. */
49
- release: string;
50
- /** Environment label. Defaults to `dev` if `__DEV__`, else `prod`. */
51
- environment?: string;
52
- /** Override ingestion URL (self-hosted). Default: https://ingest.sentori.golia.jp */
53
- ingestUrl?: string;
54
- /** Toggle individual capture sources. All enabled by default. */
55
- capture?: {
56
- globalErrors?: boolean;
57
- promiseRejections?: boolean;
58
- network?:
59
- | boolean
60
- | {
61
- /** v0.9.0 #11 — auto-extract GraphQL `operationName` from
62
- * POST request bodies and use it as the breadcrumb / span
63
- * name (instead of `POST /graphql`). Default `true`. */
64
- graphql?: boolean;
65
- };
66
- /** Session tracking: opens a session on init and on each
67
- * foreground (`AppState` → `active`), ends it on background.
68
- * Drives crash-free rate. Set `false` to opt out. */
69
- sessions?: boolean;
70
- /** Capture a screenshot of the current screen on
71
- * `captureException`. Opt-in. The capture runs through the
72
- * bundled native module — no extra peer dep required since
73
- * v0.7.3. To redact PII regions, register a mask query via
74
- * `sentori.registerMaskQuery(() => string[])` and put
75
- * `nativeID="..."` on the `<View>`s the SDK should black out.
76
- * The image is webp q=70 / jpeg q=70 at 480 px max, < 100 KB
77
- * typical. */
78
- screenshot?: boolean;
79
- /** Phase 46: record the last N steps (route changes, custom
80
- * breadcrumbs) leading up to a crash. On `captureException`
81
- * the buffer is sealed and uploaded as a `sessionTrail`
82
- * attachment. Defaults to false. */
83
- sessionTrail?: boolean;
84
- /** v2.0 W3 — when `true`, every `sentori.track(name, props)`
85
- * also pushes a `{ type: 'track', data: { name, props } }`
86
- * breadcrumb so a subsequent `captureException` /
87
- * `captureMessage` carries the customer journey leading up to
88
- * the failure. Defaults to `false` to preserve v1 customer
89
- * breadcrumb shape on upgrade; recommended `true` for new
90
- * integrations. See `docs/recipes/track-and-metrics.md`. */
91
- trackAutoBreadcrumb?: boolean;
92
- /** v2.1 W2 — auto-instrument runtime metrics (FPS, JS heap,
93
- * cold-start, route nav timing, network bytes). Drains the
94
- * shared `@goliapkg/sentori-core` ring to
95
- * `/v1/runtime-metrics:batch` every 30 s. Defaults to `true`
96
- * in W2 part 2 — cold-start only, ~6 emits per session
97
- * per device, ~zero main-thread cost. Higher-cost instruments
98
- * (FPS / route-nav) land in W2 part 3 with per-tick perf
99
- * budget tests as stop-ship gates per
100
- * `.claude/CLAUDE.md` performance bedrock. */
101
- runtimeMetrics?: boolean;
102
- /** v0.9.1 +S4 — pre-crash sentinel. Subscribes to JS-thread
103
- * frame timing; when ≥ 50% of a 60-frame window misses the
104
- * budget (default 32 ms / < 30 fps), emits a `kind: nearCrash`
105
- * event proactively so dashboards see the "about-to-die"
106
- * signal before an actual crash. */
107
- preCrashSentinel?: boolean;
108
- sentinelChannels?: PreCrashChannel[];
109
- /** v0.9.6 #4 — JS-thread long-task monitor. setInterval(50ms)
110
- * tick detects JS thread stalls ≥ 200ms (configurable) and
111
- * emits a `sentori.longtask` span. Pairs with
112
- * `preCrashSentinel` (slow frames < 32ms) to cover the
113
- * "JS thread is stuck" spectrum. */
114
- longTaskMonitor?: boolean | { thresholdMs?: number };
115
- /** v0.9.6 #2 — wireframe session replay. Native walks the iOS
116
- * UIView / Android View hierarchy at 1 Hz and serializes
117
- * visible nodes; captureException flushes the last 60 s as a
118
- * `replay` attachment. Set to `'wireframe'` to enable. */
119
- replay?: 'off' | 'wireframe' | { hz?: number; mode: 'off' | 'wireframe' };
120
- /** v1.1 #4 升级 — JS sample profiler. setInterval(50ms) idle-tick
121
- * sampler aggregates frame counts → emits sentori.profile span
122
- * every 60s with `flameData` (frame → tick count). Pairs with
123
- * longTaskMonitor (≥200ms outliers) — sample profiler 看 idle
124
- * 分布、long-task 看 outliers。 */
125
- sampleProfiler?: boolean | { flushMs?: number; sampleMs?: number };
126
- /** Analytics v1 — live-presence heartbeat. Foreground 1/min
127
- * default; opt out with `false`, or pass an options object
128
- * to tune. Powers the Audience > Live dashboard.
129
- * Iron-rule budget: < 1 KB / min, < 1 ms / call. */
130
- heartbeat?: boolean | HeartbeatOptions;
131
- /** v0.9.0 #3 — launch-crash loop guard. When two consecutive
132
- * launches don't reach `markLaunchCompleted()` (typical of an
133
- * OTA update with a fatal bug), invoke the host callback with
134
- * a 200 ms timeout to decide rollback / reset / continue. */
135
- launchCrashGuard?: {
136
- enabled: boolean;
137
- onLaunchCrashDetected?: (
138
- info: import('./launch-crash-guard').LaunchCrashInfo,
139
- ) =>
140
- | import('./launch-crash-guard').LaunchCrashAction
141
- | Promise<import('./launch-crash-guard').LaunchCrashAction>;
142
- threshold?: number;
143
- timeoutMs?: number;
144
- };
145
- };
146
- /** Phase 44 sub-B: client-side sampling. Each rate is `[0, 1]`;
147
- * absent / null keeps everything. Defaults to 1.0 for both
148
- * (no drop). Set traces to e.g. 0.1 once the app's at user
149
- * volume to keep ingest budget under control without changing
150
- * the server-side quota. Decisions are made per-event for
151
- * errors and per-trace (all spans together) for traces. */
152
- sampling?: {
153
- errors?: null | number;
154
- traces?: null | number;
155
- /** v2.0 — sampling rate for `kind: 'message'` events emitted
156
- * via `sentori.captureMessage()`. `null` / absent → keep all. */
157
- messages?: null | number;
158
- };
159
- /** v2.3 — canonical sampling field (renamed from `sampling`).
160
- * Same shape; if both are passed, `sample` wins. The older
161
- * `sampling` stays accepted indefinitely as a back-compat alias —
162
- * this is the kind of rename that's only worth doing if it's
163
- * also zero-cost for existing callers. */
164
- sample?: {
165
- errors?: null | number;
166
- traces?: null | number;
167
- messages?: null | number;
168
- };
169
- /** v2.3 — Sentori SDK's own console output gate. Default `'warn'`:
170
- * SDK is silent unless something is genuinely broken (transport
171
- * sustained failure, native module not found, internal SDK
172
- * exception). Set to `'silent'` for absolute silence; bump to
173
- * `'info'` / `'debug'` when debugging Sentori itself. */
174
- logLevel?: import('@goliapkg/sentori-core').LogLevel;
175
- /** v2.3 — fires once after init completes. Use this to know the
176
- * SDK is live instead of scanning the console. The `ReadyInfo`
177
- * carries native-module bind status + cold-start timing. */
178
- onReady?: (info: ReadyInfo) => void;
179
- /** v2.3 — mutate-or-drop hook on each outbound event (sync).
180
- * Return the event to ship it (possibly mutated), or `null` to
181
- * drop. See `BeforeSendHook` for the throwing / non-event
182
- * fallback policy. Used for host-side PII scrubbing the SDK
183
- * can't do automatically; server-side privacy_lab still runs
184
- * regardless. */
185
- beforeSend?: import('@goliapkg/sentori-core').BeforeSendHook;
186
- };
24
+ export const init = safeFn('init', (config: InitConfig): void => {
25
+ if (_initialized) return;
187
26
 
188
- const DEFAULT_INGEST_URL = 'https://ingest.sentori.golia.jp';
189
-
190
- export const init = (options: InitOptions): void => {
191
- if (!options.token || !options.token.startsWith('st_pk_')) {
192
- throw new Error("Sentori: token is required and must start with 'st_pk_'");
27
+ if (!config || typeof config.token !== 'string' || config.token.length === 0) {
28
+ // eslint-disable-next-line no-console
29
+ console.warn('[sentori] init skipped: token missing — SDK is a no-op');
30
+ return;
193
31
  }
194
- if (!options.release) {
195
- throw new Error('Sentori: release is required');
32
+ if (typeof config.ingestUrl !== 'string' || config.ingestUrl.length === 0) {
33
+ // eslint-disable-next-line no-console
34
+ console.warn('[sentori] init skipped: ingestUrl missing — SDK is a no-op');
35
+ return;
196
36
  }
197
37
 
198
- // v2.3 — set log level FIRST so any startup-time logger calls
199
- // are gated correctly. Default 'warn' from logger.ts; an explicit
200
- // host setting overrides.
201
- setLogLevel(options.logLevel);
202
-
203
- const env =
204
- options.environment ??
205
- (typeof __DEV__ !== 'undefined' && __DEV__ ? 'dev' : 'prod');
206
-
207
- // v0.9.0 #3 — launch-crash guard. Fires *before* any other setup so
208
- // a known-bad bundle can roll back instead of running JS that's
209
- // about to die again. AsyncStorage-backed; if the host doesn't have
210
- // it the guard is a no-op.
211
- const lcg = options.capture?.launchCrashGuard;
212
- if (lcg?.enabled) {
213
- void runLaunchCrashGuard(
214
- lcg,
215
- options.release,
216
- getBundleInfo()?.id ?? null,
217
- );
218
- }
38
+ _initialized = true;
219
39
 
220
40
  setConfig({
221
- token: options.token,
222
- release: options.release,
223
- environment: env,
224
- ingestUrl: options.ingestUrl ?? DEFAULT_INGEST_URL,
41
+ token: config.token,
42
+ ingestUrl: config.ingestUrl.replace(/\/+$/, ''),
43
+ release: config.release ?? '',
44
+ environment: config.environment ?? 'production',
225
45
  enabled: true,
226
- screenshotsEnabled: options.capture?.screenshot === true,
227
- // v2.3 — `sample` is the canonical name; `sampling` is the
228
- // back-compat alias. If both are supplied, `sample` wins.
229
- errorSampleRate: options.sample?.errors ?? options.sampling?.errors ?? null,
230
- traceSampleRate: options.sample?.traces ?? options.sampling?.traces ?? null,
231
- messageSampleRate: options.sample?.messages ?? options.sampling?.messages ?? null,
232
- sessionTrailEnabled: options.capture?.sessionTrail === true,
233
- // v2.0 W3 — when true, every `track()` also pushes a
234
- // `type: 'track'` breadcrumb so a subsequent captureException
235
- // carries the customer journey. Defaults false to preserve v1
236
- // breadcrumb shape on upgrade.
237
- trackAutoBreadcrumb: options.capture?.trackAutoBreadcrumb === true,
238
- // v2.3 — host-side beforeSend hook (sync). Stored on Config so
239
- // capture.ts can pull it without re-resolving the InitOptions.
240
- beforeSend: options.beforeSend,
46
+ detect: {
47
+ rageTap: config.detect?.rageTap ?? true,
48
+ longFreeze: config.detect?.longFreeze ?? true,
49
+ slowColdStart: config.detect?.slowColdStart ?? true,
50
+ slowApi: config.detect?.slowApi ?? false,
51
+ },
52
+ replaySeconds: config.replaySeconds ?? 30,
53
+ beforeSend: config.beforeSend,
241
54
  });
55
+ setLogLevel(config.logLevel ?? 'warn');
56
+
57
+ // JS-side error capture + the signal-ring feeders.
58
+ installGlobalHandler();
59
+ installPromiseHandler();
60
+ installNetworkHandler();
61
+ installLifecycleHandler();
62
+
63
+ // Warn-scenario detectors (design.md §3 minimum set). rage_tap
64
+ // rides the RageTapCapture component; the rest start here.
65
+ if (config.detect?.longFreeze !== false) startLongTaskMonitor();
66
+
67
+ // B-type replay: a rolling in-memory wireframe ring; an error/warn
68
+ // going out drains it into a replay attachment on that event.
69
+ const replaySeconds = config.replaySeconds ?? 30;
70
+ if (replaySeconds > 0) {
71
+ startReplay({ mode: 'wireframe' });
72
+ registerEmitHook((event) => {
73
+ if (event.kind !== 'error' && event.kind !== 'warn') return;
74
+ if (!event.id) return;
75
+ const lines = drainReplay();
76
+ if (!lines) return;
77
+ const base64 = base64Encode(lines);
78
+ if (!base64) return;
79
+ void uploadAttachment(
80
+ event.id,
81
+ 'replay',
82
+ { base64, mediaType: 'application/x-sentori-replay' },
83
+ { source: 'js' },
84
+ );
85
+ });
86
+ }
242
87
 
243
- // Tell the native crash handler about the config so the JSON it writes
244
- // on the next NSException / Java uncaught carries release + env.
88
+ // Native side: hand over release/environment for the crash-file
89
+ // writer, mark the bridge live, then drain crashes from previous
90
+ // launches. All fire-and-forget — init stays synchronous and fast
91
+ // (< 50 ms budget; the work below happens off the critical path).
245
92
  setNativeConfig({
246
- token: options.token,
247
- release: options.release,
248
- environment: env,
93
+ environment: config.environment ?? 'production',
94
+ release: config.release ?? '',
95
+ token: config.token,
249
96
  });
250
- // v0.9.4 #1 — finalize cold-start measurement. iOS uses the
251
- // delta from `applicationDidFinishLaunching` to this call;
252
- // Android uses Process.getStartElapsedRealtime() so the value is
253
- // computed at this point and cached.
254
97
  markNativeJsBridgeReady();
255
- // Emit a one-off cold-start span. Server aggregates these per
256
- // release for the Mobile Vitals dashboard. No-op when native
257
- // module isn't linked.
258
- const coldMs = getColdStartMs();
259
- if (coldMs !== null && coldMs > 0 && coldMs < 60_000) {
260
- const span = startSpan('sentori.cold_start', {
261
- name: 'cold-start',
262
- parent: null,
263
- startNowMs: Date.now() - coldMs,
264
- tags: { 'vital.kind': 'cold_start' },
265
- });
266
- span.finish({ status: 'ok' });
267
- }
268
98
 
269
99
  startTransport();
270
- // v1.1 +S7 升级 — control channel poll for live-debug flag.
271
- startControlChannel();
272
- // v0.8.0-c — start watching network class. No-op if NetInfo isn't
273
- // installed; events just won't carry `device.networkType` in that
274
- // case.
275
- startNetworkTypeWatch();
276
- // v0.8.3 — drain custom-metric ring every 30 s.
277
- startMetricsTimer();
278
- // v1.1 chunk B — drain `sentori.track()` ring every 30 s.
279
- startTrackTimer();
280
- // v2.1 W2 — runtime metrics auto-instrument. Defaults on; host
281
- // can opt out with `capture: { runtimeMetrics: false }`. The
282
- // ring + emit live in `@goliapkg/sentori-core`; we own the
283
- // 30 s flusher + the cold-start one-shot here. FPS / heap /
284
- // route-nav / network instruments ship in W2 part 3 (each
285
- // gated on its own per-tick perf budget CI test).
286
- if (options.capture?.runtimeMetrics !== false) {
287
- markColdStartT0();
288
- startRuntimeMetricsTimer();
289
- // Defer the emit one tick so React's first paint settles
290
- // before we stamp "cold-start ended". 0-delay setTimeout puts
291
- // the call after the current microtask queue drains.
292
- setTimeout(emitColdStart, 0);
293
- // FPS via rAF (per-tick budget < 0.5 ms — see
294
- // runtime-metrics-fps.ts header). Heap is a 30 s polling
295
- // tick; no-op when performance.memory isn't exposed (most
296
- // RN engines today; we ship the wiring anyway so the same
297
- // SDK works on web targets via @goliapkg/sentori-javascript).
298
- startFpsInstrument();
299
- startHeapInstrument();
300
- // Network bytes counters drain every 30 s. The counters
301
- // themselves are incremented inline by handlers/network.ts
302
- // on every fetch round-trip — see the recordNetworkBytes
303
- // call site there. No-op when `capture.network: false` is
304
- // set (no fetch patch → no counter increments).
305
- startNetworkBytesInstrument();
306
- // Route-nav dwell timing emits inline from `navigation.ts`'s
307
- // useTraceNavigation state listener — no extra start call
308
- // needed; the host already mounts that hook for tracing.
309
- }
310
- // v1.1 chunk S1 — warm the install-id cache. Fire-and-forget;
311
- // any event captured before the first resolve simply omits
312
- // `device.installId`. Subsequent captures pick it up via the
313
- // sync `peekInstallId()` read in collectDevice().
314
- void getInstallId();
315
- // v0.9.1 +S4 — pre-crash sentinel. Off by default; opt-in via
316
- // `capture.preCrashSentinel: true`.
317
- if (options.capture?.preCrashSentinel === true) {
318
- startPreCrashSentinel({
319
- enabled: true,
320
- channels: options.capture.sentinelChannels,
321
- });
322
- }
323
- // v0.9.6 #4 — long-task monitor. Off by default.
324
- const lt = options.capture?.longTaskMonitor;
325
- if (lt) {
326
- startLongTaskMonitor({
327
- enabled: true,
328
- thresholdMs: typeof lt === 'object' ? lt.thresholdMs : undefined,
329
- });
330
- }
331
- // v0.9.6 #2 — wireframe replay. Off by default.
332
- const rp = options.capture?.replay;
333
- if (rp === 'wireframe') {
334
- startReplay({ mode: 'wireframe' });
335
- } else if (rp && typeof rp === 'object' && rp.mode === 'wireframe') {
336
- startReplay({ hz: rp.hz, mode: 'wireframe' });
337
- }
338
- // v1.1 #4 升级 — JS sample profiler. Off by default.
339
- const sp = options.capture?.sampleProfiler;
340
- if (sp) {
341
- startSampleProfiler({
342
- enabled: true,
343
- flushMs: typeof sp === 'object' ? sp.flushMs : undefined,
344
- sampleMs: typeof sp === 'object' ? sp.sampleMs : undefined,
345
- });
346
- }
347
-
348
- const capture = options.capture ?? {};
349
- if (capture.globalErrors !== false) installGlobalHandler();
350
- if (capture.promiseRejections !== false) installPromiseHandler();
351
- if (capture.network !== false) {
352
- const netOpts = typeof capture.network === 'object' ? capture.network : undefined;
353
- installNetworkHandler({ graphql: netOpts?.graphql });
354
- }
355
- if (capture.sessions !== false) {
356
- // Open the cold-start session now (RN doesn't fire an AppState
357
- // `change` for the initial `active` state), then bind AppState so
358
- // background ends it and the next foreground opens a fresh one.
359
- startSession();
360
- installLifecycleHandler();
361
- }
362
- // Analytics v1 — live-presence heartbeat. Foreground 1/min by
363
- // default; pass `capture.heartbeat = false` to opt out, or
364
- // `{ intervalMs: 30_000 }` to override. The default budget is well
365
- // under the "几乎不能造成性能抖动" rule (CLAUDE.md).
366
- const heartbeatOpt = capture.heartbeat;
367
- if (heartbeatOpt !== false) {
368
- startHeartbeat(typeof heartbeatOpt === 'object' ? heartbeatOpt : {});
369
- }
370
-
371
- // Drain events persisted from previous session (best-effort):
372
- // - native crashes from <Documents>/sentori/pending/*.json
373
- // - JS transport offline queue from AsyncStorage
374
- drainNativePending()
375
- .then(async (items) => {
376
- for (const json of items) {
377
- try {
378
- const event = JSON.parse(json) as Event & {
379
- _pendingAttachments?: PendingAttachment[];
380
- };
381
- // Phase 42 sub-E.05 / F.09: the native crash handler couldn't
382
- // upload attachments at crash time (the app was dying); it
383
- // base64-encoded them into `_pendingAttachments` instead.
384
- // On next launch we upload each before enqueueing the event,
385
- // so the dashboard sees the refs in `event.attachments[]`.
386
- if (event._pendingAttachments && event._pendingAttachments.length > 0) {
387
- for (const p of event._pendingAttachments) {
388
- const meta = await uploadAttachment(
389
- event.id,
390
- p.kind,
391
- { base64: p.base64, mediaType: p.mediaType },
392
- { source: p.source },
393
- );
394
- if (meta) {
395
- if (!event.attachments) event.attachments = [];
396
- event.attachments.push(meta);
397
- }
398
- }
399
- delete event._pendingAttachments;
400
- }
401
- enqueue(event);
402
- } catch {
403
- // skip malformed
404
- }
405
- }
406
- })
407
- .catch(() => {});
408
- drainOfflineQueue().catch(() => {});
409
-
410
- // v0.9.0 #3 — init reached the end without throwing. Schedule the
411
- // "launch completed" marker after one tick so any synchronous user
412
- // code right after `init()` gets to run first; we want the marker to
413
- // confirm the JS bridge stayed alive, not just that `init()` returned.
414
- if (lcg?.enabled) {
415
- setTimeout(() => {
416
- void markLaunchCompleted(getBundleInfo()?.id ?? null);
417
- }, 2_000);
418
- }
419
-
420
- // v2.3 — onReady callback. Fires after setConfig + native bind +
421
- // transport start are all settled. The drain-pending work is
422
- // still in flight (it's async) but the SDK is ready to accept
423
- // new captures. Host wires this to know the SDK is live without
424
- // scanning the console. Wrapped in try/catch — host callback
425
- // throwing must not propagate (NEVER rule).
426
- if (options.onReady) {
427
- const nativeMod = (() => {
428
- try {
429
- // eslint-disable-next-line @typescript-eslint/no-require-imports
430
- const { native } = require('./native');
431
- const n = native?.();
432
- return n ?? null;
433
- } catch {
434
- return null;
435
- }
436
- })();
437
- const info: ReadyInfo = {
438
- sdkVersion: SDK_VERSION,
439
- coldStartMs: coldMs ?? undefined,
440
- native: {
441
- bound: !!nativeMod,
442
- methods: nativeMod ? Object.keys(nativeMod).sort() : [],
443
- },
100
+ checkColdStart(config.detect?.slowColdStart !== false);
101
+ void shipNativePending().catch(() => undefined);
102
+ void drainOfflineQueue();
103
+ });
104
+
105
+ /** RN's Hermes has no btoa in older releases; go through base64.ts. */
106
+ const base64Encode = (text: string): string | null => {
107
+ try {
108
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
109
+ const { encodeBase64 } = require('./base64') as {
110
+ encodeBase64: (s: string) => string;
444
111
  };
445
- try {
446
- options.onReady(info);
447
- } catch {
448
- // Host's onReady threw. NEVER rule — swallow.
449
- }
112
+ return encodeBase64(text);
113
+ } catch {
114
+ return null;
450
115
  }
451
116
  };
452
117
 
453
- // Bumped on each SDK release; surfaced in onReady payload + the
454
- // future Sentry-compat layer's identification string.
455
- const SDK_VERSION = '2.3.0';
456
-
457
- /**
458
- * Phase 42 sub-E.05: shape of each entry in the native crash JSON's
459
- * `_pendingAttachments` array. Mirrors what
460
- * `SentoriCrashHandler.write` writes on iOS and (sub-F) what
461
- * `SentoriCrashWriter` writes on Android.
462
- */
463
- type PendingAttachment = {
464
- base64: string;
465
- kind: AttachmentKind;
466
- mediaType: string;
467
- source: AttachmentSource;
118
+ export const __resetForTests = (): void => {
119
+ _initialized = false;
468
120
  };
469
-
470
- // Keep AttachmentMeta in the imports — it's part of the public type
471
- // surface re-exported from this module's bundle.
472
- export type { AttachmentMeta };