Picnic: end-to-end encrypted collaborative text editor
I felt like having a small break from my main project this month, so I'm giving myself some weeks to try out other ideas I've been putting off. Just happy coding of open source MVPs for various ideas I've had for a while. I'm giving myself a max of 7 days per app to make sure the scope stays reasonable.
The first one is pretty close to done now, and it's called picnic.
It's a simple web app that lets you create a plain text document and edit it with friends in real time (on PC and smartphone).
It's end-to-end encrypted, so the client side does the encryption with window.crypto (despite not really adding much security) and sends that to my node server over web sockets. The server saves that document and forwards it to the other clients that can decode it thanks to the key stored in the URL. "Rolling your own crypto" is generally a bad idea, but I think here i'm "using pre-made crypto" which is alright, despite a very strong feeling on "I have no idea what I'm doing".
Speaking of haphazardly reaching a decent solution : syncing edits is hard. When you type some words in the text, the full value of that text is sent over to the server, who forwards it to everyone as-is. So each client receives the latest version, but might have done its own edits that are not yet sent for syncing (two people typing at the same time).
I end up doing a three-way merge of full text, using a homemade, quite naive approach. It works most of the time, though it quite easily gets confused. Initially, just typing alone on the document would get super messy when the updates from the server would conflict with your local edits.
My 3 way merge algorithm performs well when the edits are far from each other, but it performs terribly when two users are adding a thing at the same spot in the doc. Luckily, this only really happen when a user tries to apply his own edits. So I just made the editor ignore any edits that came from itself (using a session ID that only lasts for the duration of the web page).
Furthermore, some edits would arrive in the wrong order, and my algorithm would consider a reversed addition as a deletion. I could solve that quite easily too, by just adding a version number to each document, incrementing it at each edit, and ignoring stale edits. The server keeps the authoritative value for the current version number and blocks any stale edit form propagating.
Finally, the app reconnects with exponential back-off and retries to send user's edits whenever the sync fails for any reason, until it's up-to-date with the server. Overall, I'm quite pleased with the result, it's usable, loads fast and more reliable than my previous attempt at solving the same project. It's also quite easy to self-host as the DB is simply the local file system.