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.
Files changed (32) hide show
  1. package/android/src/main/java/com/mytatvarnsdk/CgmTrackyLibModule.kt +84 -0
  2. package/android/src/main/java/com/mytatvarnsdk/activity/ConnectSensorActivity.kt +158 -18
  3. package/android/src/main/java/com/mytatvarnsdk/activity/HelpActivity.kt +3 -9
  4. package/android/src/main/java/com/mytatvarnsdk/activity/PermissionActivity.kt +51 -16
  5. package/android/src/main/java/com/mytatvarnsdk/activity/PlaceSensorActivity.kt +29 -13
  6. package/android/src/main/java/com/mytatvarnsdk/activity/PlaceTransmitterActivity.kt +29 -13
  7. package/android/src/main/java/com/mytatvarnsdk/activity/QRActivity.kt +146 -5
  8. package/android/src/main/java/com/mytatvarnsdk/activity/SearchTransmitterActivity.kt +108 -13
  9. package/android/src/main/java/com/mytatvarnsdk/activity/SensorConnectSuccessActivity.kt +43 -16
  10. package/android/src/main/java/com/mytatvarnsdk/activity/StartCGMActivity.kt +26 -15
  11. package/android/src/main/java/com/mytatvarnsdk/activity/VideoActivity.kt +96 -50
  12. package/android/src/main/java/com/mytatvarnsdk/network/AuthenticateSDKService.kt +113 -0
  13. package/android/src/main/java/com/mytatvarnsdk/utils/CgmModuleSentryLog.kt +205 -0
  14. package/android/src/main/java/com/mytatvarnsdk/utils/CgmSentryLog.kt +28 -0
  15. package/android/src/main/java/com/mytatvarnsdk/utils/CustomerSupportConfig.kt +137 -0
  16. package/android/src/main/java/com/mytatvarnsdk/utils/ToolbarSupportHelper.kt +83 -0
  17. package/ios/Custom/CustomTopUIView.swift +25 -0
  18. package/ios/MyReactNativeBridge.m +4 -0
  19. package/ios/Support/API.swift +117 -18
  20. package/ios/Support/CustomTopViewSupportHelper.swift +55 -0
  21. package/ios/Support/CustomerSupportConfig.swift +169 -0
  22. package/ios/Support/Global.swift +2 -2
  23. package/ios/Support/GlobalYouTubePlayerView.swift +102 -11
  24. package/ios/ViewControllers/AttachTransmitterViewController.swift +16 -21
  25. package/ios/ViewControllers/ChatWithExpertViewController.swift +1 -7
  26. package/ios/ViewControllers/ConnectToSensorViewController.swift +16 -21
  27. package/ios/ViewControllers/ConnectToTransmitterViewController.swift +16 -21
  28. package/ios/ViewControllers/ProvidePermissionViewController.swift +16 -21
  29. package/ios/ViewControllers/PutOnTheSensorViewController.swift +16 -21
  30. package/ios/ViewControllers/StartConnectionViewController.swift +18 -25
  31. package/ios/ViewModel/FinalViewModel.swift +2 -2
  32. 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.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
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 isFullscreen = false
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").toString()
40
+ videoId = resolveVideoId(intent.getStringExtra("VideoId"))
39
41
 
40
42
  onBackPressedDispatcher.addCallback(onBackPressedCallback)
41
43
 
42
- val iFramePlayerOptions = IFramePlayerOptions.Builder()
43
- .controls(1)
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
- handleBackPressedManually()
48
+ finish()
50
49
  }
51
50
 
52
- binding.youtubePlayerView.addFullscreenListener(object : FullscreenListener {
53
- override fun onEnterFullscreen(fullscreenView: View, exitFullscreen: () -> Unit) {
54
- isFullscreen = true
55
-
56
- // the video will continue playing in fullscreenView
57
- binding.youtubePlayerView.visibility = View.GONE
58
- binding.fullScreenViewContainer.visibility = View.VISIBLE
59
- binding.fullScreenViewContainer.addView(fullscreenView)
60
-
61
- // optionally request landscape orientation
62
- // requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
63
- }
64
-
65
- override fun onExitFullscreen() {
66
- isFullscreen = false
67
-
68
- // the video will continue playing in the player
69
- binding.youtubePlayerView.visibility = View.VISIBLE
70
- binding.fullScreenViewContainer.visibility = View.GONE
71
- binding.fullScreenViewContainer.removeAllViews()
72
- }
73
- })
74
-
75
- binding.youtubePlayerView.initialize(object : AbstractYouTubePlayerListener() {
76
- override fun onReady(youTubePlayer: YouTubePlayer) {
77
- this@VideoActivity.youTubePlayer = youTubePlayer
78
- youTubePlayer.loadVideo(videoId, 0f)
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
- }, iFramePlayerOptions)
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
- lifecycle.addObserver(binding.youtubePlayerView)
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
- handleBackPressedManually()
129
+ finish()
89
130
  }
90
131
  }
91
132
 
92
- private fun handleBackPressedManually() {
93
- if (isFullscreen) {
94
- youTubePlayer.toggleFullscreen()
95
- } else {
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
+ }