Multi-user apps
Until now, you've built an Automerge application for a single user only. But Automerge excels when there are multiple users editing a document over time, who may or may not be online at the same time.
NOTE: Syncing changes requires that both nodes are starting from the same Automerge document. If you haven't implemented some form of Save and Load behavior, your syncing will not work properly.
BroadcastChannel
In this tutorial, we will use a BroadcastChannel, which allows you to simulate a local area network. All tabs and windows on the same domain in the browser will be able to send and receive messages from each other. In a production application, you could use a WebSocket, WebRTC server, HTTP server, or simply send binary files around.
NOTE: BroadcastChannel is not available in IE or Safari. You must install the Safari Technical Preview or use another browser, such as Chrome, Brave, or Firefox.
Each channel has its own ID. We use the docId
, which allows us to send messages to any other browser tab or iframe that has the ID.
let docId = window.location.hash.replace(/^#/, '')
let channel = new BroadcastChannel(docId)
Sending a message
Every time the todo list is updated in one tab, we want to inform all the other tabs that they also need to update their copies of the document. To do this, we once again hook into the updateDoc()
function. Besides rendering and saving a document, it now also sends a message containing the updated document to the other tabs:
function updateDoc(newDoc) {
doc = newDoc
render(newDoc)
let binary = Automerge.save(newDoc)
localforage.setItem(docId, binary).catch(err => console.log(err))
channel.postMessage(binary) // <-- this line is new
}
Note that we pass the binary data generated by Automerge.save()
to channel.postMessage()
. You can't send the Automerge document object doc
directly because the browser doesn't know how to serialize it.
Receiving a message
We can now register a channel.onmessage
callback that will get called on every other browser tab on which you have opened the same URL. In the callback we first use Automerge.load()
to unserialize the binary data in the message, and then call Automerge.merge()
to merge it with the document in the current tab:
channel.onmessage = (ev) => {
let newDoc = Automerge.merge(doc, Automerge.load(ev.data))
doc = newDoc
render(newDoc)
}
After merging, we update the global variable doc
and call the render()
function to update the todo list in the page. We don't call updateDoc()
here because that would call postMessage
again, resulting in an infinite loop where two tabs continually bounce messages back and forth.
Now, test it! Copy and paste your URL into a new window in the same browser, so that you have two windows or tabs with the same docId. In one browser tab, add a new item. It should immediately appear on the other tab as well.
Hints
This approach of sending each other serialized documents, and merging them, works just as well over a network as between tabs in the same browser. However, if your documents get big, then it is inefficient to repeatedly send each other the entire serialized document.
As an alternative, it's possible to encode only the changes that have been made since the last sync, and send those. You can find more information on how to do this in the Cookbook.