Package {autosync}


Type: Package
Title: 'Automerge' Sync Server and Client
Version: 0.2.0
Description: A WebSocket-based implementation of the 'automerge-repo' synchronization protocol used by 'sync.automerge.org'. Acts as a sync server, enabling 'R' to serve as a synchronization hub for 'Automerge' clients in 'JavaScript', 'Rust', and other languages, and as a client for fetching, editing, and synchronizing documents hosted on remote servers.
License: MIT + file LICENSE
URL: https://posit-dev.github.io/autosync/, https://github.com/posit-dev/autosync
BugReports: https://github.com/posit-dev/autosync/issues
Depends: R (≥ 4.4)
Imports: automerge (≥ 0.4.0), httr2 (≥ 1.2.3), jose, later, nanonext (≥ 1.10.3), promises, secretbase (≥ 1.3.0)
Suggests: openssl, shiny, shinyreact (≥ 0.1.0), testthat (≥ 3.0.0)
Config/Needs/website: tidyverse/tidytemplate
Config/roxygen2/markdown: TRUE
Config/roxygen2/version: 8.1.0
Config/testthat/edition: 3
Encoding: UTF-8
NeedsCompilation: no
Packaged: 2026-09-23 19:53:02 UTC; shikokuchuo
Author: Charlie Gao ORCID iD [aut, cre], Posit Software, PBC ROR ID [cph, fnd]
Maintainer: Charlie Gao <charlie.gao@posit.co>
Repository: CRAN
Date/Publication: 2026-09-23 20:10:02 UTC

autosync: 'Automerge' Sync Server and Client

Description

A WebSocket-based implementation of the 'automerge-repo' synchronization protocol used by 'sync.automerge.org'. Acts as a sync server, enabling 'R' to serve as a synchronization hub for 'Automerge' clients in 'JavaScript', 'Rust', and other languages, and as a client for fetching, editing, and synchronizing documents hosted on remote servers.

Main Functions

sync_server()

Create a new sync server with ⁠$start()⁠ and ⁠$close()⁠ methods

Document Management

create_document()

Create a new document

get_document()

Retrieve a document by ID

list_documents()

List all document IDs

generate_document_id()

Generate a new document ID

Protocol

The server implements the automerge-repo sync protocol over WebSockets. Messages are CBOR-encoded and include:

join/peer

Handshake messages for connection establishment

request/sync

Document synchronization messages

ephemeral

Transient messages forwarded without persistence

error

Error notifications

Example

# Create and start a server
server <- sync_server()
server$start()
server$url

# Stop when done
server$close()

Author(s)

Maintainer: Charlie Gao charlie.gao@posit.co (ORCID)

Authors:

Other contributors:

See Also

Useful links:


Create an authentication configuration

Description

Creates a configuration object for enabling OIDC JWT authentication on an autosync server. When enabled, clients must include a valid JWT (ID token) as a Bearer token in the Authorization header of the WebSocket upgrade request. Connections without valid credentials are rejected immediately at connection time.

Usage

auth_config(
  client_id = Sys.getenv("OIDC_CLIENT_ID"),
  issuer = oidc_issuer(),
  allowed_emails = NULL,
  allowed_domains = NULL,
  custom_validator = NULL
)

Arguments

client_id

The OIDC client ID (application ID). Validated against the aud claim in JWTs. Defaults to the OIDC_CLIENT_ID environment variable.

issuer

The OIDC issuer URL. This is used to discover the provider's public keys via the .well-known/openid-configuration endpoint, and to validate the iss claim in JWTs. Defaults to the OIDC_ISSUER environment variable, falling back to Google ("https://accounts.google.com").

allowed_emails

Character vector of allowed email addresses. When set, a token is rejected unless it carries an email claim with email_verified explicitly TRUE.

allowed_domains

Character vector of allowed email domains (e.g., "mycompany.com"). Subject to the same verified-email requirement as allowed_emails.

