π Web Browser#
We have developed a library written in TypeScript that allows you to write client applications of an Olvid daemon on a web page: @olvid/bot-web. However, using this module is limited and brings constraints as well as security challenges.
Limitations and constraints#
Proxy#
Currently, a web page cannot directly communicate with a gRPC daemon. A proxy must be used. This will be set up at the same time as our daemon in the following procedure.
Sending files is not possible#
The tools we use to develop this library do not allow us to use client side streaming and bidirectional streaming methods of gRPC. As a result, it is not possible to send attachments or change profile pictures.
Security#
In case your web page / application needs to be accessible on the Internet, we strongly recommend that you exercise caution when exposing your daemon. We recommend implementing at least an HTTP authentication, and if possible, a certificate-based authentication.
Also make sure not to store your client key in plaintext on your webpage. If your gRPC proxy is authenticated (ideally by certificate), you can add the client key directly at the reverse-proxy level, by adding a header daemon-client-key.
Installation#
Daemon and Proxy#
Here is an example of a Docker Compose infrastructure for deploying a daemon and its gRPC proxy.
services:
daemon:
image: olvid/bot-daemon:2.0.1
environment:
- OLVID_ADMIN_CLIENT_KEY_CLI=SetARandomValue
volumes:
- ./data:/daemon/data
proxy:
image: olvid/grpc-web-proxy
ports:
- "8080:8080"
command: ["--backend_addr=daemon:50051", "--run_tls_server=false", "--server_http_max_read_timeout=0s", "--server_http_max_write_timeout=0s"]
restart: unless-stopped
cli:
image: olvid/bot-python-runner:2.0.1
entrypoint: "olvid-cli"
environment:
- OLVID_ADMIN_CLIENT_KEY=SetARandomValue
- OLVID_DAEMON_URL=http://daemon:50051
stdin_open: true
tty: true
profiles: ["cli"]
You can then start your daemon and its proxy.
docker compose up -d daemon proxy
For the initialization and setup of this daemon, I invite you to check out our π Quickstart section.
Client#
Once your daemon is in place, you can begin writing your webpage/application. There are many ways and frameworks to do this, we cannot detail them all, but we will cover the basics.
For our example, we will create a simple web page that sends a message in all conversations of your bot. For this, we will use npm for installing the module and the vite framework.
We start by preparing our project and installing the dependencies.
npm install @olvid/bot-web
npm install vite
npm pkg set scripts.dev="npx vite"
We can then create two files that contain the code for our demonstration page.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script type="module" src="./index.js"></script>
<title>Olvid in a Web Page</title>
</head>
<body>
<input id="send-input" type="text"/>
<button id="send-button">Send</button>
</body>
</html>
index.js
import {OlvidClient} from "@olvid/bot-web";
const client = new OlvidClient({
serverUrl: "http://localhost:8080",
clientKey: "" // TODO set me !
});
let input = document.getElementById("send-input");
let button = document.getElementById("send-button");
// callback to send a message in every bot discussion
async function broadcastMessage(body) {
if (!body) {
console.error("Message body cannot be empty");
return
}
for await (let discussion of client.discussionList()) {
await client.messageSend({discussionId: discussion.id, body: body})
}
}
// send message when you click send or press enter in input
button.onclick = async () => { await broadcastMessage(input.value) }
input.addEventListener("keydown", async (e) => {
if (e.code === "Enter") {
await broadcastMessage(input.value)
}
});
client.onMessageSent({callback: message => {
console.log("> ", message.body)
}});
client.onMessageReceived({callback: message => {
console.log("< ", message.body);
}})
Please remember to replace your client key value in the index.js file when instantiating the OlvidClient class, and modify the value of serverUrl if the default one does not suit your infrastructure.
To test your program, simply run the following command and go to the indicated address.
npm run dev
Reverse Proxy#
Once your daemon is running and your web application developed, you might need to deploy it outside of your local network. We will then look at the different possible configurations of nginx as a reverse proxy.
Basic configuration#
This is a basic example of a configuration without authentication. Warning, do not expose this proxy on a network in its current state.
server {
listen 80;
server_name daemon-proxy.example.com
access_log /var/log/nginx/daemon-proxy-access.log;
error_log /var/log/nginx/daemon-proxy-error.log;
location / {
proxy_pass http://localhost:5174;
proxy_buffering off;
proxy_read_timeout 1d;
proxy_connect_timeout 1d;
proxy_send_timeout 1d;
}
}
Add authentication#
HTTP#
First, you need to create a password file. You can follow the official nginx documentation.
You just need to activate HTTP authentication for our reverse proxy.
server {
listen 80;
server_name daemon-proxy.example.com
access_log /var/log/nginx/daemon-proxy-access.log;
error_log /var/log/nginx/daemon-proxy-error.log;
location / {
auth_basic "Daemon Proxy";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:5174;
proxy_buffering off;
proxy_read_timeout 1d;
proxy_connect_timeout 1d;
proxy_send_timeout 1d;
}
}
Certificates#
We recommend setting up a certificate-based authentication instead, for this you can refer to the official nginx documentation.
Client Key#
Warning
This section should only be followed if access to your reverse proxy is properly authenticated (see Adding Authentication) ! Otherwise, your daemon could become freely accessible on the internet.
To avoid including your client key in your webpage/application, you can specify it at the level in your reverse proxy configuration.
For this, you can add the following line (after editing it) to the location block of your nginx configuration.
proxy_set_header daemon-client-key MY_CLIENT_KEY;
β οΈ This will allow any client, authenticated on the proxy, to access the daemon with the client key specified in the Nginx configuration.
API Filtering#
It is possible to expose only the necessary API entry points for your application by filtering at the reverse proxy level.
For this, instead of specifying a single location block in our nginx configuration, we add one per entry point to expose, with the same content, but different paths.
Here is an example of a configuration that only allows the messageSend and discussionGet commands, as well as the onMessageReceived notification.
β This configuration does not set up authentication, but we strongly recommend that you do so, see adding authentication.
server {
listen 80;
server_name daemon-proxy.example.com
access_log /var/log/nginx/daemon-proxy-access.log;
error_log /var/log/nginx/daemon-proxy-error.log;
location /olvid.daemon.services.v1.MessageCommandservice/MessageSend {
proxy_pass http://localhost:5174;
proxy_buffering off;
proxy_read_timeout 1d;
proxy_connect_timeout 1d;
proxy_send_timeout 1d;
}
location /olvid.daemon.services.v1.DiscussionCommandservice/DiscussionGet {
proxy_pass http://localhost:5174;
proxy_buffering off;
proxy_read_timeout 1d;
proxy_connect_timeout 1d;
proxy_send_timeout 1d;
}
location /olvid.daemon.services.v1.MessageNotificationService/MessageReceived {
proxy_pass http://localhost:5174;
proxy_buffering off;
proxy_read_timeout 1d;
proxy_connect_timeout 1d;
proxy_send_timeout 1d;
}
}
Share domain name#
For practical reasons or to avoid Cross-Origin issues, you may need to host your proxy daemon and your web page/application on the same domain. Hereβs an example of an nginx configuration for this scenario.
In this case, you must change the connection URL to your daemon by adding the /proxy suffix in your application.
server {
listen 80;
server_name daemon-proxy.example.com
access_log /var/log/nginx/daemon-proxy-access.log;
error_log /var/log/nginx/daemon-proxy-error.log;
# proxy is only accessible with /proxy prefix, but we remove it before transfering requests to grpc-web-proxy
location /proxy/ {
rewrite /proxy/(.*) /$1 break;
proxy_pass http://localhost:5174;
proxy_buffering off;
proxy_read_timeout 1d;
proxy_connect_timeout 1d;
proxy_send_timeout 1d;
}
# configuration to your web page / application
location / {
[...]
}
}