react-native-mytatva-rn-sdk 1.2.96 → 1.2.97

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.
@@ -282,6 +282,63 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
282
282
  }
283
283
  }
284
284
 
285
+ /** SDK `latestGlucose` observer telemetry — must never throw or block queue/upload. */
286
+ private fun logSdkGlucoseReceivedSafely(pocGlucose: PocGlucose) {
287
+ try {
288
+ safeLogModuleEvent {
289
+ val activeSensorId = try {
290
+ prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
291
+ } catch (_: Throwable) {
292
+ ""
293
+ }
294
+ val pocJson = try {
295
+ Gson().toJson(pocGlucose)
296
+ } catch (_: Throwable) {
297
+ try {
298
+ pocGlucose.toString()
299
+ } catch (_: Throwable) {
300
+ ""
301
+ }
302
+ }
303
+ val attributes = try {
304
+ mapOf(
305
+ "glucoseId" to safePocField { pocGlucose.glucoseId?.toString() ?: "" },
306
+ "activeSensorId" to activeSensorId,
307
+ "timeInMillis" to safePocField { pocGlucose.timeInMillis.toString() },
308
+ "showGlucoseMG" to safePocField { pocGlucose.showGlucoseMG.toString() },
309
+ "showGlucose" to safePocField { pocGlucose.showGlucose.toString() },
310
+ "deviceId" to safePocField { pocGlucose.deviceId?.toString() ?: "" },
311
+ "errorCode" to safePocField { pocGlucose.errorCode?.toString() ?: "" },
312
+ "name" to safePocField { pocGlucose.name ?: "" }
313
+ )
314
+ } catch (_: Throwable) {
315
+ emptyMap()
316
+ }
317
+ val message = try {
318
+ "SDK latestGlucose received | glucoseId=${pocGlucose.glucoseId ?: ""} | showGlucoseMG=${pocGlucose.showGlucoseMG} | activeSensorId=$activeSensorId"
319
+ } catch (_: Throwable) {
320
+ "SDK latestGlucose received"
321
+ }
322
+ logModuleEvent(
323
+ where = "observeGlucoseData.sdkGlucoseReceived",
324
+ message = message,
325
+ level = SentryLogLevel.INFO,
326
+ jsonPayload = pocJson,
327
+ attributes = attributes
328
+ )
329
+ }
330
+ } catch (_: Throwable) {
331
+ // Observer callback must always continue to queue/upload.
332
+ }
333
+ }
334
+
335
+ private inline fun safePocField(block: () -> String): String =
336
+ try {
337
+ block()
338
+ } catch (_: Throwable) {
339
+ ""
340
+ }
341
+
285
342
  private fun cgmStatusDeviceAttributes(
286
343
  status: String,
287
344
  device: PocDevice?,
@@ -743,6 +800,8 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
743
800
  return@Observer
744
801
  }
745
802
 
803
+ logSdkGlucoseReceivedSafely(pocGlucose)
804
+
746
805
  // Check for duplicate using glucoseId instead of timestamp
