react-native-mytatva-rn-sdk 1.2.92 → 1.2.94

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.
@@ -1,4 +1,4 @@
1
- package com.mytatvarnsdk
1
+ package com.mytatvarnsdk
2
2
 
3
3
  import android.app.Application
4
4
  import android.bluetooth.BluetoothAdapter
@@ -12,6 +12,9 @@ import androidx.lifecycle.Observer
12
12
  import androidx.lifecycle.ViewModelProvider
13
13
  import androidx.lifecycle.ViewModelStore
14
14
  import cgmblelib.Enum.enumError
15
+ import cgmblelib.Enum.enumGlucoseStatus
16
+ import cgmblelib.Enum.enumSound
17
+ import cgmblelib.Enum.enumTrend
15
18
  import cgmblelib.base.BApplication
16
19
  import cgmblelib.database.entity.PocDevice
17
20
  import cgmblelib.database.entity.PocGlucose
@@ -33,19 +36,23 @@ import com.mytatvarnsdk.activity.HelpActivity
33
36
  import com.mytatvarnsdk.activity.StartCGMActivity
34
37
  import com.mytatvarnsdk.model.AllCGMLogRequest
35
38
  import com.mytatvarnsdk.model.CgmLog
39
+ import com.mytatvarnsdk.model.ErrorObject
40
+ import com.mytatvarnsdk.model.GlucoseStatusObject
41
+ import com.mytatvarnsdk.model.TrendObject
36
42
  import com.mytatvarnsdk.model.CgmSensorResponse
37
43
  import com.mytatvarnsdk.model.GlucoseLog
38
- import com.mytatvarnsdk.model.GlucoseLog.ErrorObject
39
- import com.mytatvarnsdk.model.GlucoseLog.GlucoseStatusObject
40
- import com.mytatvarnsdk.model.GlucoseLog.TrendObject
41
44
  import com.mytatvarnsdk.model.GlucoseLogRequest
42
45
  import com.mytatvarnsdk.model.MainActivityModel
43
46
  import com.mytatvarnsdk.network.AuthenticateSDKService
44
47
  import com.mytatvarnsdk.network.AuthenticateSDKService.LoaderListener
45
48
  import com.mytatvarnsdk.utils.DeviceStatus
46
49
  import com.mytatvarnsdk.utils.TATVA_ENVIRONMENT
50
+ import io.sentry.ScopeCallback
47
51
  import io.sentry.Sentry
52
+ import io.sentry.SentryAttributes
48
53
  import io.sentry.SentryLevel
54
+ import io.sentry.SentryLogLevel
55
+ import io.sentry.logger.SentryLogParameters
49
56
  import kotlinx.coroutines.CoroutineScope
50
57
  import kotlinx.coroutines.Dispatchers
51
58
  import kotlinx.coroutines.Job
@@ -116,79 +123,186 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
116
123
  return "CgmTrackyLib"
117
124
  }
118
125
 
126
+ private fun clipForSentry(text: String, maxChars: Int = MAX_SENTRY_DATA_EXTRA_CHARS): String =
127
+ try {
128
+ if (text.length <= maxChars) text else text.take(maxChars) + "…"
129
+ } catch (_: Throwable) {
130
+ ""
131
+ }
132
+
119
133
  /**
134
+ * Structured Sentry log (like RN `Sentry.logger.info/warn/error`).
120
135
  * Uses the host app's Sentry client (never call Sentry.init here).
121
- * [data]: [Throwable] → exception event; anything else → message + extra `data` (stringified, clipped);
122
- * nullerror message with location only.
136
+ *
137
+ * Sentry 8.x: `Sentry.logger()` sends to **Explore Logs** and requires native
138
+ * `enableLogs: true` (mapped from parent `@sentry/react-native` init). It is NOT a
139
+ * drop-in replacement for `captureException` / `captureMessage` (Issues tab).
140
+ *
141
+ * When logs are disabled or unavailable, falls back to captureMessage/captureException
142
+ * so operational telemetry still reaches Sentry Issues (same as Sentry 6.x behaviour).
123
143
  */