custom_validator

Function(claims) returning TRUE/FALSE for custom validation logic. Receives the decoded JWT claims as a list.

Details

Works with any OIDC-compliant identity provider: Google, Microsoft Entra, Okta, Auth0, etc.

Value

An object of class "autosync_auth_config".

Examples

# Google (default issuer)
auth_config(
  client_id = "123456789.apps.googleusercontent.com",
  allowed_domains = "mycompany.com"
)

# Microsoft Entra
auth_config(
  client_id = "abcdef-1234-5678",
  issuer = "https://login.microsoftonline.com/common/v2.0",
  allowed_emails = "alice@mycompany.com"
)

# Custom validator
auth_config(
  client_id = "0oaXXXXXXXX",
  issuer = "https://dev-123456.okta.com/oauth2/default",
  custom_validator = function(claims) "editors" %in% claims$groups
)


Authenticate a client from HTTP request headers

Description

Extracts and validates a Bearer token (JWT) from the Authorization header of the WebSocket upgrade request.

Usage

authenticate_header(auth_config, headers)

Arguments

auth_config

A autosync_auth_config object.

headers

Named list of HTTP request headers.

Value

List with valid (logical), email (character or NULL), error (character or NULL).


Create a new document on the server

Description

Creates a new empty Automerge document and registers it with the server.

Usage

create_document(server, doc_id = NULL)

Arguments

server

An autosync_server object.

doc_id

Optional document ID. If NULL, generates a new ID.

Value

Document ID string.

Examples


server <- sync_server()
doc_id <- create_document(server)
server$close()


Discover the JWKS URI for an OIDC issuer

Description

Discover the JWKS URI for an OIDC issuer

Usage

discover_jwks_uri(issuer)

Arguments

issuer

The OIDC issuer URL.

Value

Character string, the JWKS URI.


Fetch and parse a JWKS endpoint

Description

Fetch and parse a JWKS endpoint

Usage

fetch_jwks(jwks_uri)

Arguments

jwks_uri

URL of the JWKS endpoint.

Value

List with keys (named list of jose key objects by kid) and expiry (POSIXct when the cache should be refreshed).


Generate a new document ID

Description

Creates a new unique document ID compatible with automerge-repo. The ID is a 16-byte random value encoded with Base58Check.

Usage

generate_document_id()

Value

Character string (Base58Check encoded).

Examples

generate_document_id()


Get a document from the server

Description

Retrieves an Automerge document by its ID.

Usage

get_document(server, doc_id)

Arguments

server

An autosync_server object.

doc_id

Document ID string.

Value

Automerge document object, or NULL if not found.

Examples


server <- sync_server()
doc_id <- create_document(server)
get_document(server, doc_id)
server$close()


Get a signing key for JWT verification

Description

Looks up the public key for the given issuer and key ID, refreshing the cache if needed.

Usage

get_signing_key(issuer, kid)

Arguments

issuer

The OIDC issuer URL.

kid

The key ID from the JWT header.

Value

A public key object, or NULL if not found.


List all document IDs

Description

Returns the IDs of all documents currently loaded in the server.

Usage

list_documents(server)

Arguments

server

An autosync_server object.

Value

Character vector of document IDs.

Examples


server <- sync_server()
create_document(server)
list_documents(server)
server$close()


Default OIDC issuer URL

Description

Returns the OIDC_ISSUER environment variable if set and non-empty, otherwise falls back to Google ("https://accounts.google.com").

Usage

oidc_issuer()

Value

Character string, the OIDC issuer URL.


Print method for autosync_server

Description

Print method for autosync_server

Usage

## S3 method for class 'autosync_server'
print(x, ...)

Arguments

x

An autosync_server object.

...

Ignored.

Value

Invisibly returns x.


Launch the autosync browser app

Description

sync_app() runs one Shiny app for the whole workflow. It has two screens:

