fix(kotlin-sdk): thread-safety, resource leaks, URL encoding, JSON safety, deprecated API
- BLTelemetryClient/BLAuditLogger/BLCrashReporter: @Synchronized isoNow() for SimpleDateFormat thread-safety
- BLCrashReporter: replace unsafe JSON string interpolation with buildJsonObject + JsonArray
- BLBlobClient: close OkHttp Response body with .use {} to prevent resource leak
- BLFeatureFlagClient/BLKillSwitchClient: URL-encode query parameter values
- build.gradle.kts: kotlinOptions {} -> compilerOptions {} (Kotlin 2.1 convention)
This commit is contained in:
parent
4f16223996
commit
f953c2b0bc
@ -19,8 +19,8 @@ android {
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -43,6 +43,10 @@ class BLAuditLogger(
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}
|
||||
|
||||
/** Thread-safe ISO timestamp — SimpleDateFormat is NOT thread-safe. */
|
||||
@Synchronized
|
||||
private fun isoNow(): String = isoFormat.format(Date())
|
||||
|
||||
init {
|
||||
logDir.mkdirs()
|
||||
}
|
||||
@ -52,7 +56,7 @@ class BLAuditLogger(
|
||||
*/
|
||||
fun log(action: String, module: String, detail: String? = null, userId: String? = null) {
|
||||
val entry = AuditEntry(
|
||||
timestamp = isoFormat.format(Date()),
|
||||
timestamp = isoNow(),
|
||||
productId = config.productId,
|
||||
action = action,
|
||||
module = module,
|
||||
|
||||
@ -77,8 +77,9 @@ class BLBlobClient(
|
||||
.header("x-ms-blob-content-type", contentType)
|
||||
.build()
|
||||
|
||||
val response = uploadClient.newCall(request).execute()
|
||||
if (response.isSuccessful) sas.blobUrl else null
|
||||
uploadClient.newCall(request).execute().use { response ->
|
||||
if (response.isSuccessful) sas.blobUrl else null
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import java.io.PrintWriter
|
||||
@ -42,6 +43,10 @@ class BLCrashReporter(
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}
|
||||
|
||||
/** Thread-safe ISO timestamp — SimpleDateFormat is NOT thread-safe. */
|
||||
@Synchronized
|
||||
private fun isoNow(): String = isoFormat.format(Date())
|
||||
|
||||
companion object {
|
||||
private const val KEY_PENDING_CRASH = "pending_crash"
|
||||
}
|
||||
@ -66,7 +71,7 @@ class BLCrashReporter(
|
||||
put("id", UUID.randomUUID().toString())
|
||||
put("productId", config.productId)
|
||||
put("platform", config.platform)
|
||||
put("timestamp", isoFormat.format(Date()))
|
||||
put("timestamp", isoNow())
|
||||
put("thread", thread.name)
|
||||
put("exception", throwable.javaClass.name)
|
||||
put("message", throwable.message ?: "")
|
||||
@ -87,8 +92,12 @@ class BLCrashReporter(
|
||||
|
||||
scope.launch {
|
||||
try {
|
||||
val body = """{"productId":"${config.productId}","events":[$pending]}"""
|
||||
client.fireAndForget("POST", "/telemetry/events", body)
|
||||
val crashEvent = json.parseToJsonElement(pending)
|
||||
val payload = buildJsonObject {
|
||||
put("productId", config.productId)
|
||||
put("events", JsonArray(listOf(crashEvent)))
|
||||
}
|
||||
client.fireAndForget("POST", "/telemetry/events", json.encodeToString(payload))
|
||||
} catch (_: Exception) {
|
||||
// Best-effort — don't re-queue
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.net.URLEncoder
|
||||
|
||||
/**
|
||||
* Feature flag client for platform-service.
|
||||
@ -76,9 +77,10 @@ class BLFeatureFlagClient(
|
||||
|
||||
private suspend fun fetchFlags() {
|
||||
try {
|
||||
val enc = { v: String -> URLEncoder.encode(v, "UTF-8") }
|
||||
val qs = buildString {
|
||||
append("?platform=${config.platform}")
|
||||
userId?.let { append("&userId=$it") }
|
||||
append("?platform=${enc(config.platform)}")
|
||||
userId?.let { append("&userId=${enc(it)}") }
|
||||
}
|
||||
val response = client.request("GET", "/flags/poll$qs", skipAuth = true)
|
||||
val result = json.decodeFromString<FlagResponse>(response)
|
||||
|
||||
@ -2,6 +2,7 @@ package com.bytelyst.platform
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.net.URLEncoder
|
||||
|
||||
/**
|
||||
* Kill switch client for platform-service.
|
||||
@ -32,7 +33,8 @@ class BLKillSwitchClient(
|
||||
*/
|
||||
suspend fun check(): KillSwitchResult {
|
||||
return try {
|
||||
val response = client.request("GET", "/flags/kill-switch?platform=${config.platform}", skipAuth = true)
|
||||
val platform = URLEncoder.encode(config.platform, "UTF-8")
|
||||
val response = client.request("GET", "/flags/kill-switch?platform=$platform", skipAuth = true)
|
||||
json.decodeFromString<KillSwitchResult>(response)
|
||||
} catch (_: Exception) {
|
||||
KillSwitchResult.ok()
|
||||
|
||||
@ -99,6 +99,10 @@ class BLTelemetryClient(
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}
|
||||
|
||||
/** Thread-safe ISO timestamp — SimpleDateFormat is NOT thread-safe. */
|
||||
@Synchronized
|
||||
private fun isoNow(): String = isoFormat.format(Date())
|
||||
|
||||
// ── Lifecycle ────────────────────────────────────────────
|
||||
|
||||
fun start() {
|
||||
@ -155,7 +159,7 @@ class BLTelemetryClient(
|
||||
errorDomain = errorDomain,
|
||||
tags = tags,
|
||||
metrics = metrics,
|
||||
occurredAt = isoFormat.format(Date()),
|
||||
occurredAt = isoNow(),
|
||||
)
|
||||
|
||||
synchronized(queue) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user