@goliapkg/sentori-react-native 6.2.1 → 6.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.
Files changed (33) hide show
  1. package/android/build.gradle +6 -0
  2. package/android/src/main/java-core/com/sentori/Sentori.kt +33 -4
  3. package/android/src/main/java-core/com/sentori/SentoriAttachment.kt +153 -0
  4. package/android/src/main/java-core/com/sentori/SentoriConfig.kt +1 -1
  5. package/android/src/main/java-core/com/sentori/SentoriCrashHandler.kt +19 -0
  6. package/android/src/main/java-core/com/sentori/SentoriFirebaseMessagingService.kt +15 -0
  7. package/android/src/main/java-core/com/sentori/SentoriNotificationTap.kt +148 -0
  8. package/android/src/main/java-core/com/sentori/SentoriPendingCrash.kt +154 -0
  9. package/android/src/main/java-core/com/sentori/SentoriPush.kt +242 -13
  10. package/android/src/main/java-core/com/sentori/SentoriPushNotifications.kt +168 -17
  11. package/android/src/main/java-core/com/sentori/SentoriScope.kt +49 -2
  12. package/android/src/main/java-core/com/sentori/SentoriTransport.kt +95 -2
  13. package/android/src/test/java-core/com/sentori/SentoriAttachmentTest.kt +413 -0
  14. package/android/src/test/java-core/com/sentori/SentoriLiveServerTest.kt +43 -0
  15. package/android/src/test/java-core/com/sentori/SentoriPendingCrashTest.kt +194 -0
  16. package/android/src/test/java-core/com/sentori/SentoriPushTest.kt +556 -0
  17. package/ios/core/SentoriAttachment.swift +129 -0
  18. package/ios/core/SentoriCrashHandler.swift +15 -0
  19. package/ios/core/SentoriPushNotifications.swift +8 -0
  20. package/lib/push.d.ts.map +1 -1
  21. package/lib/push.js +51 -1
  22. package/lib/push.js.map +1 -1
  23. package/lib/scope.d.ts +6 -0
  24. package/lib/scope.d.ts.map +1 -1
  25. package/lib/scope.js +45 -1
  26. package/lib/scope.js.map +1 -1
  27. package/lib/transport.js +1 -1
  28. package/package.json +2 -2
  29. package/src/__tests__/push.test.ts +99 -1
  30. package/src/push.ts +49 -1
  31. package/src/scope.ts +50 -1
  32. package/src/transport.ts +1 -1
  33. package/ios/core/SentoriPush.swift +0 -340
