diff --git a/briar-headless/README.md b/briar-headless/README.md index 95cf1b043..d2782368f 100644 --- a/briar-headless/README.md +++ b/briar-headless/README.md @@ -274,8 +274,51 @@ it will send a JSON object to connected websocket clients: Note that the JSON object in `data` is exactly what the REST API returns when listing private messages. -# TODO +### A new contact was added remotely - * PendingContactStateChangedEvent - * PendingContactRemovedEvent - * ContactAddedRemotelyEvent \ No newline at end of file +When the Briar peer adds a new contact remotely, +it will send a JSON object representing the new contact to connected websocket clients: + +```json +{ + "data": { + "author": { + "formatVersion": 1, + "id": "y1wkIzAimAbYoCGgWxkWlr6vnq1F8t1QRA/UMPgI0E0=", + "name": "Test", + "publicKey": "BDu6h1S02bF4W6rgoZfZ6BMjTj/9S9hNN7EQoV05qUo=" + }, + "contactId": 1, + "verified": true + }, + "name": "ContactAddedRemotelyEvent", + "type": "event" +} +``` + +### A pending contact changed its state + +```json +{ + "data": { + "pendingContactId":"YqKjsczCuxScXohb5+RAYtFEwK71icoB4ldztV2gh7M=", + "state":"waiting_for_connection" + }, + "name": "PendingContactStateChangedEvent", + "type": "event" +} +``` + +For a list of valid states, please see the section on adding contacts above. + +### A pending contact was removed + +```json +{ + "data": { + "pendingContactId": "YqKjsczCuxScXohb5+RAYtFEwK71icoB4ldztV2gh7M=" + }, + "name": "PendingContactRemovedEvent", + "type": "event" +} +``` diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/ContactControllerImpl.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/ContactControllerImpl.kt index 1b7a639c1..16a17a532 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/ContactControllerImpl.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/ContactControllerImpl.kt @@ -5,8 +5,14 @@ import io.javalin.Context import io.javalin.NotFoundResponse import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.PendingContactId +import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent +import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent +import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.db.NoSuchPendingContactException +import org.briarproject.bramble.api.event.Event +import org.briarproject.bramble.api.event.EventListener +import org.briarproject.briar.headless.event.WebSocketController import org.briarproject.briar.headless.getContactIdFromPathParam import org.briarproject.briar.headless.getFromJson import org.briarproject.briar.headless.json.JsonDict @@ -16,12 +22,33 @@ import javax.annotation.concurrent.Immutable import javax.inject.Inject import javax.inject.Singleton +internal const val EVENT_CONTACT_ADDED_REMOTELY = "ContactAddedRemotelyEvent" +internal const val EVENT_PENDING_CONTACT_STATE_CHANGED = "PendingContactStateChangedEvent" +internal const val EVENT_PENDING_CONTACT_REMOVED = "PendingContactRemovedEvent" + @Immutable @Singleton internal class ContactControllerImpl @Inject -constructor(private val contactManager: ContactManager, private val objectMapper: ObjectMapper) : - ContactController { +constructor( + private val contactManager: ContactManager, + private val objectMapper: ObjectMapper, + private val webSocket: WebSocketController +) : ContactController, EventListener { + + override fun eventOccurred(e: Event) = when (e) { + is ContactAddedRemotelyEvent -> { + webSocket.sendEvent(EVENT_CONTACT_ADDED_REMOTELY, e.output()) + } + is PendingContactStateChangedEvent -> { + webSocket.sendEvent(EVENT_PENDING_CONTACT_STATE_CHANGED, e.output()) + } + is PendingContactRemovedEvent -> { + webSocket.sendEvent(EVENT_PENDING_CONTACT_REMOVED, e.output()) + } + else -> { + } + } override fun list(ctx: Context): Context { val contacts = contactManager.contacts.map { contact -> diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/HeadlessContactModule.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/HeadlessContactModule.kt index bf91804b3..032af9778 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/HeadlessContactModule.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/HeadlessContactModule.kt @@ -2,6 +2,7 @@ package org.briarproject.briar.headless.contact import dagger.Module import dagger.Provides +import org.briarproject.bramble.api.event.EventBus import javax.inject.Singleton @Module @@ -9,7 +10,11 @@ class HeadlessContactModule { @Provides @Singleton - internal fun provideContactController(contactController: ContactControllerImpl): ContactController { + internal fun provideContactController( + eventBus: EventBus, + contactController: ContactControllerImpl + ): ContactController { + eventBus.addListener(contactController) return contactController } diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputContact.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputContact.kt index d0ec5898c..232ce74dc 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputContact.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputContact.kt @@ -1,6 +1,7 @@ package org.briarproject.briar.headless.contact import org.briarproject.bramble.api.contact.Contact +import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent import org.briarproject.bramble.identity.output import org.briarproject.briar.headless.json.JsonDict @@ -8,4 +9,6 @@ internal fun Contact.output() = JsonDict( "contactId" to id.int, "author" to author.output(), "verified" to isVerified -) \ No newline at end of file +) + +internal fun ContactAddedRemotelyEvent.output() = contact.output() diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputPendingContact.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputPendingContact.kt index ac224e307..1988bb342 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputPendingContact.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/contact/OutputPendingContact.kt @@ -1,18 +1,32 @@ package org.briarproject.briar.headless.contact import org.briarproject.bramble.api.contact.PendingContact +import org.briarproject.bramble.api.contact.PendingContactState import org.briarproject.bramble.api.contact.PendingContactState.* +import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent +import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent import org.briarproject.briar.headless.json.JsonDict internal fun PendingContact.output() = JsonDict( "pendingContactId" to id.bytes, "alias" to alias, - "state" to when(state) { - WAITING_FOR_CONNECTION -> "waiting_for_connection" - CONNECTED -> "connected" - ADDING_CONTACT -> "adding_contact" - FAILED -> "failed" - else -> throw AssertionError() - }, + "state" to state.output(), "timestamp" to timestamp -) \ No newline at end of file +) + +internal fun PendingContactState.output() = when(this) { + WAITING_FOR_CONNECTION -> "waiting_for_connection" + CONNECTED -> "connected" + ADDING_CONTACT -> "adding_contact" + FAILED -> "failed" + else -> throw AssertionError() +} + +internal fun PendingContactStateChangedEvent.output() = JsonDict( + "pendingContactId" to id.bytes, + "state" to pendingContactState.output() +) + +internal fun PendingContactRemovedEvent.output() = JsonDict( + "pendingContactId" to id.bytes +) diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt index d9ffe4872..b461cb5f8 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/ControllerTest.kt @@ -14,6 +14,7 @@ import org.briarproject.bramble.api.sync.Message import org.briarproject.bramble.api.system.Clock import org.briarproject.bramble.test.TestUtils.* import org.briarproject.bramble.util.StringUtils.getRandomString +import org.briarproject.briar.headless.event.WebSocketController import org.skyscreamer.jsonassert.JSONAssert.assertEquals import org.skyscreamer.jsonassert.JSONCompareMode.STRICT import javax.servlet.http.HttpServletRequest @@ -26,6 +27,8 @@ abstract class ControllerTest { protected val clock = mockk() protected val ctx = mockk() + protected val webSocketController = mockk() + private val request = mockk(relaxed = true) private val response = mockk(relaxed = true) private val outputCtx = ContextUtil.init(request, response) diff --git a/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerTest.kt b/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerTest.kt index a0498ea6e..118c279f6 100644 --- a/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerTest.kt +++ b/briar-headless/src/test/java/org/briarproject/briar/headless/contact/ContactControllerTest.kt @@ -5,9 +5,14 @@ import io.javalin.json.JavalinJson.toJson import io.mockk.Runs import io.mockk.every import io.mockk.just +import io.mockk.runs import org.briarproject.bramble.api.contact.Contact import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.PendingContactId +import org.briarproject.bramble.api.contact.PendingContactState.FAILED +import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent +import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent +import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.db.NoSuchPendingContactException import org.briarproject.bramble.identity.output @@ -20,9 +25,11 @@ import org.junit.jupiter.api.Test internal class ContactControllerTest : ControllerTest() { - private val controller = ContactControllerImpl(contactManager, objectMapper) private val pendingContact = getPendingContact() + private val controller = + ContactControllerImpl(contactManager, objectMapper, webSocketController) + @Test fun testEmptyContactList() { every { contactManager.contacts } returns emptyList() @@ -134,6 +141,48 @@ internal class ContactControllerTest : ControllerTest() { } } + @Test + fun testContactAddedRemotelyEvent() { + val event = ContactAddedRemotelyEvent(contact) + + every { + webSocketController.sendEvent( + EVENT_CONTACT_ADDED_REMOTELY, + event.output() + ) + } just runs + + controller.eventOccurred(event) + } + + @Test + fun testPendingContactStateChangedEvent() { + val event = PendingContactStateChangedEvent(pendingContact.id, FAILED) + + every { + webSocketController.sendEvent( + EVENT_PENDING_CONTACT_STATE_CHANGED, + event.output() + ) + } just runs + + controller.eventOccurred(event) + } + + @Test + fun testPendingContactRemovedEvent() { + val event = PendingContactRemovedEvent(pendingContact.id) + + every { + webSocketController.sendEvent( + EVENT_PENDING_CONTACT_REMOVED, + event.output() + ) + } just runs + + controller.eventOccurred(event) + } + @Test fun testOutputContact() { val json = """ @@ -159,6 +208,12 @@ internal class ContactControllerTest : ControllerTest() { assertJsonEquals(json, author.output()) } + @Test + fun testOutputContactAddedRemotelyEvent() { + val event = ContactAddedRemotelyEvent(contact) + assertJsonEquals(toJson(contact.output()), event.output()) + } + @Test fun testOutputPendingContact() { val json = """ @@ -172,4 +227,27 @@ internal class ContactControllerTest : ControllerTest() { assertJsonEquals(json, pendingContact.output()) } + @Test + fun testOutputPendingContactStateChangedEvent() { + val event = PendingContactStateChangedEvent(pendingContact.id, FAILED) + val json = """ + { + "pendingContactId": ${toJson(pendingContact.id.bytes)}, + "state": "failed" + } + """ + assertJsonEquals(json, event.output()) + } + + @Test + fun testOutputPendingContactRemovedEvent() { + val event = PendingContactRemovedEvent(pendingContact.id) + val json = """ + { + "pendingContactId": ${toJson(pendingContact.id.bytes)} + } + """ + assertJsonEquals(json, event.output()) + } + } 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 c438a768b..d5ab17dc9 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 @@ -23,7 +23,6 @@ import org.briarproject.briar.api.messaging.PrivateMessageFactory import org.briarproject.briar.api.messaging.PrivateMessageHeader import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent import org.briarproject.briar.headless.ControllerTest -import org.briarproject.briar.headless.event.WebSocketController import org.briarproject.briar.headless.event.output import org.briarproject.briar.headless.json.JsonDict import org.junit.jupiter.api.Assertions.assertEquals @@ -35,7 +34,6 @@ internal class MessagingControllerImplTest : ControllerTest() { private val messagingManager = mockk() private val conversationManager = mockk() private val privateMessageFactory = mockk() - private val webSocketController = mockk() private val dbExecutor = ImmediateExecutor() private val controller = MessagingControllerImpl(