[headless] expose contact connected state to REST API

This commit is contained in:
Torsten Grote
2020-07-06 08:16:53 -03:00
parent 9d96ce6db0
commit 3f0d9233d9
5 changed files with 110 additions and 10 deletions

View File

@@ -68,7 +68,8 @@ Returns a JSON array of contacts:
"alias" : "A local nickname", "alias" : "A local nickname",
"handshakePublicKey": "XnYRd7a7E4CTqgAvh4hCxh/YZ0EPscxknB9ZcEOpSzY=", "handshakePublicKey": "XnYRd7a7E4CTqgAvh4hCxh/YZ0EPscxknB9ZcEOpSzY=",
"verified": true, "verified": true,
"lastChatActivity": 1557838312175 "lastChatActivity": 1557838312175,
"connected": false
} }
``` ```
@@ -392,3 +393,19 @@ will no longer work on making this `pendingContact` become `contact`.
"type": "event" "type": "event"
} }
``` ```
### A contact connected or disconnected
When Briar establishes a connection to a contact (the contact comes online),
it sends a `ContactConnectedEvent`.
When the last connection is lost (the contact goes offline), it sends a `ContactDisconnectedEvent`.
```json
{
"data": {
"contactId": 1
},
"name": "ContactConnectedEvent",
"type": "event"
}
```