124
- private fun captureModuleException(
144
+ private fun logModuleEvent(
125
145
  where: String,
126
- data: Any?,
127
- level: SentryLevel = SentryLevel.ERROR
146
+ message: String = "",
147
+ level: SentryLogLevel = SentryLogLevel.INFO,
148
+ jsonPayload: String? = null,
149
+ throwable: Throwable? = null,
150
+ attributes: Map<String, String> = emptyMap()
128
151
  ) {
129
152
  try {
130
- Sentry.withScope { scope ->
131
- scope.setTag("cgm_tracky_lib", "true")
132
- scope.setExtra("location", where)
133
- if (env.isNotEmpty()) {
134
- scope.setExtra("tatva_env", env)
153
+ if (!Sentry.isEnabled()) {
154
+ Log.w("logModuleEvent", "Sentry not initialized — skipping [$where]")
155
+ return
156
+ }
157
+
158
+ val resolvedMessage = when {
159
+ message.isNotEmpty() -> message
160
+ throwable != null -> {
161
+ val typeName = throwable.javaClass.simpleName ?: "Throwable"
162
+ "$typeName: ${throwable.message ?: ""}"
135
163
  }
164
+ else -> where
165
+ }
166
+ val mergedAttributes = try {
167
+ attributes.toMutableMap()
168
+ } catch (_: Throwable) {
169
+ mutableMapOf()
170
+ }
171
+ throwable?.let {
172
+ mergedAttributes["exception"] = it.javaClass.simpleName ?: "Throwable"
173
+ mergedAttributes["exception_message"] = it.message ?: ""
174
+ }
136
175
 
137
- when (data) {
138
- null ->
139
- run {
140
- Sentry.captureMessage("CgmTrackyLib: $where", level)
141
- }
176
+ val clippedJson = jsonPayload?.let { clipForSentry(it) }
177
+ val logMessage = clipForSentry(
178
+ "CgmTrackyLib: $where | $resolvedMessage",
179
+ MAX_SENTRY_MESSAGE_DATA_CHARS
180
+ )
142
181
 
143
- is Throwable ->
144
- run {
145
- // Put exception summary into `extra.data` for quick inspection.
146
- val throwableText =
147
- "${data.javaClass.simpleName}: ${data.message ?: ""}".trim()
148
- val clipped =
149
- if (throwableText.length > MAX_SENTRY_DATA_EXTRA_CHARS) {
150
- throwableText.take(MAX_SENTRY_DATA_EXTRA_CHARS) + ""
151
- } else {
152
- throwableText
153
- }
154
- scope.setExtra("data", clipped)
155
- Sentry.captureException(data)
156
- }
182
+ val sentryAttributes = mutableMapOf<String, Any>(
183
+ "cgm_tracky_lib" to "true",
184
+ "location" to where,
185
+ )
186
+ if (env.isNotEmpty()) {
187
+ sentryAttributes["tatva_env"] = env
188
+ }
189
+ clippedJson?.let { sentryAttributes["json"] = it }
190
+ mergedAttributes.forEach { (key, value) ->
191
+ sentryAttributes[key] = clipForSentry(value, 500)
192
+ }
157
193
 
158
- else -> {
159
- val text = when (data) {
160
- is String -> data
161
- else -> data.toString()
162
- }
163
- val clipped =
164
- if (text.length > MAX_SENTRY_DATA_EXTRA_CHARS) {
165
- text.take(MAX_SENTRY_DATA_EXTRA_CHARS) + "…"
166
- } else {
167
- text
168
- }
169
- scope.setExtra("data", clipped)
170
- // Duplicate under a more explicit key so it is easy to find in Sentry UI.
171
- scope.setExtra("json", clipped)
172
-
173
- // Also include a prefix in the message itself so it appears in the main event row.
174
- // (In some Sentry views, extra fields are not shown by default.)
175
- val messageData =
176
- if (clipped.length > MAX_SENTRY_MESSAGE_DATA_CHARS) {
177
- clipped.take(MAX_SENTRY_MESSAGE_DATA_CHARS) + "…"
178
- } else {
179
- clipped
180
- }
194
+ val logsEnabled = try {
195
+ Sentry.getCurrentScopes()?.options?.logs?.isEnabled == true
196
+ } catch (_: Throwable) {
197
+ false
198
+ }
181
199
 
182
- Sentry.captureMessage("CgmTrackyLib: $where | data=$messageData", level)
183
- }
200
+ if (logsEnabled) {
201
+ val logParams = SentryLogParameters.create(
202
+ SentryAttributes.fromMap(sentryAttributes)
203
+ )
204
+ logParams.origin = "cgm-tracky-lib"
205
+ Sentry.logger().log(level, logParams, logMessage)
206
+ // Exceptions logged via logger should also appear under Issues (pre-8.x behaviour).
207
+ if (throwable != null && level >= SentryLogLevel.ERROR) {
208
+ captureModuleException(where, throwable, sentryAttributes)
184
209
  }
210
+ } else {
211
+ captureModuleEventFallback(where, logMessage, level, throwable, sentryAttributes)
212
+ }
213
+ } catch (sentryEx: Throwable) {
214
+ try {
215
+ Log.w(
216
+ "logModuleEvent",
217
+ "Sentry logger failed for [$where]: ${sentryEx.message}",
218
+ sentryEx
219
+ )
220
+ Log.e("CgmTrackyLib", "[$where]", throwable ?: sentryEx)
221
+ } catch (_: Throwable) {
222
+ // Last resort: never propagate to callers.
223
+ }
224
+ }
225
+ }
226
+
227
+ private fun mapLogLevelToEventLevel(level: SentryLogLevel): SentryLevel = when (level) {
228
+ SentryLogLevel.TRACE, SentryLogLevel.DEBUG -> SentryLevel.DEBUG
229
+ SentryLogLevel.INFO -> SentryLevel.INFO
230
+ SentryLogLevel.WARN -> SentryLevel.WARNING
231
+ SentryLogLevel.ERROR -> SentryLevel.ERROR
232
+ SentryLogLevel.FATAL -> SentryLevel.FATAL
233
+ }
234
+
235
+ private fun applyModuleScopeExtras(
236
+ where: String,
237
+ attributes: Map<String, Any>
238
+ ): ScopeCallback = ScopeCallback { scope ->
239
+ scope.setTag("cgm_tracky_lib", "true")
240
+ scope.setTag("location", where)
241
+ if (env.isNotEmpty()) {
242
+ scope.setTag("tatva_env", env)
243
+ }
244
+ attributes.forEach { (key, value) ->
245
+ scope.setExtra(key, value.toString())
246
+ }
247
+ }
248
+
249
+ /** Fallback when native logs are disabled — sends to Issues via captureMessage. */
250
+ private fun captureModuleEventFallback(
251
+ where: String,
252
+ message: String,
253
+ level: SentryLogLevel,
254
+ throwable: Throwable?,
255
+ attributes: Map<String, Any>
256
+ ) {
257
+ val scopeCallback = applyModuleScopeExtras(where, attributes)
258
+ if (throwable != null) {
259
+ Sentry.captureException(throwable, scopeCallback)
260
+ } else {
261
+ Sentry.captureMessage(message, mapLogLevelToEventLevel(level), scopeCallback)
262
+ }
263
+ }
264
+
265
+ private fun captureModuleException(
266
+ where: String,
267
+ throwable: Throwable,
268
+ attributes: Map<String, Any>
269
+ ) {
270
+ Sentry.captureException(throwable, applyModuleScopeExtras(where, attributes))
271
+ }
272
+
273
+ /** Wraps log preparation + emission so a bad payload/toString() cannot break CGM flows. */
274
+ private inline fun safeLogModuleEvent(block: () -> Unit) {
275
+ try {
276
+ block()
277
+ } catch (t: Throwable) {
278
+ try {
279
+ Log.w("logModuleEvent", "Failed to prepare Sentry log: ${t.message}", t)
280
+ } catch (_: Throwable) {
281
+ // Never propagate to callers.
185
282
  }
186
- } catch (sentryEx: Exception) {
187
- // If Sentry itself fails, never crash the CGM processing flow.
188
- Log.e("captureModuleException", "Sentry capture failed: ${sentryEx.message}", sentryEx)
189
283
  }
190
284
  }
191
285
 
286
+ private fun cgmStatusDeviceAttributes(
287
+ status: String,
288
+ device: PocDevice?,
289
+ sensorId: String?,
290
+ bleEnabled: Boolean,
291
+ extra: Map<String, String> = emptyMap(),
292
+ ): Map<String, String> {
293
+ val attrs = mutableMapOf(
294
+ "cgm_status" to status,
295
+ "sensor_id" to (sensorId ?: ""),
296
+ "ble_enabled" to bleEnabled.toString(),
297
+ "is_bound_and_connect" to (device?.isBoundAndConnect?.toString() ?: "unknown"),
298
+ "is_unbind" to (device?.isUnBind?.toString() ?: "unknown"),
299
+ "is_bound_but_disconnect" to (device?.isBoundButDisConnect?.toString() ?: "unknown"),
300
+ "transmitter_name" to (device?.name.orEmpty()),
301
+ )
302
+ attrs.putAll(extra)
303
+ return attrs
304
+ }
305
+
192
306
  private fun updateUserData(data: ReadableMap?) {
193
307
  currentUserData = data
194
308
  // Always reset so stale values from a previous user don't persist
@@ -203,7 +317,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
203
317
  }
204
318
  } catch (e: Exception) {
205
319
  Log.e("updateUserData", "Error extracting last_connect_cgm: ${e.message}", e)
206
- captureModuleException("updateUserData", e)
320
+ safeLogModuleEvent {
321
+ logModuleEvent("updateUserData", throwable = e, level = SentryLogLevel.ERROR)
322
+ }
207
323
  }
208
324
  }
209
325
 
@@ -237,7 +353,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
237
353
  }
238
354
  } catch (e: Exception) {
239
355
  Log.e("observeDeviceStatus", "observeDeviceStatus: ${e.message}", e)
240
- captureModuleException("observeDeviceStatus", e)
356
+ safeLogModuleEvent {
357
+ logModuleEvent("observeDeviceStatus", throwable = e, level = SentryLogLevel.ERROR)
358
+ }
241
359
  }
