briar-headless: Add a websocket controller for private message events

Also version API URLs
parent 34896801
......@@ -22,6 +22,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.LinuxTorPluginFactory;
import org.briarproject.bramble.system.JavaSystemModule;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.headless.messaging.MessagingModule;
import java.io.File;
import java.security.GeneralSecurityException;
......@@ -42,7 +43,8 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL
@Module(includes = {
JavaNetworkModule.class,
JavaSystemModule.class,
CircumventionModule.class
CircumventionModule.class,
MessagingModule.class
})
public class HeadlessModule {
......@@ -119,6 +121,14 @@ public class HeadlessModule {
return devConfig;
}
@Provides
@Singleton
WebSocketController provideWebSocketHandler(
WebSocketControllerImpl webSocketController) {
return webSocketController;
}
private File appDir(ConfigurationManager configurationManager,
String file) {
return new File(configurationManager.getAppDir(), file);
......
......@@ -5,7 +5,6 @@ import io.javalin.JavalinEvent.SERVER_START_FAILED
import io.javalin.JavalinEvent.SERVER_STOPPED
import io.javalin.apibuilder.ApiBuilder.*
import org.briarproject.briar.headless.blogs.BlogController
import org.briarproject.briar.headless.contact.ContactController
import org.briarproject.briar.headless.forums.ForumController
import org.briarproject.briar.headless.messaging.MessagingController
import java.lang.Runtime.getRuntime
......@@ -19,7 +18,8 @@ import kotlin.system.exitProcess
class Router @Inject
constructor(
private val briarService: BriarService,
private val contactController: ContactController,
private val webSocketController: WebSocketController,
private val contactController: MessagingController,
private val messagingController: MessagingController,
private val forumController: ForumController,
private val blogController: BlogController
......@@ -40,24 +40,30 @@ constructor(
.start()
app.routes {
path("/contacts") {
get { ctx -> contactController.list(ctx) }
}
path("/messages/:contactId") {
get { ctx -> messagingController.list(ctx) }
post { ctx -> messagingController.write(ctx) }
}
path("/forums") {
get { ctx -> forumController.list(ctx) }
post { ctx -> forumController.create(ctx) }
}
path("/blogs") {
path("/posts") {
get { ctx -> blogController.listPosts(ctx) }
post { ctx -> blogController.createPost(ctx) }
path("/v1") {
path("/contacts") {
get { ctx -> contactController.list(ctx) }
}
path("/messages/:contactId") {
get { ctx -> messagingController.list(ctx) }
post { ctx -> messagingController.write(ctx) }
}
path("/forums") {
get { ctx -> forumController.list(ctx) }
post { ctx -> forumController.create(ctx) }
}
path("/blogs") {
path("/posts") {
get { ctx -> blogController.listPosts(ctx) }
post { ctx -> blogController.createPost(ctx) }
}
}
}
}
app.ws("/v1/ws") { ws ->
ws.onConnect { session -> webSocketController.sessions.add(session) }
ws.onClose { session, _, _ -> webSocketController.sessions.remove(session) }
}
}
private fun stop() {
......
package org.briarproject.briar.headless
import io.javalin.websocket.WsSession
interface WebSocketController {
val sessions: MutableSet<WsSession>
fun sendEvent(name: String, obj: Any)
}
package org.briarproject.briar.headless
import io.javalin.json.JavalinJson
import io.javalin.websocket.WsSession
import java.util.concurrent.ConcurrentHashMap
import javax.annotation.concurrent.Immutable
import javax.inject.Inject
import javax.inject.Singleton
@Immutable
@Singleton
internal class WebSocketControllerImpl @Inject constructor() : WebSocketController {
override val sessions: MutableSet<WsSession> = ConcurrentHashMap.newKeySet<WsSession>()
override fun sendEvent(name: String, obj: Any) {
sessions.forEach { session ->
val event = OutputEvent(name, obj)
val json = JavalinJson.toJsonMapper.map(event)
session.send(json)
}
}
}
@Immutable
@Suppress("unused")
internal class OutputEvent(val name: String, val data: Any) {
val type = "event"
}
package org.briarproject.briar.headless.messaging
import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.briar.api.messaging.PrivateMessage
import org.briarproject.briar.api.messaging.PrivateMessageHeader
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent
internal fun PrivateMessageHeader.output(body: String) = OutputPrivateMessage(this, body)
internal fun PrivateMessageHeader.output(contactId: ContactId, body: String) =
OutputPrivateMessage(this, contactId, body)
internal fun PrivateMessage.output(body: String) = OutputPrivateMessage(this, body)
internal fun PrivateMessage.output(contactId: ContactId, body: String) =
OutputPrivateMessage(this, contactId, body)
internal fun PrivateMessageReceivedEvent.output(body: String) =
messageHeader.output(contactId, body)
package org.briarproject.briar.headless.messaging
import io.javalin.BadRequestResponse
import io.javalin.Context
import io.javalin.NotFoundResponse
import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.bramble.api.contact.ContactManager
import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.system.Clock
import org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH
import org.briarproject.briar.api.messaging.MessagingManager
import org.briarproject.briar.api.messaging.PrivateMessageFactory
import javax.annotation.concurrent.Immutable
import javax.inject.Inject
import javax.inject.Singleton
@Immutable
@Singleton
class MessagingController @Inject
constructor(
private val messagingManager: MessagingManager,
private val privateMessageFactory: PrivateMessageFactory,
private val contactManager: ContactManager,
private val clock: Clock
) {
interface MessagingController {
fun list(ctx: Context): Context {
val contact = getContact(ctx)
fun list(ctx: Context): Context
val messages = messagingManager.getMessageHeaders(contact.id).map { header ->
val body = messagingManager.getMessageBody(header.id)
header.output(body)
}
return ctx.json(messages)
}
fun write(ctx: Context): Context {
val contact = getContact(ctx)
val message = ctx.formParam("message")
if (message == null || message.isEmpty())
throw BadRequestResponse("Expecting Message text")
if (message.length > MAX_PRIVATE_MESSAGE_BODY_LENGTH)
throw BadRequestResponse("Message text too large")
val group = messagingManager.getContactGroup(contact)
val now = clock.currentTimeMillis()
val m = privateMessageFactory.createPrivateMessage(group.id, now, message)
messagingManager.addLocalMessage(m)
return ctx.json(m.output(message))
}
private fun getContact(ctx: Context): Contact {
val contactString = ctx.pathParam("contactId")
val contactInt = Integer.parseInt(contactString)
val contactId = ContactId(contactInt)
return try {
contactManager.getContact(contactId)
} catch (e: NoSuchContactException) {
throw NotFoundResponse()
}
}
fun write(ctx: Context): Context
}
package org.briarproject.briar.headless.messaging
import io.javalin.BadRequestResponse
import io.javalin.Context
import io.javalin.NotFoundResponse
import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.bramble.api.contact.ContactManager
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.system.Clock
import org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH
import org.briarproject.briar.api.messaging.MessagingManager
import org.briarproject.briar.api.messaging.PrivateMessageFactory
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent
import org.briarproject.briar.headless.WebSocketController
import java.util.concurrent.Executor
import javax.annotation.concurrent.Immutable
import javax.inject.Inject
import javax.inject.Singleton
@Immutable
@Singleton
internal class MessagingControllerImpl @Inject
constructor(
private val messagingManager: MessagingManager,
private val privateMessageFactory: PrivateMessageFactory,
private val contactManager: ContactManager,
private val webSocketController: WebSocketController,
@DatabaseExecutor private val dbExecutor: Executor,
private val clock: Clock
) : MessagingController, EventListener {
override fun list(ctx: Context): Context {
val contact = getContact(ctx)
val messages = messagingManager.getMessageHeaders(contact.id).map { header ->
val body = messagingManager.getMessageBody(header.id)
header.output(contact.id, body)
}
return ctx.json(messages)
}
override fun write(ctx: Context): Context {
val contact = getContact(ctx)
val message = ctx.formParam("message")
if (message == null || message.isEmpty())
throw BadRequestResponse("Expecting Message text")
if (message.length > MAX_PRIVATE_MESSAGE_BODY_LENGTH)
throw BadRequestResponse("Message text too large")
val group = messagingManager.getContactGroup(contact)
val now = clock.currentTimeMillis()
val m = privateMessageFactory.createPrivateMessage(group.id, now, message)
messagingManager.addLocalMessage(m)
return ctx.json(m.output(contact.id, message))
}
override fun eventOccurred(e: Event) {
when (e) {
is PrivateMessageReceivedEvent -> dbExecutor.run {
val name =
"org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent"
val body = messagingManager.getMessageBody(e.messageHeader.id)
webSocketController.sendEvent(name, e.output(body))
}
}
}
private fun getContact(ctx: Context): Contact {
val contactString = ctx.pathParam("contactId")
val contactInt = Integer.parseInt(contactString)
val contactId = ContactId(contactInt)
return try {
contactManager.getContact(contactId)
} catch (e: NoSuchContactException) {
throw NotFoundResponse()
}
}
}
package org.briarproject.briar.headless.messaging
import dagger.Module
import dagger.Provides
import org.briarproject.bramble.api.event.EventBus
import javax.inject.Singleton
@Module
class MessagingModule {
@Provides
@Singleton
internal fun provideMessagingController(
eventBus: EventBus, messagingController: MessagingControllerImpl
): MessagingController {
eventBus.addListener(messagingController)
return messagingController
}
}
package org.briarproject.briar.headless.messaging
import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.briar.api.messaging.PrivateMessage
import org.briarproject.briar.api.messaging.PrivateMessageHeader
import javax.annotation.concurrent.Immutable
......@@ -16,8 +17,9 @@ internal class OutputPrivateMessage {
val local: Boolean
val id: ByteArray
val groupId: ByteArray
val contactId: Int
internal constructor(header: PrivateMessageHeader, body: String) {
internal constructor(header: PrivateMessageHeader, contactId: ContactId, body: String) {
this.body = body
this.timestamp = header.timestamp
this.read = header.isRead
......@@ -26,12 +28,13 @@ internal class OutputPrivateMessage {
this.local = header.isLocal
this.id = header.id.bytes
this.groupId = header.groupId.bytes
this.contactId = contactId.int
}
/**
* Only meant for own [PrivateMessage]s directly after creation.
*/
internal constructor(m: PrivateMessage, body: String) {
internal constructor(m: PrivateMessage, contactId: ContactId, body: String) {
this.body = body
this.timestamp = m.message.timestamp
this.read = true
......@@ -40,5 +43,6 @@ internal class OutputPrivateMessage {
this.local = true
this.id = m.message.id.bytes
this.groupId = m.message.groupId.bytes
this.contactId = contactId.int
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment