Kokkoja as of 2026-10-10: the teacher's requests, the Session window, notices, the QR code, the license
This commit is contained in:
1 parent
95c80d04c9
commit
1d087af633
32 files changed
+4917
-890
No files matched your search
+68
-20
@@ -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
|
||||
|
||||
Reference in new issue
Block a user