Usage

sync_app(
  server = "",
  proj_id = "",
  token = NULL,
  tls = NULL,
  timeout = 5000L,
  files_key = "files",
  debounce = 300L
)

Arguments

server

Sync-server URL to prefill in the connect form. Default "".

proj_id

Project document ID to prefill. Default "".

token

(optional) A JWT from an earlier sync_token() call. When supplied, the app starts signed in. Default NULL.

tls

(optional) for secure wss:// connections to servers with self-signed or custom CA certificates, a TLS configuration object created by nanonext::tls_config().

timeout

Timeout in milliseconds for each receive operation. Default 5000.

files_key

Key of the files map within the project document. Default "files".

debounce

Milliseconds to debounce outgoing editor changes, passed through to the live editor. Default 300.

Details

The app builds one sync_project() connection and reuses it for every file opened during the session.

The interface is a React frontend rendered with the shinyreact package. The file tree uses the @pierre/trees component, and the editor uses CodeMirror 6. R owns the live Automerge documents and all syncing. Requires the shiny and shinyreact packages and an interactive session.

Value

Invisibly NULL, when the app window is closed.

Examples


# Start with empty fields and fill them in the form:
sync_app()

# Or prefill the server and project:
sync_app("wss://quarto-hub.com/ws", proj_id = "4F63WJPDzbHkkfKa66h1Qrr1sC5U")

# Reuse a token obtained earlier, so the app starts signed in:
token <- sync_token()
sync_app("wss://quarto-hub.com/ws", proj_id = "4F63WJPD...", token = token)


Open a persistent sync connection

Description

Connects to an automerge-repo sync server and maintains a persistent WebSocket connection. The connection performs the protocol handshake but holds no documents on its own; open one or more live documents over it with the ⁠$open_doc()⁠ method. Each document stays synced — receiving real-time updates from other peers and flushing local changes — for as long as the connection is open. Unlike sync_fetch(), which performs a one-off retrieval over a throwaway connection, several documents can share a single connection here.

Usage

sync_client(url, timeout = 5000L, tls = NULL, token = NULL, interval = 1000L)

Arguments

url

WebSocket URL of the sync server (e.g., "ws://localhost:3030/" or "wss://sync.automerge.org/"). Note: trailing slash may be required.

timeout

Timeout in milliseconds for each receive operation. Default 5000.

tls

(optional) for secure wss:// connections to servers with self-signed or custom CA certificates, a TLS configuration object created by nanonext::tls_config().

token

(optional) JWT (ID token) for authenticated servers. Sent as a Bearer token in the Authorization header of the WebSocket upgrade request.

interval

Interval in milliseconds for pushing local changes to the server. Default 1000. Uses later::later() to periodically check for and send local changes for every open document. This is a cheap no-op when there are no changes.

Details

Opening the connection performs a synchronous handshake before returning. ⁠$open_doc()⁠ then performs a synchronous initial sync, so the returned handle's ⁠$doc⁠ has meaningful content immediately. After that, incoming changes are received asynchronously via a self-chaining promise loop, and local changes are flushed periodically via a later::later() timer.

Neither close() flushes pending local changes. Call ⁠$push()⁠ first if you have unsynced edits — otherwise any changes made since the last sync-interval tick may be lost.

Live editing. A handle's ⁠$edit()⁠ opens its text object in a live CodeMirror editor (a React frontend rendered with shinyreact) that syncs both ways: as you type, the minimal diff is written to the live document and pushed (debounced); when the text changes remotely, the editor updates to the merged result. There is no Save button — every edit is applied live. It syncs whole-text snapshots, not granular operations, so a remote edit arriving in the brief window between a keystroke and its debounced push can be overwritten by that push; a smaller debounce narrows the window. The original's trailing-newline state is preserved.

Value

An environment of class "autosync_client" with reference semantics, representing the connection:

open_doc(doc_id, timeout)

