Kokkoja as of 2026-10-10: the teacher's requests, the Session window, notices, the QR code, the license

This commit is contained in:
ack committed 2026-10-10 19:01:20 +02:00
1 parent 95c80d04c9
commit 1d087af633
32 files changed
+4917 -890

No files matched your search

+68 -20
View File
@@ -37,13 +37,16 @@ page, an image once. There is no central copy.
Two computers behind home routers generally cannot connect to each other
directly, so something in between has to pass the messages. The relay is
the smallest such thing: a WebSocket server with *rooms*. A client
connects to `wss://<server>/relay/room/<room id>`; the relay gives the
connection a number, tells the room that a peer has arrived, and from
then on sends every message it receives from one member to all the
others, stamped with the sender's number. When the last member leaves,
the room is gone. It holds at most 32 members per room and 500 rooms,
and refuses messages over 1 MB; clients split anything larger.
the smallest such thing: a WebSocket server that passes messages among
the members of a session. What a user calls a session, the relay calls a
*room*: the word appears in the relay's code and log and in the address a
client connects to, `wss://<server>/relay/room/<room id>`, and nowhere
else. The relay gives each connection a number, tells the others that a
peer has arrived, and from then on sends every message it receives from
one member to all the others, stamped with the sender's number. When
the last member leaves, nothing of the session remains at the relay. It
holds at most 32 members per session and 500 sessions, and refuses
messages over 1 MB; clients split anything larger.
## Sessions and encryption
@@ -53,7 +56,7 @@ The invitation is
The *secret* is 16 random bytes, made by the host's browser. From it
every client derives two values with HKDF-SHA-256: the *room id*, which
names the room at the relay, and the *message key*, a 256-bit AES-GCM
names the session at the relay, and the *message key*, a 256-bit AES-GCM
key with which every message is encrypted before it is sent and
decrypted on arrival. The relay sees the room id, the members' network
addresses, and the size and timing of encrypted messages. It cannot
@@ -66,7 +69,21 @@ A link invitation wraps the same text:
https://<site>/#join=<invitation>
Browsers never send the part of an address after `#` to the site, so the
secret stays between the host and the guests.
secret stays between the host and the guests. The Session window also
shows the link as a QR code, so that a phone or a tablet in the room can
join by camera; the encoder is Kokkoja's own (`qr_code.js`: byte mode,
error correction level M, any version), checked by the tests against
Nayuki's reference encoder.
The client writes its singular events (pages, images, the connection,
the co-host right, the first input report) to the browser's console
and keeps them, with a trace of every press, release and cancellation
of the pointer (an eraser's release says how many strokes and text
blocks it removed), every change of tool and the touch events, in
memory (`log.js`, the last 400 entries). The Session window shows the record with a Copy button,
so that a guest on a tablet, where there is no console, can send it
when the pen misbehaves. The record stays in the browser tab; nothing
of it is sent anywhere by Kokkoja.
## Identity and layers
@@ -74,7 +91,7 @@ Each participant has a signing key pair (ECDSA on P-256), made in the
browser the first time and then kept, by the local program in its
configuration directory or by the browser in its own storage. The
*participant id* is the first 16 bytes of the SHA-256 hash of the public
key. When a client joins a room it announces itself with a signed
key. When a client connects to a session it announces itself with a signed
`hello` carrying its name and public key; the signature covers the room
id and the peer number the relay stamped on the message, so nobody can
replay it elsewhere or claim another peer's connection. Until a peer has
@@ -89,23 +106,37 @@ kept, a person is the same participant in every session from the same
computer, and their writing on a page loaded weeks later is still theirs.
The host is whoever holds the key whose id is in the invitation. Pages,
images and the list of guests allowed to start pages are accepted only
images and the list of co-hosts are accepted only
from the host (or from an allowed guest, for pages and images).
## Pages
Page coordinates are in units of 0.1 mm. A blank page is an A4 sheet,
2100 by 2970 units. Writing within 8 mm of an edge grows the sheet by
8 mm on that side. An image is placed at its natural size, taken from
Page coordinates are in units of 0.1 mm. A blank page is an A4 sheet
in landscape, 2970 by 2100 units. "Widen" makes the sheet wider, and
"Shrink" makes it the bounding box of everything on the page plus the
greater of the two margins, exactly, on every side; both are changes to the page like adding
an image, carried in the page descriptor (its `left` and `top` edges and
its size) with the images' revision raised. Writing within 5 mm of an edge grows the page by
5 mm on that side, counted from the sheet's edge. An image is placed at its natural size, taken from
the resolution recorded in the file, or 0.1 mm per pixel when there is
none, centred across the sheet, 8 mm from the top or from the image
above, scaled down if wider than the sheet inside its margins (or, if the
person adding it chooses, placed at natural size with the sheet widened
to hold it). A page holds at most 12 images and 24 MB of image data.
none (the nominal 72 and 96 dpi that cameras and screenshots write count
as none), centred across the page, 5 mm from the top or from the image
above, the sheet widened equally on both sides if it is wider than the
page; only one wider than a page can be, 2 m, is scaled down. A page holds at most 12 images and 24 MB of image data.
An image file over 2400 pixels on a side or 1.5 MB, or not PNG or JPEG,
is redrawn at most 2400 pixels and stored as JPEG before it is shared;
smaller PNG and JPEG files are shared byte for byte.
A text block is free text, or a *text rectangle* with a width and a
height, drawn with a red frame on a white ground; the ground is drawn
beneath every layer, so that it hides an image and never anyone's
writing, and the frame within the owner's layer. A *Hide* rectangle is
a page object like an image, in the page descriptor and the page file,
drawn white above every layer with blue corners on screen; made, moved
and removed by the host and co-hosts. Both are moved by a hold of
the pen and resized from their corners, like images but with their
shape free; an image keeps its shape.
A stroke is a list of points with pressure, and the line width is a
function of pressure: nominal at a pressure of 0.4, about 2.6 times that
at 0.75. Points are sent while the stroke is drawn, so the others see it
@@ -127,7 +158,7 @@ it, rewritten a moment after every change (at most every 400 ms while
writing continues, at least every 2 s). A *kept* page is a copy of
`Current.svg` named `YYYY_MM_DD_NN.svg`, the day being when the page was
last changed and NN a serial number within that day. A copy is made when
the user asks (**Keep page**), and by the program whenever `Current.svg`
the user asks (**Keep**), and by the program whenever `Current.svg`
is about to be given to another page: when the session moves to a new
page, when the notebook is changed, and when a session begins and finds
a page of another session in the notebook. Nothing is copied if the
@@ -213,7 +244,7 @@ computer behind a home router is not without arrangement.
The client uses the browser's pointer events, which deliver every sample
a pen produces, with pressure, in Firefox and in Chromium-based browsers
on Linux and macOS. The panel's last line shows what arrives. When a
on Linux and macOS. The browser's console shows what arrives. When a
pen has been used, touches are ignored, so a hand resting on a touch
screen does not draw; this rule has not yet been tested with a device
that has both a pen and a touch sensor. Kokkoja opens none of the
@@ -221,6 +252,23 @@ browser's system dialogs (the colour palette and, with the local
program, the file chooser are its own), because one such dialog crashed
Firefox on Linux.
## Adjustable choices
Some choices of the interface are named constants at the top of the
file that uses them, so that they can be changed in one place, or offered
as preferences later: the line widths and the five colours (`main.js`),
the zoom step of the wheel and the zoom per 100 pixels of a pen drag
(`renderer.js`), the dead zone of that drag (`pen_input.js`), the margin
by which the page grows (`page_model.js`), the tooltip delay
(`tooltip.js`), the hold that moves an image (600 ms, with 6 pixels of
slack, `pen_input.js`), the reach of an image's corner (24 pixels,
`renderer.js`), the smallest an image can be scaled to (1 cm,
`page_model.js`), how often a dragged image's place is sent (40 ms,
`session.js`) and the points of a stroke (25 ms, `session.js`), the colour and width of a text rectangle's frame
(`page_file.js`) and whether the frame is shown whole or as corners on
screen (`TEXT_FRAME_STYLE` in `renderer.js`; the file and the export
always hold the whole frame).
## Development
The code is Python and JavaScript only. The tests run with