Skip to content

Fixing getTranscript loop on reconnection flow #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 11, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ class ChatServiceImpl @Inject constructor(
SDKLogger.logger.logDebug { "Connection Re-Established" }
connectionDetailsProvider.setChatSessionState(true)
removeTypingIndicators() // Make sure to remove typing indicators if still present
fetchReconnectedTranscript(internalTranscript)
fetchReconnectedTranscript()
}

ChatEvent.ChatEnded -> {
Expand Down Expand Up @@ -715,7 +715,7 @@ class ChatServiceImpl @Inject constructor(
}
}

private suspend fun fetchReconnectedTranscript(internalTranscript: List<TranscriptItem>) {
private suspend fun fetchReconnectedTranscript() {
val lastItem = internalTranscript.lastOrNull { (it as? Message)?.metadata?.status != MessageStatus.Failed }
?: return

Expand All @@ -728,19 +728,28 @@ class ChatServiceImpl @Inject constructor(
fetchTranscriptWith(startPosition)
}

private fun isItemInInternalTranscript(Id: String?): Boolean {
if (Id == null) return false
for (item in internalTranscript.reversed()) {
if (item.id == Id) {
return true
}
}
return false
}

private suspend fun fetchTranscriptWith(startPosition: StartPosition?) {
getTranscript(startPosition = startPosition,
scanDirection = ScanDirection.FORWARD,
sortKey = SortKey.ASCENDING,
maxResults = 30,
maxResults = 100,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what made us to increase the maxResults ? will not increase the response object size & latency ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will only return a larger object/latency if there are more messages, and if there are more messages, it would be better to have one API call vs. two smaller API calls.

Since this logic is to fetch messages that are missing during disconnection, we wouldn't want to retrieve only part of the missing messages.

nextToken = null).onSuccess { transcriptResponse ->
if (transcriptResponse.nextToken?.isNotEmpty() == true) {
val newStartPosition = transcriptResponse.transcript.lastOrNull()?.let {
StartPosition().apply {
id = it.id
}
val lastItem = transcriptResponse.transcript.lastOrNull()
if (transcriptResponse.nextToken?.isNotEmpty() == true && lastItem != null) {
val newStartPosition = StartPosition().apply { id = lastItem.id }
if (!isItemInInternalTranscript(lastItem?.id)) {
fetchTranscriptWith(startPosition = newStartPosition)
}
fetchTranscriptWith(startPosition = newStartPosition)
}
}.onFailure { error ->
SDKLogger.logger.logError { "Error fetching transcript with startPosition $startPosition: ${error.localizedMessage}" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.amazon.connect.chat.sdk.provider.ConnectionDetailsProvider
import com.amazonaws.regions.Regions
import com.amazonaws.services.connectparticipant.model.DisconnectParticipantResult
import com.amazonaws.services.connectparticipant.model.GetTranscriptResult
import com.amazonaws.services.connectparticipant.model.Item
import com.amazonaws.services.connectparticipant.model.ScanDirection
import com.amazonaws.services.connectparticipant.model.SendEventResult
import com.amazonaws.services.connectparticipant.model.SendMessageResult
Expand Down Expand Up @@ -48,6 +49,12 @@ import org.robolectric.RobolectricTestRunner
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import junit.framework.TestCase.fail
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.setMain
import org.mockito.kotlin.any
import java.util.UUID
import java.net.URL

Expand Down Expand Up @@ -76,18 +83,20 @@ class ChatServiceImplTest {
@Mock
private lateinit var messageReceiptsManager: MessageReceiptsManager

private lateinit var chatService: ChatService
private lateinit var chatService: ChatServiceImpl
private lateinit var eventSharedFlow: MutableSharedFlow<ChatEvent>
private lateinit var transcriptSharedFlow: MutableSharedFlow<TranscriptItem>
private lateinit var chatSessionStateFlow: MutableStateFlow<Boolean>
private lateinit var transcriptListSharedFlow: MutableSharedFlow<List<TranscriptItem>>
private lateinit var newWsUrlFlow: MutableSharedFlow<Unit>

private val mockUri: Uri = Uri.parse("https://example.com/dummy.pdf")
private val testDispatcher = StandardTestDispatcher()

@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
Dispatchers.setMain(testDispatcher)

eventSharedFlow = MutableSharedFlow()
transcriptSharedFlow = MutableSharedFlow()
Expand Down Expand Up @@ -510,6 +519,48 @@ class ChatServiceImplTest {
verify(awsClient).getTranscript(anyOrNull())
}

private fun createMockItem(id: String, timestamp: String): Item {
val item = Item()
item.absoluteTime = timestamp
item.content = "test${id}"
item.contentType = "text/plain"
item.id = id
item.type = "MESSAGE"
item.participantId = id
item.displayName = "test${id}"
item.participantRole = "CUSTOMER"
return item
}

@Test
fun test_fetchReconnectedTranscript_success() = runTest {
val chatDetails = ChatDetails(participantToken = "token")
val mockConnectionDetails = createMockConnectionDetails("valid_token")
`when`(connectionDetailsProvider.getConnectionDetails()).thenReturn(mockConnectionDetails)
chatService.createChatSession(chatDetails)
advanceUntilIdle()

// Create a mock GetTranscriptResult and configure it to return expected values
val mockGetTranscriptResult = mock<GetTranscriptResult>()

`when`(mockGetTranscriptResult.initialContactId).thenReturn("")
`when`(awsClient.getTranscript(anyOrNull())).thenReturn(Result.success(mockGetTranscriptResult))

// Add items to the internal transcript and emit reconnection event.
// This scenario should call getTranscript once since the empty transcript response.
`when`(mockGetTranscriptResult.transcript).thenReturn(listOf())
`when`(mockGetTranscriptResult.nextToken).thenReturn("nextToken1")

val transcriptItem1 = Message(id = "1", timeStamp = "2024-01-01T00:00:00Z", participant = "user", contentType = "text/plain", text = "Hello")
val transcriptItem2 = Message(id = "2", timeStamp = "2025-01-01T00:01:00Z", participant = "agent", contentType = "text/plain", text = "Hi")
chatService.internalTranscript.add(transcriptItem1)
chatService.internalTranscript.add(transcriptItem2)
val chatEvent = ChatEvent.ConnectionReEstablished
eventSharedFlow.emit(chatEvent)
advanceUntilIdle()
verify(awsClient, times(1)).getTranscript(anyOrNull())
}

@Test
fun test_sendMessageReceipt_success() = runTest {
val messageId = "messageId123"
Expand Down Expand Up @@ -615,15 +666,15 @@ class ChatServiceImplTest {
// Add message in internal transcript
val transcriptItem = Message(id = "1", timeStamp = "mockedTimestamp", participant = "user",
contentType = "text/plain", text = "Hello")
(chatService as ChatServiceImpl).internalTranscript.add(0, transcriptItem)
chatService.internalTranscript.add(0, transcriptItem)

// Execute reset
chatService.reset()

// Validate that websocket disconnected, tokens are reset and internal transcript is deleted
verify(webSocketManager).disconnect("Resetting ChatService")
verify(connectionDetailsProvider).reset()
assertEquals(0, (chatService as ChatServiceImpl).internalTranscript.size)
assertEquals(0, chatService.internalTranscript.size)
}

private fun createMockConnectionDetails(token : String): ConnectionDetails {
Expand Down
2 changes: 1 addition & 1 deletion chat-sdk/version.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
sdkVersion=1.0.9
sdkVersion=1.0.10
groupId=software.aws.connect
artifactId=amazon-connect-chat-android
Loading