Open a live document over this connection and return a autosync_doc handle for it (see below). Repeated calls for the same doc_id reuse the document already open on the connection rather than requesting it again.

close()

Disconnect and stop syncing all open documents.

active

Logical, whether the connection is active.

A autosync_doc handle returned by ⁠$open_doc()⁠ is itself an environment with:

doc

The live automerge document, kept in sync with the server.

push()

Push this document's local changes to the server immediately.

edit(at = "text", ext = NULL, debounce = 300L)

Open this document's text object at at in a live Shiny code editor that syncs both ways, blocking until closed (requires shiny and shinyreact). at is a key or character-vector path to an am_text object, ext (e.g. ".md") selects syntax highlighting, and debounce is the millisecond delay before pushing keystrokes. See Details.

close()

Stop syncing this one document (detach it from the connection); the connection and its other documents are unaffected.

active

Logical, whether the document is still open on an active connection.

Examples


server <- sync_server()
server$start()
doc_id <- create_document(server)

conn <- sync_client(server$url)
doc <- conn$open_doc(doc_id)
automerge::am_keys(doc$doc)

# Make local changes and push
automerge::am_put(doc$doc, automerge::AM_ROOT, "key", "value")
doc$push()

# Open another document over the same connection
other <- conn$open_doc(create_document(server))

# Disconnect (closes every document on the connection)
conn$close()
server$close()


Fetch a document from a sync server

Description

Connects to an automerge-repo sync server and retrieves a document by ID. This is useful for debugging sync issues or fetching documents from remote servers like sync.automerge.org.

Usage

sync_fetch(
  url,
  doc_id,
  timeout = 5000L,
  tls = NULL,
  token = NULL,
  verbose = FALSE
)

Arguments

url

WebSocket URL of the sync server (e.g., "ws://localhost:3030/" or "wss://sync.automerge.org/"). Note: trailing slash may be required.

doc_id

Document ID (base58check encoded string)

timeout

Timeout in milliseconds for each receive operation. Default 5000.

tls

(optional) for secure wss:// connections to servers with self-signed or custom CA certificates, a TLS configuration object created by nanonext::tls_config().

token

(optional) JWT (ID token) for authenticated servers. Sent as a Bearer token in the Authorization header of the WebSocket upgrade request.

verbose

Logical, print debug messages. Default FALSE.

Details

The function implements the automerge-repo sync protocol:

  1. Connects via WebSocket

  2. Sends join message with peer ID

  3. Receives peer response from server

  4. Sends sync request for the specified document

  5. Receives and applies sync messages until complete

Sync is considered complete when no new messages arrive within the timeout after at least one sync round.

Value

An automerge document object containing the fetched data.

Examples


# Fetch from public sync server
doc <- sync_fetch("wss://sync.automerge.org", "4F63WJPDzbHkkfKa66h1Qrr1sC5U")

# Fetch from local server with debug output
doc <- sync_fetch(server$url, "myDocId", verbose = TRUE)

# Fetch from server with self-signed certificate
cert <- nanonext::write_cert()
tls <- nanonext::tls_config(client = cert$client)
doc <- sync_fetch(server$url, "myDocId", tls = tls)

# Fetch from authenticated server
doc <- sync_fetch(
  "wss://secure.example.com",
  "myDocId",
  token = "eyJhbGciOi..."
)

# Inspect the document
automerge::am_keys(doc)


Open files from a project document

Description

Given a sync server URL and a project document ID, opens a persistent connection to the server, syncs the project document over it, and exposes its file tree for opening individual files. A project is an Automerge document with a files map whose keys are file paths and whose values are text objects holding each file's own document ID.

Usage

sync_project(
  url,
  proj_id,
  token = NULL,
  tls = NULL,
  timeout = 5000L,
  files_key = "files"
)

Arguments

url

WebSocket URL of the sync server (e.g., "ws://localhost:3030/" or "wss://sync.automerge.org/"). Note: trailing slash may be required.

