diff --git a/briar-headless/README.md b/briar-headless/README.md index ca704a1a7bd7bd76a96fb0146b6eb83443043acf..5b4b9feee38253e75d6408f7cb62ebf4eec108a7 100644 --- a/briar-headless/README.md +++ b/briar-headless/README.md @@ -1,17 +1,17 @@ # Briar REST API -This is a headless Briar client that exposes a REST API +This is a headless Briar peer that exposes a REST API with an integrated HTTP server instead of a traditional user interface. -You can use this API to script the client behavior +You can use this API to script the peer behavior or to develop your own user interface for it. ## How to use -The REST API client comes as a `jar` file +The REST API peer comes as a `jar` file and needs a Java Runtime Environment (JRE) that supports at least Java 8. It currently works only on GNU/Linux operating systems. -You can start the client (and its API server) like this: +You can start the peer (and its API server) like this: $ java -jar briar-headless/build/libs/briar-headless.jar @@ -69,7 +69,7 @@ Returns a JSON array of contacts: *Not yet implemented* The only workaround is to add a contact to the Briar app running on a rooted Android phone -and then move its database (and key files) to the headless client. +and then move its database (and key files) to the headless peer. ### Listing all private messages @@ -93,7 +93,7 @@ It returns a JSON array of private messages: } ``` -If `local` is `true`, the message was sent by the Briar client instead of its remote contact. +If `local` is `true`, the message was sent by the Briar peer instead of its remote contact. Attention: There can messages of other `type`s where the message `body` is `null`. @@ -136,7 +136,7 @@ The text of the blog post should be included in the form parameter `text`. ## Websocket API -The Briar client uses a websocket to notify a connected API client about new events. +The Briar peer uses a websocket to notify a connected API client about new events. `WS /v1/ws` @@ -157,7 +157,7 @@ Your websocket client will most likely add these headers automatically. ### Receiving new private messages -When the Briar client receives a new private message, +When the Briar peer receives a new private message, it will send a JSON object to connected websocket clients: ```json diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/BriarService.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/BriarService.kt index 40e76af335aaca211f792454cd5d436569a417a4..5f13b739e97c2a33bbbaa16c8dc6e3fc227e2ac6 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/BriarService.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/BriarService.kt @@ -16,7 +16,8 @@ import javax.inject.Singleton @Immutable @Singleton internal class BriarService -@Inject constructor( +@Inject +constructor( private val accountManager: AccountManager, private val lifecycleManager: LifecycleManager, private val passwordStrengthEstimator: PasswordStrengthEstimator diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/Router.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/Router.kt index 2914be0b7a9e366dd4055eb05a540c2fc6bac3a5..c1d4563cf395416962949199160fc2da56f43a2a 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/Router.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/Router.kt @@ -5,7 +5,7 @@ import io.javalin.JavalinEvent.SERVER_START_FAILED import io.javalin.JavalinEvent.SERVER_STOPPED import io.javalin.apibuilder.ApiBuilder.* import io.javalin.core.util.ContextUtil -import io.javalin.core.util.Header +import io.javalin.core.util.Header.AUTHORIZATION import org.briarproject.briar.headless.blogs.BlogController import org.briarproject.briar.headless.contact.ContactController import org.briarproject.briar.headless.event.WebSocketController @@ -21,7 +21,8 @@ import kotlin.system.exitProcess @Immutable @Singleton -internal class Router @Inject +internal class Router +@Inject constructor( private val briarService: BriarService, private val webSocketController: WebSocketController, @@ -36,7 +37,7 @@ constructor( fun start(authToken: String, port: Int, debug: Boolean) { briarService.start() - getRuntime().addShutdownHook(Thread(Runnable { stop() })) + getRuntime().addShutdownHook(Thread(this::stop)) val app = Javalin.create() .port(port) @@ -48,7 +49,7 @@ constructor( app.start() app.accessManager { handler, ctx, _ -> - if (ctx.header("Authorization") == "Bearer $authToken") { + if (ctx.header(AUTHORIZATION) == "Bearer $authToken") { handler.handle(ctx) } else { ctx.status(401).result("Unauthorized") @@ -77,7 +78,7 @@ constructor( } app.ws("/v1/ws") { ws -> ws.onConnect { session -> - val authHeader = session.header(Header.AUTHORIZATION) + val authHeader = session.header(AUTHORIZATION) val token = ContextUtil.getBasicAuthCredentials(authHeader)?.username if (authToken == token) { logger.info("Adding websocket session with ${session.remoteAddress}") diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt index 4125dfba1c8c30a530e7bd2decc6c8f590966d3f..d3f179e4855add55780693ed72a19b5608e26374 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/blogs/BlogControllerImpl.kt @@ -15,7 +15,8 @@ import javax.inject.Singleton @Immutable @Singleton internal class BlogControllerImpl -@Inject constructor( +@Inject +constructor( private val blogManager: BlogManager, private val blogPostFactory: BlogPostFactory, private val identityManager: IdentityManager, 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 7a53476a907becaef21a76fb8b32c58cd8f24f6e..bf94c70a562b3a8a75176ef8660006643fb75cb3 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 @@ -9,7 +9,8 @@ import javax.inject.Singleton @Immutable @Singleton internal class ContactControllerImpl -@Inject constructor(private val contactManager: ContactManager) : ContactController { +@Inject +constructor(private val contactManager: ContactManager) : ContactController { override fun list(ctx: Context): Context { val contacts = contactManager.activeContacts.map { contact -> diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketController.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketController.kt index ed723d55690bee939db20d6b90144e6793696f22..abbbc81e5b8fb3ee72c9a6fd738b4a93c5436e0d 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketController.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketController.kt @@ -2,6 +2,7 @@ package org.briarproject.briar.headless.event import io.javalin.websocket.WsSession import org.briarproject.bramble.api.lifecycle.IoExecutor +import org.briarproject.briar.headless.json.JsonDict import javax.annotation.concurrent.ThreadSafe @ThreadSafe @@ -12,6 +13,6 @@ interface WebSocketController { /** * Sends an event to all open sessions using the [IoExecutor]. */ - fun sendEvent(name: String, obj: Any) + fun sendEvent(name: String, obj: JsonDict) } diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketControllerImpl.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketControllerImpl.kt index 6ea7bf6e5ee467d48c14ba14c764c3d1d455afe2..c36156ca1410f4ae6e508af45212d6d9ad64f1d9 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketControllerImpl.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/event/WebSocketControllerImpl.kt @@ -4,11 +4,12 @@ import io.javalin.json.JavalinJson.toJson import io.javalin.websocket.WsSession import org.briarproject.bramble.api.lifecycle.IoExecutor import org.briarproject.bramble.util.LogUtils.logException +import org.briarproject.briar.headless.json.JsonDict import org.eclipse.jetty.websocket.api.WebSocketException import java.io.IOException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor -import java.util.logging.Level +import java.util.logging.Level.WARNING import java.util.logging.Logger.getLogger import javax.annotation.concurrent.Immutable import javax.inject.Inject @@ -17,21 +18,24 @@ import javax.inject.Singleton @Immutable @Singleton internal class WebSocketControllerImpl -@Inject constructor(@IoExecutor private val ioExecutor: Executor) : WebSocketController { +@Inject +constructor(@IoExecutor private val ioExecutor: Executor) : WebSocketController { private val logger = getLogger(WebSocketControllerImpl::javaClass.name) override val sessions: MutableSet<WsSession> = ConcurrentHashMap.newKeySet<WsSession>() - override fun sendEvent(name: String, obj: Any) = ioExecutor.execute { + override fun sendEvent(name: String, obj: JsonDict) { + val event = toJson(OutputEvent(name, obj)) sessions.forEach { session -> - val event = OutputEvent(name, obj) - try { - session.send(toJson(event)) - } catch (e: WebSocketException) { - logException(logger, Level.WARNING, e) - } catch (e: IOException) { - logException(logger, Level.WARNING, e) + ioExecutor.execute { + try { + session.send(event) + } catch (e: WebSocketException) { + logException(logger, WARNING, e) + } catch (e: IOException) { + logException(logger, WARNING, e) + } } } } diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/forums/ForumControllerImpl.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/forums/ForumControllerImpl.kt index 1e0d49d78ab5264838e65aa41eb02e276bc58a03..952f6e8eec12080d15d59d1a2b9fdf972f100ce2 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/forums/ForumControllerImpl.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/forums/ForumControllerImpl.kt @@ -12,7 +12,8 @@ import javax.inject.Singleton @Immutable @Singleton internal class ForumControllerImpl -@Inject constructor(private val forumManager: ForumManager) : ForumController { +@Inject +constructor(private val forumManager: ForumManager) : ForumController { override fun list(ctx: Context): Context { return ctx.json(forumManager.forums.output()) 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 0d9c546dfc08ea1782d3964225f811e08176b87a..34fbca73ede760e129072d8b7c196cfb5d54d33d 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 @@ -36,7 +36,8 @@ internal const val EVENT_PRIVATE_MESSAGE = "PrivateMessageReceivedEvent" @Immutable @Singleton internal class MessagingControllerImpl -@Inject constructor( +@Inject +constructor( private val messagingManager: MessagingManager, private val conversationManager: ConversationManager, private val privateMessageFactory: PrivateMessageFactory, diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputPrivateResponse.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputPrivateResponse.kt index ee0f8189317de841f24e87df7b01d9f6eea24d7d..f0b3fbda32e4c3a84b65a3ed011b9194af14d95c 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputPrivateResponse.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/messaging/OutputPrivateResponse.kt @@ -44,7 +44,7 @@ internal fun BlogInvitationResponse.output(contactId: ContactId): JsonDict { internal fun ForumInvitationResponse.output(contactId: ContactId): JsonDict { val dict = (this as InvitationResponse).output(contactId) - dict["type"] = "BlogInvitationResponse" + dict["type"] = "ForumInvitationResponse" return dict } 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 7ead89482c9f1177554b3d925490ee18c52879ac..c567e547c0633e7776d7f84e834b2b5eea871a34 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 @@ -44,6 +44,7 @@ internal class MessagingControllerImplTest : ControllerTest() { private val header = PrivateMessageHeader(message.id, group.id, timestamp, true, true, true, true) private val sessionId = SessionId(getRandomId()) + private val privateMessage = PrivateMessage(message) @Test fun list() { @@ -91,7 +92,6 @@ internal class MessagingControllerImplTest : ControllerTest() { @Test fun write() { - val privateMessage = PrivateMessage(message) val slot = CapturingSlot<JsonDict>() expectGetContact() @@ -110,12 +110,7 @@ internal class MessagingControllerImplTest : ControllerTest() { controller.write(ctx) - val output = slot.captured - assertEquals(privateMessage.output(contact.id, body), output) - assertEquals(contact.id.int, output["contactId"]) - assertEquals(body, output["body"]) - assertEquals(message.id.bytes, output["id"]) - assertEquals("PrivateMessage", output["type"]) + assertEquals(privateMessage.output(contact.id, body), slot.captured) } @Test @@ -181,6 +176,25 @@ internal class MessagingControllerImplTest : ControllerTest() { assertJsonEquals(json, header.output(contact.id, body)) } + @Test + fun testOutputPrivateMessage() { + val json = """ + { + "body": "$body", + "type": "PrivateMessage", + "timestamp": ${message.timestamp}, + "groupId": ${toJson(message.groupId.bytes)}, + "contactId": ${contact.id.int}, + "local": true, + "seen": true, + "read": true, + "sent": true, + "id": ${toJson(message.id.bytes)} + } + """ + assertJsonEquals(json, privateMessage.output(contact.id, body)) + } + @Test fun testIntroductionRequestWithEmptyBody() { val request = IntroductionRequest(