package ai.sirrus.reactnativesdk import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType import org.json.JSONArray import org.json.JSONObject object SirrusNotificationDataParser { private val sourceKeys = listOf("source", "sdk_source", "vendor", "provider") private val sourceValues = setOf("martech") private val campaignKeys = listOf( "sirrusCampaignId", "sirrus_campaign_id" ) private val templateKeys = listOf( "sirrusTemplateId", "sirrus_template_id" ) @JvmStatic fun isSirrusPayload(data: Map): Boolean { val hasSirrusSource = sourceKeys.any { key -> data[key]?.trim()?.lowercase() in sourceValues } if (hasSirrusSource) { return true } if (campaignKeys.any { !data[it].isNullOrBlank() }) { return true } return templateKeys.any { !data[it].isNullOrBlank() } } @JvmStatic fun fromRemoteMessage( data: Map, fallbackMessageId: String? = null, fallbackTitle: String? = null, fallbackBody: String? = null, fallbackImageUrl: String? = null ): SirrusNotificationPayload { return SirrusNotificationPayload( title = firstNonBlank( fallbackTitle, data["title"], data["sirrus_title"] ).orEmpty(), body = firstNonBlank( fallbackBody, data["body"], data["sirrus_body"] ).orEmpty(), subtitle = firstNonBlank( data["subtitle"], data["sirrus_subtitle"] ), imageUrl = firstNonBlank( fallbackImageUrl, data["imageUrl"], data["image_url"], data["sirrusImageUrl"], data["sirrus_image_url"], data["mediaUrl"], data["media_url"] ), gifUrl = firstNonBlank( data["gifUrl"], data["gif_url"], data["sirrusGifUrl"], data["sirrus_gif_url"] ), videoUrl = firstNonBlank( data["videoUrl"], data["video_url"], data["sirrusVideoUrl"], data["sirrus_video_url"] ), deeplinkUrl = firstNonBlank( data["deeplinkUrl"], data["deepLinkUrl"], data["deep_link_url"], data["sirrusDeeplinkUrl"], data["sirrus_deeplink_url"], data["click_action"], data["link"] ), campaignId = firstNonBlank( data["sirrusCampaignId"], data["sirrus_campaign_id"], data["campaignId"], data["campaign_id"] ), templateId = firstNonBlank( data["sirrusTemplateId"], data["sirrus_template_id"], data["templateId"], data["template_id"] ), messageId = firstNonBlank( data["sirrus_message_id"], data["sirrusMessageId"], data["messageId"], data["message_id"], fallbackMessageId ), provider = firstNonBlank( data["provider"], data["sirrus_provider"] ) ?: "fcm", channelId = firstNonBlank( data["channelId"], data["channel_id"], data["sirrusChannelId"], data["sirrus_channel_id"], data["android_channel_id"] ), categoryId = firstNonBlank( data["categoryId"], data["category_id"], data["sirrusCategoryId"], data["sirrus_category_id"] ), sound = firstNonBlank( data["sound"], data["sirrusSound"], data["sirrus_sound"] ), actions = parseActions( firstNonBlank( data["sirrusActions"], data["sirrus_actions"], data["actions"] ) ), rawData = data ) } @JvmStatic fun fromReadableMap(payload: ReadableMap): SirrusNotificationPayload { val actions = mutableListOf() if (payload.hasKey("actions") && payload.getType("actions") == ReadableType.Array) { val actionArray = payload.getArray("actions") if (actionArray != null) { for (index in 0 until actionArray.size()) { val action = actionArray.getMap(index) ?: continue val id = firstNonBlank( action.getStringOrNull("id"), action.getStringOrNull("actionId"), action.getStringOrNull("action_id"), action.getStringOrNull("identifier"), action.getStringOrNull("postback"), action.getStringOrNull("actionType") ) ?: "action_${index + 1}" val title = firstNonBlank( action.getStringOrNull("title"), action.getStringOrNull("label"), action.getStringOrNull("name"), action.getStringOrNull("text") ) ?: "Open" actions.add( SirrusNotificationAction( id = id, title = title, label = firstNonBlank( action.getStringOrNull("label"), action.getStringOrNull("title"), action.getStringOrNull("name"), action.getStringOrNull("text") ) ?: title, actionType = action.getStringOrNull("actionType"), postback = action.getStringOrNull("postback"), deeplinkUrl = firstNonBlank( action.getStringOrNull("deeplinkUrl"), action.getStringOrNull("deepLinkUrl"), action.getStringOrNull("deep_link_url") ), destructive = action.getBooleanOrDefault("destructive", false), foreground = action.getBooleanOrDefault("foreground", true), authenticationRequired = action.getBooleanOrDefault( "authenticationRequired", false ) ) ) } } } return SirrusNotificationPayload( title = payload.getString("title").orEmpty(), body = payload.getString("body").orEmpty(), subtitle = payload.getString("subtitle"), imageUrl = payload.getString("imageUrl"), gifUrl = payload.getString("gifUrl"), videoUrl = payload.getString("videoUrl"), deeplinkUrl = payload.getString("deeplinkUrl"), campaignId = payload.getString("campaignId"), templateId = payload.getString("templateId"), messageId = payload.getString("messageId"), provider = payload.getString("provider") ?: "fcm", channelId = payload.getString("channelId"), categoryId = payload.getString("categoryId"), sound = payload.getString("sound"), actions = actions, rawData = readableMapToStringMap( payload.getMapOrNullLocal("rawPayload") ?: payload.getMapOrNullLocal("data") ) ) } private fun ReadableMap.getMapOrNullLocal(key: String): ReadableMap? { if (!hasKey(key) || getType(key) != ReadableType.Map) { return null } return getMap(key) } private fun readableMapToStringMap(map: ReadableMap?): Map { if (map == null) { return emptyMap() } val result = mutableMapOf() val iterator = map.keySetIterator() while (iterator.hasNextKey()) { val key = iterator.nextKey() result[key] = when (map.getType(key)) { ReadableType.String -> map.getString(key) ReadableType.Boolean -> map.getBoolean(key).toString() ReadableType.Number -> map.getDouble(key).toString() ReadableType.Null -> null else -> null } } return result } private fun parseActions(serialized: String?): List { if (serialized.isNullOrBlank()) { return emptyList() } return try { val jsonArray = JSONArray(serialized) buildList { for (index in 0 until jsonArray.length()) { val rawAction = jsonArray.optJSONObject(index) ?: continue val id = firstNonBlank( rawAction.optString("id"), rawAction.optString("actionId"), rawAction.optString("action_id"), rawAction.optString("identifier"), rawAction.optString("postback"), rawAction.optString("actionType") ) ?: "action_${index + 1}" val title = firstNonBlank( rawAction.optString("title"), rawAction.optString("label"), rawAction.optString("name"), rawAction.optString("text") ) ?: "Open" add( SirrusNotificationAction( id = id, title = title, label = firstNonBlank( rawAction.optString("label"), rawAction.optString("title"), rawAction.optString("name"), rawAction.optString("text") ), actionType = firstNonBlank(rawAction.optString("actionType")), postback = firstNonBlank(rawAction.optString("postback")), deeplinkUrl = firstNonBlank( rawAction.optString("deeplinkUrl"), rawAction.optString("deepLinkUrl"), rawAction.optString("deep_link_url") ), destructive = optBoolean( rawAction, "destructive", false ), foreground = optBoolean( rawAction, "foreground", true ), authenticationRequired = optBoolean( rawAction, "authenticationRequired", false ) || optBoolean(rawAction, "authentication_required", false) ) ) } } } catch (_: Throwable) { emptyList() } } private fun optBoolean(json: JSONObject, key: String, fallback: Boolean): Boolean { if (!json.has(key)) { return fallback } return when (val value = json.opt(key)) { is Boolean -> value is Number -> value.toInt() != 0 is String -> value.equals("true", ignoreCase = true) || value == "1" else -> fallback } } private fun firstNonBlank(vararg values: String?): String? { return values.firstOrNull { !it.isNullOrBlank() }?.trim() } }