proj_id

Document ID of the project.

token

(optional) JWT (ID token) for authenticated servers. Sent as a Bearer token in the Authorization header of the WebSocket upgrade request.

tls

(optional) for secure wss:// connections to servers with self-signed or custom CA certificates, a TLS configuration object created by nanonext::tls_config().

timeout

Timeout in milliseconds for each receive operation. Default 5000.

files_key

Key of the files map within the project document. Default "files".

Details

Opening a file syncs that file's document over the same connection rather than dialing the server again, so a session reuses a single WebSocket throughout. Call the opened file's ⁠$edit()⁠ method to edit it live, or use sync_app() for an interactive browser. Call ⁠$close()⁠ when finished to disconnect.

Value

An environment of class "autosync_project" (reference semantics) with the following fields and methods:

doc

The live project document, kept in sync with the server.

conn

The underlying sync_client() connection.

paths()

Current sorted file paths.

doc_id(path)

Resolve a path to its document ID.

open(path)

Open the file's document over the project connection and return its autosync_doc handle. Reuses the connection and any already-open document.

refresh()

Re-resolve the file tree to pick up added or removed files (the project document syncs live, so this just settles pending updates).

close()

Disconnect the project connection.

Examples


proj <- sync_project("wss://quarto-hub.com/ws", proj_id, token = sync_token())
proj                                   # prints the file tree
doc <- proj$open("/charlie/index.qmd") # open a file over the connection
doc$edit(at = "text", ext = ".qmd")    # edit it live
proj$close()                           # disconnect when finished


Create an Automerge sync server

Description

Creates a WebSocket server that implements the automerge-repo sync protocol, compatible with JavaScript, Rust, and other Automerge clients.

Usage

sync_server(
  port = 0L,
  host = "127.0.0.1",
  data_dir = tempfile("autosync"),
  auto_create_docs = TRUE,
  storage_id = NULL,
  tls = NULL,
  auth = NULL,
  share = NA
)

Arguments

port

Port to listen on. Default 0 (binds to a random available port). The actual URL is retrieved via server$url.

host

Host address to bind to. Default "127.0.0.1" (localhost).

data_dir

Directory for document storage. Defaults to a session-temporary directory, so documents do not persist across R sessions. Supply an explicit path to persist documents across sessions.

auto_create_docs

Logical, whether to auto-create documents when clients request unknown document IDs. Default TRUE.

storage_id

Optional storage ID for this server. If NULL (default), generates a new persistent identity. Set to NA for an ephemeral server (no persistence identity).

tls

(optional) for secure wss:// connections, a TLS configuration object created by nanonext::tls_config().

auth

Optional authentication configuration created by auth_config(). When provided, clients must include a valid JWT (ID token) as a Bearer token in the Authorization header of the WebSocket upgrade request. Connections without valid credentials are rejected immediately. Note: TLS is required when authentication is enabled to protect tokens.

share

Controls document sharing policy for connected clients. This unified parameter governs both proactive document announcement (pushing documents to clients) and access control (allowing clients to request documents). Accepts one of:

  • NA (default) — never announce but allow all requests.

  • TRUE — announce all documents to all clients and allow all requests.

  • FALSE — never announce and deny all requests (sends doc-unavailable).

  • A function with signature ⁠function(client_id, doc_id)⁠ returning TRUE (announce and allow), NA (allow on request only), or FALSE (deny access). Called per client and per document.

Details

The returned server inherits from nanonext's nanoServer class and provides ⁠$start()⁠ and ⁠$close()⁠ methods for non-blocking operation.

Value

An autosync_server object inheriting from 'nanoServer', with ⁠$start()⁠ and ⁠$close()⁠ methods.

Examples


# Create and start a server
server <- sync_server()
server$start()

# Server is now running in the background
# ...do other work...

# Stop when done
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$url
server$close()