242
360
  }
243
361
 
@@ -365,7 +483,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
365
483
  }
366
484
  } catch (e: Exception) {
367
485
  Log.e("observeTransmitterUnbindStatus", "observeTransmitterUnbindStatus: ${e.message}", e)
368
- captureModuleException("observeTransmitterUnbindStatus", e)
486
+ safeLogModuleEvent {
487
+ logModuleEvent("observeTransmitterUnbindStatus", throwable = e, level = SentryLogLevel.ERROR)
488
+ }
369
489
  }
370
490
  }
371
491
 
@@ -396,11 +516,29 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
396
516
 
397
517
  Log.d("Device event Status", "Device event Status: $status")
398
518
 
519
+ safeLogModuleEvent {
520
+ logModuleEvent(
521
+ where = "postEventDataToAPI.cgmStatusResolved",
522
+ message = "CGM status resolved: $status | bleEnabled=$bleStatus",
523
+ level = SentryLogLevel.INFO,
524
+ attributes = cgmStatusDeviceAttributes(status, device, sensorId, bleStatus),
525
+ )
526
+ }
527
+
399
528
  if (status == DeviceStatus.DISCONNECTED.id && !isDebounceTimerActive) {
400
529
  // Start debounce timer for 1 minutes
401
530
  debounceDeviceTimer = Timer()
402
531
  isDebounceTimerActive = true
403
532
 
533
+ safeLogModuleEvent {
534
+ logModuleEvent(
535
+ where = "postEventDataToAPI.cgmStatusDebounceScheduled",
536
+ message = "CGM DISCONNECTED debounce started (1 min) | status=$status",
537
+ level = SentryLogLevel.INFO,
538
+ attributes = cgmStatusDeviceAttributes(status, device, sensorId, bleStatus),
539
+ )
540
+ }
541
+
404
542
  debounceDeviceTimer?.schedule(object : TimerTask() {
405
543
  override fun run() {
406
544
  isDebounceTimerActive = false
@@ -422,7 +560,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
422
560
 
423
561
  } catch (e: Exception) {
424
562
  Log.e("postEventDataToAPI", "postEventDataToAPI: ${e.message}", e)
425
- captureModuleException("postEventDataToAPI", e)
563
+ safeLogModuleEvent {
564
+ logModuleEvent("postEventDataToAPI", throwable = e, level = SentryLogLevel.ERROR)
565
+ }
426
566
  }
427
567
  }
428
568
  }
@@ -438,6 +578,7 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
438
578
  Log.d("Device event Status", "Last Device event lastDeviceStatus API: $lastDeviceStatus")
439
579
 
440
580
  if (status.isNotEmpty() && status != lastDeviceStatus) {
581
+ val previousStatus = lastDeviceStatus
441
582
  lastDeviceStatus = status
442
583
 
443
584
  Log.d("Device event Status", "Device event Status API: $status")
@@ -455,6 +596,25 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
455
596
  put("rawData", rawData)
456
597
  }
457
598
  Log.d("commingg", "Yuppp")
599
+
600
+ safeLogModuleEvent {
601
+ logModuleEvent(
602
+ where = "callEventAPI.cgmStatusChanged",
603
+ message = "CGM status changed | previous=${previousStatus ?: "none"} | new=$status",
604
+ level = SentryLogLevel.INFO,
605
+ jsonPayload = obj.toString(),
606
+ attributes = cgmStatusDeviceAttributes(
607
+ status = status,
608
+ device = device,
609
+ sensorId = sensorId,
610
+ bleEnabled = BluetoothAdapter.getDefaultAdapter()?.isEnabled == true,
611
+ extra = mapOf(
612
+ "previous_cgm_status" to (previousStatus ?: ""),
613
+ ),
614
+ ),
615
+ )
616
+ }
617
+
458
618
  authenticateSDKService.postDeviceData(
459
619
  environment = if (envType.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
460
620
  data = obj.toString(),
@@ -489,7 +649,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
489
649
  currentActivity?.startActivity(intent)
490
650
  } catch (e: Exception) {
491
651
  Log.e("startCgmTracky", "startCgmTracky: ${e.message}", e)
492
- captureModuleException("startCgmTracky", e)
652
+ safeLogModuleEvent {
653
+ logModuleEvent("startCgmTracky", throwable = e, level = SentryLogLevel.ERROR)
654
+ }
493
655
  }
494
656
  }
495
657
 
@@ -518,7 +680,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
518
680
  )
519
681
  } catch (e: Exception) {
520
682
  Log.e("reconnectCgmTracky", "reconnectCgmTracky: ${e.message}", e)
521
- captureModuleException("reconnectCgmTracky", e)
683
+ safeLogModuleEvent {
684
+ logModuleEvent("reconnectCgmTracky", throwable = e, level = SentryLogLevel.ERROR)
685
+ }
522
686
  }
523
687
  }
524
688
 
@@ -529,7 +693,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
529
693
  currentActivity?.startActivity(intent)
530
694
  } catch (e: Exception) {
531
695
  Log.e("openHelpSupport", "openHelpSupport: ${e.message}", e)
532
- captureModuleException("openHelpSupport", e)
696
+ safeLogModuleEvent {
697
+ logModuleEvent("openHelpSupport", throwable = e, level = SentryLogLevel.ERROR)
698
+ }
533
699
  }
534
700
  }
535
701
 
@@ -601,7 +767,13 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
601
767
  Log.d("observeGlucoseData", "Live glucose observer started successfully")
602
768
  } catch (e: Exception) {
603
769
  Log.e("observeGlucoseData", "Error adding observer: ${e.message}", e)
604
- captureModuleException("observeGlucoseData.observeForever", e)
770
+ safeLogModuleEvent {
771
+ logModuleEvent(
772
+ "observeGlucoseData.observeForever",
773
+ throwable = e,
774
+ level = SentryLogLevel.ERROR
775
+ )
776
+ }
605
777
  glucoseObserver = null
606
778
  isObserving = false
607
779
  }
@@ -610,7 +782,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
610
782
 
611
783
  } catch (e: Exception) {
612
784
  Log.e("observeGlucoseData", "observeGlucoseData: ${e.message}", e)
613
- captureModuleException("observeGlucoseData", e)
785
+ safeLogModuleEvent {
786
+ logModuleEvent("observeGlucoseData", throwable = e, level = SentryLogLevel.ERROR)
787
+ }
614
788
  isObserving = false
615
789
  glucoseObserver = null
616
790
  }
