@goliapkg/sentori-react-native 0.2.0 → 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.
- package/android/src/main/java/com/sentori/SentoriAnrWatchdog.kt +227 -0
- package/android/src/main/java/com/sentori/SentoriModule.kt +18 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/native.d.ts +16 -0
- package/lib/native.d.ts.map +1 -1
- package/lib/native.js +26 -0
- package/lib/native.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +5 -1
- package/src/native.ts +42 -0
|
@@ -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
|
package/lib/index.d.ts
CHANGED
|
@@ -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
|
package/lib/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
package/lib/native.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
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;
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
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",
|
|
@@ -64,6 +64,6 @@
|
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@goliapkg/sentori-core": "0.
|
|
67
|
+
"@goliapkg/sentori-core": "0.2.0"
|
|
68
68
|
}
|
|
69
69
|
}
|
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 {
|
|
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
|
+
}
|