@@ -0,0 +1,194 @@
1
+ // GENERATED MIRROR — do not edit.
2
+ // Source of truth: sdk/native/android/src/test/java/com/sentori/SentoriPendingCrashTest.kt
3
+ // Run `node scripts/sync-native-core.mjs` after editing it.
4
+ package com.sentori
5
+
6
+ import androidx.test.core.app.ApplicationProvider
7
+ import org.json.JSONObject
8
+ import org.junit.After
9
+ import org.junit.Assert.assertEquals
10
+ import org.junit.Assert.assertNotNull
11
+ import org.junit.Assert.assertTrue
12
+ import org.junit.Before
13
+ import org.junit.Test
14
+ import org.junit.runner.RunWith
15
+ import org.robolectric.RobolectricTestRunner
16
+
17
+ /**
18
+ * The crash that killed the app has to survive the app.
19
+ *
20
+ * [SentoriCrashHandler] wrote one JSON file per crash and nothing in
21
+ * this package read them: the directory filled up and no crash was
22
+ * ever sent.
23
+ *
24
+ * Deliberately the same assertions as `SentoriPendingCrashTests.swift`
25
+ * — a crash must arrive identical whichever SDK sent it.
26
+ */
27
+ @RunWith(RobolectricTestRunner::class)
28
+ class SentoriPendingCrashTest {
29
+
30
+ private val context get() = ApplicationProvider.getApplicationContext<android.content.Context>()
31
+
32
+ private val onDisk = JSONObject(
33
+ """
34
+ {
35
+ "id": "019ff080-2aeb-7e30-aba1-4431b296d120",
36
+ "timestamp": "2026-08-11T09:00:00.000Z",
37
+ "kind": "error",
38
+ "platform": "android",
39
+ "release": "com.example@1.4.0+220",
40
+ "environment": "production",
41
+ "device": { "os": "android", "osVersion": "15", "model": "Google Pixel 9" },
42
+ "app": { "version": "1.4.0" },
43
+ "error": {
44
+ "type": "java.lang.IllegalStateException",
45
+ "message": "boom",
46
+ "stack": [
47
+ { "function": "com.example.Foo.bar", "file": "Foo.kt", "line": 42, "inApp": true },
48
+ { "function": "main" }
49
+ ]
50
+ }
51
+ }
52
+ """.trimIndent()
53
+ )
54
+
55
+ @Before
56
+ fun setUp() {
57
+ SentoriTransport.resetForTests()
58
+ SentoriConfig.resetForTests()
59
+ // The pending directory is one place on disk shared by every
60
+ // test in the process, and `SentoriCrashHandlerTest` writes
61
+ // there too. Without this one test drains another's crash and
62
+ // asserts on the wrong exception.
63
+ SentoriCrashHandler.register(context)
64
+ SentoriCrashHandler.consumePending()
65
+ }
66
+
67
+ @After
68
+ fun tearDown() {
69
+ SentoriTransport.resetForTests()
70
+ SentoriConfig.resetForTests()
71
+ }
72
+
73
+ @Test
74
+ fun everyFieldSurvivesTheConversion() {
75
+ val wire = SentoriPendingCrash.toWire(onDisk)
76
+
77
+ assertEquals("019ff080-2aeb-7e30-aba1-4431b296d120", wire["id"])
78
+ assertEquals("error", wire["kind"])
79
+ assertEquals("android", wire["platform"])
80
+ assertEquals("com.example@1.4.0+220", wire["release"])
81
+ assertEquals("production", wire["environment"])
82
+
83
+ // The file says `timestamp`, the wire says `occurredAt`, and
84
+ // the value is the moment the app died — not the moment the
85
+ // next launch noticed. Losing it puts every crash at the time
86
+ // of the following start-up.
87
+ assertEquals("2026-08-11T09:00:00.000Z", wire["occurredAt"])
88
+
89
+ @Suppress("UNCHECKED_CAST") val payload = wire["payload"] as Map<String, Any?>
90
+ @Suppress("UNCHECKED_CAST") val error = payload["error"] as Map<String, Any?>
91
+ assertEquals("java.lang.IllegalStateException", error["type"])
92
+ assertEquals("boom", error["message"])
93
+
94
+ @Suppress("UNCHECKED_CAST") val stack = error["stack"] as List<Map<String, Any?>>
95
+ assertEquals(2, stack.size)
96
+ assertEquals("com.example.Foo.bar", stack[0]["function"])
97
+ assertEquals(42, stack[0]["line"])
98
+ assertEquals(true, stack[0]["inApp"])
99
+ // A frame with only a function keeps only a function rather
100
+ // than gaining nulls the server would have to ignore.
101
+ assertEquals(1, stack[1].size)
102
+
103
+ @Suppress("UNCHECKED_CAST") val device = payload["device"] as Map<String, Any?>
104
+ assertEquals("Google Pixel 9", device["model"])
105
+ assertEquals(true, payload["nativeCrash"])
106
+ }
107
+
108
+ @Test
109
+ fun aMissingErrorStillProducesAReportableEvent() {
110
+ // A crash file written mid-teardown can be missing anything.
111
+ // Dropping the event would lose the only record that the app
112
+ // died at all.
113
+ val wire = SentoriPendingCrash.toWire(JSONObject("""{"platform":"android"}"""))
114
+ @Suppress("UNCHECKED_CAST") val payload = wire["payload"] as Map<String, Any?>
115
+ @Suppress("UNCHECKED_CAST") val error = payload["error"] as Map<String, Any?>
116
+
117
+ assertEquals("NativeCrash", error["type"])
118
+ assertEquals("", error["message"])
119
+ assertEquals(0, (error["stack"] as List<*>).size)
120
+ assertEquals(36, (wire["id"] as String).length)
121
+ assertNotNull(wire["occurredAt"])
122
+ }
123
+
124
+ @Test
125
+ fun iosFilesKeepTheirPlatform() {
126
+ // The same converter runs on both, and a crash filed as
127
+ // `android` would symbolicate against the wrong artifact.
128
+ assertEquals(
129
+ "ios",
130
+ SentoriPendingCrash.toWire(JSONObject("""{"platform":"ios"}"""))["platform"],
131
+ )
132
+ // Anything unrecognised is this platform rather than a value
133
+ // the server has no column for.
134
+ assertEquals(
135
+ "android",
136
+ SentoriPendingCrash.toWire(JSONObject("""{"platform":"haiku"}"""))["platform"],
137
+ )
138
+ }
139
+
140
+ @Test
141
+ fun theConvertedEventIsEncodable() {
142
+ // It goes through `org.json` on the way out; a value that
143
+ // cannot encode would lose the batch it rides in.
144
+ val json = SentoriTransport.toJson(SentoriPendingCrash.toWire(onDisk)).toString()
145
+ assertTrue(json.contains("nativeCrash"))
146
+ assertTrue(json.contains("com.example.Foo.bar"))
147
+ }
148
+
149
+ @Test
150
+ fun startIsWhatDrainsThem() {
151
+ // Through `Sentori.start`, not `ship` directly.
152
+ //
153
+ // The iOS suite's first version called `ship()` itself, so
154
+ // deleting the call from `start` left every test green — the
155
+ // same shape as the bug being fixed, one level up. A unit test
156
+ // of a function nobody calls proves the function, not the
157
+ // feature.
158
+ // `persistForTesting` writes the file the way the handler
159
+ // does. Invoking the installed handler directly recurses
160
+ // through it into itself and dies with a StackOverflowError —
161
+ // which is a fact about the test, not about the SDK.
162
+ SentoriCrashHandler.installForTesting(context)
163
+ SentoriCrashHandler.persistForTesting(IllegalStateException("StartupDrain"))
164
+
165
+ Sentori.start(
166
+ SentoriConfig(
167
+ token = "st_test",
168
+ ingestUrl = "http://127.0.0.1:9",
169
+ release = "app@1.0.0",
170
+ environment = "test",
171
+ ),
172
+ context,
173
+ )
174
+
175
+ val deadline = System.currentTimeMillis() + 10_000
176
+ var found = false
177
+ while (System.currentTimeMillis() < deadline && !found) {
178
+ val all = SentoriTransport.peekQueue() + SentoriTransport.peekPersisted()
179
+ found =
180
+ all.any { ev ->
181
+ @Suppress("UNCHECKED_CAST")
182
+ val p = ev["payload"] as? Map<String, Any?>
183
+ @Suppress("UNCHECKED_CAST")
184
+ val e = p?.get("error") as? Map<String, Any?>
185
+ (e?.get("message") as? String)?.contains("StartupDrain") == true
186
+ }
187
+ if (!found) Thread.sleep(100)
188
+ }
189
+ assertTrue(
190
+ "start did not drain the pending crash — the handler writes files nobody reads",
191
+ found,
192
+ )
193
+ }
194
+ }