mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
briar-headless: Next round of review comments
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user