@goliapkg/sentori-react-native 0.1.3 → 0.3.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.
@@ -0,0 +1,227 @@
1
+ package com.sentori
2
+
3
+ import android.content.Context
4
+ import android.os.Build
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import org.json.JSONArray
8
+ import org.json.JSONObject
9
+ import java.io.File
10
+ import java.text.SimpleDateFormat
11
+ import java.util.Date
12
+ import java.util.Locale
13
+ import java.util.TimeZone
14
+ import java.util.UUID
15
+ import java.util.concurrent.atomic.AtomicBoolean
16
+
17
+ /**
18
+ * ANR (Application Not Responding) detector.
19
+ *
20
+ * Lightweight watchdog: a worker thread posts a tick onto the main
21
+ * Looper every `intervalMs` and sleeps for `timeoutMs`. If the main
22
+ * thread didn't acknowledge the tick within that window we capture the
23
+ * main thread's stack trace as a Sentori event with `kind = "anr"`
24
+ * and write it to the same pending dir SentoriCrashHandler uses, so
25
+ * the next-launch drain delivers it.
26
+ *
27
+ * Design notes:
28
+ * - Single-shot per ANR: once we report, we wait for the main
29
+ * thread to recover before re-arming. Otherwise a 30 s freeze
30
+ * would dump six events.
31
+ * - We DON'T kill the process or rethrow. The OS does that on its
32
+ * own ANR threshold (~5 s for input-driven, longer otherwise).
33
+ * - Worker thread is a daemon so it doesn't keep the process alive.
34
+ * - Disabled in debug builds by default — the JS debugger pauses
35
+ * the main thread routinely and we don't want a flood. The host
36
+ * app can override via `SentoriAnrWatchdog.start(ctx, force = true)`.
37
+ */
38
+ object SentoriAnrWatchdog {
39
+
40
+ private const val DEFAULT_TIMEOUT_MS = 5_000L
41
+ private const val DEFAULT_INTERVAL_MS = 1_000L
42
+ private const val PENDING_DIR_NAME = "sentori/pending"
43
+
44
+ @Volatile private var running = AtomicBoolean(false)
45
+ @Volatile private var thread: Thread? = null
46
+ @Volatile private var appCtx: Context? = null
47
+
48
+ /**
49
+ * Start the watchdog. Idempotent — calling start() twice is a
50
+ * no-op. Pass `force = true` to enable in debug builds.
51
+ */
52
+ @JvmStatic
53
+ @JvmOverloads
54
+ fun start(
55
+ context: Context,
56
+ timeoutMs: Long = DEFAULT_TIMEOUT_MS,
57
+ intervalMs: Long = DEFAULT_INTERVAL_MS,
58
+ force: Boolean = false,
59
+ ) {
60
+ if (!force && isDebuggable(context)) return
61
+ if (running.getAndSet(true)) return
62
+ appCtx = context.applicationContext
63
+
64
+ val mainHandler = Handler(Looper.getMainLooper())
65
+ val watchdogThread = Thread {
66
+ val tick = MainTick()
67
+ while (running.get()) {
68
+ tick.armed = true
69
+ mainHandler.post(tick)
70
+ try {
71
+ Thread.sleep(timeoutMs)
72
+ } catch (_: InterruptedException) {
73
+ return@Thread
74
+ }
75
+ if (tick.armed) {
76
+ // Main thread is wedged — capture once, then wait
77
+ // for the tick to land before arming again.
78
+ captureAnr()
79
+ while (running.get() && tick.armed) {
80
+ try {
81
+ Thread.sleep(intervalMs)
82
+ } catch (_: InterruptedException) {
83
+ return@Thread
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ watchdogThread.name = "Sentori-ANR-Watchdog"
90
+ watchdogThread.isDaemon = true
91
+ watchdogThread.start()
92
+ thread = watchdogThread
93
+ }
94
+
95
+ @JvmStatic
96
+ fun stop() {
97
+ running.set(false)
98
+ thread?.interrupt()
99
+ thread = null
100
+ }
101
+
102
+ private fun captureAnr() {
103
+ val ctx = appCtx ?: return
104
+ try {
105
+ val mainStack = Looper.getMainLooper().thread.stackTrace
106
+ val event = buildAnrEvent(ctx, mainStack)
107
+ val dir = File(ctx.filesDir, PENDING_DIR_NAME)
108
+ if (!dir.exists()) dir.mkdirs()
109
+ val file = File(dir, "${uuid()}.json")
110
+ file.writeText(event.toString())
111
+ } catch (_: Throwable) {
112
+ // never throw from inside the watchdog — losing one
113
+ // capture beats killing the worker thread.
114
+ }
115
+ }
116
+
117
+ private fun buildAnrEvent(ctx: Context, mainStack: Array<StackTraceElement>): JSONObject {
118
+ val cfg = configMap(ctx)
119
+ val release = cfg["release"] ?: "unknown"
120
+ val environment = cfg["environment"] ?: "prod"
121
+
122
+ val device = JSONObject().apply {
123
+ put("os", "android")
124
+ put("osVersion", Build.VERSION.RELEASE)
125
+ put("model", "${Build.MANUFACTURER} ${Build.MODEL}")
126
+ }
127
+ val app = JSONObject().apply {
128
+ put("version", appVersion(ctx))
129
+ put("build", appBuild(ctx))
130
+ }
131
+
132
+ val frames = JSONArray()
133
+ for (f in mainStack) {
134
+ frames.put(
135
+ JSONObject().apply {
136
+ put("function", "${f.className}.${f.methodName}")
137
+ put("file", f.fileName ?: "<unknown>")
138
+ put("line", f.lineNumber.coerceAtLeast(0))
139
+ put("inApp", isInApp(f.className))
140
+ },
141
+ )
142
+ }
143
+
144
+ val error = JSONObject().apply {
145
+ put("type", "ApplicationNotResponding")
146
+ put("message", "Main thread blocked for ≥ ${DEFAULT_TIMEOUT_MS} ms")
147
+ put("stack", frames)
148
+ put("cause", JSONObject.NULL)
149
+ }
150
+
151
+ return JSONObject().apply {
152
+ put("id", uuid())
153
+ put("timestamp", iso8601Now())
154
+ put("kind", "anr")
155
+ put("platform", "android")
156
+ put("release", release)
157
+ put("environment", environment)
158
+ put("device", device)
159
+ put("app", app)
160
+ put("user", JSONObject.NULL)
161
+ put("tags", JSONObject().apply { put("source", "sentori.anrWatchdog") })
162
+ put("breadcrumbs", JSONArray())
163
+ put("error", error)
164
+ put("fingerprint", JSONArray())
165
+ put("traceId", JSONObject.NULL)
166
+ put("spanId", JSONObject.NULL)
167
+ }
168
+ }
169
+
170
+ private fun configMap(ctx: Context): Map<String, String> {
171
+ val prefs = ctx.getSharedPreferences("sentori", Context.MODE_PRIVATE)
172
+ val out = mutableMapOf<String, String>()
173
+ for ((k, v) in prefs.all) if (v is String) out[k] = v
174
+ return out
175
+ }
176
+
177
+ private fun isInApp(cls: String): Boolean {
178
+ if (cls.startsWith("android.")) return false
179
+ if (cls.startsWith("androidx.")) return false
180
+ if (cls.startsWith("java.")) return false
181
+ if (cls.startsWith("javax.")) return false
182
+ if (cls.startsWith("kotlin.")) return false
183
+ if (cls.startsWith("kotlinx.")) return false
184
+ if (cls.startsWith("com.facebook.react.")) return false
185
+ if (cls.startsWith("com.android.")) return false
186
+ if (cls.startsWith("dalvik.")) return false
187
+ if (cls.startsWith("sun.")) return false
188
+ return true
189
+ }
190
+
191
+ private fun iso8601Now(): String {
192
+ val f = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
193
+ f.timeZone = TimeZone.getTimeZone("UTC")
194
+ return f.format(Date())
195
+ }
196
+
197
+ private fun uuid(): String = UUID.randomUUID().toString().lowercase(Locale.US)
198
+
199
+ private fun appVersion(ctx: Context): String =
200
+ try {
201
+ val pi = ctx.packageManager.getPackageInfo(ctx.packageName, 0)
202
+ pi.versionName ?: "0.0.0"
203
+ } catch (_: Throwable) {
204
+ "0.0.0"
205
+ }
206
+
207
+ private fun appBuild(ctx: Context): String =
208
+ try {
209
+ val pi = ctx.packageManager.getPackageInfo(ctx.packageName, 0)
210
+ pi.longVersionCode.toString()
211
+ } catch (_: Throwable) {
212
+ "0"
213
+ }
214
+
215
+ private fun isDebuggable(ctx: Context): Boolean =
216
+ (ctx.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
217
+
218
+ /** Posted to the main thread every poll. `armed` is flipped to
219
+ * false once it actually runs. */
220
+ private class MainTick : Runnable {
221
+ @Volatile var armed = true
222
+
223
+ override fun run() {
224
+ armed = false
225
+ }
226
+ }
227
+ }
@@ -8,6 +8,8 @@ import expo.modules.kotlin.modules.ModuleDefinition
8
8
  * as the iOS module:
9
9
  * - setConfig({ token, release, environment })
10
10
  * - drainPending() -> List<String> (JSON bodies)
11
+ * - startAnrWatchdog({ timeoutMs?, intervalMs?, force? })
12
+ * - stopAnrWatchdog()
11
13
  */
12
14
  class SentoriModule : Module() {
13
15
  override fun definition() = ModuleDefinition {
@@ -26,6 +28,22 @@ class SentoriModule : Module() {
26
28
  SentoriCrashHandler.consumePending()
27
29
  }
28
30
 
31
+ // Watchdog is opt-in from JS so the host app picks the
32
+ // trade-off — stricter detection vs noise from the Metro
33
+ // debugger pausing the main thread. Pass `force: true` to
34
+ // run in debug builds.
35
+ Function("startAnrWatchdog") { options: Map<String, Any?>? ->
36
+ val ctx = appContext.reactContext ?: return@Function
37
+ val timeoutMs = (options?.get("timeoutMs") as? Number)?.toLong() ?: 5_000L
38
+ val intervalMs = (options?.get("intervalMs") as? Number)?.toLong() ?: 1_000L
39
+ val force = (options?.get("force") as? Boolean) ?: false
40
+ SentoriAnrWatchdog.start(ctx, timeoutMs, intervalMs, force)
41
+ }
42
+
43
+ Function("stopAnrWatchdog") {
44
+ SentoriAnrWatchdog.stop()
45
+ }
46
+
29
47
  // Dev-only helper — schedules an uncaught RuntimeException after
30
48
  // a tick so the JS bridge has time to return; the crash is then
31
49
  // captured by SentoriCrashHandler and written to
@@ -1,8 +1,8 @@
1
1
  import type { Breadcrumb, BreadcrumbType } from './types';
2
2
  export type AddBreadcrumbInput = {
3
- type: BreadcrumbType;
4
3
  data: Record<string, unknown>;
5
4
  timestamp?: string;
5
+ type: BreadcrumbType;
6
6
  };
7
7
  export declare const addBreadcrumb: (input: AddBreadcrumbInput) => void;
8
8
  export declare const getBreadcrumbs: () => Breadcrumb[];
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAM1D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,kBAAkB,KAAG,IAUzD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,UAAU,EAAkB,CAAC;AAE/D,eAAO,MAAM,gBAAgB,QAAO,IAEnC,CAAC;AAEF,eAAO,MAAM,eAAe,QAAO,IAElC,CAAC"}
1
+ {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEzD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,cAAc,CAAA;CACrB,CAAA;AAID,eAAO,MAAM,aAAa,GAAI,OAAO,kBAAkB,KAAG,IAWzD,CAAA;AAED,eAAO,MAAM,cAAc,QAAO,UAAU,EAAe,CAAA;AAE3D,eAAO,MAAM,gBAAgB,QAAO,IAGnC,CAAA;AAED,eAAO,MAAM,eAAe,QAAO,IAA0B,CAAA"}
@@ -1,21 +1,26 @@
1
- const MAX_BREADCRUMBS = 100;
2
- let _buffer = [];
1
+ // Phase 21: ring buffer logic lives in @goliapkg/sentori-core. The
2
+ // public surface here keeps its object-form `addBreadcrumb({ type,
3
+ // data, timestamp? })` so existing callers don't break, and we
4
+ // expose `__resetForTests` for the test suite.
5
+ import { BreadcrumbBuffer, clearBreadcrumbs as clearCore, getBreadcrumbs as getCore, addBreadcrumb as pushCore, } from '@goliapkg/sentori-core';
6
+ const _shadow = new BreadcrumbBuffer();
3
7
  export const addBreadcrumb = (input) => {
4
- const crumb = {
5
- timestamp: input.timestamp ?? new Date().toISOString(),
6
- type: input.type,
7
- data: input.data,
8
- };
9
- _buffer.push(crumb);
10
- if (_buffer.length > MAX_BREADCRUMBS) {
11
- _buffer.shift();
8
+ if (input.timestamp) {
9
+ // Caller wants a specific timestamp pushCore stamps `now()`, so
10
+ // we go through a private buffer to preserve that field. This path
11
+ // is rarely used (most callers omit timestamp).
12
+ _shadow.push(input.type, input.data);
13
+ const last = _shadow.snapshot().at(-1);
14
+ if (last)
15
+ last.timestamp = input.timestamp;
16
+ return;
12
17
  }
18
+ pushCore(input.type, input.data);
13
19
  };
14
- export const getBreadcrumbs = () => [..._buffer];
20
+ export const getBreadcrumbs = () => getCore();
15
21
  export const clearBreadcrumbs = () => {
16
- _buffer = [];
17
- };
18
- export const __resetForTests = () => {
19
- _buffer = [];
22
+ clearCore();
23
+ _shadow.clear();
20
24
  };
25
+ export const __resetForTests = () => clearBreadcrumbs();
21
26
  //# sourceMappingURL=breadcrumbs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAEA,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,IAAI,OAAO,GAAiB,EAAE,CAAC;AAQ/B,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAyB,EAAQ,EAAE;IAC/D,MAAM,KAAK,GAAe;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACtD,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpB,IAAI,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACrC,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,GAAiB,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;AAE/D,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAS,EAAE;IACzC,OAAO,GAAG,EAAE,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE;IACxC,OAAO,GAAG,EAAE,CAAC;AACf,CAAC,CAAC"}
1
+ {"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,mEAAmE;AACnE,+DAA+D;AAC/D,+CAA+C;AAC/C,OAAO,EACL,gBAAgB,EAChB,gBAAgB,IAAI,SAAS,EAC7B,cAAc,IAAI,OAAO,EACzB,aAAa,IAAI,QAAQ,GAC1B,MAAM,wBAAwB,CAAA;AAU/B,MAAM,OAAO,GAAG,IAAI,gBAAgB,EAAE,CAAA;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAyB,EAAQ,EAAE;IAC/D,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,kEAAkE;QAClE,mEAAmE;QACnE,gDAAgD;QAChD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACtC,IAAI,IAAI;YAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;QAC1C,OAAM;IACR,CAAC;IACD,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;AAClC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,GAAiB,EAAE,CAAC,OAAO,EAAE,CAAA;AAE3D,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAS,EAAE;IACzC,SAAS,EAAE,CAAA;IACX,OAAO,CAAC,KAAK,EAAE,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE,CAAC,gBAAgB,EAAE,CAAA"}
package/lib/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { ErrorBoundary } from './error-boundary';
2
2
  export declare const sentori: {
3
3
  init: (options: import("./init").InitOptions) => void;
4
4
  addBreadcrumb: (input: import("./breadcrumbs").AddBreadcrumbInput) => void;
5
- setUser: (user: import("./types").User | null) => void;
6
- getUser: () => import("./types").User | null;
5
+ setUser: (user: import("@goliapkg/sentori-core").User | null) => void;
6
+ getUser: () => import("@goliapkg/sentori-core").User | null;
7
7
  captureError: (error: Error, extras?: import("./capture").CaptureExtras) => void;
8
8
  captureException: (error: Error, extras?: import("./capture").CaptureExtras) => void;
9
9
  ErrorBoundary: typeof ErrorBoundary;
@@ -13,6 +13,6 @@ export { init, init as initSentori } from './init';
13
13
  export { addBreadcrumb } from './breadcrumbs';
14
14
  export { setUser, getUser, captureError, captureException } from './capture';
15
15
  export { ErrorBoundary } from './error-boundary';
16
- export { triggerNativeCrash } from './native';
16
+ export { startAnrWatchdog, stopAnrWatchdog, triggerNativeCrash, } from './native';
17
17
  export type { Event, SentoriError, Frame, Breadcrumb, BreadcrumbType, Device, DeviceOS, App, User, Tags, EventKind, Platform, } from './types';
18
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,eAAO,MAAM,OAAO;;;;;;;;CAQnB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,YAAY,EACV,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,cAAc,EACd,MAAM,EACN,QAAQ,EACR,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,eAAO,MAAM,OAAO;;;;;;;;CAQnB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,cAAc,EACd,MAAM,EACN,QAAQ,EACR,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,MAAM,SAAS,CAAC"}
package/lib/index.js CHANGED
@@ -16,5 +16,5 @@ export { init, init as initSentori } from './init';
16
16
  export { addBreadcrumb } from './breadcrumbs';
17
17
  export { setUser, getUser, captureError, captureException } from './capture';
18
18
  export { ErrorBoundary } from './error-boundary';
19
- export { triggerNativeCrash } from './native';
19
+ export { startAnrWatchdog, stopAnrWatchdog, triggerNativeCrash, } from './native';
20
20
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI;IACJ,aAAa;IACb,OAAO;IACP,OAAO;IACP,YAAY;IACZ,gBAAgB;IAChB,aAAa;CACd,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI;IACJ,aAAa;IACb,OAAO;IACP,OAAO;IACP,YAAY;IACZ,gBAAgB;IAChB,aAAa;CACd,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC"}
package/lib/native.d.ts CHANGED
@@ -20,4 +20,20 @@ export declare function drainNativePending(): Promise<string[]>;
20
20
  * No-op when the native module isn't installed (jest, bun test, web).
21
21
  */
22
22
  export declare function triggerNativeCrash(): void;
23
+ /**
24
+ * Phase 22 sub-D: start the Android ANR watchdog.
25
+ *
26
+ * startAnrWatchdog() // default 5s/1s, prod-only
27
+ * startAnrWatchdog({ force: true }) // include debug builds
28
+ * startAnrWatchdog({ timeoutMs: 3000 }) // tighter threshold
29
+ *
30
+ * Returns silently on iOS / web / jest. iOS hang detection (sub-E)
31
+ * will hook the same JS function once landed.
32
+ */
33
+ export declare function startAnrWatchdog(options?: {
34
+ force?: boolean;
35
+ intervalMs?: number;
36
+ timeoutMs?: number;
37
+ }): void;
38
+ export declare function stopAnrWatchdog(): void;
23
39
  //# sourceMappingURL=native.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4BH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,IAAI,CAMP;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC"}
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwCH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,IAAI,CAMP;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IACzC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,IAAI,CAMP;AAED,wBAAgB,eAAe,IAAI,IAAI,CAMtC"}
package/lib/native.js CHANGED
@@ -53,4 +53,30 @@ export function triggerNativeCrash() {
53
53
  // never throw from a debugging helper
54
54
  }
55
55
  }
56
+ /**
57
+ * Phase 22 sub-D: start the Android ANR watchdog.
58
+ *
59
+ * startAnrWatchdog() // default 5s/1s, prod-only
60
+ * startAnrWatchdog({ force: true }) // include debug builds
61
+ * startAnrWatchdog({ timeoutMs: 3000 }) // tighter threshold
62
+ *
63
+ * Returns silently on iOS / web / jest. iOS hang detection (sub-E)
64
+ * will hook the same JS function once landed.
65
+ */
66
+ export function startAnrWatchdog(options) {
67
+ try {
68
+ native()?.startAnrWatchdog?.(options);
69
+ }
70
+ catch {
71
+ // never throw from init helpers
72
+ }
73
+ }
74
+ export function stopAnrWatchdog() {
75
+ try {
76
+ native()?.stopAnrWatchdog?.();
77
+ }
78
+ catch {
79
+ // ignore
80
+ }
81
+ }
56
82
  //# sourceMappingURL=native.js.map
package/lib/native.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,IAAI,OAA+C,CAAA;AAEnD,SAAS,MAAM;IACb,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAA;IACzC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,mBAAmB,CAEvC,CAAA;QACD,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAsB,SAAS,CAAC,CAAA;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,IAAI,CAAA;IAChB,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAI/B;IACC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,CAAA;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;IAClB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACjB,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,CAAC,YAAY,EAAE,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,sBAAsB,EAAE,EAAE,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyBH,IAAI,OAA+C,CAAA;AAEnD,SAAS,MAAM;IACb,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAA;IACzC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,mBAAmB,CAEvC,CAAA;QACD,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAsB,SAAS,CAAC,CAAA;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,IAAI,CAAA;IAChB,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAI/B;IACC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,CAAA;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;IAClB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACjB,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,CAAC,YAAY,EAAE,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,sBAAsB,EAAE,EAAE,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAIhC;IACC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,gBAAgB,EAAE,CAAC,OAAO,CAAC,CAAA;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;IAClC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,eAAe,EAAE,EAAE,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAWrC,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,KAAK,EAgB3D,CAAC"}
1
+ {"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,KAAK,EACrC,CAAA"}
package/lib/stack.js CHANGED
@@ -1,69 +1,7 @@
1
- // V8 / Hermes (RN 0.71+):
2
- // " at functionName (file:line:col)"
3
- // " at file:line:col"
4
- const V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?)(?::(\d+))?(?::(\d+))?\)?\s*$/;
5
- // SpiderMonkey / older Hermes:
6
- // "functionName@file:line:col"
7
- const AT_FRAME = /^(.+?)@(.+?)(?::(\d+))?(?::(\d+))?$/;
8
- export const parseStack = (stack) => {
9
- if (!stack || typeof stack !== 'string')
10
- return [];
11
- const lines = stack.split('\n');
12
- const frames = [];
13
- for (const raw of lines) {
14
- const line = raw.trim();
15
- if (!line)
16
- continue;
17
- // Skip the "ErrorType: message" header line.
18
- if (!line.startsWith('at ') && !line.includes('@'))
19
- continue;
20
- const frame = parseV8(line) ?? parseAt(line);
21
- if (frame)
22
- frames.push(frame);
23
- }
24
- return frames;
25
- };
26
- const parseV8 = (line) => {
27
- if (!line.startsWith('at '))
28
- return null;
29
- const m = V8_FRAME.exec(line);
30
- if (!m)
31
- return null;
32
- const fn = m[1] ? m[1].trim() : undefined;
33
- const file = m[2] ? m[2].trim() : '<anonymous>';
34
- const lineNo = m[3] ? parseInt(m[3], 10) : 0;
35
- const col = m[4] ? parseInt(m[4], 10) : undefined;
36
- return {
37
- function: fn,
38
- file,
39
- line: lineNo,
40
- column: col,
41
- inApp: isInApp(file),
42
- };
43
- };
44
- const parseAt = (line) => {
45
- const m = AT_FRAME.exec(line);
46
- if (!m)
47
- return null;
48
- const fn = m[1] ? m[1].trim() : undefined;
49
- const file = m[2] ? m[2].trim() : '<anonymous>';
50
- const lineNo = m[3] ? parseInt(m[3], 10) : 0;
51
- const col = m[4] ? parseInt(m[4], 10) : undefined;
52
- return {
53
- function: fn,
54
- file,
55
- line: lineNo,
56
- column: col,
57
- inApp: isInApp(file),
58
- };
59
- };
60
- const isInApp = (file) => {
61
- if (!file || file === '<anonymous>')
62
- return false;
63
- if (file.includes('node_modules/'))
64
- return false;
65
- if (/^https?:\/\//.test(file))
66
- return false;
67
- return true;
68
- };
1
+ // Phase 21: regex + parse loop moved to @goliapkg/sentori-core.
2
+ // RN keeps the long path (no shortFilenames option) because Hermes
3
+ // paths are already short and native symbolication needs the
4
+ // absolute form.
5
+ import { parseStack as parseStackCore } from '@goliapkg/sentori-core';
6
+ export const parseStack = (stack) => parseStackCore(stack);
69
7
  //# sourceMappingURL=stack.js.map
package/lib/stack.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"stack.js","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stack.js","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,mEAAmE;AACnE,6DAA6D;AAC7D,iBAAiB;AACjB,OAAO,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAIrE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAyB,EAAW,EAAE,CAC/D,cAAc,CAAC,KAAK,CAAC,CAAA"}
package/lib/types.d.ts CHANGED
@@ -1,62 +1,2 @@
1
- export type Platform = 'javascript' | 'ios' | 'android';
2
- export type DeviceOS = 'ios' | 'android' | 'web' | 'other';
3
- export type EventKind = 'error';
4
- export type BreadcrumbType = 'nav' | 'net' | 'log' | 'user' | 'custom';
5
- export type Event = {
6
- id: string;
7
- timestamp: string;
8
- kind: EventKind;
9
- platform: Platform;
10
- release: string;
11
- environment: string;
12
- device: Device;
13
- app: App;
14
- user?: User | null;
15
- tags?: Tags;
16
- breadcrumbs?: Breadcrumb[];
17
- error: SentoriError;
18
- fingerprint?: string[];
19
- traceId?: string | null;
20
- spanId?: string | null;
21
- };
22
- export type Device = {
23
- os: DeviceOS;
24
- osVersion: string;
25
- model?: string;
26
- locale?: string;
27
- };
28
- export type App = {
29
- version: string;
30
- build?: string;
31
- framework?: {
32
- name: string;
33
- version: string;
34
- };
35
- };
36
- export type User = {
37
- id?: string;
38
- anonymous?: boolean;
39
- };
40
- export type Tags = Record<string, string>;
41
- export type SentoriError = {
42
- type: string;
43
- message: string;
44
- stack: Frame[];
45
- cause?: SentoriError | null;
46
- };
47
- export type Frame = {
48
- function?: string;
49
- file: string;
50
- line: number;
51
- column?: number;
52
- inApp: boolean;
53
- absolutePath?: string;
54
- preContext?: string[];
55
- postContext?: string[];
56
- };
57
- export type Breadcrumb = {
58
- timestamp: string;
59
- type: BreadcrumbType;
60
- data: Record<string, unknown>;
61
- };
1
+ export type { App, Breadcrumb, BreadcrumbType, CaptureExtras, Device, DeviceOS, Event, EventKind, Frame, Platform, SentoriError, Tags, User, } from '@goliapkg/sentori-core';
62
2
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,KAAK,GAAG,SAAS,CAAC;AACxD,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAC3D,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC;AAChC,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvE,MAAM,MAAM,KAAK,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,EAAE,EAAE,QAAQ,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,GAAG,GAAG;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAExD,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1C,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,GAAG,EACH,UAAU,EACV,cAAc,EACd,aAAa,EACb,MAAM,EACN,QAAQ,EACR,KAAK,EACL,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,IAAI,GACL,MAAM,wBAAwB,CAAA"}
package/lib/types.js CHANGED
@@ -1,2 +1,4 @@
1
+ // Phase 21: wire-format types moved to @goliapkg/sentori-core. Re-
2
+ // exported here so existing relative imports keep working.
1
3
  export {};
2
4
  //# sourceMappingURL=types.js.map
package/lib/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,2DAA2D"}
package/lib/uuid.d.ts CHANGED
@@ -1,11 +1,2 @@
1
- /**
2
- * RFC 9562 UUID v7 generator.
3
- * Layout:
4
- * bytes 0-5 (48 bits) — Unix epoch milliseconds, big-endian
5
- * byte 6 (high nibble) — version 7 (0x70)
6
- * byte 6 (low nibble) + byte 7 — 12 random bits
7
- * byte 8 (high 2 bits) — variant 10
8
- * byte 8 (low 6 bits) + bytes 9-15 — 62 random bits
9
- */
10
- export declare const uuidV7: () => string;
1
+ export { uuidV7 } from '@goliapkg/sentori-core';
11
2
  //# sourceMappingURL=uuid.d.ts.map
package/lib/uuid.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,QAAO,MA4BzB,CAAC"}
1
+ {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA"}
package/lib/uuid.js CHANGED
@@ -1,46 +1,3 @@
1
- /**
2
- * RFC 9562 UUID v7 generator.
3
- * Layout:
4
- * bytes 0-5 (48 bits) — Unix epoch milliseconds, big-endian
5
- * byte 6 (high nibble) — version 7 (0x70)
6
- * byte 6 (low nibble) + byte 7 — 12 random bits
7
- * byte 8 (high 2 bits) — variant 10
8
- * byte 8 (low 6 bits) + bytes 9-15 — 62 random bits
9
- */
10
- export const uuidV7 = () => {
11
- const ts = Date.now();
12
- const buf = new Uint8Array(16);
13
- buf[0] = Math.floor(ts / 0x10000000000) & 0xff;
14
- buf[1] = Math.floor(ts / 0x100000000) & 0xff;
15
- buf[2] = Math.floor(ts / 0x1000000) & 0xff;
16
- buf[3] = Math.floor(ts / 0x10000) & 0xff;
17
- buf[4] = Math.floor(ts / 0x100) & 0xff;
18
- buf[5] = ts & 0xff;
19
- fillRandom(buf.subarray(6));
20
- buf[6] = (buf[6] & 0x0f) | 0x70; // version 7
21
- buf[8] = (buf[8] & 0x3f) | 0x80; // variant 10xx
22
- const hex = Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('');
23
- return (hex.slice(0, 8) +
24
- '-' +
25
- hex.slice(8, 12) +
26
- '-' +
27
- hex.slice(12, 16) +
28
- '-' +
29
- hex.slice(16, 20) +
30
- '-' +
31
- hex.slice(20, 32));
32
- };
33
- const fillRandom = (buf) => {
34
- // Prefer crypto.getRandomValues (Hermes 0.74+, browsers, Node, Bun).
35
- // RN apps targeting older Hermes should add `react-native-get-random-values`
36
- // before importing the SDK.
37
- const cryptoObj = globalThis.crypto;
38
- if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
39
- cryptoObj.getRandomValues(buf);
40
- return;
41
- }
42
- for (let i = 0; i < buf.length; i++) {
43
- buf[i] = Math.floor(Math.random() * 256);
44
- }
45
- };
1
+ // Phase 21: moved to @goliapkg/sentori-core.
2
+ export { uuidV7 } from '@goliapkg/sentori-core';
46
3
  //# sourceMappingURL=uuid.js.map
package/lib/uuid.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,GAAW,EAAE;IACjC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACtB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAE/B,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,aAAa,CAAC,GAAG,IAAI,CAAC;IAC/C,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC;IAC7C,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;IAC3C,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IACvC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAEnB,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAE5B,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,YAAY;IAC9C,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,eAAe;IAEjD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7E,OAAO,CACL,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QACf,GAAG;QACH,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QAChB,GAAG;QACH,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;QACjB,GAAG;QACH,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;QACjB,GAAG;QACH,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAClB,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,GAAe,EAAQ,EAAE;IAC3C,qEAAqE;IACrE,6EAA6E;IAC7E,4BAA4B;IAC5B,MAAM,SAAS,GACb,UAGD,CAAC,MAAM,CAAC;IACT,IAAI,SAAS,IAAI,OAAO,SAAS,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;QACjE,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO;IACT,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@goliapkg/sentori-react-native",
3
- "version": "0.1.3",
4
- "description": "Sentori SDK for React Native JS-layer error capture, native crash handlers (iOS / Android), batched transport.",
3
+ "version": "0.3.0",
4
+ "description": "Sentori SDK for React Native \u2014 JS-layer error capture, native crash handlers (iOS / Android), batched transport.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentori.golia.jp",
7
7
  "repository": {
@@ -62,5 +62,8 @@
62
62
  },
63
63
  "publishConfig": {
64
64
  "access": "public"
65
+ },
66
+ "dependencies": {
67
+ "@goliapkg/sentori-core": "0.2.0"
65
68
  }
66
69
  }
@@ -17,12 +17,14 @@ describe('parseStack', () => {
17
17
  at onPress (src/components/Button.tsx:15:5)`;
18
18
  const frames = parseStack(stack);
19
19
  expect(frames).toHaveLength(2);
20
- expect(frames[0]).toEqual({
21
- function: 'handleSubmit',
22
- file: 'src/screens/Checkout.tsx',
23
- line: 42,
20
+ // Phase 21: core's parseStack also populates absolutePath, so we
21
+ // match the subset of fields rather than full equality.
22
+ expect(frames[0]).toMatchObject({
24
23
  column: 10,
24
+ file: 'src/screens/Checkout.tsx',
25
+ function: 'handleSubmit',
25
26
  inApp: true,
27
+ line: 42,
26
28
  });
27
29
  expect(frames[1]?.function).toBe('onPress');
28
30
  });
@@ -1,33 +1,42 @@
1
- import type { Breadcrumb, BreadcrumbType } from './types';
1
+ // Phase 21: ring buffer logic lives in @goliapkg/sentori-core. The
2
+ // public surface here keeps its object-form `addBreadcrumb({ type,
3
+ // data, timestamp? })` so existing callers don't break, and we
4
+ // expose `__resetForTests` for the test suite.
5
+ import {
6
+ BreadcrumbBuffer,
7
+ clearBreadcrumbs as clearCore,
8
+ getBreadcrumbs as getCore,
9
+ addBreadcrumb as pushCore,
10
+ } from '@goliapkg/sentori-core'
2
11
 
3
- const MAX_BREADCRUMBS = 100;
4
-
5
- let _buffer: Breadcrumb[] = [];
12
+ import type { Breadcrumb, BreadcrumbType } from './types'
6
13
 
7
14
  export type AddBreadcrumbInput = {
8
- type: BreadcrumbType;
9
- data: Record<string, unknown>;
10
- timestamp?: string;
11
- };
15
+ data: Record<string, unknown>
16
+ timestamp?: string
17
+ type: BreadcrumbType
18
+ }
19
+
20
+ const _shadow = new BreadcrumbBuffer()
12
21
 
13
22
  export const addBreadcrumb = (input: AddBreadcrumbInput): void => {
14
- const crumb: Breadcrumb = {
15
- timestamp: input.timestamp ?? new Date().toISOString(),
16
- type: input.type,
17
- data: input.data,
18
- };
19
- _buffer.push(crumb);
20
- if (_buffer.length > MAX_BREADCRUMBS) {
21
- _buffer.shift();
23
+ if (input.timestamp) {
24
+ // Caller wants a specific timestamp pushCore stamps `now()`, so
25
+ // we go through a private buffer to preserve that field. This path
26
+ // is rarely used (most callers omit timestamp).
27
+ _shadow.push(input.type, input.data)
28
+ const last = _shadow.snapshot().at(-1)
29
+ if (last) last.timestamp = input.timestamp
30
+ return
22
31
  }
23
- };
32
+ pushCore(input.type, input.data)
33
+ }
24
34
 
25
- export const getBreadcrumbs = (): Breadcrumb[] => [..._buffer];
35
+ export const getBreadcrumbs = (): Breadcrumb[] => getCore()
26
36
 
27
37
  export const clearBreadcrumbs = (): void => {
28
- _buffer = [];
29
- };
38
+ clearCore()
39
+ _shadow.clear()
40
+ }
30
41
 
31
- export const __resetForTests = (): void => {
32
- _buffer = [];
33
- };
42
+ export const __resetForTests = (): void => clearBreadcrumbs()
package/src/index.ts CHANGED
@@ -19,7 +19,11 @@ export { init, init as initSentori } from './init';
19
19
  export { addBreadcrumb } from './breadcrumbs';
20
20
  export { setUser, getUser, captureError, captureException } from './capture';
21
21
  export { ErrorBoundary } from './error-boundary';
22
- export { triggerNativeCrash } from './native';
22
+ export {
23
+ startAnrWatchdog,
24
+ stopAnrWatchdog,
25
+ triggerNativeCrash,
26
+ } from './native';
23
27
 
24
28
  export type {
25
29
  Event,
package/src/native.ts CHANGED
@@ -11,6 +11,18 @@ type SentoriNativeModule = {
11
11
  release: string
12
12
  token: string
13
13
  }) => void
14
+ /**
15
+ * Phase 22 sub-D: opt-in Android ANR watchdog. Posts a tick to the
16
+ * main looper every `intervalMs`; if not acknowledged within
17
+ * `timeoutMs`, captures the main-thread stack as an `anr` event.
18
+ * No-op on iOS today — iOS hang detection lands in sub-E.
19
+ */
20
+ startAnrWatchdog?: (options?: {
21
+ force?: boolean
22
+ intervalMs?: number
23
+ timeoutMs?: number
24
+ }) => void
25
+ stopAnrWatchdog?: () => void
14
26
  /** Dev-only — example app uses this to verify the crash flow. */
15
27
  triggerTestNativeCrash?: () => void
16
28
  }
@@ -69,3 +81,33 @@ export function triggerNativeCrash(): void {
69
81
  // never throw from a debugging helper
70
82
  }
71
83
  }
84
+
85
+ /**
86
+ * Phase 22 sub-D: start the Android ANR watchdog.
87
+ *
88
+ * startAnrWatchdog() // default 5s/1s, prod-only
89
+ * startAnrWatchdog({ force: true }) // include debug builds
90
+ * startAnrWatchdog({ timeoutMs: 3000 }) // tighter threshold
91
+ *
92
+ * Returns silently on iOS / web / jest. iOS hang detection (sub-E)
93
+ * will hook the same JS function once landed.
94
+ */
95
+ export function startAnrWatchdog(options?: {
96
+ force?: boolean
97
+ intervalMs?: number
98
+ timeoutMs?: number
99
+ }): void {
100
+ try {
101
+ native()?.startAnrWatchdog?.(options)
102
+ } catch {
103
+ // never throw from init helpers
104
+ }
105
+ }
106
+
107
+ export function stopAnrWatchdog(): void {
108
+ try {
109
+ native()?.stopAnrWatchdog?.()
110
+ } catch {
111
+ // ignore
112
+ }
113
+ }
package/src/stack.ts CHANGED
@@ -1,72 +1,10 @@
1
- import type { Frame } from './types';
1
+ // Phase 21: regex + parse loop moved to @goliapkg/sentori-core.
2
+ // RN keeps the long path (no shortFilenames option) because Hermes
3
+ // paths are already short and native symbolication needs the
4
+ // absolute form.
5
+ import { parseStack as parseStackCore } from '@goliapkg/sentori-core'
2
6
 
3
- // V8 / Hermes (RN 0.71+):
4
- // " at functionName (file:line:col)"
5
- // " at file:line:col"
6
- const V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?)(?::(\d+))?(?::(\d+))?\)?\s*$/;
7
+ import type { Frame } from './types'
7
8
 
8
- // SpiderMonkey / older Hermes:
9
- // "functionName@file:line:col"
10
- const AT_FRAME = /^(.+?)@(.+?)(?::(\d+))?(?::(\d+))?$/;
11
-
12
- export const parseStack = (stack: string | undefined): Frame[] => {
13
- if (!stack || typeof stack !== 'string') return [];
14
- const lines = stack.split('\n');
15
- const frames: Frame[] = [];
16
-
17
- for (const raw of lines) {
18
- const line = raw.trim();
19
- if (!line) continue;
20
- // Skip the "ErrorType: message" header line.
21
- if (!line.startsWith('at ') && !line.includes('@')) continue;
22
-
23
- const frame = parseV8(line) ?? parseAt(line);
24
- if (frame) frames.push(frame);
25
- }
26
-
27
- return frames;
28
- };
29
-
30
- const parseV8 = (line: string): Frame | null => {
31
- if (!line.startsWith('at ')) return null;
32
- const m = V8_FRAME.exec(line);
33
- if (!m) return null;
34
-
35
- const fn = m[1] ? m[1].trim() : undefined;
36
- const file = m[2] ? m[2].trim() : '<anonymous>';
37
- const lineNo = m[3] ? parseInt(m[3], 10) : 0;
38
- const col = m[4] ? parseInt(m[4], 10) : undefined;
39
-
40
- return {
41
- function: fn,
42
- file,
43
- line: lineNo,
44
- column: col,
45
- inApp: isInApp(file),
46
- };
47
- };
48
-
49
- const parseAt = (line: string): Frame | null => {
50
- const m = AT_FRAME.exec(line);
51
- if (!m) return null;
52
-
53
- const fn = m[1] ? m[1].trim() : undefined;
54
- const file = m[2] ? m[2].trim() : '<anonymous>';
55
- const lineNo = m[3] ? parseInt(m[3], 10) : 0;
56
- const col = m[4] ? parseInt(m[4], 10) : undefined;
57
-
58
- return {
59
- function: fn,
60
- file,
61
- line: lineNo,
62
- column: col,
63
- inApp: isInApp(file),
64
- };
65
- };
66
-
67
- const isInApp = (file: string): boolean => {
68
- if (!file || file === '<anonymous>') return false;
69
- if (file.includes('node_modules/')) return false;
70
- if (/^https?:\/\//.test(file)) return false;
71
- return true;
72
- };
9
+ export const parseStack = (stack: string | undefined): Frame[] =>
10
+ parseStackCore(stack)
package/src/types.ts CHANGED
@@ -1,63 +1,18 @@
1
- export type Platform = 'javascript' | 'ios' | 'android';
2
- export type DeviceOS = 'ios' | 'android' | 'web' | 'other';
3
- export type EventKind = 'error';
4
- export type BreadcrumbType = 'nav' | 'net' | 'log' | 'user' | 'custom';
5
-
6
- export type Event = {
7
- id: string;
8
- timestamp: string;
9
- kind: EventKind;
10
- platform: Platform;
11
- release: string;
12
- environment: string;
13
- device: Device;
14
- app: App;
15
- user?: User | null;
16
- tags?: Tags;
17
- breadcrumbs?: Breadcrumb[];
18
- error: SentoriError;
19
- fingerprint?: string[];
20
- traceId?: string | null;
21
- spanId?: string | null;
22
- };
23
-
24
- export type Device = {
25
- os: DeviceOS;
26
- osVersion: string;
27
- model?: string;
28
- locale?: string;
29
- };
30
-
31
- export type App = {
32
- version: string;
33
- build?: string;
34
- framework?: { name: string; version: string };
35
- };
36
-
37
- export type User = { id?: string; anonymous?: boolean };
38
-
39
- export type Tags = Record<string, string>;
40
-
41
- export type SentoriError = {
42
- type: string;
43
- message: string;
44
- stack: Frame[];
45
- cause?: SentoriError | null;
46
- };
47
-
48
- export type Frame = {
49
- function?: string;
50
- file: string;
51
- line: number;
52
- column?: number;
53
- inApp: boolean;
54
- absolutePath?: string;
55
- preContext?: string[];
56
- postContext?: string[];
57
- };
58
-
59
- export type Breadcrumb = {
60
- timestamp: string;
61
- type: BreadcrumbType;
62
- data: Record<string, unknown>;
63
- };
1
+ // Phase 21: wire-format types moved to @goliapkg/sentori-core. Re-
2
+ // exported here so existing relative imports keep working.
3
+
4
+ export type {
5
+ App,
6
+ Breadcrumb,
7
+ BreadcrumbType,
8
+ CaptureExtras,
9
+ Device,
10
+ DeviceOS,
11
+ Event,
12
+ EventKind,
13
+ Frame,
14
+ Platform,
15
+ SentoriError,
16
+ Tags,
17
+ User,
18
+ } from '@goliapkg/sentori-core'
package/src/uuid.ts CHANGED
@@ -1,56 +1,2 @@
1
- /**
2
- * RFC 9562 UUID v7 generator.
3
- * Layout:
4
- * bytes 0-5 (48 bits) — Unix epoch milliseconds, big-endian
5
- * byte 6 (high nibble) — version 7 (0x70)
6
- * byte 6 (low nibble) + byte 7 — 12 random bits
7
- * byte 8 (high 2 bits) — variant 10
8
- * byte 8 (low 6 bits) + bytes 9-15 — 62 random bits
9
- */
10
- export const uuidV7 = (): string => {
11
- const ts = Date.now();
12
- const buf = new Uint8Array(16);
13
-
14
- buf[0] = Math.floor(ts / 0x10000000000) & 0xff;
15
- buf[1] = Math.floor(ts / 0x100000000) & 0xff;
16
- buf[2] = Math.floor(ts / 0x1000000) & 0xff;
17
- buf[3] = Math.floor(ts / 0x10000) & 0xff;
18
- buf[4] = Math.floor(ts / 0x100) & 0xff;
19
- buf[5] = ts & 0xff;
20
-
21
- fillRandom(buf.subarray(6));
22
-
23
- buf[6] = (buf[6]! & 0x0f) | 0x70; // version 7
24
- buf[8] = (buf[8]! & 0x3f) | 0x80; // variant 10xx
25
-
26
- const hex = Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('');
27
- return (
28
- hex.slice(0, 8) +
29
- '-' +
30
- hex.slice(8, 12) +
31
- '-' +
32
- hex.slice(12, 16) +
33
- '-' +
34
- hex.slice(16, 20) +
35
- '-' +
36
- hex.slice(20, 32)
37
- );
38
- };
39
-
40
- const fillRandom = (buf: Uint8Array): void => {
41
- // Prefer crypto.getRandomValues (Hermes 0.74+, browsers, Node, Bun).
42
- // RN apps targeting older Hermes should add `react-native-get-random-values`
43
- // before importing the SDK.
44
- const cryptoObj = (
45
- globalThis as {
46
- crypto?: { getRandomValues?: (b: Uint8Array) => Uint8Array };
47
- }
48
- ).crypto;
49
- if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
50
- cryptoObj.getRandomValues(buf);
51
- return;
52
- }
53
- for (let i = 0; i < buf.length; i++) {
54
- buf[i] = Math.floor(Math.random() * 256);
55
- }
56
- };
1
+ // Phase 21: moved to @goliapkg/sentori-core.
2
+ export { uuidV7 } from '@goliapkg/sentori-core'