747
806
  val glucoseId = pocGlucose.glucoseId ?: run {
748
807
  Log.w("observeGlucoseData", "Glucose ID is null, skipping")
@@ -42,6 +42,9 @@ import com.mytatvarnsdk.myView.progress.ProgressManagement
42
42
  import com.mytatvarnsdk.myView.progress.ProgressType
43
43
  import com.mytatvarnsdk.network.AuthenticateSDKService
44
44
  import com.mytatvarnsdk.utils.TATVA_ENVIRONMENT
45
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
46
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
47
+ import io.sentry.SentryLogLevel
45
48
  import kotlinx.coroutines.CoroutineScope
46
49
  import kotlinx.coroutines.Dispatchers
47
50
  import kotlinx.coroutines.SupervisorJob
@@ -139,6 +142,21 @@ class ConnectSensorActivity : BaseBleActivity() {
139
142
  fun manageErrorState(showError: Boolean, errorStatus: String) {
140
143
  ProgressManagement.getInstance().dismissWait(this)
141
144
  if (showError) {
145
+ if (errorStatus.isNotEmpty()) {
146
+ safeLogModuleEvent {
147
+ logModuleEvent(
148
+ where = "ConnectSensorActivity.error",
149
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorStatus",
150
+ level = SentryLogLevel.WARN,
151
+ attributes = mapOf(
152
+ "error_message" to errorStatus,
153
+ "screen" to javaClass.simpleName,
154
+ "is_reconnect" to isForReconnect.toString(),
155
+ ),
156
+ logsOnly = true,
157
+ )
158
+ }
159
+ }
142
160
  binding.clFail.visibility = View.VISIBLE
143
161
  binding.clMain.visibility = View.GONE
144
162
  binding.commonButton.root.visibility = View.GONE
@@ -299,6 +317,20 @@ class ConnectSensorActivity : BaseBleActivity() {
299
317
  if (requestCode == REQUEST_QR && resultCode == RESULT_OK && data != null) {
300
318
  val device: PocDevice? = data.getParcelableExtra("device")
301
319
  if (device != null) {
320
+ safeLogModuleEvent {
321
+ logModuleEvent(
322
+ where = "cgmJourney.cgm_qr_activity_result_ok",
323
+ message = "CGM journey | screen=${javaClass.simpleName} | step=cgm_qr_activity_result_ok",
324
+ level = SentryLogLevel.INFO,
325
+ attributes = mapOf(
326
+ "journey_step" to "cgm_qr_activity_result_ok",
327
+ "screen" to javaClass.simpleName,
328
+ "is_reconnect" to isForReconnect.toString(),
329
+ "transmitter_name" to (device.name ?: ""),
330
+ ),
331
+ logsOnly = true,
332
+ )
333
+ }
302
334
  val lastConnectCgm = CgmTrackyLibModule.lastConnectCgm
303
335
  Log.d("ConnectSensor", "lastConnectCgm: $lastConnectCgm")
304
336
 
@@ -337,6 +369,17 @@ class ConnectSensorActivity : BaseBleActivity() {
337
369
  val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
338
370
  val authenticateSDKService = AuthenticateSDKService(scope = scope)
339
371
 
372
+ safeLogModuleEvent {
373
+ logModuleEvent(
374
+ where = "ConnectSensorActivity.verifyDevice.request",
375
+ message = "Diasens device verification request | sensorId=$scannedSensorId",
376
+ level = SentryLogLevel.INFO,
377
+ jsonPayload = """{"sensorId":"$scannedSensorId","deviceType":"CGM","vendor":"diasens"}""",
378
+ attributes = mapOf("sensor_id" to scannedSensorId),
379
+ logsOnly = true,
380
+ )
381
+ }
382
+
340
383
  authenticateSDKService.verifyDevice(
341
384
  environment = if (envType.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
342
385
  token = token,
@@ -345,6 +388,16 @@ class ConnectSensorActivity : BaseBleActivity() {
345
388
  override fun onResponseSuccess(response: String) {
346
389
  try {
347
390
  Log.d("ConnectSensor", "Device verification API response: $response")
391
+ safeLogModuleEvent {
392
+ logModuleEvent(
393
+ where = "ConnectSensorActivity.verifyDevice.success",
394
+ message = "Diasens device verification response",
395
+ level = SentryLogLevel.INFO,
396
+ jsonPayload = "response=$response",
397
+ attributes = mapOf("sensor_id" to scannedSensorId),
398
+ logsOnly = true,
399
+ )
400
+ }
348
401
 
349
402
  val verificationResponse = Gson().fromJson(response, DeviceVerificationResponse::class.java)
350
403
  val code = when (val rawCode = verificationResponse.code) {
@@ -375,6 +428,21 @@ class ConnectSensorActivity : BaseBleActivity() {
375
428
  }
376
429
  } catch (e: Exception) {
377
430
  Log.e("ConnectSensor", "Error parsing device verification response: ${e.message}")
431
+ safeLogModuleEvent {
432
+ val errorMessage = e.message ?: "Failed to parse verification response"
433
+ logModuleEvent(
434
+ where = "ConnectSensorActivity.verifyDevice.parseError",
435
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
436
+ level = SentryLogLevel.WARN,
437
+ throwable = e,
438
+ attributes = mapOf(
439
+ "error_message" to errorMessage,
440
+ "screen" to javaClass.simpleName,
441
+ "is_reconnect" to isForReconnect.toString(),
442
+ ),
443
+ logsOnly = true,
444
+ )
445
+ }
378
446
  runOnUiThread {
379
447
  ProgressManagement.getInstance().dismissWait(this@ConnectSensorActivity)
380
448
  manageErrorState(true, "Try Again")
@@ -382,23 +450,66 @@ class ConnectSensorActivity : BaseBleActivity() {
382
450
  }
383
451
  }
384
452
 
385
- override fun onResponseFail() {
386
- Log.e("ConnectSensor", "Device verification API call failed")
387
- runOnUiThread {
388
- ProgressManagement.getInstance().dismissWait(this@ConnectSensorActivity)
389
- manageErrorState(true, "Try Again")
453
+ override fun onResponseFail() {
454
+ Log.e("ConnectSensor", "Device verification API call failed")
455
+ safeLogModuleEvent {
456
+ val errorMessage =
457
+ "Diasens device verification API failed | sensorId=$scannedSensorId"
458
+ logModuleEvent(
459
+ where = "ConnectSensorActivity.verifyDevice.fail",
460
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
461
+ level = SentryLogLevel.WARN,
462
+ attributes = mapOf(
463
+ "error_message" to errorMessage,
464
+ "screen" to javaClass.simpleName,
465
+ "is_reconnect" to isForReconnect.toString(),
466
+ ),
467
+ logsOnly = true,
468
+ )
469
+ }
470
+ runOnUiThread {
471
+ ProgressManagement.getInstance().dismissWait(this@ConnectSensorActivity)
472
+ manageErrorState(true, "Try Again")
473
+ }
390
474
  }
391
475
  }
392
- }
393
476
  )
394
477
  } catch (e: Exception) {
395
478
  Log.e("ConnectSensor", "Error validating diasens sensor: ${e.message}")
479
+ safeLogModuleEvent {
480
+ val errorMessage = e.message ?: "Diasens validation exception"
481
+ logModuleEvent(
482
+ where = "ConnectSensorActivity.validateDiasensSensor",
483
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
484
+ level = SentryLogLevel.WARN,
485
+ throwable = e,
486
+ attributes = mapOf(
487
+ "error_message" to errorMessage,
488
+ "screen" to javaClass.simpleName,
489
+ "is_reconnect" to isForReconnect.toString(),
490
+ ),
491
+ logsOnly = true,
492
+ )
493
+ }
396
494
  ProgressManagement.getInstance().dismissWait(this)
397
495
  manageErrorState(true, "Try Again")
398
496
  }
399
497
  }
400
498
 
401
499
  private fun manageDeviceVerificationError(message: String) {
500
+ safeLogModuleEvent {
501
+ logModuleEvent(
502
+ where = "ConnectSensorActivity.deviceVerificationError",
503
+ message = "CGM journey error | screen=${javaClass.simpleName} | $message",
504
+ level = SentryLogLevel.WARN,
505
+ attributes = mapOf(
506
+ "error_message" to message,
507
+ "screen" to javaClass.simpleName,
508
+ "is_reconnect" to isForReconnect.toString(),
509
+ ),
510
+ logsOnly = true,
511
+ )
512
+ }
402
513
  ProgressManagement.getInstance().dismissWait(this)
403
514
  binding.clFail.visibility = View.VISIBLE
404
515
  binding.clMain.visibility = View.GONE
@@ -454,6 +565,21 @@ class ConnectSensorActivity : BaseBleActivity() {
454
565
  }
455
566
  } catch (e: Exception) {
456
567
  Log.d("bind::-> ", "bind: ${e.message}")
568
+ safeLogModuleEvent {
569
+ val errorMessage = e.message ?: "Sensor bind exception"
570
+ logModuleEvent(
571
+ where = "ConnectSensorActivity.bind",
572
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
573
+ level = SentryLogLevel.WARN,
574
+ throwable = e,
575
+ attributes = mapOf(
576
+ "error_message" to errorMessage,
577
+ "screen" to javaClass.simpleName,
578
+ "is_reconnect" to isForReconnect.toString(),
579
+ ),
580
+ logsOnly = true,
581
+ )
582
+ }
457
583
  }
458
584
  }
459
585
 
@@ -482,6 +608,20 @@ class ConnectSensorActivity : BaseBleActivity() {
482
608
  }
483
609
 
484
610
  private fun sendDataToRN(data: String, status: String) {
611
+ safeLogModuleEvent {
612
+ logModuleEvent(
613
+ where = "cgmJourney.$status",
614
+ message = "CGM journey | screen=${javaClass.simpleName} | step=$status",
615
+ level = SentryLogLevel.INFO,
616
+ jsonPayload = data.takeIf { it.isNotEmpty() },
617
+ attributes = mapOf(
618
+ "journey_step" to status,
619
+ "screen" to javaClass.simpleName,
620
+ "is_reconnect" to isForReconnect.toString(),
621
+ ),
622
+ logsOnly = true,
623
+ )
624
+ }
485
625
  if (reactContext != null) {
486
626
  try {
487
627
  val catalystInstance: CatalystInstance = reactContext.catalystInstance
@@ -39,6 +39,9 @@ import com.mytatvarnsdk.base.BasePermissionActivity
39
39
  import com.mytatvarnsdk.databinding.ActivityPermissionBinding
40
40
  import com.mytatvarnsdk.databinding.BluetoothDialogBottomsheetBinding
41
41
  import com.mytatvarnsdk.databinding.ExitDialogBottomsheetBinding
42
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
43
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
44
+ import io.sentry.SentryLogLevel
42
45
  import org.json.JSONObject
43
46
 
44
47
  class PermissionActivity : BasePermissionActivity() {
@@ -202,6 +205,20 @@ class PermissionActivity : BasePermissionActivity() {
202
205
  }
203
206
 
204
207
  private fun sendDataToRN(data: String, status: String) {
208
+ safeLogModuleEvent {
209
+ logModuleEvent(
210
+ where = "cgmJourney.$status",
211
+ message = "CGM journey | screen=${javaClass.simpleName} | step=$status",
212
+ level = SentryLogLevel.INFO,
213
+ jsonPayload = data.takeIf { it.isNotEmpty() },
214
+ attributes = mapOf(
215
+ "journey_step" to status,
216
+ "screen" to javaClass.simpleName,
217
+ "is_reconnect" to isForReconnect.toString(),
218
+ ),
219
+ logsOnly = true,
220
+ )
221
+ }
205
222
  if (reactContext != null) {
206
223
  try {
207
224
  val catalystInstance: CatalystInstance = reactContext.catalystInstance
@@ -643,12 +660,38 @@ class PermissionActivity : BasePermissionActivity() {
643
660
  }
644
661
 
645
662
  private fun onLocationPermissionGranted() {
663
+ safeLogModuleEvent {
664
+ logModuleEvent(
665
+ where = "cgmJourney.cgm_location_permission_granted",
666
+ message = "CGM journey | screen=${javaClass.simpleName} | step=cgm_location_permission_granted",
667
+ level = SentryLogLevel.INFO,
668
+ attributes = mapOf(
669
+ "journey_step" to "cgm_location_permission_granted",
670
+ "screen" to javaClass.simpleName,
671
+ "is_reconnect" to isForReconnect.toString(),
672
+ ),
673
+ logsOnly = true,
674
+ )
675
+ }
646
676
  setSwitchChecked(binding.switchLocation, true)
647
677
  updateProceedButtonState()
648
678
  }
649
679
 
650
680
  @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
651
681
  private fun onBluetoothPermissionGranted() {
682
+ safeLogModuleEvent {
683
+ logModuleEvent(
684
+ where = "cgmJourney.cgm_bluetooth_permission_granted",
685
+ message = "CGM journey | screen=${javaClass.simpleName} | step=cgm_bluetooth_permission_granted",
686
+ level = SentryLogLevel.INFO,
687
+ attributes = mapOf(
688
+ "journey_step" to "cgm_bluetooth_permission_granted",
689
+ "screen" to javaClass.simpleName,
690
+ "is_reconnect" to isForReconnect.toString(),
691
+ ),
692
+ logsOnly = true,
693
+ )
694
+ }
652
695
  if (bluetoothAdapter == null) {
653
696
  Toast.makeText(this, "Bluetooth not supported", Toast.LENGTH_SHORT).show()
654
697
  } else {
@@ -25,6 +25,9 @@ import com.mytatvarnsdk.CgmTrackyLibModule
25
25
  import com.mytatvarnsdk.R
26
26
  import com.mytatvarnsdk.databinding.ActivityPlaceSensorBinding
27
27
  import com.mytatvarnsdk.databinding.ExitDialogBottomsheetBinding
28
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
29
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
30
+ import io.sentry.SentryLogLevel
28
31
 
29
32
  class PlaceSensorActivity : AppCompatActivity() {
30
33
  private lateinit var binding: ActivityPlaceSensorBinding
@@ -99,6 +102,20 @@ class PlaceSensorActivity : AppCompatActivity() {
99
102
  }
100
103
 
101
104
  private fun sendDataToRN(data: String, status: String) {
105
+ safeLogModuleEvent {
106
+ logModuleEvent(
107
+ where = "cgmJourney.$status",
108
+ message = "CGM journey | screen=${javaClass.simpleName} | step=$status",
109
+ level = SentryLogLevel.INFO,
110
+ jsonPayload = data.takeIf { it.isNotEmpty() },
111
+ attributes = mapOf(
112
+ "journey_step" to status,
113
+ "screen" to javaClass.simpleName,
114
+ "is_reconnect" to isForReconnect.toString(),
115
+ ),
116
+ logsOnly = true,
117
+ )
118
+ }
102
119
  if (reactContext != null) {
103
120
  try {
104
121
  val catalystInstance: CatalystInstance = reactContext.catalystInstance
@@ -25,6 +25,9 @@ import com.mytatvarnsdk.CgmTrackyLibModule
25
25
  import com.mytatvarnsdk.R
26
26
  import com.mytatvarnsdk.databinding.ActivityPlaceTransmitterBinding
27
27
  import com.mytatvarnsdk.databinding.ExitDialogBottomsheetBinding
28
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
29
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
30
+ import io.sentry.SentryLogLevel
28
31
 
29
32
  class PlaceTransmitterActivity : AppCompatActivity() {
30
33
  private lateinit var binding: ActivityPlaceTransmitterBinding
@@ -136,6 +139,20 @@ class PlaceTransmitterActivity : AppCompatActivity() {
136
139
  }
137
140
 
138
141
  private fun sendDataToRN(data: String, status: String) {
142
+ safeLogModuleEvent {
143
+ logModuleEvent(
144
+ where = "cgmJourney.$status",
145
+ message = "CGM journey | screen=${javaClass.simpleName} | step=$status",
146
+ level = SentryLogLevel.INFO,
147
+ jsonPayload = data.takeIf { it.isNotEmpty() },
148
+ attributes = mapOf(
149
+ "journey_step" to status,
150
+ "screen" to javaClass.simpleName,
151
+ "is_reconnect" to isForReconnect.toString(),
152
+ ),
153
+ logsOnly = true,
154
+ )
155
+ }
139
156
  if (reactContext != null) {
140
157
  try {
141
158
  val catalystInstance: CatalystInstance = reactContext.catalystInstance
@@ -15,6 +15,9 @@ import cgmblelib.utils.SharedPreferencesLibraryUtil
15
15
  import com.myc.library_zxing.QRFragment
16
16
  import com.myc.library_zxing.view.OnFragmentListener
17
17
  import com.mytatvarnsdk.R
18
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
19
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
20
+ import io.sentry.SentryLogLevel
18
21
  import ist.com.sdk.AlgorithmTools
19
22
  import ist.com.sdk.KRDecodeData
20
23
 
@@ -28,6 +31,18 @@ class QRActivity : AppCompatActivity(), View.OnClickListener {
28
31
  findViewById<View?>(R.id.btnLeft).setOnClickListener(this)
29
32
  mPocDevice = intent.getParcelableExtra("device")
30
33
  if (mPocDevice == null) {
34
+ safeLogModuleEvent {
35
+ logModuleEvent(
36
+ where = "QRActivity.missingDevice",
37
+ message = "CGM journey error | screen=${javaClass.simpleName} | QR scan started without transmitter device",
38
+ level = SentryLogLevel.WARN,
39
+ attributes = mapOf(
40
+ "error_message" to "QR scan started without transmitter device",
41
+ "screen" to javaClass.simpleName,
42
+ ),
43
+ logsOnly = true,
44
+ )
45
+ }
31
46
  finish()
32
47
  }
33
48
  }
@@ -36,9 +51,41 @@ class QRActivity : AppCompatActivity(), View.OnClickListener {
36
51
  super.onResume()
37
52
  mQRFragment = QRFragment.newInstance(object : OnFragmentListener {
38
53
  override fun onPermissionRequestFail(isForever: Boolean) {
54
+ val errorMessage = if (isForever) {
55
+ "Camera permission denied permanently"
56
+ } else {
57
+ "Camera permission denied"
58
+ }
59
+ safeLogModuleEvent {
60
+ logModuleEvent(
61
+ where = "QRActivity.cameraPermissionDenied",
62
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
63
+ level = SentryLogLevel.WARN,
64
+ attributes = mapOf(
65
+ "error_message" to errorMessage,
66
+ "screen" to javaClass.simpleName,
67
+ "permission_denied_forever" to isForever.toString(),
68
+ ),
69
+ logsOnly = true,
70
+ )
71
+ }
39
72
  }
40
73
 
41
74
  override fun onCameraOpenError(e: Exception?) {
75
+ val errorMessage = e?.message ?: "Camera failed to open"
76
+ safeLogModuleEvent {
77
+ logModuleEvent(
78
+ where = "QRActivity.cameraOpenError",
79
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
80
+ level = SentryLogLevel.WARN,
81
+ throwable = e,
82
+ attributes = mapOf(
83
+ "error_message" to errorMessage,
84
+ "screen" to javaClass.simpleName,
85
+ ),
86
+ logsOnly = true,
87
+ )
88
+ }
42
89
  }
43
90
 
44
91
  override fun onScanResult(messages: String?, bitmap: Bitmap?) {
@@ -71,12 +118,47 @@ class QRActivity : AppCompatActivity(), View.OnClickListener {
71
118
  */
72
119
  private fun decodeMessageCt3(message: String) {
73
120
  Log.d("QR Info ==> ", "decodeMessageCt3: $message")
121
+ try {
122
+ safeLogModuleEvent {
123
+ logModuleEvent(
124
+ where = "cgmJourney.cgm_qr_scanned",
125
+ message = "CGM journey | screen=${javaClass.simpleName} | step=cgm_qr_scanned",
126
+ level = SentryLogLevel.INFO,
127
+ jsonPayload = message,
128
+ attributes = mapOf(
129
+ "journey_step" to "cgm_qr_scanned",
130
+ "screen" to javaClass.simpleName,
131
+ ),
132
+ logsOnly = true,
133
+ )
134
+ }
74
135
 
75
- val decodeData: KRDecodeData = AlgorithmTools.getInstance().decodeCT(message.toCharArray())
136
+ val decodeData: KRDecodeData = AlgorithmTools.getInstance().decodeCT(message.toCharArray())
76
137
 
77
- Log.d("QR Info ==> ", "decodeData: $decodeData")
138
+ Log.d("QR Info ==> ", "decodeData: $decodeData")
78
139
 
79
- showMessage(message, decodeData.k, decodeData.r, message)
140
+ showMessage(message, decodeData.k, decodeData.r, message)
141
+ } catch (e: Exception) {
142
+ val errorMessage = e.message ?: "QR decode failed"
143
+ safeLogModuleEvent {
144
+ logModuleEvent(
145
+ where = "QRActivity.decodeMessageCt3",
146
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
147
+ level = SentryLogLevel.WARN,
148
+ throwable = e,
149
+ attributes = mapOf(
150
+ "error_message" to errorMessage,
151
+ "screen" to javaClass.simpleName,
152
+ "qr_raw" to message,
153
+ ),
154
+ logsOnly = true,
155
+ )
156
+ }
157
+ try {
158
+ mQRFragment?.restartPreviewAfterDelay(0)
159
+ } catch (_: Throwable) {
160
+ }
161
+ }
80
162
  }
81
163
 
82
164
  fun showMessage(message: String?, k: Float, r: Float, sensorInfo: String?) {
@@ -87,20 +169,79 @@ class QRActivity : AppCompatActivity(), View.OnClickListener {
87
169
  val qrInformation: QRInformation? = prefsHelper.qrInformation
88
170
 
89
171
  Log.d("qrInformation==> ", "mPocDevice: ${mPocDevice.toString()}")
90
- if (qrInformation != null && qrInformation.isEffective(mPocDevice?.name, r, k)) {
172
+ val isEffective = qrInformation?.isEffective(mPocDevice?.name, r, k) ?: false
173
+ safeLogModuleEvent {
174
+ logModuleEvent(
175
+ where = "cgmJourney.cgm_qr_is_effective",
176
+ message = "CGM journey | screen=${javaClass.simpleName} | isEffective=$isEffective",
177
+ level = SentryLogLevel.INFO,
178
+ jsonPayload = isEffective.toString(),
179
+ attributes = mapOf(
180
+ "journey_step" to "cgm_qr_is_effective",
181
+ "screen" to javaClass.simpleName,
182
+ "is_effective" to isEffective.toString(),
183
+ ),
184
+ logsOnly = true,
185
+ )
186
+ }
187
+ if (isEffective && qrInformation != null) {
91
188
  Log.d("qrInformation==> ", "qrInformation: $qrInformation")
189
+ safeLogModuleEvent {
190
+ logModuleEvent(
191
+ where = "cgmJourney.cgm_qr_validation_success",
192
+ message = "CGM journey | screen=${javaClass.simpleName} | step=cgm_qr_validation_success",
193
+ level = SentryLogLevel.INFO,
194
+ jsonPayload = qrInformation.toString(),
195
+ attributes = mapOf(
196
+ "journey_step" to "cgm_qr_validation_success",
197
+ "screen" to javaClass.simpleName,
198
+ "transmitter_name" to (mPocDevice?.name ?: ""),
199
+ "sensor_id" to (qrInformation.sensor ?: ""),
200
+ ),
201
+ logsOnly = true,
202
+ )
203
+ }
92
204
  setResult(RESULT_OK, Intent().putExtra("device", mPocDevice as Parcelable))
93
205
  finish()
94
206
  } else {
207
+ val failMessage = getString(R.string.verification_fail)
208
+ safeLogModuleEvent {
209
+ logModuleEvent(
210
+ where = "QRActivity.qrValidationFailed",
211
+ message = "CGM journey error | screen=${javaClass.simpleName} | $failMessage",
212
+ level = SentryLogLevel.WARN,
213
+ attributes = mapOf(
214
+ "error_message" to failMessage,
215
+ "screen" to javaClass.simpleName,
216
+ "transmitter_name" to (mPocDevice?.name ?: ""),
217
+ "qr_raw" to (message ?: ""),
218
+ ),
219
+ logsOnly = true,
220
+ )
221
+ }
95
222
  Toast.makeText(
96
223
  this@QRActivity,
97
- getString(R.string.verification_fail),
224
+ failMessage,
98
225
  Toast.LENGTH_SHORT
99
226
  )
100
227
  .show()
101
228
  mQRFragment!!.restartPreviewAfterDelay(0)
102
229
  }
103
230
  } catch (e: Exception) {
231
+ val errorMessage = e.message ?: "QR validation exception"
232
+ safeLogModuleEvent {
233
+ logModuleEvent(
234
+ where = "QRActivity.showMessage",
235
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
236
+ level = SentryLogLevel.WARN,
237
+ throwable = e,
238
+ attributes = mapOf(
239
+ "error_message" to errorMessage,
240
+ "screen" to javaClass.simpleName,
241
+ ),
242
+ logsOnly = true,
243
+ )
244
+ }
104
245
  e.printStackTrace()
105
246
  finish()
106
247
  }
@@ -52,6 +52,9 @@ import com.mytatvarnsdk.myView.dialog.WarnDialogUtils
52
52
  import com.mytatvarnsdk.myView.progress.ProgressManagement
53
53
  import com.mytatvarnsdk.myView.progress.ProgressType
54
54
  import com.mytatvarnsdk.network.AuthenticateSDKService
55
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
56
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
57
+ import io.sentry.SentryLogLevel
55
58
  import kotlinx.coroutines.CoroutineScope
56
59
  import kotlinx.coroutines.Dispatchers
57
60
  import kotlinx.coroutines.Job
@@ -127,6 +130,21 @@ class SearchTransmitterActivity : BaseBleActivity() {
127
130
 
128
131
  fun manageErrorState(showError: Boolean, errorStatus: String) {
129
132
  if (showError) {
133
+ if (errorStatus.isNotEmpty()) {
134
+ safeLogModuleEvent {
135
+ logModuleEvent(
136
+ where = "SearchTransmitterActivity.error",
137
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorStatus",
138
+ level = SentryLogLevel.WARN,
139
+ attributes = mapOf(
140
+ "error_message" to errorStatus,
141
+ "screen" to javaClass.simpleName,
142
+ "is_reconnect" to isForReconnect.toString(),
143
+ ),
144
+ logsOnly = true,
145
+ )
146
+ }
147
+ }
130
148
  binding.clFail.visibility = View.VISIBLE
131
149
  binding.clMain.visibility = View.GONE
132
150
  binding.commonButton.root.visibility = View.GONE
@@ -296,11 +314,37 @@ class SearchTransmitterActivity : BaseBleActivity() {
296
314
 
297
315
  public override fun checkFailForLowPower() {
298
316
  ProgressManagement.getInstance().dismissWait(this)
317
+ safeLogModuleEvent {
318
+ logModuleEvent(
319
+ where = "SearchTransmitterActivity.checkFailForLowPower",
320
+ message = "CGM journey error | screen=${javaClass.simpleName} | Transmitter low power",
321
+ level = SentryLogLevel.WARN,
322
+ attributes = mapOf(
323
+ "error_message" to "Transmitter low power",
324
+ "screen" to javaClass.simpleName,
325
+ "is_reconnect" to isForReconnect.toString(),
326
+ ),
327
+ logsOnly = true,
328
+ )
329
+ }
299
330
  WarnDialogUtils.getInstance().showWarnDialog(this, DialogType.TYPE_LOW_POWER)
300
331
  }
301
332
 
302
333
  public override fun checkFailErrorTemperature() {
303
334
  ProgressManagement.getInstance().dismissWait(this)
335
+ safeLogModuleEvent {
336
+ logModuleEvent(
337
+ where = "SearchTransmitterActivity.checkFailErrorTemperature",
338
+ message = "CGM journey error | screen=${javaClass.simpleName} | Transmitter temperature error",
339
+ level = SentryLogLevel.WARN,
340
+ attributes = mapOf(
341
+ "error_message" to "Transmitter temperature error",
342
+ "screen" to javaClass.simpleName,
343
+ "is_reconnect" to isForReconnect.toString(),
344
+ ),
345
+ logsOnly = true,
346
+ )
347
+ }
304
348
  }
305
349
 
306
350
  public override fun onlineLost() {
@@ -318,6 +362,21 @@ class SearchTransmitterActivity : BaseBleActivity() {
318
362
  }
319
363
  } catch (e: Exception) {
320
364
  Log.d("bind::-> ", "bind: ${e.message}")
365
+ safeLogModuleEvent {
366
+ val errorMessage = e.message ?: "Transmitter bind exception"
367
+ logModuleEvent(
368
+ where = "SearchTransmitterActivity.bind",
369
+ message = "CGM journey error | screen=${javaClass.simpleName} | $errorMessage",
370
+ level = SentryLogLevel.WARN,
371
+ throwable = e,
372
+ attributes = mapOf(
373
+ "error_message" to errorMessage,
374
+ "screen" to javaClass.simpleName,
375
+ "is_reconnect" to isForReconnect.toString(),
376
+ ),
377
+ logsOnly = true,
378
+ )
379
+ }
321
380
  }
322
381
  }
323
382
 
@@ -440,6 +499,20 @@ class SearchTransmitterActivity : BaseBleActivity() {
440
499
  }
441
500
 
442
501
  private fun sendDataToRN(data: String, status: String) {
502
+ safeLogModuleEvent {
503
+ logModuleEvent(
504
+ where = "cgmJourney.$status",
505
+ message = "CGM journey | screen=${javaClass.simpleName} | step=$status",
506
+ level = SentryLogLevel.INFO,
507
+ jsonPayload = data.takeIf { it.isNotEmpty() },
508
+ attributes = mapOf(
509
+ "journey_step" to status,
510
+ "screen" to javaClass.simpleName,
511
+ "is_reconnect" to isForReconnect.toString(),
512
+ ),
513
+ logsOnly = true,
514
+ )
515
+ }
443
516
  if (reactContext != null) {
444
517
  try {
445
518
  val catalystInstance: CatalystInstance = reactContext.catalystInstance
@@ -487,6 +560,19 @@ class SearchTransmitterActivity : BaseBleActivity() {
487
560
  }
488
561
 
489
562
  private fun startDeviceSearch() {
563
+ safeLogModuleEvent {
564
+ logModuleEvent(
565
+ where = "cgmJourney.cgm_transmitter_scan_started",
566
+ message = "CGM journey | screen=${javaClass.simpleName} | step=cgm_transmitter_scan_started",
567
+ level = SentryLogLevel.INFO,
568
+ attributes = mapOf(
569
+ "journey_step" to "cgm_transmitter_scan_started",
570
+ "screen" to javaClass.simpleName,
571
+ "is_reconnect" to isForReconnect.toString(),
572
+ ),
573
+ logsOnly = true,
574
+ )
575
+ }
490
576
  mModel = ViewModelProviders.of(this)[MainActivityModel::class.java]
491
577
 
492
578
  ReconnectManagement.getInstance()
@@ -544,6 +630,20 @@ class SearchTransmitterActivity : BaseBleActivity() {
544
630
  isTransmitterConnected = true
545
631
 
546
632
  if (transmitterDeviceInfo != null) {
633
+ safeLogModuleEvent {
634
+ logModuleEvent(
635
+ where = "cgmJourney.cgm_transmitter_connect_clicked",
636
+ message = "CGM journey | screen=${javaClass.simpleName} | step=cgm_transmitter_connect_clicked",
637
+ level = SentryLogLevel.INFO,
638
+ jsonPayload = transmitterDeviceInfo?.pocDevice?.name ?: "",
639
+ attributes = mapOf(
640
+ "journey_step" to "cgm_transmitter_connect_clicked",
641
+ "screen" to javaClass.simpleName,
642
+ "is_reconnect" to isForReconnect.toString(),
643
+ ),
644
+ logsOnly = true,
645
+ )
646
+ }
547
647
  if (isForReconnect) {
548
648
  GattTool.unBindInDatabase(this, enumUnBind.UNBIND_FORCE)
549
649
  }
@@ -31,6 +31,9 @@ import com.mytatvarnsdk.network.AuthenticateSDKService
31
31
  import com.mytatvarnsdk.network.AuthenticateSDKService.LoaderListener
32
32
  import com.mytatvarnsdk.utils.DeviceStatus
33
33
  import com.mytatvarnsdk.utils.TATVA_ENVIRONMENT
34
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
35
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
36
+ import io.sentry.SentryLogLevel
34
37
  import kotlinx.coroutines.CoroutineScope
35
38
  import kotlinx.coroutines.Dispatchers
36
39
  import kotlinx.coroutines.Job
@@ -121,6 +124,20 @@ class SensorConnectSuccessActivity : AppCompatActivity() {
121
124
  }
122
125
 
123
126
  private fun sendDataToRN(data: String, status: String) {
127
+ safeLogModuleEvent {
128
+ logModuleEvent(
129
+ where = "cgmJourney.$status",
130
+ message = "CGM journey | screen=${javaClass.simpleName} | step=$status",
131
+ level = SentryLogLevel.INFO,
132
+ jsonPayload = data.takeIf { it.isNotEmpty() },
133
+ attributes = mapOf(
134
+ "journey_step" to status,
135
+ "screen" to javaClass.simpleName,
136
+ "is_reconnect" to isForReconnect.toString(),
137
+ ),
138
+ logsOnly = true,
139
+ )
140
+ }
124
141
  if (reactContext != null) {
125
142
  try {
126
143
  val catalystInstance: CatalystInstance = reactContext.catalystInstance
@@ -173,6 +190,20 @@ class SensorConnectSuccessActivity : AppCompatActivity() {
173
190
  obj.put("status", DeviceStatus.CONNECTED.id)
174
191
  obj.put("rawData", rawData)
175
192
 
193
+ safeLogModuleEvent {
194
+ logModuleEvent(
195
+ where = "SensorConnectSuccessActivity.postDeviceData.request",
196
+ message = "POST /cgm/connection CONNECTED on sensor success screen",
197
+ level = SentryLogLevel.INFO,
198
+ jsonPayload = "request=${obj}",
199
+ attributes = mapOf(
200
+ "cgm_status" to DeviceStatus.CONNECTED.id,
201
+ "sensor_id" to (information.sensor ?: ""),
202
+ ),
203
+ logsOnly = true,
204
+ )
205
+ }
206
+
176
207
  authenticateSDKService.postDeviceData(
177
208
  environment = if (CgmTrackyLibModule.env.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
178
209
  data = obj.toString(),
@@ -28,6 +28,9 @@ import com.mytatvarnsdk.CgmTrackyLibModule.Companion.mReactContext
28
28
  import com.mytatvarnsdk.R
29
29
  import com.mytatvarnsdk.databinding.ActivityStartCgmactivityBinding
30
30
  import com.mytatvarnsdk.databinding.ExitDialogBottomsheetBinding
31
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.logModuleEvent
32
+ import com.mytatvarnsdk.utils.CgmModuleSentryLog.safeLogModuleEvent
33
+ import io.sentry.SentryLogLevel
31
34
 
32
35
 
33
36
  class StartCGMActivity : AppCompatActivity() {
@@ -168,6 +171,20 @@ class StartCGMActivity : AppCompatActivity() {
168
171
  }
169
172
 
170
173
  private fun sendDataToRN(data: String, status: String) {
174
+ safeLogModuleEvent {
175
+ logModuleEvent(
176
+ where = "cgmJourney.$status",
177
+ message = "CGM journey | screen=${javaClass.simpleName} | step=$status",
178
+ level = SentryLogLevel.INFO,
179
+ jsonPayload = data.takeIf { it.isNotEmpty() },
180
+ attributes = mapOf(
181
+ "journey_step" to status,
182
+ "screen" to javaClass.simpleName,
183
+ "is_reconnect" to isForReconnect.toString(),
184
+ ),
185
+ logsOnly = true,
186
+ )
187
+ }
171
188
  if (reactContext != null) {
172
189
  try {
173
190
  val catalystInstance: CatalystInstance = reactContext.catalystInstance
@@ -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 {
@@ -267,8 +303,35 @@ class AuthenticateSDKService(val scope: CoroutineScope) {
267
303
 
268
304
  Log.d("API Response", "Device Verification API Response: $decryptedResponse")
269
305
 
306
+ safeLogModuleEvent {
307
+ logModuleEvent(
308
+ where = "AuthenticateSDKService.verifyDevice.success",
309
+ message = "GET device_verification succeeded | sensorId=$sensorId",
310
+ level = SentryLogLevel.INFO,
311
+ jsonPayload = "response=$decryptedResponse",
312
+ attributes = mapOf("sensor_id" to sensorId),
313
+ logsOnly = true,
314
+ )
315
+ }
316
+
270
317
  responseListener.onResponseSuccess(decryptedResponse)
271
318
  } catch (e: Exception) {
319
+ safeLogModuleEvent {
320
+ val errorMessage =
321
+ e.message ?: "GET device_verification failed | sensorId=$sensorId"
322
+ logModuleEvent(
323
+ where = "AuthenticateSDKService.verifyDevice.fail",
324
+ message = "CGM journey error | screen=AuthenticateSDKService | $errorMessage",
325
+ level = SentryLogLevel.WARN,
326
+ throwable = e,
327
+ attributes = mapOf(
328
+ "error_message" to errorMessage,
329
+ "screen" to "AuthenticateSDKService",
330
+ "sensor_id" to sensorId,
331
+ ),
332
+ logsOnly = true,
333
+ )
334
+ }
272
335
  responseListener.onResponseFail()
273
336
  e.printStackTrace()
274
337
  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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mytatva-rn-sdk",
3
- "version": "1.2.96",
3
+ "version": "1.2.97",
4
4
  "description": "a package to inject data into visit health pwa",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",