View File

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.http.BadRequestResponse import io.javalin.http.BadRequestResponse
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import org.briarproject.bramble.api.connection.ConnectionRegistry
import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.ContactManager
import org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX import org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX
import org.briarproject.bramble.api.contact.PendingContactId import org.briarproject.bramble.api.contact.PendingContactId
@@ -16,6 +17,8 @@ import org.briarproject.bramble.api.db.NoSuchPendingContactException
import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.Event
import org.briarproject.bramble.api.event.EventListener import org.briarproject.bramble.api.event.EventListener
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
import org.briarproject.bramble.util.StringUtils.toUtf8 import org.briarproject.bramble.util.StringUtils.toUtf8
import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.ConversationManager
import org.briarproject.briar.headless.event.WebSocketController import org.briarproject.briar.headless.event.WebSocketController
@@ -32,6 +35,8 @@ internal const val EVENT_CONTACT_ADDED = "ContactAddedEvent"
internal const val EVENT_PENDING_CONTACT_STATE_CHANGED = "PendingContactStateChangedEvent" internal const val EVENT_PENDING_CONTACT_STATE_CHANGED = "PendingContactStateChangedEvent"
internal const val EVENT_PENDING_CONTACT_ADDED = "PendingContactAddedEvent" internal const val EVENT_PENDING_CONTACT_ADDED = "PendingContactAddedEvent"
internal const val EVENT_PENDING_CONTACT_REMOVED = "PendingContactRemovedEvent" internal const val EVENT_PENDING_CONTACT_REMOVED = "PendingContactRemovedEvent"
internal const val EVENT_CONTACT_CONNECTED = "ContactConnectedEvent"
internal const val EVENT_CONTACT_DISCONNECTED = "ContactDisconnectedEvent"
@Immutable @Immutable
@Singleton @Singleton
@@ -41,7 +46,8 @@ constructor(
private val contactManager: ContactManager, private val contactManager: ContactManager,
private val conversationManager: ConversationManager, private val conversationManager: ConversationManager,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val webSocket: WebSocketController private val webSocket: WebSocketController,
private val connectionRegistry: ConnectionRegistry
) : ContactController, EventListener { ) : ContactController, EventListener {
override fun eventOccurred(e: Event) = when (e) { override fun eventOccurred(e: Event) = when (e) {
@@ -57,6 +63,12 @@ constructor(
is PendingContactRemovedEvent -> { is PendingContactRemovedEvent -> {
webSocket.sendEvent(EVENT_PENDING_CONTACT_REMOVED, e.output()) webSocket.sendEvent(EVENT_PENDING_CONTACT_REMOVED, e.output())
} }
is ContactConnectedEvent -> {
webSocket.sendEvent(EVENT_CONTACT_CONNECTED, e.output())
}
is ContactDisconnectedEvent -> {
webSocket.sendEvent(EVENT_CONTACT_DISCONNECTED, e.output())
}
else -> { else -> {
} }
} }
@@ -64,7 +76,8 @@ constructor(
override fun list(ctx: Context): Context { override fun list(ctx: Context): Context {
val contacts = contactManager.contacts.map { contact -> val contacts = contactManager.contacts.map { contact ->
val latestMsgTime = conversationManager.getGroupCount(contact.id).latestMsgTime val latestMsgTime = conversationManager.getGroupCount(contact.id).latestMsgTime
contact.output(latestMsgTime) val connected = connectionRegistry.isConnected(contact.id)
contact.output(latestMsgTime, connected)
} }
return ctx.json(contacts) return ctx.json(contacts)
} }

View File

@@ -2,15 +2,17 @@ package org.briarproject.briar.headless.contact
import org.briarproject.bramble.api.contact.Contact import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.contact.event.ContactAddedEvent import org.briarproject.bramble.api.contact.event.ContactAddedEvent
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
import org.briarproject.bramble.identity.output import org.briarproject.bramble.identity.output
import org.briarproject.briar.api.conversation.ConversationManager
import org.briarproject.briar.headless.json.JsonDict import org.briarproject.briar.headless.json.JsonDict
internal fun Contact.output(latestMsgTime: Long) = JsonDict( internal fun Contact.output(latestMsgTime: Long, connected: Boolean) = JsonDict(
"contactId" to id.int, "contactId" to id.int,
"author" to author.output(), "author" to author.output(),
"verified" to isVerified, "verified" to isVerified,
"lastChatActivity" to latestMsgTime "lastChatActivity" to latestMsgTime,
"connected" to connected
).apply { ).apply {
alias?.let { put("alias", it) } alias?.let { put("alias", it) }
handshakePublicKey?.let { put("handshakePublicKey", it.encoded) } handshakePublicKey?.let { put("handshakePublicKey", it.encoded) }
@@ -20,3 +22,11 @@ internal fun ContactAddedEvent.output() = JsonDict(
"contactId" to contactId.int, "contactId" to contactId.int,
"verified" to isVerified "verified" to isVerified
) )
internal fun ContactConnectedEvent.output() = JsonDict(
"contactId" to contactId.int
)
internal fun ContactDisconnectedEvent.output() = JsonDict(
"contactId" to contactId.int
)

View File

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.util.ContextUtil import io.javalin.http.util.ContextUtil
import io.mockk.mockk import io.mockk.mockk
import org.briarproject.bramble.api.connection.ConnectionRegistry
import org.briarproject.bramble.api.contact.Contact import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.ContactManager
import org.briarproject.bramble.api.identity.Author import org.briarproject.bramble.api.identity.Author
@@ -26,6 +27,7 @@ abstract class ControllerTest {
protected val contactManager = mockk<ContactManager>() protected val contactManager = mockk<ContactManager>()
protected val conversationManager = mockk<ConversationManager>() protected val conversationManager = mockk<ConversationManager>()
protected val identityManager = mockk<IdentityManager>() protected val identityManager = mockk<IdentityManager>()
protected val connectionRegistry = mockk<ConnectionRegistry>()
protected val clock = mockk<Clock>() protected val clock = mockk<Clock>()
protected val ctx = mockk<Context>() protected val ctx = mockk<Context>()

View File

@@ -20,6 +20,8 @@ import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEven
import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.db.NoSuchPendingContactException import org.briarproject.bramble.api.db.NoSuchPendingContactException
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
import org.briarproject.bramble.identity.output import org.briarproject.bramble.identity.output
import org.briarproject.bramble.test.TestUtils.getPendingContact import org.briarproject.bramble.test.TestUtils.getPendingContact
import org.briarproject.bramble.test.TestUtils.getRandomBytes import org.briarproject.bramble.test.TestUtils.getRandomBytes
@@ -29,6 +31,7 @@ import org.briarproject.briar.headless.json.JsonDict
import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.random.Random
internal class ContactControllerTest : ControllerTest() { internal class ContactControllerTest : ControllerTest() {
@@ -38,7 +41,8 @@ internal class ContactControllerTest : ControllerTest() {
contactManager, contactManager,
conversationManager, conversationManager,
objectMapper, objectMapper,
webSocketController webSocketController,
connectionRegistry
) )
@Test @Test
@@ -50,9 +54,11 @@ internal class ContactControllerTest : ControllerTest() {
@Test @Test
fun testList() { fun testList() {
val connected = Random.nextBoolean()
every { contactManager.contacts } returns listOf(contact) every { contactManager.contacts } returns listOf(contact)
every { conversationManager.getGroupCount(contact.id).latestMsgTime } returns timestamp every { conversationManager.getGroupCount(contact.id).latestMsgTime } returns timestamp
every { ctx.json(listOf(contact.output(timestamp))) } returns ctx every { connectionRegistry.isConnected(contact.id) } returns connected
every { ctx.json(listOf(contact.output(timestamp, connected))) } returns ctx
controller.list(ctx) controller.list(ctx)
} }
@@ -267,8 +273,37 @@ internal class ContactControllerTest : ControllerTest() {
controller.eventOccurred(event) controller.eventOccurred(event)
} }
@Test
fun testContactConnectedEvent() {
val event = ContactConnectedEvent(contact.id)
every {
webSocketController.sendEvent(
EVENT_CONTACT_CONNECTED,
event.output()
)
} just runs
controller.eventOccurred(event)
}
@Test
fun testContactDisconnectedEvent() {
val event = ContactDisconnectedEvent(contact.id)
every {
webSocketController.sendEvent(
EVENT_CONTACT_DISCONNECTED,
event.output()
)
} just runs
controller.eventOccurred(event)
}
@Test @Test
fun testOutputContact() { fun testOutputContact() {
val connected = Random.nextBoolean()
assertNotNull(contact.handshakePublicKey) assertNotNull(contact.handshakePublicKey)
val json = """ val json = """
{ {
@@ -277,10 +312,11 @@ internal class ContactControllerTest : ControllerTest() {
"alias" : "${contact.alias}", "alias" : "${contact.alias}",
"handshakePublicKey": ${toJson(contact.handshakePublicKey!!.encoded)}, "handshakePublicKey": ${toJson(contact.handshakePublicKey!!.encoded)},
"verified": ${contact.isVerified}, "verified": ${contact.isVerified},
"lastChatActivity": $timestamp "lastChatActivity": $timestamp,
"connected": $connected
} }
""" """
assertJsonEquals(json, contact.output(timestamp)) assertJsonEquals(json, contact.output(timestamp, connected))
} }
@Test @Test
@@ -358,4 +394,26 @@ internal class ContactControllerTest : ControllerTest() {
assertJsonEquals(json, event.output()) assertJsonEquals(json, event.output())
} }
@Test
fun testOutputContactConnectedEvent() {
val event = ContactConnectedEvent(contact.id)
val json = """
{
"contactId": ${contact.id.int}
}
"""
assertJsonEquals(json, event.output())
}
@Test
fun testOutputContactDisconnectedEvent() {
val event = ContactDisconnectedEvent(contact.id)
val json = """
{
"contactId": ${contact.id.int}
}
"""
assertJsonEquals(json, event.output())
}
} }