Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ keep the same API surface without storing or displaying bridge messages.

| Method | Description |
|--------|-------------|
| `onWebToAppRequest()` | Log a Web-to-App request. `requestId` is optional for fire-and-forget messages. |
| `onWebToAppRequest()` | Log a Web-to-App request. An overload accepts a separate optional `displayName`. |
| `onWebToAppResponse()` | Log the response to a Web-to-App request. Skipped if `requestId` is null. |
| `onAppToWebRequest()` | Log an App-to-Web message. `requestId` is optional for fire-and-forget messages. |
| `onAppToWebRequest()` | Log an App-to-Web message. An overload accepts a separate optional `displayName`. |
| `onAppToWebResponse()` | Log the response to an App-to-Web message. Skipped if `requestId` is null. |

`ProtobufDariInterceptor` extends this interface with matching `on*ProtobufRequest()` and `on*ProtobufResponse()` methods that accept `ByteArray` payloads.
Expand All @@ -291,6 +291,14 @@ interface DariInterceptor {
fireAndForget: Boolean? = null,
)

fun onWebToAppRequest(
handlerName: String,
displayName: String?,
requestId: String?,
requestData: String?,
fireAndForget: Boolean? = null,
)

/** Called when a response is sent for a Web -> App request */
fun onWebToAppResponse(
handlerName: String,
Expand All @@ -307,6 +315,14 @@ interface DariInterceptor {
fireAndForget: Boolean? = null,
)

fun onAppToWebRequest(
handlerName: String,
displayName: String?,
requestId: String?,
data: String?,
fireAndForget: Boolean? = null,
)

/** Called when a web response is received for an App -> Web request */
fun onAppToWebResponse(
requestId: String?,
Expand Down
37 changes: 37 additions & 0 deletions dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package com.easyhooon.dari
* @property id Auto-generated unique identifier for list key and entry lookup.
* @property requestId Optional external request ID for matching request-response pairs.
* When null, the entry is treated as a standalone (fire-and-forget) message.
* @property handlerName Stable bridge handler identifier.
* @property displayName Optional human-readable handler description.
*/
data class MessageEntry(
val id: Long = 0L,
Expand All @@ -23,8 +25,43 @@ data class MessageEntry(
val responseTimestamp: Long? = null,
val requestPayloadMetadata: MessagePayloadMetadata? = null,
val responsePayloadMetadata: MessagePayloadMetadata? = null,
val displayName: String? = null,
) {

/** Preserves the pre-display-name constructor signature for binary callers. */
constructor(
id: Long,
requestId: String?,
handlerName: String,
direction: MessageDirection,
tag: String?,
requestData: String?,
responseData: String?,
requestDataTruncated: Boolean,
responseDataTruncated: Boolean,
status: MessageStatus,
requestTimestamp: Long,
responseTimestamp: Long?,
requestPayloadMetadata: MessagePayloadMetadata?,
responsePayloadMetadata: MessagePayloadMetadata?,
) : this(
id = id,
requestId = requestId,
handlerName = handlerName,
direction = direction,
tag = tag,
requestData = requestData,
responseData = responseData,
requestDataTruncated = requestDataTruncated,
responseDataTruncated = responseDataTruncated,
status = status,
requestTimestamp = requestTimestamp,
responseTimestamp = responseTimestamp,
requestPayloadMetadata = requestPayloadMetadata,
responsePayloadMetadata = responsePayloadMetadata,
displayName = null,
)

/** Preserves the pre-protobuf constructor for binary compatibility. */
constructor(
id: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,34 @@ interface DariInterceptor {
/** Called when a Web -> App request is received */
fun onWebToAppRequest(handlerName: String, requestId: String?, requestData: String?, fireAndForget: Boolean? = null)

/** Called when a Web -> App request has a separate human-readable name. */
fun onWebToAppRequest(
handlerName: String,
displayName: String?,
requestId: String?,
requestData: String?,
fireAndForget: Boolean? = null,
) {
onWebToAppRequest(handlerName, requestId, requestData, fireAndForget)
}

/** Called when a response is sent for a Web -> App request */
fun onWebToAppResponse(handlerName: String, requestId: String?, responseData: String?, isSuccess: Boolean)

/** Called when an App -> Web request is sent */
fun onAppToWebRequest(handlerName: String, requestId: String?, data: String?, fireAndForget: Boolean? = null)

/** Called when an App -> Web request has a separate human-readable name. */
fun onAppToWebRequest(
handlerName: String,
displayName: String?,
requestId: String?,
data: String?,
fireAndForget: Boolean? = null,
) {
onAppToWebRequest(handlerName, requestId, data, fireAndForget)
}

/** Called when a web response is received for an App -> Web request */
fun onAppToWebResponse(requestId: String?, isSuccess: Boolean, responseData: String?)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ interface ProtobufDariInterceptor : DariInterceptor {
fireAndForget: Boolean? = null,
)

fun onWebToAppProtobufRequest(
handlerName: String,
displayName: String?,
requestId: String?,
requestData: ByteArray,
fireAndForget: Boolean? = null,
) {
onWebToAppProtobufRequest(handlerName, requestId, requestData, fireAndForget)
}

fun onWebToAppProtobufResponse(
handlerName: String,
requestId: String?,
Expand All @@ -46,6 +56,16 @@ interface ProtobufDariInterceptor : DariInterceptor {
fireAndForget: Boolean? = null,
)

fun onAppToWebProtobufRequest(
handlerName: String,
displayName: String?,
requestId: String?,
requestData: ByteArray,
fireAndForget: Boolean? = null,
) {
onAppToWebProtobufRequest(handlerName, requestId, requestData, fireAndForget)
}

fun onAppToWebProtobufResponse(
requestId: String?,
isSuccess: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,29 @@ class MessageEntryTest {
assertEquals(2, entry.responseSizeBytes)
assertEquals(13, entry.totalSizeBytes)
}

@Test
fun `display name is stored separately from the stable handler name`() {
val entry = MessageEntry(
handlerName = "N:4-1",
direction = MessageDirection.WEB_TO_APP,
displayName = "오늘의 건강목표",
)

assertEquals("N:4-1", entry.handlerName)
assertEquals("오늘의 건강목표", entry.displayName)
}

@Test
fun `display name remains attached when a response updates the entry`() {
val entry = MessageEntry(
handlerName = "N:4-1",
direction = MessageDirection.WEB_TO_APP,
displayName = "오늘의 건강목표",
)

val completed = entry.copy(status = MessageStatus.SUCCESS, responseData = "done")

assertEquals("오늘의 건강목표", completed.displayName)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.easyhooon.dari

import com.easyhooon.dari.interceptor.DariInterceptor
import com.easyhooon.dari.interceptor.ProtobufDariInterceptor
import kotlin.test.Test
import kotlin.test.assertEquals

Expand Down Expand Up @@ -31,6 +32,30 @@ class PublicApiSmokeTest {
assertEquals("handler", (interceptor as RecordingInterceptor).handlerName)
}

@Test
fun `display name overload remains compatible with existing interceptor implementations`() {
val interceptor: DariInterceptor = RecordingInterceptor()

interceptor.onWebToAppRequest(
handlerName = "N:4-1",
displayName = "오늘의 건강목표",
requestId = "request-id",
requestData = "payload",
)

assertEquals("N:4-1", (interceptor as RecordingInterceptor).handlerName)
}

@Test
fun `protobuf display name overloads remain compatible with existing implementations`() {
val interceptor: ProtobufDariInterceptor = RecordingProtobufInterceptor()

interceptor.onWebToAppProtobufRequest("N:4-1", "오늘의 건강목표", "web-request", byteArrayOf(1))
interceptor.onAppToWebProtobufRequest("N:4-2", "이번 주 건강목표", "app-request", byteArrayOf(2))

assertEquals(listOf("N:4-1", "N:4-2"), (interceptor as RecordingProtobufInterceptor).handlerNames)
}

private class RecordingInterceptor : DariInterceptor {
var handlerName: String? = null

Expand All @@ -39,7 +64,9 @@ class PublicApiSmokeTest {
requestId: String?,
requestData: String?,
fireAndForget: Boolean?,
) = Unit
) {
this.handlerName = handlerName
}

override fun onWebToAppResponse(
handlerName: String,
Expand All @@ -59,4 +86,62 @@ class PublicApiSmokeTest {

override fun onAppToWebResponse(requestId: String?, isSuccess: Boolean, responseData: String?) = Unit
}

private class RecordingProtobufInterceptor : ProtobufDariInterceptor {
val handlerNames = mutableListOf<String>()

override fun onWebToAppRequest(
handlerName: String,
requestId: String?,
requestData: String?,
fireAndForget: Boolean?,
) = Unit

override fun onWebToAppResponse(
handlerName: String,
requestId: String?,
responseData: String?,
isSuccess: Boolean,
) = Unit

override fun onAppToWebRequest(
handlerName: String,
requestId: String?,
data: String?,
fireAndForget: Boolean?,
) = Unit

override fun onAppToWebResponse(requestId: String?, isSuccess: Boolean, responseData: String?) = Unit

override fun onWebToAppProtobufRequest(
handlerName: String,
requestId: String?,
requestData: ByteArray,
fireAndForget: Boolean?,
) {
handlerNames += handlerName
}

override fun onWebToAppProtobufResponse(
handlerName: String,
requestId: String?,
responseData: ByteArray,
isSuccess: Boolean,
) = Unit

override fun onAppToWebProtobufRequest(
handlerName: String,
requestId: String?,
requestData: ByteArray,
fireAndForget: Boolean?,
) {
handlerNames += handlerName
}

override fun onAppToWebProtobufResponse(
requestId: String?,
isSuccess: Boolean,
responseData: ByteArray,
) = Unit
}
}
20 changes: 20 additions & 0 deletions dari-noop/src/test/kotlin/com/easyhooon/dari/DariNoopApiTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.easyhooon.dari

import org.junit.Assert.assertNull
import org.junit.Test

class DariNoopApiTest {
@Test
fun `display name request API remains a no-op`() {
val interceptor = Dari.createInterceptor()

interceptor?.onWebToAppRequest(
handlerName = "N:4-1",
displayName = "오늘의 건강목표",
requestId = "request-id",
requestData = "payload",
)

assertNull(interceptor)
}
}
9 changes: 7 additions & 2 deletions dari/src/main/kotlin/com/easyhooon/dari/Dari.kt
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,13 @@ object Dari {
/**
* Adds a new message to the notification.
*/
internal fun postMessageNotification(handlerName: String, direction: MessageDirection, tag: String? = null) {
notification?.postMessage(handlerName, direction, tag)
internal fun postMessageNotification(
handlerName: String,
displayName: String?,
direction: MessageDirection,
tag: String? = null,
) {
notification?.postMessage(handlerName, displayName, direction, tag)
}

/**
Expand Down
10 changes: 10 additions & 0 deletions dari/src/main/kotlin/com/easyhooon/dari/HandlerPresentation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.easyhooon.dari

internal fun handlerLabel(handlerName: String, displayName: String?): String {
val label = displayName?.takeIf { it.isNotBlank() } ?: return handlerName
return "$handlerName($label)"
}

internal fun MessageEntry.matchesHandlerQuery(query: String): Boolean =
handlerName.contains(query, ignoreCase = true) ||
displayName?.contains(query, ignoreCase = true) == true
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase

@Database(
entities = [MessageEntity::class],
version = 5,
version = 6,
exportSchema = false,
)
@TypeConverters(Converters::class)
Expand All @@ -23,7 +23,7 @@ internal abstract class DariDatabase : RoomDatabase() {

fun create(context: Context): DariDatabase {
return Room.databaseBuilder(context, DariDatabase::class.java, DB_NAME)
.addMigrations(MIGRATION_3_4, MIGRATION_4_5)
.addMigrations(MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
.fallbackToDestructiveMigration(dropAllTables = true)
.build()
}
Expand All @@ -49,5 +49,11 @@ internal abstract class DariDatabase : RoomDatabase() {
db.execSQL("ALTER TABLE `messages` ADD COLUMN `responseRawPreviewTruncated` INTEGER")
}
}

private val MIGRATION_5_6 = object : Migration(5, 6) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `messages` ADD COLUMN `displayName` TEXT")
}
}
}
}
Loading
Loading