@@ -694,8 +868,18 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
694
868
  val json = singlePayloadJsonObject.toString()
695
869
 
696
870
  Log.d("Glucose data 3 min==> ", "Glucose data 3 min==> final Json: $json")
697
- // String → captureModuleException puts JSON in scope extra "data" (clipped); INFO avoids error-rate noise.
698
- // captureModuleException("handleGlucoseData==>postCGMData", json, level = SentryLevel.INFO)
871
+ safeLogModuleEvent {
872
+ logModuleEvent(
873
+ where = "handleGlucoseData.postCGMData",
874
+ message = "Posting single glucose data | glucoseId=${pocGlucose.glucoseId ?: ""}",
875
+ level = SentryLogLevel.INFO,
876
+ jsonPayload = json,
877
+ attributes = mapOf(
878
+ "glucoseId" to (pocGlucose.glucoseId?.toString() ?: "")
879
+ )
880
+ )
881
+ }
882
+
699
883
  authenticateSDKService.postCGMData(
700
884
  environment = if (envType.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
701
885
  data = json,
@@ -703,31 +887,36 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
703
887
  responseListener = object : AuthenticateSDKService.ResponseListener {
704
888
  override fun onResponseSuccess(response: String) {
705
889
  updateSyncMetadata(pocGlucose)
706
- // captureModuleException(
707
- // "handleGlucoseData.postCGMData.success",
708
- // json,
709
- // extras =
710
- // mapOf(
711
- // "pocGlucose" to pocGlucose.toString(),
712
- // "sdk_response" to response
713
- // ),
714
- // level = SentryLevel.INFO
715
- // )
890
+ safeLogModuleEvent {
891
+ logModuleEvent(
892
+ where = "handleGlucoseData.postCGMData.success",
893
+ message = "Single glucose data uploaded successfully | glucoseId=${pocGlucose.glucoseId ?: ""}",
894
+ level = SentryLogLevel.INFO,
895
+ jsonPayload = json,
896
+ attributes = mapOf(
897
+ "glucoseId" to (pocGlucose.glucoseId?.toString() ?: ""),
898
+ "sdk_response" to response
899
+ )
900
+ )
901
+ }
716
902
  Log.d("CGM Data", "Single glucose data uploaded successfully")
717
903
  }
718
904
 
719
905
  override fun onResponseFail() {
720
- val lastSync = prefsHelper?.lastSyncData
721
- val contextData =
722
- "failure_reason=Failed to upload single glucose data;" +
723
- "pocGlucose=${pocGlucose};" +
724
- "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
725
- "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
726
- captureModuleException(
727
- "handleGlucoseData.postCGMData.fail",
728
- "$json | $contextData",
729
- level = SentryLevel.WARNING
730
- )
906
+ safeLogModuleEvent {
907
+ val lastSync = prefsHelper?.lastSyncData
908
+ val contextData =
909
+ "failure_reason=Failed to upload single glucose data;" +
910
+ "pocGlucose=${pocGlucose};" +
911
+ "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
912
+ "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
913
+ logModuleEvent(
914
+ where = "handleGlucoseData.postCGMData.fail",
915
+ message = contextData,
916
+ level = SentryLogLevel.WARN,
917
+ jsonPayload = json
918
+ )
919
+ }
731
920
  Log.e("CGM Data", "Failed to upload single glucose data")
732
921
  }
733
922
  }
@@ -741,31 +930,37 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
741
930
  } */
742
931
  } else {
743
932
  Log.d("handleGlucoseData", "Glucose data has error: ${pocGlucose.errorCode}")
933
+ safeLogModuleEvent {
934
+ val lastSync = prefsHelper?.lastSyncData
935
+ val contextData =
936
+ "failure_reason=Glucose data has error: ${pocGlucose.errorCode};" +
937
+ "pocGlucose=${pocGlucose};" +
938
+ "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
939
+ "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
940
+ logModuleEvent(
941
+ where = "handleGlucoseData==>postCGMData",
942
+ message = "Glucose data has error: ${pocGlucose.errorCode} | $contextData",
943
+ level = SentryLogLevel.INFO
944
+ )
945
+ }
946
+ handleGlucoseError(pocGlucose, envType)
947
+ }
948
+ } catch (e: Exception) {
949
+ Log.e("handleGlucoseData", "Error handling glucose data: ${e.message}", e)
950
+ safeLogModuleEvent {
744
951
  val lastSync = prefsHelper?.lastSyncData
745
952
  val contextData =
746
- "failure_reason=Glucose data has error: ${pocGlucose.errorCode};" +
953
+ "failure_reason=Exception in handleGlucoseData;" +
747
954
  "pocGlucose=${pocGlucose};" +
748
955
  "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
749
956
  "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
750
- captureModuleException(
751
- "handleGlucoseData==>postCGMData",
752
- "Glucose data has error: ${pocGlucose.errorCode} | $contextData",
753
- level = SentryLevel.INFO
957
+ logModuleEvent(
958
+ where = "handleGlucoseData",
959
+ message = "handleGlucoseData failed | $contextData",
960
+ throwable = e,
961
+ level = SentryLogLevel.ERROR
754
962
  )
755
- handleGlucoseError(pocGlucose, envType)
756
963
  }
757
- } catch (e: Exception) {
758
- Log.e("handleGlucoseData", "Error handling glucose data: ${e.message}", e)
759
- val lastSync = prefsHelper?.lastSyncData
760
- val contextData =
761
- "failure_reason=Exception in handleGlucoseData;" +
762
- "pocGlucose=${pocGlucose};" +
763
- "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
764
- "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
765
- captureModuleException(
766
- "handleGlucoseData",
767
- RuntimeException("handleGlucoseData failed | $contextData", e)
768
- )
769
964
  }
770
965
  }
771
966
 
@@ -811,7 +1006,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
811
1006
  }
812
1007
  } catch (e: Exception) {
813
1008
  Log.e("handleGlucoseError", "Error handling glucose error: ${e.message}", e)
814
- captureModuleException("handleGlucoseError", e)
1009
+ safeLogModuleEvent {
1010
+ logModuleEvent("handleGlucoseError", throwable = e, level = SentryLogLevel.ERROR)
1011
+ }
815
1012
  }
816
1013
  }
817
1014
 
@@ -924,7 +1121,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
924
1121
  }
