R sync server and client for Automerge CRDT documents. Implements
the automerge-repo WebSocket protocol, enabling R to serve
as a synchronization hub for Automerge clients in R, JavaScript, Rust,
and other languages, and to act as a client that fetches, edits, and
synchronizes documents hosted on remote servers.
automerge-repo protocol compatibility
(interoperates with sync.automerge.org)Install the released version from CRAN:
install.packages("autosync")Or the development version from GitHub:
pak::pak("posit-dev/autosync")Create a WebSocket sync server:
library(autosync)
server <- sync_server()
server$start()
server
# Server runs non-blocking in the background
server$close()With TLS for secure connections:
cert <- nanonext::write_cert()
tls <- nanonext::tls_config(server = cert$server)
server <- sync_server(tls = tls)
server$start()
server$urlEnable OIDC authentication to restrict access. The defaults point to
Google, but any OIDC-compliant provider (Microsoft Entra, Okta, Auth0,
etc.) can be used by setting the issuer argument.
Register your OAuth client as a “Desktop app” and
set the OIDC_CLIENT_ID and OIDC_CLIENT_SECRET
environment variables (e.g. in .Renviron):
# .Renviron
OIDC_CLIENT_ID=YOUR_CLIENT_ID
OIDC_CLIENT_SECRET=YOUR_CLIENT_SECRETGoogle issues a client secret for Desktop app clients and requires it
in the token exchange, but — unlike a “Web application” secret — it is
not treated as confidential: it is designed to be distributed with
installed apps, so it is safe to keep in .Renviron. Google
states that for installed apps “the client secret is obviously not
treated as a secret” (Google
OAuth 2.0 docs), consistent with the OAuth 2.0 for Native Apps
standard (RFC
8252 §8.5). The interactive flow also uses PKCE, so the secret alone
does not grant access.
Other providers (Microsoft Entra, Okta, Auth0, etc.) can register the
client as a native / public client, which authenticates
via PKCE alone — for those, set only OIDC_CLIENT_ID and
leave OIDC_CLIENT_SECRET unset.
cert <- nanonext::write_cert()
tls <- nanonext::tls_config(server = cert$server)
server <- sync_server(
tls = tls, # TLS required when auth is enabled
auth = auth_config(allowed_domains = "posit.co")
)
server$start()Clients obtain a token interactively, then pass it when fetching:
token <- sync_token()
tlsclient <- nanonext::tls_config(client = cert$client)
doc <- sync_fetch(
server$url,
"35ei6ouA7nLhtjmf3d9xk1KKvtKv",
token = token,
tls = tlsclient
)The share parameter controls which documents are
announced to clients and which requests are allowed. It accepts
TRUE, FALSE, NA, or a function
returning one of these per client and document:
| Value | Announce | Allow requests |
|---|---|---|
NA (default) |
No | Yes |
TRUE |
Yes | Yes |
FALSE |
No | No |
Announce all documents to every client that connects:
server <- sync_server(share = TRUE)Combine with authentication and use a share function for
fine-grained access control. The function receives the
client_id and doc_id, and can look up the
authenticated email on the connection to decide:
# Allow list of emails that can access documents
allowed <- c("alice@example.com", "bob@example.com")
server <- sync_server(
tls = tls,
auth = auth_config(),
share = function(client_id, doc_id) {
state <- attr(server, "sync")
conn <- state$connections[[client_id]]
if (conn$authenticated_email %in% allowed) NA else FALSE
}
)# Create a new document
doc_id <- create_document(server)
# List all documents
list_documents(server)
# Get a document
doc <- get_document(server, doc_id)import { Repo } from "@automerge/automerge-repo"
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket"
const repo = new Repo({
network: [new BrowserWebSocketClientAdapter("ws://localhost:8080")]
})Fetch a document from any automerge-repo sync server in a single, one-off retrieval over a throwaway connection:
# Fetch from public sync server
doc <- sync_fetch("wss://sync.automerge.org", "your-document-id")
# Inspect document structure
str(doc)
# Or verbose fetch for debugging sync issues
doc <- sync_fetch("wss://sync.automerge.org", "your-document-id", verbose = TRUE)For a persistent connection that keeps documents in sync — receiving
real-time updates from other peers and flushing local changes — use
sync_client(). Several documents can share a single
connection, each opened with $open_doc():
conn <- sync_client("wss://sync.automerge.org")
# Open a live document over the connection
doc <- conn$open_doc("your-document-id")
automerge::am_keys(doc$doc)
# Make local changes and push them to the server
automerge::am_put(doc$doc, automerge::AM_ROOT, "key", "value")
doc$push()
# Open another document over the same connection
other <- conn$open_doc("another-document-id")
# Disconnect (closes every document on the connection)
conn$close()Generate document IDs compatible with automerge-repo:
doc_id <- generate_document_id()