package expo.modules.iap import android.util.Log import org.json.JSONArray import org.json.JSONObject import java.util.Locale internal object ExpoIapLog { private const val TAG = "ExpoIap" private val SENSITIVE_KEY_FRAGMENTS = setOf( "token", "apikey", "secret", "jws", "receipt", "clientpayload", "dataandroid", "signatureandroid", "userid", "password", "bearer", ) private val SENSITIVE_AUTH_KEYS = setOf( "auth", "authorization", "authheader", ) fun payload( name: String, payload: Any?, ) { debug("$name payload: ${stringify(payload)}") } fun result( name: String, value: Any?, ) { debug("$name result: ${stringify(value)}") } fun failure( name: String, error: Throwable, ) { Log.e(TAG, "$name failed: ${error.localizedMessage}", error) } fun warning(message: String) { Log.w(TAG, message) } fun debug(message: String) { if (BuildConfig.DEBUG || Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, message) } } internal fun stringify(value: Any?): String { val sanitized = sanitize(value) ?: return "null" return when (sanitized) { is String -> sanitized is Number, is Boolean -> sanitized.toString() is Map<*, *> -> JSONObject(sanitized).toString() is List<*> -> JSONArray(sanitized).toString() else -> sanitized.toString() } } private fun sanitize(value: Any?): Any? { if (value == null || value === JSONObject.NULL) return null return when (value) { is String -> sanitizeJsonString(value) is JSONObject -> sanitizeMap( value.keys().asSequence().associateWith(value::opt), ) is JSONArray -> (0 until value.length()).mapNotNull { sanitize(value.opt(it)) } is Map<*, *> -> sanitizeMap(value) is List<*> -> value.mapNotNull { sanitize(it) } is Array<*> -> value.mapNotNull { sanitize(it) } else -> value } } private fun sanitizeJsonString(value: String): Any { val trimmed = value.trim() val json = runCatching { when (trimmed.firstOrNull()) { '{' -> JSONObject(trimmed) '[' -> JSONArray(trimmed) else -> null } }.getOrNull() ?: return value return sanitize(json) ?: value } private fun sanitizeMap(source: Map<*, *>): Map { fun isSensitiveKey(key: String): Boolean { val normalized = key.lowercase(Locale.ROOT).filter { it.isLetterOrDigit() } return SENSITIVE_KEY_FRAGMENTS.any { normalized.contains(it) } || normalized in SENSITIVE_AUTH_KEYS } val sanitized = linkedMapOf() for ((rawKey, rawValue) in source) { val key = rawKey as? String ?: continue if (rawValue == null || rawValue === JSONObject.NULL) { sanitized[key] = JSONObject.NULL continue } if (isSensitiveKey(key)) { sanitized[key] = "hidden" continue } sanitized[key] = sanitize(rawValue) } return sanitized } }