925
1122
  } catch (e: Exception) {
926
1123
  Log.e("observeAllGlucoseData", "Error in batch processing: ${e.message}", e)
927
- captureModuleException("observeAllGlucoseData", e)
1124
+ safeLogModuleEvent {
1125
+ logModuleEvent("observeAllGlucoseData", throwable = e, level = SentryLogLevel.ERROR)
1126
+ }
928
1127
  } finally {
929
1128
  // Always reset the flag
930
1129
  isBatchProcessing.set(false)
@@ -973,7 +1172,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
973
1172
 
974
1173
  } catch (e: Exception) {
975
1174
  Log.e("processBatchDataAndStartObserver", "Error in batch processing: ${e.message}", e)
976
- captureModuleException("processBatchDataAndStartObserver", e)
1175
+ safeLogModuleEvent {
1176
+ logModuleEvent("processBatchDataAndStartObserver", throwable = e, level = SentryLogLevel.ERROR)
1177
+ }
977
1178
  // Start live observation even on error
978
1179
  withContext(Dispatchers.Main) {
979
1180
  if (!isObserving) {
@@ -993,7 +1194,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
993
1194
  promise.resolve(dbFile.absolutePath)
994
1195
  } catch (e: Exception) {
995
1196
  Log.d("dbFileerrrr======", "dbFileerrrr======", e)
996
- captureModuleException("getTrackLibDbPath", e)
1197
+ safeLogModuleEvent {
1198
+ logModuleEvent("getTrackLibDbPath", throwable = e, level = SentryLogLevel.ERROR)
1199
+ }
997
1200
  promise.reject("ERROR_DB_PATH", e)
998
1201
  }
999
1202
  }
@@ -1024,7 +1227,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1024
1227
 
1025
1228
  } catch (e: Exception) {
1026
1229
  Log.e("resetCgmState", "Error resetting CGM state: ${e.message}", e)
1027
- captureModuleException("resetCgmState", e)
1230
+ safeLogModuleEvent {
1231
+ logModuleEvent("resetCgmState", throwable = e, level = SentryLogLevel.ERROR)
1232
+ }
1028
1233
  }
1029
1234
  }
1030
1235
 
@@ -1085,11 +1290,54 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1085
1290
 
1086
1291
  } catch (e: Exception) {
1087
1292
  Log.e("getCgmLogFiles", "Error getting log files: ${e.message}", e)
1088
- captureModuleException("getCgmLogFiles", e)
1293
+ safeLogModuleEvent {
1294
+ logModuleEvent("getCgmLogFiles", throwable = e, level = SentryLogLevel.ERROR)
1295
+ }
1089
1296
  promise.reject("ERROR_GET_LOG_FILES", e.message, e)
1090
1297
  }
1091
1298
  }
1092
1299
 
1300
+ /** POC field: use SDK value when present, otherwise type-appropriate default. */
1301
+ private fun mapPocGlucoseToCgmLog(pocGlucose: PocGlucose): CgmLog {
1302
+ val trend = pocGlucose.trend ?: enumTrend.NONE
1303
+ val glucoseStatus = pocGlucose.glucoseStatus ?: enumGlucoseStatus.NONE
1304
+ val errorCode = pocGlucose.errorCode ?: enumError.NONE
1305
+
1306
+ return CgmLog(
1307
+ timeInMillis = pocGlucose.timeInMillis,
1308
+ countdownMinutes = pocGlucose.countdownMinutes,
1309
+ countdownDays = pocGlucose.countdownDays,
1310
+ hypoglycemiaEarlyWarnMinutes = pocGlucose.hypoglycemiaEarlyWarnMinutes,
1311
+ showGlucoseMG = pocGlucose.showGlucoseMG,
1312
+ glucoseId = pocGlucose.glucoseId ?: 0,
1313
+ name = pocGlucose.name.orEmpty(),
1314
+ bytes = pocGlucose.bytes ?: ByteArray(0),
1315
+ showGlucose = pocGlucose.showGlucose,
1316
+ Ib = pocGlucose.ib,
1317
+ Iw = pocGlucose.iw,
1318
+ countdownHours = pocGlucose.countdownHours,
1319
+ T = pocGlucose.t,
1320
+ year = pocGlucose.year,
1321
+ month = pocGlucose.month,
1322
+ day = pocGlucose.day,
1323
+ hour = pocGlucose.hour,
1324
+ minute = pocGlucose.minute,
1325
+ trendObject = TrendObject(
1326
+ trendId = trend.trendId,
1327
+ drawableId = trend.drawableId,
1328
+ widgetImg = trend.widgetImg,
1329
+ apsChangeRate = trend.apsChangeRate.orEmpty()
1330
+ ),
1331
+ glucoseStatusObject = GlucoseStatusObject(
1332
+ statusId = glucoseStatus.statusId
1333
+ ),
1334
+ errorObject = ErrorObject(
1335
+ errorId = errorCode.errorId,
1336
+ sound = errorCode.sound ?: enumSound.NONE
1337
+ )
1338
+ )
1339
+ }
1340
+
1093
1341
  // Updated batch processing method with better sync control
