diff --git a/briar-headless/README.md b/briar-headless/README.md index 2acfdac3a..fbeb7c804 100644 --- a/briar-headless/README.md +++ b/briar-headless/README.md @@ -410,3 +410,39 @@ When the last connection is lost (the contact goes offline), it sends a `Contact "type": "event" } ``` + +### A message was sent + +When Briar sent a message to a contact, it sends a `MessagesSentEvent`. This is indicated in Briar +by showing one tick next to the message. + +```json +{ + "data": { + "contactId": 1, + "messageIds": [ + "+AIMMgOCPFF8HDEhiEHYjbfKrg7v0G94inKxjvjYzA8=" + ] + }, + "name": "MessagesSentEvent", + "type": "event" +} +``` + +### A message was acknowledged + +When a contact acknowledges that they received a message, Briar sends a `MessagesAckedEvent`. +This is indicated in Briar by showing two ticks next to the message. + +```json +{ + "data": { + "contactId": 1, + "messageIds": [ + "+AIMMgOCPFF8HDEhiEHYjbfKrg7v0G94inKxjvjYzA8=" + ] + }, + "name": "MessagesAckedEvent", + "type": "event" +} +``` diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/MessagingControllerImpl.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/MessagingControllerImpl.kt index d3ee6457e..df5607170 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/MessagingControllerImpl.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/MessagingControllerImpl.kt @@ -11,6 +11,8 @@ import org.briarproject.bramble.api.db.DatabaseExecutor import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventListener +import org.briarproject.bramble.api.sync.event.MessagesAckedEvent +import org.briarproject.bramble.api.sync.event.MessagesSentEvent import org.briarproject.bramble.api.system.Clock import org.briarproject.bramble.util.StringUtils.utf8IsTooLong import org.briarproject.briar.api.blog.BlogInvitationRequest @@ -39,6 +41,8 @@ import javax.inject.Inject import javax.inject.Singleton internal const val EVENT_CONVERSATION_MESSAGE = "ConversationMessageReceivedEvent" +internal const val EVENT_MESSAGES_ACKED = "MessagesAckedEvent" +internal const val EVENT_MESSAGES_SENT = "MessagesSentEvent" @Immutable @Singleton @@ -90,6 +94,12 @@ constructor( webSocketController.sendEvent(EVENT_CONVERSATION_MESSAGE, e.output()) } } + is MessagesSentEvent -> { + webSocketController.sendEvent(EVENT_MESSAGES_SENT, e.output()) + } + is MessagesAckedEvent -> { + webSocketController.sendEvent(EVENT_MESSAGES_ACKED, e.output()) + } } } diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputConversationMessage.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputConversationMessage.kt index ddd9c807c..3c42a39d0 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputConversationMessage.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputConversationMessage.kt @@ -1,6 +1,9 @@ package org.briarproject.briar.headless.messaging import org.briarproject.bramble.api.contact.ContactId +import org.briarproject.bramble.api.sync.MessageId +import org.briarproject.bramble.api.sync.event.MessagesAckedEvent +import org.briarproject.bramble.api.sync.event.MessagesSentEvent import org.briarproject.briar.api.conversation.ConversationMessageHeader import org.briarproject.briar.api.messaging.PrivateMessage import org.briarproject.briar.api.messaging.PrivateMessageHeader @@ -43,3 +46,15 @@ internal fun PrivateMessage.output(contactId: ContactId, text: String) = JsonDic "groupId" to message.groupId.bytes, "text" to text ) + +internal fun MessagesAckedEvent.output() = JsonDict( + "contactId" to contactId.int, + "messageIds" to messageIds.toJson() +) + +internal fun MessagesSentEvent.output() = JsonDict( + "contactId" to contactId.int, + "messageIds" to messageIds.toJson() +) + +internal fun Collection.toJson() = map { it.bytes } diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt index 4968f9849..6feac73e2 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/messaging/MessagingControllerImplTest.kt @@ -10,11 +10,13 @@ import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.identity.AuthorInfo import org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED import org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED +import org.briarproject.bramble.api.sync.MessageId +import org.briarproject.bramble.api.sync.event.MessagesAckedEvent +import org.briarproject.bramble.api.sync.event.MessagesSentEvent import org.briarproject.bramble.test.ImmediateExecutor import org.briarproject.bramble.test.TestUtils.getRandomId import org.briarproject.bramble.util.StringUtils.getRandomString import org.briarproject.briar.api.client.SessionId -import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.introduction.IntroductionRequest import org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH import org.briarproject.briar.api.messaging.MessagingManager @@ -100,6 +102,40 @@ internal class MessagingControllerImplTest : ControllerTest() { testInvalidContactId { controller.list(ctx) } } + @Test + fun testMessagesAckedEvent() { + val messageId1 = MessageId(getRandomId()) + val messageId2 = MessageId(getRandomId()) + val messageIds = listOf(messageId1, messageId2) + val event = MessagesAckedEvent(contact.id, messageIds) + + every { + webSocketController.sendEvent( + EVENT_MESSAGES_ACKED, + event.output() + ) + } just runs + + controller.eventOccurred(event) + } + + @Test + fun testMessagesSentEvent() { + val messageId1 = MessageId(getRandomId()) + val messageId2 = MessageId(getRandomId()) + val messageIds = listOf(messageId1, messageId2) + val event = MessagesSentEvent(contact.id, messageIds) + + every { + webSocketController.sendEvent( + EVENT_MESSAGES_SENT, + event.output() + ) + } just runs + + controller.eventOccurred(event) + } + @Test fun listNonexistentContactId() { testNonexistentContactId { controller.list(ctx) } @@ -177,6 +213,43 @@ internal class MessagingControllerImplTest : ControllerTest() { controller.eventOccurred(event) } + @Test + fun testOutputMessagesAckedEvent() { + val messageId1 = MessageId(getRandomId()) + val messageId2 = MessageId(getRandomId()) + val messageIds = listOf(messageId1, messageId2) + val event = MessagesAckedEvent(contact.id, messageIds) + val json = """ + { + "contactId": ${contact.id.int}, + "messageIds": [ + ${toJson(messageId1.bytes)}, + ${toJson(messageId2.bytes)} + ] + } + """ + assertJsonEquals(json, event.output()) + } + + @Test + fun testOutputMessagesSentEvent() { + val messageId1 = MessageId(getRandomId()) + val messageId2 = MessageId(getRandomId()) + val messageIds = listOf(messageId1, messageId2) + val event = MessagesSentEvent(contact.id, messageIds) + + val json = """ + { + "contactId": ${contact.id.int}, + "messageIds": [ + ${toJson(messageId1.bytes)}, + ${toJson(messageId2.bytes)} + ] + } + """ + assertJsonEquals(json, event.output()) + } + @Test fun testOutputPrivateMessageHeader() { val json = """