braid-text
v0.2.115
Published
Library for collaborative text over http using braid.
Downloads
1,779
Readme
Collaborative text over Braid-HTTP
This library provides a simple http route handler, along with client code, enabling fast text synchronization over a standard protocol.
- Supports Braid-HTTP protocol
- Supports Simpleton merge-type
- Enables light clients
- As little as 50 lines of code!
- With zero history overhead on client
- Supports backpressure to run smoothly on constrained servers
- Server merges with Diamond-Types
- Enables light clients
- Supports Diamond Types merge-type
- Fully peer-to-peer CRDT
- Fast / Robust / Extensively fuzz-tested
- Developed in braid.org
This library makes it safe, easy & efficient to add collaborative text editing to every user-editable string in your web app. Make your app multiplayer!
Check out the demo video 📺 from the Braid 86 release!
Demo: a Wiki!
This will run a collaboratively-editable wiki:
npm install
node server-demo.jsNow open these URLs in your browser:
- http://localhost:8888/demo (to see the demo text)
- http://localhost:8888/demo?editor (to edit the text)
- http://localhost:8888/demo?markdown-editor (to edit it as markdown)
- http://localhost:8888/any-other-path?editor (to create a new page, just go to its URL, and then start editing)
Or try opening the URL in Braid-Chrome, or another Braid client, to edit it directly!
Check out the server-demo.js file to see examples for how to add simple access control, where a user need only enter a password into a cookie in the javascript console like: document.cookie = 'password'; and a /pages endpoint to show all the edited pages.
General Use as Server
Install it in your project:
npm install braid-textImport the request handler into your code, and use it to handle HTTP requests wherever you want:
var braid_text = require("braid-text")
http_server.on("request", (req, res) => {
// Your server logic...
// Whenever desired, serve braid text for this request/response:
braid_text.serve(req, res)
})Server API
braid_text.db_folder = './braid-text-db' // <-- this is the default
- This is where the Diamond-Types history files will be stored for each resource.
- This folder will be created if it doesn't exist.
- The files for a resource will all be prefixed with a url-encoding of
keywithin this folder.
braid_text.serve(req, res, options)
req: Incoming HTTP request object.res: Outgoing HTTP response object.options: [optional] An object containing additional options:key: [optional] ID of text resource to sync with. Defaults toreq.url.
- This is the main method of this library, and does all the work to handle Braid-HTTP
GETandPUTrequests concerned with a specific text resource.
await braid_text.get(key)
key: ID of text resource.- Returns the text of the resource as a string.
await braid_text.get(key, options)
key: ID of text resource.options: An object containing additional options, like http headers:version: [optional] The version to get, as an array of strings. (The array is typically length 1.)parents: [optional] The version to start the subscription at, as an array of strings.subscribe: cb: [optional] Instead of returning the state; subscribes to the state, and callscbwith the initial state and each update. The functioncbwill be called with a Braid update of the formcb({version, parents, body, patches}).merge_type: [optional] The CRDT/OT merge-type algorithm to emulate. Currently supports"simpleton"(default) and"dt".peer: [optional] Unique string ID that identifies the peer making the subscription. Mutations will not be echoed back to the same peer thatPUTs them, for anyPUTsetting the samepeerheader.
- If NOT subscribing, returns
{version: <current_version>, body: <current-text>}. If subscribing, returns nothing.
await braid_text.put(key, options)
key: ID of text resource.options: An object containing additional options, like http headers:version: [optional] The version beingPUT, as an array of strings. Will be generated if not provided.parents: [optional] The previous version being updated, as array of strings. Defaults to the server’s current version.body: [optional] Use this to completely replace the existing text with this new text. See Braid updates.patches: [optional] Array of patches, each of the form{unit: 'text', range: '[1:3]', content: 'hi'}, which would replace the second and third unicode code-points in the text withhi. See Braid Range-Patches.peer: [optional] Identifies this peer. This mutation will not be echoed back togetsubscriptions that use this samepeerheader.
General Use as Client
Here's a basic running example to start:
<!-- 1. Your textarea -->
<textarea id="my_textarea"></textarea>
<!-- 2. Include the libraries -->
<script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
<script src="https://unpkg.com/braid-text/simpleton-client.js"></script>
<!-- 3. Wire it up -->
<script>
// Connect to server
var simpleton = simpleton_client('https://braid.org/public-sandbox', {
on_state: state => my_textarea.value = state, // incoming changes
get_state: () => my_textarea.value // outgoing changes
})
// Tell simpleton when user types
my_textarea.oninput = () => simpleton.changed()
</script>You should see some text in a box if you run this, and if you run it in another tab, you should be able to edit that text collaboratively.
How It Works
The client uses a decoupled update mechanism for efficiency:
- When users type, you call
simpleton.changed()to notify the client that something changed - The client decides when to actually fetch and send updates based on network conditions
- When ready, it calls your
get_statefunction to get the current text
This design prevents network congestion and handles disconnections gracefully. For example, if you edit offline for hours, the client will send just one efficient diff when reconnecting, rather than thousands of individual keystrokes.
Advanced Integration
For better performance and control, you can work with patches instead of full text:
Patch Format
Each patch is an object with two properties:
range:[start, end]- The range of characters to deletecontent: The text to insert at that position
Patches in an array each have positions which refer to the original text before any other patches are applied.
Receiving Patches
Instead of receiving complete text updates, you can process individual changes:
var simpleton = simpleton_client(url, {
on_patches: (patches) => {
// Apply each patch to your editor..
},
get_state: () => editor.getValue()
})This is more efficient for large documents and helps preserve cursor position.
Custom Patch Generation
You can provide your own diff algorithm or use patches from your editor's API:
var simpleton = simpleton_client(url, {
on_state: state => editor.setValue(state),
get_state: () => editor.getValue(),
get_patches: (prev_state) => {
// Use your own diff algorithm or editor's change tracking
return compute_patches(prev_state, editor.getValue())
}
})See editor.html for a complete example.
Client API
Constructor
simpleton = simpleton_client(url, options)Creates a new Simpleton client that synchronizes with a Braid-Text server.
Parameters:
url: The URL of the resource to synchronize withoptions: Configuration object with the following properties:
Required Options
get_state: [required] Function that returns the current text state() => current_text_string
Incoming Updates (choose one)
on_state: [optional] Callback for receiving complete state updates(state) => { /* update your UI with new text */ }on_patches: [optional] Callback for receiving incremental changes(patches) => { /* apply patches to your editor */ }Each patch has:
range:[start, end]- positions to delete (in original text coordinates)content: Text to insert at that position
Note: All patches reference positions in the original text before any patches are applied.
Outgoing Updates
get_patches: [optional] Custom function to generate patches(previous_state) => array_of_patchesIf not provided, uses a simple prefix/suffix diff algorithm.
Note: All patches must reference positions in the original text before any patches are applied.
Additional Options
content_type: [optional] MIME type forAcceptandContent-Typeheaders
Methods
simpleton.changed(): Notify the client that local changes have occurred. Call this in your editor's change event handler. The client will callget_patchesandget_statewhen it's ready to send updates.
Deprecated Options
The following options are deprecated and should be replaced with the new API:
- ~~
apply_remote_update~~ → Useon_patchesoron_stateinstead - ~~
generate_local_diff_update~~ → Useget_patchesandget_stateinstead
Testing
to run unit tests:
first run the test server:
npm install
node test/server.jsthen open http://localhost:8889/test.html, and the boxes should turn green as the tests pass.
to run fuzz tests:
npm install
node test/test.jsif the last output line looks like this, good:
t = 9999, seed = 1397019, best_n = Infinity @ NaNbut it's bad if it looks like this:
t = 9999, seed = 1397019, best_n = 5 @ 1396791the number at the end is the random seed that generated the simplest error example