# Server with OIDC authentication (requires TLS)
cert <- nanonext::write_cert()
tls <- nanonext::tls_config(server = cert$server)
server <- sync_server(
  tls = tls,
  auth = auth_config(
    client_id = "123456789.apps.googleusercontent.com",
    allowed_domains = "mycompany.com"
  )
)


Obtain an OIDC token interactively

Description

Performs the OAuth 2.0 Authorization Code flow with PKCE to obtain a JWT (ID token) from an OIDC provider, delegating the browser handshake and token exchange to httr2. Endpoints are discovered from the issuer's .well-known metadata via httr2::oauth_server_metadata(), and the flow is run by httr2::oauth_flow_auth_code(): it opens the system browser, listens on a loopback redirect for the callback, and returns the ID token for use with sync_fetch().

Usage

sync_token(
  client_id = Sys.getenv("OIDC_CLIENT_ID"),
  client_secret = Sys.getenv("OIDC_CLIENT_SECRET"),
  issuer = oidc_issuer(),
  scopes = "openid email",
  redirect_uri = oauth_redirect_uri()
)

Arguments

client_id

The OIDC client ID (application ID). Defaults to the OIDC_CLIENT_ID environment variable.

client_secret

The OIDC client secret. Required by Google (Desktop app) and "Web application" client types; leave unset for native / public clients, which authenticate via PKCE alone. Defaults to the OIDC_CLIENT_SECRET environment variable.

issuer

The OIDC issuer URL. Defaults to the OIDC_ISSUER environment variable, falling back to Google ("https://accounts.google.com").

scopes

Space-separated OAuth scopes to request. Default "openid email".

redirect_uri

Local redirect URI for the OAuth callback. Defaults to httr2::oauth_redirect_uri(), i.e. "http://localhost" with an OS-assigned random port (or the HTTR2_OAUTH_REDIRECT_URL environment variable on hosted platforms). Supply an explicit port (e.g. "http://localhost:8080") when your OIDC provider requires the redirect URI to match a pre-registered value.

Details

For Google, register the OAuth client as a "Desktop app" and set both OIDC_CLIENT_ID and OIDC_CLIENT_SECRET. Google's Desktop app secret is required in the token exchange but, unlike a "Web application" secret, is not treated as confidential: Google states that for installed apps "the client secret is obviously not treated as a secret" (https://developers.google.com/identity/protocols/oauth2#installed), consistent with the OAuth 2.0 for Native Apps standard (RFC 8252 section 8.5, https://datatracker.ietf.org/doc/html/rfc8252#section-8.5). Providers that support native / public clients (Microsoft Entra, Okta, Auth0, etc.) need only client_id, authenticating via PKCE alone; leave client_secret unset for these.

Value

A JWT (ID token) as a character string.

Examples


# Uses OIDC_CLIENT_ID and OIDC_CLIENT_SECRET env vars by default
token <- sync_token()

# Or supply credentials directly
token <- sync_token(
  client_id = "YOUR_CLIENT_ID.apps.googleusercontent.com",
  client_secret = "YOUR_CLIENT_SECRET"
)

# Use with sync_fetch
doc <- sync_fetch(server$url, "myDocId", token = token, tls = tls)


Validate a JWT token

Description

Verifies a JWT locally using the issuer's public keys (JWKS) and validates standard OIDC claims.

Usage

validate_token(
  token,
  issuer,
  client_id,
  allowed_emails = NULL,
  allowed_domains = NULL,
  custom_validator = NULL
)

Arguments

token

Character string, the JWT.

issuer

Expected issuer URL.

client_id

Expected audience (client ID).

allowed_emails

Character vector of allowed email addresses (optional).

allowed_domains

Character vector of allowed email domains (optional).

custom_validator

Function(claims) returning TRUE/FALSE (optional).

Value

List with valid (logical), email (character or NULL), error (character or NULL).