react-native-mytatva-rn-sdk 1.2.96 → 1.2.98
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/mytatvarnsdk/CgmTrackyLibModule.kt +84 -0
- package/android/src/main/java/com/mytatvarnsdk/activity/ConnectSensorActivity.kt +158 -18
- package/android/src/main/java/com/mytatvarnsdk/activity/HelpActivity.kt +3 -9
- package/android/src/main/java/com/mytatvarnsdk/activity/PermissionActivity.kt +51 -16
- package/android/src/main/java/com/mytatvarnsdk/activity/PlaceSensorActivity.kt +29 -13
- package/android/src/main/java/com/mytatvarnsdk/activity/PlaceTransmitterActivity.kt +29 -13
- package/android/src/main/java/com/mytatvarnsdk/activity/QRActivity.kt +146 -5
- package/android/src/main/java/com/mytatvarnsdk/activity/SearchTransmitterActivity.kt +108 -13
- package/android/src/main/java/com/mytatvarnsdk/activity/SensorConnectSuccessActivity.kt +43 -16
- package/android/src/main/java/com/mytatvarnsdk/activity/StartCGMActivity.kt +26 -15
- package/android/src/main/java/com/mytatvarnsdk/activity/VideoActivity.kt +96 -50
- package/android/src/main/java/com/mytatvarnsdk/network/AuthenticateSDKService.kt +113 -0
- package/android/src/main/java/com/mytatvarnsdk/utils/CgmModuleSentryLog.kt +205 -0
- package/android/src/main/java/com/mytatvarnsdk/utils/CgmSentryLog.kt +28 -0
- package/android/src/main/java/com/mytatvarnsdk/utils/CustomerSupportConfig.kt +137 -0
- package/android/src/main/java/com/mytatvarnsdk/utils/ToolbarSupportHelper.kt +83 -0
- package/ios/Custom/CustomTopUIView.swift +25 -0
- package/ios/MyReactNativeBridge.m +4 -0
- package/ios/Support/API.swift +117 -18
- package/ios/Support/CustomTopViewSupportHelper.swift +55 -0
- package/ios/Support/CustomerSupportConfig.swift +169 -0
- package/ios/Support/Global.swift +2 -2
- package/ios/Support/GlobalYouTubePlayerView.swift +102 -11
- package/ios/ViewControllers/AttachTransmitterViewController.swift +16 -21
- package/ios/ViewControllers/ChatWithExpertViewController.swift +1 -7
- package/ios/ViewControllers/ConnectToSensorViewController.swift +16 -21
- package/ios/ViewControllers/ConnectToTransmitterViewController.swift +16 -21
- package/ios/ViewControllers/ProvidePermissionViewController.swift +16 -21
- package/ios/ViewControllers/PutOnTheSensorViewController.swift +16 -21
- package/ios/ViewControllers/StartConnectionViewController.swift +18 -25
- package/ios/ViewModel/FinalViewModel.swift +2 -2
- package/package.json +1 -1
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
package com.mytatvarnsdk.activity
|
|
2
2
|
|
|
3
|
+
import android.annotation.SuppressLint
|
|
3
4
|
import android.os.Bundle
|
|
4
5
|
import android.view.View
|
|
6
|
+
import android.webkit.WebChromeClient
|
|
7
|
+
import android.webkit.WebView
|
|
8
|
+
import android.webkit.WebViewClient
|
|
5
9
|
import androidx.activity.OnBackPressedCallback
|
|
6
10
|
import androidx.activity.enableEdgeToEdge
|
|
7
11
|
import androidx.appcompat.app.AppCompatActivity
|
|
12
|
+
import androidx.constraintlayout.widget.ConstraintLayout
|
|
8
13
|
import androidx.core.view.ViewCompat
|
|
9
14
|
import androidx.core.view.WindowInsetsCompat
|
|
10
15
|
import com.mytatvarnsdk.R
|
|
11
16
|
import com.mytatvarnsdk.databinding.ActivityVideoBinding
|
|
12
|
-
import com.
|
|
13
|
-
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
|
|
14
|
-
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.FullscreenListener
|
|
15
|
-
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.options.IFramePlayerOptions
|
|
17
|
+
import com.mytatvarnsdk.utils.CustomerSupportConfig
|
|
16
18
|
|
|
17
19
|
class VideoActivity : AppCompatActivity() {
|
|
18
20
|
private lateinit var binding: ActivityVideoBinding
|
|
19
|
-
private var
|
|
20
|
-
private lateinit var youTubePlayer: YouTubePlayer
|
|
21
|
+
private var webView: WebView? = null
|
|
21
22
|
private var videoId = ""
|
|
22
23
|
|
|
23
24
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
@@ -34,66 +35,111 @@ class VideoActivity : AppCompatActivity() {
|
|
|
34
35
|
init()
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
@SuppressLint("SetJavaScriptEnabled")
|
|
37
39
|
private fun init() {
|
|
38
|
-
videoId = intent.getStringExtra("VideoId")
|
|
40
|
+
videoId = resolveVideoId(intent.getStringExtra("VideoId"))
|
|
39
41
|
|
|
40
42
|
onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.fullscreen(1)
|
|
45
|
-
.build()
|
|
46
|
-
binding.youtubePlayerView.enableAutomaticInitialization = false
|
|
44
|
+
binding.youtubePlayerView.visibility = View.GONE
|
|
45
|
+
binding.fullScreenViewContainer.visibility = View.GONE
|
|
47
46
|
|
|
48
47
|
binding.ivClose.setOnClickListener {
|
|
49
|
-
|
|
48
|
+
finish()
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
51
|
+
val playerWebView = WebView(this)
|
|
52
|
+
webView = playerWebView
|
|
53
|
+
playerWebView.settings.apply {
|
|
54
|
+
javaScriptEnabled = true
|
|
55
|
+
domStorageEnabled = true
|
|
56
|
+
mediaPlaybackRequiresUserGesture = false
|
|
57
|
+
}
|
|
58
|
+
playerWebView.webViewClient = WebViewClient()
|
|
59
|
+
playerWebView.webChromeClient = WebChromeClient()
|
|
60
|
+
|
|
61
|
+
val layoutParams = ConstraintLayout.LayoutParams(
|
|
62
|
+
ConstraintLayout.LayoutParams.MATCH_PARENT,
|
|
63
|
+
ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
|
|
64
|
+
).apply {
|
|
65
|
+
topToBottom = R.id.ivClose
|
|
66
|
+
bottomToBottom = R.id.main
|
|
67
|
+
startToStart = R.id.main
|
|
68
|
+
endToEnd = R.id.main
|
|
69
|
+
}
|
|
70
|
+
(binding.root as ConstraintLayout).addView(playerWebView, layoutParams)
|
|
71
|
+
binding.ivClose.bringToFront()
|
|
72
|
+
binding.ivClose.elevation = 12f
|
|
73
|
+
|
|
74
|
+
val html = buildYouTubeEmbedHtml(videoId)
|
|
75
|
+
// Base URL + referrer meta are required by YouTube (errors 152-4 / 153).
|
|
76
|
+
playerWebView.loadDataWithBaseURL(
|
|
77
|
+
EMBED_BASE_URL,
|
|
78
|
+
html,
|
|
79
|
+
"text/html",
|
|
80
|
+
"UTF-8",
|
|
81
|
+
null
|
|
82
|
+
)
|
|
83
|
+
}
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
private fun resolveVideoId(raw: String?): String {
|
|
86
|
+
val value = raw?.trim().orEmpty()
|
|
87
|
+
if (value.isEmpty() || value == "null") {
|
|
88
|
+
return CustomerSupportConfig.DEFAULT_DEMO_VIDEO_ID
|
|
89
|
+
}
|
|
90
|
+
return CustomerSupportConfig.extractYouTubeVideoId(value) ?: value
|
|
91
|
+
}
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
private fun buildYouTubeEmbedHtml(videoId: String): String {
|
|
94
|
+
return """
|
|
95
|
+
<!DOCTYPE html>
|
|
96
|
+
<html>
|
|
97
|
+
<head>
|
|
98
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
99
|
+
<meta name="referrer" content="strict-origin-when-cross-origin">
|
|
100
|
+
<style>
|
|
101
|
+
html, body { margin: 0; padding: 0; background: #000; height: 100%; }
|
|
102
|
+
.video-container { position: relative; width: 100%; height: 100%; }
|
|
103
|
+
iframe {
|
|
104
|
+
position: absolute;
|
|
105
|
+
top: 0;
|
|
106
|
+
left: 0;
|
|
107
|
+
width: 100%;
|
|
108
|
+
height: 100%;
|
|
109
|
+
border: 0;
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
<div class="video-container">
|
|
115
|
+
<iframe
|
|
116
|
+
src="https://www.youtube-nocookie.com/embed/$videoId?playsinline=1&rel=0&modestbranding=1"
|
|
117
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
118
|
+
allowfullscreen
|
|
119
|
+
referrerpolicy="strict-origin-when-cross-origin">
|
|
120
|
+
</iframe>
|
|
121
|
+
</div>
|
|
122
|
+
</body>
|
|
123
|
+
</html>
|
|
124
|
+
""".trimIndent()
|
|
84
125
|
}
|
|
85
126
|
|
|
86
127
|
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
|
87
128
|
override fun handleOnBackPressed() {
|
|
88
|
-
|
|
129
|
+
finish()
|
|
89
130
|
}
|
|
90
131
|
}
|
|
91
132
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
finish()
|
|
133
|
+
override fun onDestroy() {
|
|
134
|
+
webView?.apply {
|
|
135
|
+
stopLoading()
|
|
136
|
+
destroy()
|
|
97
137
|
}
|
|
138
|
+
webView = null
|
|
139
|
+
super.onDestroy()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
companion object {
|
|
143
|
+
private const val EMBED_BASE_URL = "https://www.mytatva.in/"
|
|
98
144
|
}
|
|
99
|
-
}
|
|
145
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
package com.mytatvarnsdk.network
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
+
import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
|
|
5
|
+
import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
|
|
4
6
|
import com.mytatvarnsdk.utils.EncryptionUtil.getDecryptedData
|
|
5
7
|
import com.mytatvarnsdk.utils.EncryptionUtil.getEncryptedText
|
|
6
8
|
import com.mytatvarnsdk.utils.EncryptionUtil.getEncryptionIv
|
|
7
9
|
import com.mytatvarnsdk.utils.EncryptionUtil.getEncryptionKey
|
|
8
10
|
import com.mytatvarnsdk.utils.RetrofitInstance
|
|
9
11
|
import com.mytatvarnsdk.utils.TATVA_ENVIRONMENT
|
|
12
|
+
import io.sentry.SentryLogLevel
|
|
10
13
|
import com.mytatvarnsdk.utils.TatvaEncryptionConfig.PROD_API_KEY
|
|
11
14
|
import com.mytatvarnsdk.utils.TatvaEncryptionConfig.PROD_BASE_URL
|
|
12
15
|
import com.mytatvarnsdk.utils.TatvaEncryptionConfig.PROD_ENC_IV
|
|
@@ -159,7 +162,29 @@ class AuthenticateSDKService(val scope: CoroutineScope) {
|
|
|
159
162
|
|
|
160
163
|
Log.d("API Response", "API response: $decryptedResponse")
|
|
161
164
|
|
|
165
|
+
safeLogModuleEvent {
|
|
166
|
+
logModuleEvent(
|
|
167
|
+
where = "AuthenticateSDKService.postDeviceData.success",
|
|
168
|
+
message = "POST /cgm/connection succeeded",
|
|
169
|
+
level = SentryLogLevel.INFO,
|
|
170
|
+
jsonPayload = "request=$data | response=$decryptedResponse",
|
|
171
|
+
attributes = mapOf("endpoint" to "/cgm/connection"),
|
|
172
|
+
logsOnly = true,
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
} catch (e: HttpException) {
|
|
177
|
+
safeLogModuleEvent {
|
|
178
|
+
logModuleEvent(
|
|
179
|
+
where = "AuthenticateSDKService.postDeviceData.httpError",
|
|
180
|
+
message = "POST /cgm/connection HTTP ${e.code()}",
|
|
181
|
+
level = SentryLogLevel.WARN,
|
|
182
|
+
jsonPayload = "request=$data",
|
|
183
|
+
throwable = e,
|
|
184
|
+
attributes = mapOf("http_code" to e.code().toString()),
|
|
185
|
+
logsOnly = true,
|
|
186
|
+
)
|
|
187
|
+
}
|
|
163
188
|
if (e.code() == 401) {
|
|
164
189
|
Log.d("API Error", "401 Unauthorized - Token: $token")
|
|
165
190
|
// Further handling if necessary
|
|
@@ -167,6 +192,17 @@ class AuthenticateSDKService(val scope: CoroutineScope) {
|
|
|
167
192
|
throw e
|
|
168
193
|
}
|
|
169
194
|
} catch (e: Exception) {
|
|
195
|
+
safeLogModuleEvent {
|
|
196
|
+
logModuleEvent(
|
|
197
|
+
where = "AuthenticateSDKService.postDeviceData.fail",
|
|
198
|
+
message = "POST /cgm/connection failed",
|
|
199
|
+
level = SentryLogLevel.WARN,
|
|
200
|
+
jsonPayload = "request=$data",
|
|
201
|
+
throwable = e,
|
|
202
|
+
attributes = mapOf("endpoint" to "/cgm/connection"),
|
|
203
|
+
logsOnly = true,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
170
206
|
e.printStackTrace()
|
|
171
207
|
Log.d("API Response", "Exception: ${e.message}")
|
|
172
208
|
} finally {
|
|
@@ -224,6 +260,56 @@ class AuthenticateSDKService(val scope: CoroutineScope) {
|
|
|
224
260
|
}
|
|
225
261
|
}
|
|
226
262
|
|
|
263
|
+
fun fetchCustomerSupportDetails(
|
|
264
|
+
environment: TATVA_ENVIRONMENT,
|
|
265
|
+
token: String,
|
|
266
|
+
onSuccess: (String) -> Unit = {},
|
|
267
|
+
onFailure: () -> Unit = {}
|
|
268
|
+
) {
|
|
269
|
+
scope.launch {
|
|
270
|
+
try {
|
|
271
|
+
val apiServiceInterface = RetrofitInstance.getRetroInstance().create(
|
|
272
|
+
ApiServiceInterface::class.java
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
val encryptionKey = getEncryptionKey(
|
|
276
|
+
env = TATVA_ENVIRONMENT.PROD,
|
|
277
|
+
prodEncKey = PROD_ENC_KEY,
|
|
278
|
+
stagEncKey = STAGE_ENC_KEY
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
val encryptionIv = getEncryptionIv(
|
|
282
|
+
env = TATVA_ENVIRONMENT.PROD, prodEncIv = PROD_ENC_IV, stagEncIv = STAGE_ENC_IV
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
val headers = mapOf(
|
|
286
|
+
"api-key" to if (environment == TATVA_ENVIRONMENT.PROD) PROD_API_KEY else STAGE_API_KEY,
|
|
287
|
+
"token" to token
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
val baseUrl = if (environment == TATVA_ENVIRONMENT.PROD) PROD_BASE_URL else STAGE_BASE_URL
|
|
291
|
+
val url = "$baseUrl/helper/customer_support_details"
|
|
292
|
+
|
|
293
|
+
Log.d("API Response", "Customer Support Details URL: $url")
|
|
294
|
+
|
|
295
|
+
val response = apiServiceInterface.getCgmStatus(url, headers).string()
|
|
296
|
+
|
|
297
|
+
val decryptedResponse = getDecryptedData(
|
|
298
|
+
cipherText = response.toString(),
|
|
299
|
+
encryptionKey = encryptionKey,
|
|
300
|
+
encryptionIv = encryptionIv
|
|
301
|
+
) ?: response.toString()
|
|
302
|
+
|
|
303
|
+
Log.d("API Response", "Customer Support Details Response: $decryptedResponse")
|
|
304
|
+
onSuccess(decryptedResponse)
|
|
305
|
+
} catch (e: Exception) {
|
|
306
|
+
Log.d("API Response", "Customer Support Details Exception: ${e.message}")
|
|
307
|
+
e.printStackTrace()
|
|
308
|
+
onFailure()
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
227
313
|
fun verifyDevice(
|
|
228
314
|
environment: TATVA_ENVIRONMENT,
|
|
229
315
|
token: String,
|
|
@@ -267,8 +353,35 @@ class AuthenticateSDKService(val scope: CoroutineScope) {
|
|
|
267
353
|
|
|
268
354
|
Log.d("API Response", "Device Verification API Response: $decryptedResponse")
|
|
269
355
|
|
|
356
|
+
safeLogModuleEvent {
|
|
357
|
+
logModuleEvent(
|
|
358
|
+
where = "AuthenticateSDKService.verifyDevice.success",
|
|
359
|
+
message = "GET device_verification succeeded | sensorId=$sensorId",
|
|
360
|
+
level = SentryLogLevel.INFO,
|
|
361
|
+
jsonPayload = "response=$decryptedResponse",
|
|
362
|
+
attributes = mapOf("sensor_id" to sensorId),
|
|
363
|
+
logsOnly = true,
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
270
367
|
responseListener.onResponseSuccess(decryptedResponse)
|
|
271
368
|
} catch (e: Exception) {
|
|
369
|
+
safeLogModuleEvent {
|
|
370
|
+
val errorMessage =
|
|
371
|
+
e.message ?: "GET device_verification failed | sensorId=$sensorId"
|
|
372
|
+
logModuleEvent(
|
|
373
|
+
where = "AuthenticateSDKService.verifyDevice.fail",
|
|
374
|
+
message = "CGM journey error | screen=AuthenticateSDKService | $errorMessage",
|
|
375
|
+
level = SentryLogLevel.WARN,
|
|
376
|
+
throwable = e,
|
|
377
|
+
attributes = mapOf(
|
|
378
|
+
"error_message" to errorMessage,
|
|
379
|
+
"screen" to "AuthenticateSDKService",
|
|
380
|
+
"sensor_id" to sensorId,
|
|
381
|
+
),
|
|
382
|
+
logsOnly = true,
|
|
383
|
+
)
|
|
384
|
+
}
|
|
272
385
|
responseListener.onResponseFail()
|
|
273
386
|
e.printStackTrace()
|
|
274
387
|
Log.d("API Response", "Device Verification Exception: ${e.message}")
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
package com.mytatvarnsdk.utils
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.mytatvarnsdk.CgmTrackyLibModule
|
|
5
|
+
import io.sentry.ScopeCallback
|
|
6
|
+
import io.sentry.Sentry
|
|
7
|
+
import io.sentry.SentryAttributes
|
|
8
|
+
import io.sentry.SentryLevel
|
|
9
|
+
import io.sentry.SentryLogLevel
|
|
10
|
+
import io.sentry.logger.SentryLogParameters
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Shared Sentry logging for [com.mytatvarnsdk.CgmTrackyLibModule] and journey activities.
|
|
14
|
+
*
|
|
15
|
+
* Use:
|
|
16
|
+
* ```
|
|
17
|
+
* safeLogModuleEvent {
|
|
18
|
+
* logModuleEvent("MyLocation", message = "...", level = SentryLogLevel.INFO)
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Never throws — CGM flows must continue if logging fails.
|
|
23
|
+
*/
|
|
24
|
+
object CgmModuleSentryLog {
|
|
25
|
+
private const val MAX_SENTRY_DATA_EXTRA_CHARS = 16_384
|
|
26
|
+
private const val MAX_SENTRY_MESSAGE_DATA_CHARS = 2_000
|
|
27
|
+
|
|
28
|
+
private fun clipForSentry(text: String, maxChars: Int = MAX_SENTRY_DATA_EXTRA_CHARS): String =
|
|
29
|
+
try {
|
|
30
|
+
if (text.length <= maxChars) text else text.take(maxChars) + "…"
|
|
31
|
+
} catch (_: Throwable) {
|
|
32
|
+
""
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Wraps log preparation + emission so a bad payload cannot break CGM flows. */
|
|
36
|
+
inline fun safeLogModuleEvent(block: () -> Unit) {
|
|
37
|
+
try {
|
|
38
|
+
block()
|
|
39
|
+
} catch (t: Throwable) {
|
|
40
|
+
try {
|
|
41
|
+
Log.w("logModuleEvent", "Failed to prepare Sentry log: ${t.message}", t)
|
|
42
|
+
} catch (_: Throwable) {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Structured Sentry log (like RN `Sentry.logger.info/warn/error`).
|
|
49
|
+
* Uses the host app's Sentry client (never call Sentry.init here).
|
|
50
|
+
*
|
|
51
|
+
* @param logsOnly When true, writes to Sentry Logs only — never creates Issues
|
|
52
|
+
* (use for journey QR/bind/scan errors shown to the user).
|
|
53
|
+
*/
|
|
54
|
+
fun logModuleEvent(
|
|
55
|
+
where: String,
|
|
56
|
+
message: String = "",
|
|
57
|
+
level: SentryLogLevel = SentryLogLevel.INFO,
|
|
58
|
+
jsonPayload: String? = null,
|
|
59
|
+
throwable: Throwable? = null,
|
|
60
|
+
attributes: Map<String, String> = emptyMap(),
|
|
61
|
+
logsOnly: Boolean = false,
|
|
62
|
+
) {
|
|
63
|
+
try {
|
|
64
|
+
if (!Sentry.isEnabled()) {
|
|
65
|
+
Log.w("logModuleEvent", "Sentry not initialized — skipping [$where]")
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
val resolvedMessage = when {
|
|
70
|
+
message.isNotEmpty() -> message
|
|
71
|
+
throwable != null -> {
|
|
72
|
+
val typeName = throwable.javaClass.simpleName ?: "Throwable"
|
|
73
|
+
"$typeName: ${throwable.message ?: ""}"
|
|
74
|
+
}
|
|
75
|
+
else -> where
|
|
76
|
+
}
|
|
77
|
+
val mergedAttributes = try {
|
|
78
|
+
attributes.toMutableMap()
|
|
79
|
+
} catch (_: Throwable) {
|
|
80
|
+
mutableMapOf()
|
|
81
|
+
}
|
|
82
|
+
throwable?.let {
|
|
83
|
+
mergedAttributes["exception"] = it.javaClass.simpleName ?: "Throwable"
|
|
84
|
+
mergedAttributes["exception_message"] = it.message ?: ""
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val clippedJson = jsonPayload?.let { clipForSentry(it) }
|
|
88
|
+
val logMessage = clipForSentry(
|
|
89
|
+
"CgmTrackyLib: $where | $resolvedMessage",
|
|
90
|
+
MAX_SENTRY_MESSAGE_DATA_CHARS
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
val env = CgmTrackyLibModule.env
|
|
94
|
+
val sentryAttributes = mutableMapOf<String, Any>(
|
|
95
|
+
"cgm_tracky_lib" to "true",
|
|
96
|
+
"location" to where,
|
|
97
|
+
)
|
|
98
|
+
if (env.isNotEmpty()) {
|
|
99
|
+
sentryAttributes["tatva_env"] = env
|
|
100
|
+
}
|
|
101
|
+
clippedJson?.let { sentryAttributes["json"] = it }
|
|
102
|
+
mergedAttributes.forEach { (key, value) ->
|
|
103
|
+
try {
|
|
104
|
+
sentryAttributes[key] = clipForSentry(value, 500)
|
|
105
|
+
} catch (_: Throwable) {
|
|
106
|
+
sentryAttributes[key] = ""
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
val logsEnabled = try {
|
|
111
|
+
Sentry.getCurrentScopes()?.options?.logs?.isEnabled == true
|
|
112
|
+
} catch (_: Throwable) {
|
|
113
|
+
false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (logsEnabled) {
|
|
117
|
+
val logParams = SentryLogParameters.create(
|
|
118
|
+
SentryAttributes.fromMap(sentryAttributes)
|
|
119
|
+
)
|
|
120
|
+
logParams.origin = "cgm-tracky-lib"
|
|
121
|
+
Sentry.logger().log(level, logParams, logMessage)
|
|
122
|
+
if (!logsOnly && throwable != null && level >= SentryLogLevel.ERROR) {
|
|
123
|
+
captureModuleException(where, throwable, sentryAttributes)
|
|
124
|
+
}
|
|
125
|
+
} else if (!logsOnly) {
|
|
126
|
+
captureModuleEventFallback(where, logMessage, level, throwable, sentryAttributes)
|
|
127
|
+
} else {
|
|
128
|
+
Log.w("logModuleEvent", "[$where] $logMessage (native Sentry Logs disabled)")
|
|
129
|
+
}
|
|
130
|
+
} catch (sentryEx: Throwable) {
|
|
131
|
+
try {
|
|
132
|
+
Log.w(
|
|
133
|
+
"logModuleEvent",
|
|
134
|
+
"Sentry logger failed for [$where]: ${sentryEx.message}",
|
|
135
|
+
sentryEx
|
|
136
|
+
)
|
|
137
|
+
Log.e("CgmTrackyLib", "[$where]", throwable ?: sentryEx)
|
|
138
|
+
} catch (_: Throwable) {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private fun mapLogLevelToEventLevel(level: SentryLogLevel): SentryLevel = when (level) {
|
|
144
|
+
SentryLogLevel.TRACE, SentryLogLevel.DEBUG -> SentryLevel.DEBUG
|
|
145
|
+
SentryLogLevel.INFO -> SentryLevel.INFO
|
|
146
|
+
SentryLogLevel.WARN -> SentryLevel.WARNING
|
|
147
|
+
SentryLogLevel.ERROR -> SentryLevel.ERROR
|
|
148
|
+
SentryLogLevel.FATAL -> SentryLevel.FATAL
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun applyModuleScopeExtras(
|
|
152
|
+
where: String,
|
|
153
|
+
attributes: Map<String, Any>
|
|
154
|
+
): ScopeCallback = ScopeCallback { scope ->
|
|
155
|
+
scope.setTag("cgm_tracky_lib", "true")
|
|
156
|
+
scope.setTag("location", where)
|
|
157
|
+
val env = CgmTrackyLibModule.env
|
|
158
|
+
if (env.isNotEmpty()) {
|
|
159
|
+
scope.setTag("tatva_env", env)
|
|
160
|
+
}
|
|
161
|
+
attributes.forEach { (key, value) ->
|
|
162
|
+
try {
|
|
163
|
+
scope.setExtra(key, value.toString())
|
|
164
|
+
} catch (_: Throwable) {
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private fun captureModuleEventFallback(
|
|
170
|
+
where: String,
|
|
171
|
+
message: String,
|
|
172
|
+
level: SentryLogLevel,
|
|
173
|
+
throwable: Throwable?,
|
|
174
|
+
attributes: Map<String, Any>
|
|
175
|
+
) {
|
|
176
|
+
try {
|
|
177
|
+
val scopeCallback = applyModuleScopeExtras(where, attributes)
|
|
178
|
+
if (throwable != null) {
|
|
179
|
+
Sentry.captureException(throwable, scopeCallback)
|
|
180
|
+
} else {
|
|
181
|
+
Sentry.captureMessage(message, mapLogLevelToEventLevel(level), scopeCallback)
|
|
182
|
+
}
|
|
183
|
+
} catch (t: Throwable) {
|
|
184
|
+
try {
|
|
185
|
+
Log.w("logModuleEvent", "Sentry fallback capture failed for [$where]: ${t.message}", t)
|
|
186
|
+
} catch (_: Throwable) {
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private fun captureModuleException(
|
|
192
|
+
where: String,
|
|
193
|
+
throwable: Throwable,
|
|
194
|
+
attributes: Map<String, Any>
|
|
195
|
+
) {
|
|
196
|
+
try {
|
|
197
|
+
Sentry.captureException(throwable, applyModuleScopeExtras(where, attributes))
|
|
198
|
+
} catch (t: Throwable) {
|
|
199
|
+
try {
|
|
200
|
+
Log.w("logModuleEvent", "Sentry captureException failed for [$where]: ${t.message}", t)
|
|
201
|
+
} catch (_: Throwable) {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.mytatvarnsdk.utils
|
|
2
|
+
|
|
3
|
+
import io.sentry.SentryLogLevel
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Thin compatibility wrapper for [com.mytatvarnsdk.CgmTrackyLibModule] journey launch calls.
|
|
7
|
+
* Journey activities should use [CgmModuleSentryLog.safeLogModuleEvent] + [CgmModuleSentryLog.logModuleEvent] directly.
|
|
8
|
+
*/
|
|
9
|
+
object CgmSentryLog {
|
|
10
|
+
fun logJourneyLaunch(where: String, isReconnect: Boolean, envType: String) {
|
|
11
|
+
try {
|
|
12
|
+
CgmModuleSentryLog.safeLogModuleEvent {
|
|
13
|
+
CgmModuleSentryLog.logModuleEvent(
|
|
14
|
+
where = where,
|
|
15
|
+
message = "CGM journey launched | isReconnect=$isReconnect | env=$envType",
|
|
16
|
+
level = SentryLogLevel.INFO,
|
|
17
|
+
attributes = mapOf(
|
|
18
|
+
"is_reconnect" to isReconnect.toString(),
|
|
19
|
+
"env_type" to envType,
|
|
20
|
+
),
|
|
21
|
+
logsOnly = true,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
} catch (_: Throwable) {
|
|
25
|
+
// Never propagate — CGM launch must continue.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|