briar-headless: Next round of review comments

parent c7eb0cbb
# 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
......
......@@ -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
......
......@@ -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}")
......
......@@ -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,
......
......@@ -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 ->
......
......@@ -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)
}
......@@ -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)
}
}
}
}
......
......@@ -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())
......
......@@ -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,
......
......@@ -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
}
......
......@@ -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(
......
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