1094
1342
  private suspend fun processBatchDataSynchronously(
1095
1343
  dataList: List<PocGlucose>,
@@ -1141,42 +1389,25 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1141
1389
 
1142
1390
  for ((index, batch) in chunks.withIndex()) {
1143
1391
  try {
1144
- val transformedLogs = batch.map { pocGlucose ->
1145
- CgmLog(
1146
- timeInMillis = pocGlucose.timeInMillis,
1147
- countdownMinutes = pocGlucose.countdownMinutes,
1148
- countdownDays = pocGlucose.countdownDays,
1149
- hypoglycemiaEarlyWarnMinutes = pocGlucose.hypoglycemiaEarlyWarnMinutes,
1150
- showGlucoseMG = pocGlucose.showGlucoseMG,
1151
- glucoseId = pocGlucose.glucoseId,
1152
- name = pocGlucose.name,
1153
- bytes = pocGlucose.bytes,
1154
- showGlucose = pocGlucose.showGlucose,
1155
- Ib = pocGlucose.ib,
1156
- Iw = pocGlucose.iw,
1157
- countdownHours = pocGlucose.countdownHours,
1158
- T = pocGlucose.t,
1159
- year = pocGlucose.year,
1160
- month = pocGlucose.month,
1161
- day = pocGlucose.day,
1162
- hour = pocGlucose.hour,
1163
- minute = pocGlucose.minute,
1164
- trendObject = com.mytatvarnsdk.model.TrendObject(
1165
- trendId = pocGlucose.trend.trendId,
1166
- drawableId = pocGlucose.trend.drawableId,
1167
- widgetImg = pocGlucose.trend.widgetImg,
1168
- apsChangeRate = pocGlucose.trend.apsChangeRate
1169
- ),
1170
- glucoseStatusObject = com.mytatvarnsdk.model.GlucoseStatusObject(
1171
- statusId = pocGlucose.glucoseStatus.statusId
1172
- ),
1173
- errorObject = com.mytatvarnsdk.model.ErrorObject(
1174
- errorId = pocGlucose.errorCode.errorId,
1175
- sound = pocGlucose.errorCode.sound
1392
+ safeLogModuleEvent {
1393
+ logModuleEvent(
1394
+ where = "processBatchDataSynchronously.batchStarted",
1395
+ message = "Batch $index started | rawRecords=${batch.size} | totalBatches=${chunks.size}",
1396
+ level = SentryLogLevel.INFO,
1397
+ attributes = mapOf(
1398
+ "batchIndex" to index.toString(),
1399
+ "batch_raw_records" to batch.size.toString(),
1400
+ "total_batches" to chunks.size.toString(),
1401
+ "first_glucoseId" to (batch.firstOrNull()?.glucoseId?.toString() ?: ""),
1402
+ "last_glucoseId" to (batch.lastOrNull()?.glucoseId?.toString() ?: "")
1176
1403
  )
1177
1404
  )
1178
1405
  }
1179
1406
 
1407
+ val transformedLogs = batch.map { pocGlucose ->
1408
+ mapPocGlucoseToCgmLog(pocGlucose)
1409
+ }
1410
+
1180
1411
  if (transformedLogs.isEmpty()) {
1181
1412
  Log.d(
1182
1413
  "processBatchDataSynchronously",
@@ -1195,14 +1426,45 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1195
1426
 
1196
1427
  val allResult = AllCGMLogRequest(vendor = "GoodFlip", logs = transformedLogs)
1197
1428
 
1429
+ safeLogModuleEvent {
1430
+ logModuleEvent(
1431
+ where = "processBatchDataSynchronously.batchAssembled",
1432
+ message = "Batch $index assembled | records=${transformedLogs.size}",
1433
+ level = SentryLogLevel.INFO,
1434
+ jsonPayload = Gson().toJson(allResult),
1435
+ attributes = mapOf(
1436
+ "batchIndex" to index.toString(),
1437
+ "batch_records" to transformedLogs.size.toString(),
1438
+ "first_glucoseId" to (batch.firstOrNull()?.glucoseId?.toString() ?: ""),
1439
+ "last_glucoseId" to (batch.lastOrNull()?.glucoseId?.toString() ?: "")
1440
+ )
1441
+ )
1442
+ }
1443
+
1198
1444
  // Attach sensorId at the top level of the batch payload sent to /cgm/logs
1445
+ val activeSensorId =
1446
+ prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
1199
1447
  val sensorPayloadJsonObject = JSONObject(Gson().toJson(allResult)).apply {
1200
- val activeSensorId =
1201
- prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
1202
1448
  put("sensorId", activeSensorId)
1203
1449
  }
1204
1450
  val json = sensorPayloadJsonObject.toString()
1205
1451
 
1452
+ safeLogModuleEvent {
1453
+ logModuleEvent(
1454
+ where = "processBatchDataSynchronously.batchPayloadBuilt",
1455
+ message = "Batch $index payload built | records=${transformedLogs.size} | activeSensorId=$activeSensorId",
1456
+ level = SentryLogLevel.INFO,
1457
+ jsonPayload = json,
1458
+ attributes = mapOf(
1459
+ "batchIndex" to index.toString(),
1460
+ "batch_records" to transformedLogs.size.toString(),
1461
+ "activeSensorId" to activeSensorId,
1462
+ "first_glucoseId" to (batch.firstOrNull()?.glucoseId?.toString() ?: ""),
1463
+ "last_glucoseId" to (batch.lastOrNull()?.glucoseId?.toString() ?: "")
1464
+ )
1465
+ )
1466
+ }
1467
+
1206
1468
  // Check if logs array is empty - skip API call if so
1207
1469
  if (transformedLogs.isEmpty()) {
1208
1470
  Log.d(
@@ -1222,44 +1484,69 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1222
1484
  )
1223
1485
  logLongJson("Batch $index JSON=>>> ", json)
1224
1486
 
1487
+ safeLogModuleEvent {
1488
+ logModuleEvent(
1489
+ where = "processBatchDataSynchronously.postCGMData",
1490
+ message = "Posting batch $index | records=${transformedLogs.size}",
1491
+ level = SentryLogLevel.INFO,
1492
+ jsonPayload = json,
1493
+ attributes = mapOf(
1494
+ "batchIndex" to index.toString(),
1495
+ "batch_records" to transformedLogs.size.toString(),
1496
+ "first_glucoseId" to (batch.firstOrNull()?.glucoseId?.toString() ?: ""),
1497
+ "last_glucoseId" to (batch.lastOrNull()?.glucoseId?.toString() ?: "")
1498
+ )
1499
+ )
1500
+ }
1501
+
1225
1502
  val uploadSuccessful = uploadBatchSynchronously(json, index, envType)
1226
1503
 
1227
1504
  if (uploadSuccessful) {
1228
1505
  lastSyncedRecord = batch.lastOrNull()
1229
1506
  // Update sync metadata after each successful batch
1230
1507
  updateSyncMetadata(lastSyncedRecord)
1231
- // captureModuleException(
1508
+ // logModuleEvent(
1232
1509
  // "processBatchDataSynchronously.batchSuccess",
1233
- // json,
1234
- // extras =
1235
- // mapOf(
1236
- // "batchIndex" to index.toString(),
1237
- // "lastSyncedRecord" to (lastSyncedRecord?.toString() ?: "")
1238
- // ),
1239
- // level = SentryLevel.INFO
1510
+ // jsonPayload = json,
1511
+ // level = SentryLogLevel.INFO,
1512
+ // attributes = mapOf("batchIndex" to index.toString())
1240
1513
  // )
1241
1514
 
1242
1515
  Log.d("Batch Upload", "✅ Batch $index uploaded and synced successfully")
1243
1516
  } else {
1244
1517
  allBatchesSuccessful = false
1245
1518
  Log.e("Batch Upload", "❌ Batch $index failed")
1246
- val lastSync = prefsHelper?.lastSyncData
1247
- val contextData =
1248
- "failure_reason=postCGMData returned failure;" +
1249
- "batchIndex=$index;" +
1250
- "batch_records=${batch.size};" +
1251
- "payload_chars=${json.length};" +
1252
- "first_glucoseId=${batch.firstOrNull()?.glucoseId ?: ""};" +
1253
- "last_glucoseId=${batch.lastOrNull()?.glucoseId ?: ""};" +
1254
- "first_timeInMillis=${batch.firstOrNull()?.timeInMillis ?: ""};" +
1255
- "last_timeInMillis=${batch.lastOrNull()?.timeInMillis ?: ""};" +
1256
- "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
1257
- "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
1258
- captureModuleException(
1259
- "processBatchDataSynchronously.batchUploadFailed",
1260
- "postCGMData returned failure for batch $index | $contextData",
1261
- level = SentryLevel.WARNING
1262
- )
1519
+ safeLogModuleEvent {
1520
+ val lastSync = prefsHelper?.lastSyncData
1521
+ val contextData =
1522
+ "failure_reason=postCGMData returned failure;" +
1523
+ "batchIndex=$index;" +
1524
+ "batch_records=${batch.size};" +
1525
+ "payload_chars=${json.length};" +
1526
+ "first_glucoseId=${batch.firstOrNull()?.glucoseId ?: ""};" +
1527
+ "last_glucoseId=${batch.lastOrNull()?.glucoseId ?: ""};" +
1528
+ "first_timeInMillis=${batch.firstOrNull()?.timeInMillis ?: ""};" +
1529
+ "last_timeInMillis=${batch.lastOrNull()?.timeInMillis ?: ""};" +
1530
+ "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
1531
+ "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
1532
+ logModuleEvent(
1533
+ where = "processBatchDataSynchronously.batchUploadFailed",
1534
+ message = "postCGMData returned failure for batch $index | $contextData",
1535
+ level = SentryLogLevel.WARN,
1536
+ jsonPayload = json,
1537
+ attributes = mapOf(
1538
+ "batchIndex" to index.toString(),
1539
+ "batch_records" to batch.size.toString(),
1540
+ "payload_chars" to json.length.toString(),
1541
+ "first_glucoseId" to (batch.firstOrNull()?.glucoseId?.toString() ?: ""),
1542
+ "last_glucoseId" to (batch.lastOrNull()?.glucoseId?.toString() ?: ""),
1543
+ "first_timeInMillis" to (batch.firstOrNull()?.timeInMillis?.toString() ?: ""),
1544
+ "last_timeInMillis" to (batch.lastOrNull()?.timeInMillis?.toString() ?: ""),
1545
+ "lastSync_glucoseId" to (lastSync?.glucoseId?.toString() ?: ""),
1546
+ "lastSync_timeInMillis" to (lastSync?.timeInMillis?.toString() ?: "")
1547
+ )
1548
+ )
1549
+ }
1263
1550
  // Continue with next batch instead of breaking (optional based on your needs)
1264
1551
  // break
1265
1552
  }
@@ -1269,13 +1556,15 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1269
1556
 
1270
1557
  } catch (e: Exception) {
1271
1558
  Log.e("Batch Upload", "❌ Batch $index exception: ${e.message}", e)
1272
- captureModuleException(
1273
- "processBatchDataSynchronously.batch",
1274
- RuntimeException(
1275
- "processBatchDataSynchronously.batch failed | batchIndex=$index",
1276
- e
1559
+ safeLogModuleEvent {
1560
+ logModuleEvent(
1561
+ where = "processBatchDataSynchronously.batch",
1562
+ message = "processBatchDataSynchronously.batch failed | batchIndex=$index",
1563
+ throwable = e,
1564
+ level = SentryLogLevel.ERROR,
1565
+ attributes = mapOf("batchIndex" to index.toString())
1277
1566
  )
1278
- )
1567
+ }
1279
1568
  allBatchesSuccessful = false
1280
1569
  // Continue processing other batches
1281
1570
  }
@@ -1320,7 +1609,13 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1320
1609
  Log.d("stopObservingGlucoseData", "Device observer removed successfully")
1321
1610
  } catch (e: Exception) {
1322
1611
  Log.e("stopObservingGlucoseData", "Error removing observers: ${e.message}", e)
1323
- captureModuleException("stopObservingGlucoseData.removeObservers", e)
1612
+ safeLogModuleEvent {
1613
+ logModuleEvent(
1614
+ "stopObservingGlucoseData.removeObservers",
1615
+ throwable = e,
1616
+ level = SentryLogLevel.ERROR
1617
+ )
1618
+ }
1324
1619
  }
1325
1620
  }
1326
1621
 
@@ -1330,7 +1625,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1330
1625
 
1331
1626
  } catch (e: Exception) {
1332
1627
  Log.e("stopObservingGlucoseData", "Error stopping observer: ${e.message}", e)
1333
- captureModuleException("stopObservingGlucoseData", e)
1628
+ safeLogModuleEvent {
1629
+ logModuleEvent("stopObservingGlucoseData", throwable = e, level = SentryLogLevel.ERROR)
1630
+ }
1334
1631
  }
1335
1632
  }
1336
1633
 
@@ -1342,29 +1639,89 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1342
1639
  ): Boolean {
1343
1640
  return suspendCoroutine { continuation ->
1344
1641
  try {
1642
+ safeLogModuleEvent {
1643
+ val activeSensorId =
1644
+ prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
1645
+ logModuleEvent(
1646
+ where = "uploadBatchSynchronously.postCGMData",
1647
+ message = "Posting batch upload | batchIndex=$batchIndex | activeSensorId=$activeSensorId",
1648
+ level = SentryLogLevel.INFO,
1649
+ jsonPayload = json,
1650
+ attributes = mapOf(
1651
+ "batchIndex" to batchIndex.toString(),
1652
+ "activeSensorId" to activeSensorId,
1653
+ "payload_chars" to json.length.toString()
1654
+ )
1655
+ )
1656
+ }
1657
+
1345
1658
  authenticateSDKService.postCGMData(
1346
1659
  environment = if (envType.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
1347
1660
  data = json,
1348
1661
  token = userToken,
1349
1662
  responseListener = object : AuthenticateSDKService.ResponseListener {
1350
1663
  override fun onResponseSuccess(response: String) {
1664
+ safeLogModuleEvent {
1665
+ val activeSensorId =
1666
+ prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
1667
+ logModuleEvent(
1668
+ where = "uploadBatchSynchronously.postCGMData.success",
1669
+ message = "Batch upload succeeded | batchIndex=$batchIndex | activeSensorId=$activeSensorId",
1670
+ level = SentryLogLevel.INFO,
1671
+ jsonPayload = json,
1672
+ attributes = mapOf(
1673
+ "batchIndex" to batchIndex.toString(),
1674
+ "activeSensorId" to activeSensorId,
1675
+ "payload_chars" to json.length.toString(),
1676
+ "sdk_response" to response
1677
+ )
1678
+ )
1679
+ }
1351
1680
  continuation.resume(true)
1352
1681
  }
1353
1682
 
1354
1683
  override fun onResponseFail() {
1684
+ safeLogModuleEvent {
1685
+ val activeSensorId =
1686
+ prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
1687
+ val lastSync = prefsHelper?.lastSyncData
1688
+ val contextData =
1689
+ "failure_reason=postCGMData returned failure;" +
1690
+ "batchIndex=$batchIndex;" +
1691
+ "payload_chars=${json.length};" +
1692
+ "activeSensorId=$activeSensorId;" +
1693
+ "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
1694
+ "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
1695
+ logModuleEvent(
1696
+ where = "uploadBatchSynchronously.postCGMData.fail",
1697
+ message = contextData,
1698
+ level = SentryLogLevel.WARN,
1699
+ jsonPayload = json,
1700
+ attributes = mapOf(
1701
+ "batchIndex" to batchIndex.toString(),
1702
+ "activeSensorId" to activeSensorId,
1703
+ "payload_chars" to json.length.toString(),
1704
+ "lastSync_glucoseId" to (lastSync?.glucoseId?.toString() ?: ""),
1705
+ "lastSync_timeInMillis" to (lastSync?.timeInMillis?.toString() ?: "")
1706
+ )
1707
+ )
1708
+ }
1355
1709
  continuation.resume(false)
1356
1710
  }
1357
1711
  }
1358
1712
  )
1359
1713
  } catch (e: Exception) {
1360
1714
  Log.e("uploadBatchSynchronously", "Exception in batch $batchIndex: ${e.message}", e)
1361
- captureModuleException(
1362
- "uploadBatchSynchronously",
1363
- RuntimeException(
1364
- "uploadBatchSynchronously failed | batchIndex=$batchIndex",
1365
- e
1715
+ safeLogModuleEvent {
1716
+ logModuleEvent(
1717
+ where = "uploadBatchSynchronously",
1718
+ message = "uploadBatchSynchronously failed | batchIndex=$batchIndex",
1719
+ throwable = e,
1720
+ level = SentryLogLevel.ERROR,
1721
+ jsonPayload = json,
1722
+ attributes = mapOf("batchIndex" to batchIndex.toString())
1366
1723
  )
1367
- )
1724
+ }
1368
1725
  continuation.resume(false)
1369
1726
  }
1370
1727
  }
@@ -1392,7 +1749,9 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1392
1749
  )
1393
1750
  } catch (e: Exception) {
1394
1751
  Log.e("updateSyncMetadata", "Error updating sync metadata: ${e.message}", e)
1395
- captureModuleException("updateSyncMetadata", e)
1752
+ safeLogModuleEvent {
1753
+ logModuleEvent("updateSyncMetadata", throwable = e, level = SentryLogLevel.ERROR)
1754
+ }
1396
1755
  }
1397
1756
  }
1398
1757
  }
@@ -1412,48 +1771,46 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1412
1771
  }
1413
1772
 
1414
1773
  fun mapToDto(glucose: PocGlucose): GlucoseLog {
1415
- val dto: GlucoseLog = GlucoseLog()
1416
- dto.timeInMillis = glucose.getTimeInMillis()
1417
- dto.countdownMinutes = glucose.getCountdownMinutes()
1418
- dto.countdownHours = glucose.getCountdownHours()
1419
- dto.countdownDays = glucose.getCountdownDays()
1420
- dto.hypoglycemiaEarlyWarnMinutes = glucose.getHypoglycemiaEarlyWarnMinutes()
1421
- dto.showGlucoseMG = glucose.getShowGlucoseMG()
1422
- dto.glucoseId = glucose.getGlucoseId()
1423
- dto.name = glucose.getName()
1424
- dto.showGlucose = glucose.getShowGlucose()
1425
- dto.Ib = glucose.getIb()
1426
- dto.Iw = glucose.getIw()
1427
- dto.T = glucose.getT()
1428
- dto.year = glucose.getYear()
1429
- dto.month = glucose.getMonth()
1430
- dto.day = glucose.getDay()
1431
- dto.hour = glucose.getHour()
1432
- dto.minute = glucose.getMinute()
1433
-
1434
- // Convert byte[] to List<Integer>
1774
+ val log = mapPocGlucoseToCgmLog(glucose)
1775
+
1776
+ val dto = GlucoseLog()
1777
+ dto.timeInMillis = log.timeInMillis
1778
+ dto.countdownMinutes = log.countdownMinutes
1779
+ dto.countdownHours = log.countdownHours
1780
+ dto.countdownDays = log.countdownDays
1781
+ dto.hypoglycemiaEarlyWarnMinutes = log.hypoglycemiaEarlyWarnMinutes
1782
+ dto.showGlucoseMG = log.showGlucoseMG
1783
+ dto.glucoseId = log.glucoseId
1784
+ dto.name = log.name
1785
+ dto.showGlucose = log.showGlucose
1786
+ dto.Ib = log.Ib
1787
+ dto.Iw = log.Iw
1788
+ dto.T = log.T
1789
+ dto.year = log.year
1790
+ dto.month = log.month
1791
+ dto.day = log.day
1792
+ dto.hour = log.hour
1793
+ dto.minute = log.minute
1794
+
1435
1795
  dto.bytes = ArrayList()
1436
- for (b in glucose.getBytes()) {
1437
- dto.bytes?.add(b.toInt() and 0xFF) // Prevent negative values
1796
+ log.bytes?.forEach { b ->
1797
+ dto.bytes?.add(b.toInt() and 0xFF)
1438
1798
  }
1439
1799
 
1440
- // Trend
1441
- val trendObj: TrendObject = TrendObject()
1442
- trendObj.trendId = glucose.getTrend().getTrendId()
1443
- trendObj.drawableId = glucose.getTrend().getDrawableId()
1444
- trendObj.widgetImg = glucose.getTrend().getWidgetImg()
1445
- trendObj.apsChangeRate = glucose.getTrend().getApsChangeRate()
1800
+ val trendObj = GlucoseLog.TrendObject()
1801
+ trendObj.trendId = log.trendObject.trendId
1802
+ trendObj.drawableId = log.trendObject.drawableId
1803
+ trendObj.widgetImg = log.trendObject.widgetImg
1804
+ trendObj.apsChangeRate = log.trendObject.apsChangeRate
1446
1805
  dto.trendObject = trendObj
1447
1806
 
1448
- // Status
1449
- val statusObj: GlucoseStatusObject = GlucoseStatusObject()
1450
- statusObj.statusId = glucose.getGlucoseStatus().getStatusId()
1807
+ val statusObj = GlucoseLog.GlucoseStatusObject()
1808
+ statusObj.statusId = log.glucoseStatusObject.statusId
1451
1809
  dto.glucoseStatusObject = statusObj
1452
1810
 
1453
- // Error
1454
- val errorObj: ErrorObject = ErrorObject()
1455
- errorObj.errorId = glucose.getErrorCode().getErrorId()
1456
- errorObj.sound = glucose.getErrorCode().getSound().toString()
1811
+ val errorObj = GlucoseLog.ErrorObject()
1812
+ errorObj.errorId = log.errorObject.errorId
1813
+ errorObj.sound = log.errorObject.sound.toString()
1457
1814
  dto.errorObject = errorObj
1458
1815
 
1459
1816
  return dto
@@ -1476,13 +1833,18 @@ class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
1476
1833
  ?.emit(eventName, map)
1477
1834
  } catch (e: Exception) {
1478
1835
  Log.e("Error sendDataToReact: ", e.message.toString(), e)
1479
- captureModuleException(
1480
- "sendDataToReact",
1481
- RuntimeException(
1482
- "sendDataToReact failed | eventName=$eventName;status=$status",
1483
- e
1836
+ safeLogModuleEvent {
1837
+ logModuleEvent(
1838
+ where = "sendDataToReact",
1839
+ message = "sendDataToReact failed | eventName=$eventName;status=$status",
1840
+ throwable = e,
1841
+ level = SentryLogLevel.ERROR,
1842
+ attributes = mapOf(
1843
+ "eventName" to eventName,
1844
+ "status" to status
1845
+ )
1484
1846
  )
1485
- )
1847
+ }
1486
1848
  }
1487
1849
  }
1488
1850