Use Matrix as a backend for local-first applications with the Matrix-CRDT Yjs provider.

Overview

Matrix CRDT

Discord Matrix

npm version Coverage Status

Matrix-CRDT enables you to use Matrix as a backend for distributed, real-time collaborative web applications that sync automatically.

The MatrixProvider is a sync provider for Yjs, a proven, high performance CRDT implementation.

TL;DR

Create apps like this:

screencapture

And connect Matrix as transport + backend storage. Instead of chat messages (primary use-case of Matrix), we send an event stream of data model updates (for the rich-text demo, these are document edits) to Matrix.

Live demo

In the examples directory, you'll find some live examples:

Motivation

CRDTs (Conflict-free Replicated Data Types) make it easy to build decentralized, fast, collaborative local-first applications.

Read more about the benefits of Local-first software in this essay

When building local-first software on top of CRDTs, you probably still need a backend so users can access their data across devices and collaborate with each other.

While Matrix is primarily designed for messaging (chat), it's versatile enough to use as a backend for collaborative applications (see Architecture). The idea is that by building on top of Matrix, developers can focus on building clients and get the following benefits from the Matrix ecosystem out-of-the-box:

  • An open standard and active community
  • Multiple server implementations (including hosted servers)
  • Authentication (including support for SSO and 3rd party providers)
  • Access control via Rooms and Spaces
  • E2EE
  • A decentralized architecture with support for federation

Usage

Matrix-CRDT currently works with Yjs or SyncedStore.

Usage with Yjs

To setup Matrix-CRDT, 3 steps are needed:

  • Create a Yjs Y.Doc
  • Create and authenticate a client from matrix-js-sdk
  • Create and initialize your Matrix-CRDT MatrixProvider
import { MatrixProvider } from "matrix-crdt";
import * as Y from "yjs";
import sdk from "matrix-js-sdk";

// See https://matrix.org/docs/guides/usage-of-the-matrix-js-sdk
// for login methods
const matrixClient = sdk.createClient({
  baseUrl: "https://matrix.org",
  accessToken: "....MDAxM2lkZW50aWZpZXIga2V5CjAwMTBjaWQgZ2Vu....",
  userId: "@USERID:matrix.org",
});

// Create a new Y.Doc and connect the MatrixProvider
const ydoc = new Y.Doc();
const provider = new MatrixProvider(ydoc, matrixClient, {
  type: "alias",
  alias: "matrix-room-alias",
});
provider.initialize();

// array of numbers which produce a sum
const yarray = ydoc.getArray("count");

// observe changes of the sum
yarray.observe((event) => {
  // print updates when the data changes
  console.log("new sum: " + yarray.toArray().reduce((a, b) => a + b));
});

// add 1 to the sum
yarray.push([1]); // => "new sum: 1"

SyncedStore

You can also use SyncedStore and use Matrix-CRDT as SyncProvider.

API

new MatrixProvider (doc, matrixClient, room, awareness?, opts?): MatrixProvider

The MatrixProvider syncs a Matrix room with a Yjs document.

Parameters

Name Type Description
doc Y.Doc The Y.Doc to sync over the Matrix room.
matrixClient MatrixClient A matrix-js-sdk client with permissions to read (and/or write) from the room.
room { type: "id"; id: string; } or { type: "alias"; alias: string } The room ID or Alias to sync with.
awareness (optional) awarenessProtocol.Awareness A y-protocols Awareness instance that can be used to sync "awareness" data over the experimental webrtc bridge.
opts (optional) MatrixProviderOptions (see below) Configure advanced properties, see below.

MatrixProviderOptions

Additional configuration options that can be passed to the MatrixProvider constructor.

Defaults to:

{
  // Options for `ThrottledMatrixWriter`
  writer: {
    // throttle flushing write events to matrix by 500ms
    flushInterval: number = 500,
    // if writing to the room fails, wait 30 seconds before retrying
    retryIfForbiddenInterval: number = 30000
  },
  // Options for `MatrixCRDTEventTranslator`
  translator: {
    // set to true to send everything encapsulated in a m.room.message,
    // so you can view and debug messages easily in element or other matrix clients
    updatesAsRegularMessages: false,
    // The event type to use for updates
    updateEventType: "matrix-crdt.doc_update",
    // The event type to use for snapshots
    snapshotEventType: "matrix-crdt.doc_snapshot",
  }
  // Experimental; we can use WebRTC to sync updates instantly over WebRTC.
  // See SignedWebrtcProvider.ts for more details + motivation
  enableExperimentalWebrtcSync: boolean = false
  // Options for MatrixReader
  reader: {
    // How often to send a summary snapshot (defaults to once every 30 events)
    snapshotInterval: number = 30,
  },
}

Architecture

CRDT updates (in our case, Yjs document updates) are very similar to (chat) Messages, that Matrix has been optimized for.

Matrix-CRDT bridges Yjs documents to Matrix Rooms. and Yjs updates to Matrix events (regular chat messages are also a specific event type in Matrix). Yjs document updates are sent as base64-encoded events to the Matrix room.

When registering a MatrixProvider, we:

  • Listen to new matrix-crdt.doc_update events in the Matrix Room, and apply updates to the Yjs document.
  • Listen to Yjs document updates and send these to the Matrix room as matrix-crdt.doc_update events.

CRDTs are specifically designed to be eventually consistent. This means that the state of your data is eventually reconciled, regardless of the order of update events that reach each client or server (as long as you eventually get all updates).

This makes it possible to work offline, or for servers / clients to be out of sync for a while.

Snapshots

To reconstruct your application state (that is, the Yjs document), we eventually need to access all previous events. When there have been a lot of updates, it would be inefficient to read the entire document / room history from Matrix.

Matrix-CRDT sends periodic snapshots that contain a summary of all previous events. When retrieving a snapshot (stored as a Matrix event with type matrix-crdt.doc_snapshot), clients can reconstruct application state from that snapshot and don't need to fetch events occuring before that snapshots last_event_id (stored on the event).

WebRTC (experimental)

Matrix-CRDT by default throttles sent events every 500ms (for example, to prevent sending an event every keystroke when building a rich text editor). It also does not support Yjs Awareness updates (for presence information, etc) over Matrix.

You can use the (experimental) WebRTC provider to connect to peers over WebRTC and send updates (regular and Awareness updates) instantly.

Ideally, we'd replace this with Matrix Custom Ephemeral events when that Spec has landed.

Development

See CONTRIBUTING.md for instructions how to work with this repo (e.g.: installing and building using lerna).

Comments
  • Buffer is not defined error

    Buffer is not defined error

    "When I run the "todo-simple-react" example and try to add a todo, I get the following error.

    This may be related to this. https://github.com/webpack/changelog-v5/issues/10

    image
    opened by ysds 3
  • Any room that isn't `matrix-crdt-test` doesn't seem to connect

    Any room that isn't `matrix-crdt-test` doesn't seem to connect

    When connecting to a room that isn't matrix-crdt-test initialization never completes and requests keep being sent to messages api.

    Steps to reproduce:

    1. Connect to room that isn't prefixed with matrix-crdt-test
    2. Open Network Tab
    3. See endless requests to messages api

    Loom walkthrough -> https://www.loom.com/share/ce12cdbecf494c4d987449e77dc7b62f

    opened by juliankrispel 3
  • remove require and use es6 import instead

    remove require and use es6 import instead

    Hi, thank you for making this library! Please consider this PR to make it easier to use it in environments that do not support require (such as Vite).

    Thank you again for building this – I'm working on a little project on top of this and getting to use Matrix as a backend and user provider is wonderful!

    opened by shuesken 3
  • test-server error

    test-server error

    hello, nice project, bravo !!! but running docke-compose up in the test-server folder give me

    synapse    | Traceback (most recent call last):
    synapse    |   File "/usr/local/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    synapse    |     return _run_code(code, main_globals, None,
    synapse    |   File "/usr/local/lib/python3.8/runpy.py", line 87, in _run_code
    synapse    |     exec(code, run_globals)
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/app/homeserver.py", line 472, in <module>
    synapse    |     main()
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/app/homeserver.py", line 462, in main
    synapse    |     hs = setup(sys.argv[1:])
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/app/homeserver.py", line 355, in setup
    synapse    |     config = HomeServerConfig.load_or_generate_config(
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/config/_base.py", line 697, in load_or_generate_config
    synapse    |     obj.parse_config_dict(
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/config/_base.py", line 721, in parse_config_dict
    synapse    |     self.invoke_all(
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/config/_base.py", line 347, in invoke_all
    synapse    |     res[config_class.section] = getattr(config, func_name)(*args, **kwargs)
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/config/repository.py", line 120, in read_config
    synapse    |     self.media_store_path = self.ensure_directory(
    synapse    |   File "/usr/local/lib/python3.8/site-packages/synapse/config/_base.py", line 199, in ensure_directory
    synapse    |     os.makedirs(dir_path, exist_ok=True)
    synapse    |   File "/usr/local/lib/python3.8/os.py", line 223, in makedirs
    synapse    |     mkdir(name, mode)
    synapse    | PermissionError: [Errno 13] Permission denied: '/data/media_store'
    synapse exited with code 1
    

    any idea ?

    opened by scenaristeur 2
  • Cannot read properties of undefined (reading 'lazyLoadMembers'

    Cannot read properties of undefined (reading 'lazyLoadMembers'

    I can send messages but not receive events. Getting this error matrix:initialize err TypeError: Cannot read properties of undefined (reading 'lazyLoadMembers') at MatrixClient.createMessagesRequest (client.js:4501:1) at MatrixReader. (MatrixReader.js:206:1) at Generator.next () at MatrixReader.js:29:1 at new Promise () at push../node_modules/matrix-crdt/dist/reader/MatrixReader.js.__awaiter (MatrixReader.js:8:1) at MatrixReader.getInitialDocumentUpdateEvents (MatrixReader.js:199:1) at MatrixProvider. (MatrixProvider.js:320:1) at Generator.next () at MatrixProvider.js:31:1 at new Promise () at push../node_modules/matrix-crdt/dist/MatrixProvider.js.__awaiter (MatrixProvider.js:10:1) at MatrixProvider.initializeReader (MatrixProvider.js:307:1) at MatrixProvider. (MatrixProvider.js:270:1) at Generator.next () at fulfilled (MatrixProvider.js:13:1)

    const matrixClient = sdk.createClient({ baseUrl: "https://matrix.org", accessToken: "xxxxxxx", userId: "@xxxxx", lazyLoadMembers: false });

    const mprovider = new MatrixProvider( YSDoc as Y.Doc, matrixClient, { type: "alias", alias: "#xxxxxxxx" }, undefined, { translator: { updatesAsRegularMessages: true }, reader: { snapshotInterval: 10 }, writer: { flushInterval: 500 }, } ); console.log("matrix:loading"); mprovider.initialize().then(()=>{ console.log("matrix:initialize done"); }).catch((e)=>{ console.error("matrix:initialize err",e); })

    opened by paul1868 1
  • Error sending event M_LIMIT_EXCEEDED: Too Many Requests

    Error sending event M_LIMIT_EXCEEDED: Too Many Requests

    I am trying to use Matrix-CRDT to develop a collaboration tool like an online whiteboard. I'm not sure this is a Matrix-CRDT issue or not, but very frequent updates cause an M_LIMIT_EXCEEDEDED error. Is there any way to prevent this?

    image

    Reproduction procedure:

    1. Go to https://klwxt.csb.app/
    2. Connect to any Matrix Room
    3. Create a large number of ToDos very quickly
    opened by ysds 0
  • wip: e2ee

    wip: e2ee

    This PR adds support for private rooms and end to end encryption (e2ee).

    • added private rooms
    • added support for startClient / olm in tests
    • cleaned up unit tests + added unit tests for encrypted / private rooms

    In this PR, we move from a per-room polling based approach, to using the sync API. We are now calling startClient which causes matrix-js-sdk to call the Matrix sync API and keeps all rooms in sync. This is necessary because matrix-js-sdk takes care of crypto events and key sharing, so we can easily send / receive encrypted messages by reusing the SDK.

    (Before, we pretty much only used the matrix-js-sdk as a wrapper to the Matrix HS APIs, not to keep track of any state like rooms, keys, etc.)

    This move has a few downsides (which is why we initially went for the per-room polling approach):

    • matrix-js-sdk keeps events in memory when we don't need them anymore
    • matrix-js-sdk syncs ALL rooms a user has joined. With the previous poll-based approach, Matrix-CRDT would only sync rooms we're currently interested in. This should be improved once Matrix releases Sync v3 / Sliding Sync

    Migrating to startClient brings a few extra todos:

    • [ ] Actually disable polling as we now get messages received by its sync calls in matrixRoomListener instead (we now use both methods at the same time)
    • [ ] Prevent Matrix-js-sdk from keeping all updates in memory. After applying the updates to the Y.Doc, we don't need the event anymore.
    • [ ] Sync only rooms we're interested in, when Sliding Sync is released
    • [ ] (minor) Migrate away from MatrixMemberReader as we can now use maySendEvent of matrix-js-sdk

    Other open todos

    • [ ] Private rooms are now readable + writable by anyone who is in the room. Add support for private read-only rooms (using power levels)
    • [ ] (minor) implement getMatrixRoomSecurity and updateMatrixRoomAccess for private rooms
    • [ ] "late-joiner"; how can we make it possible for new users to decrypt an existing document? They won't have the keys to decrypt older events (https://github.com/vector-im/element-web/issues/2996 might be relevant)
    • [ ] Furthermore, having support for e2ee is nice, but creating a client on top of matrix-js-sdk that supports all UI flows required (cross signing / backups / key management) is quite cumbersome. This should be made easier before we can expect people to actually start using this
    opened by YousefED 1
  • Matrix events have a size limit; may interact badly with snapshots

    Matrix events have a size limit; may interact badly with snapshots

    The title describes it all really, but Matrix events have a 65536 byte size limit (with the server-side attributes included), so this currently places an upper bound on the size of a snapshot (and thus presumably on the size of a document?) as they are encoded as events.

    Do you have anything in mind to work around this; e.g. encoding snapshots as (encrypted) media?

    opened by reivilibre 3
Owner
Yousef
After cofounding Relive (www.relive.cc), now exploring future of programming concepts and local-first software dev. Stay updated on www.typecell.org
Yousef
A matrix bot to monitor and respond to investment scam spamming across the matrix platform, for example in rooms with a permanently offline admin.

Spam Police A matrix bot to monitor and respond to investment scam spamming across the matrix platform, for example in rooms with a permanently offlin

jjj333_p 7 Dec 26, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
A tiny CRDT implementation in Javascript.

Tiny Merge A tiny CRDT implemented in Javascript. The philosophy behind Tiny Merge is to strategically reduce the functionality of CRDT's in favour of

James Addison 10 Dec 2, 2022
A tiny CRDT library.

A tiny CRDT library Manages state synchronisation. License MIT License Copyright (c) 2022 James Addison Permission is hereby granted, free of charge,

James Addison 4 Oct 29, 2022
Syncronize a YJS document to/from a plain old javascript object

y-pojo Syncronize a YJS document to/from a plain old javascript object This library enables multiple users to share state in the form of a Plain ol' J

null 17 Nov 12, 2022
Socket IO Connector for Yjs (Inspired by y-websocket)

Welcome to y-socket.io ?? Socket IO Connector for Yjs (Inspired by y-websocket) Y-socket.io is a YJS document synchronization implementation over the

Iván Topp Sandoval 18 Dec 21, 2022
A high-resolution local database that uses precise algorithms to easily record data in local files within a project with persistent JSON and YAML support designed to be easy to set up and use

About A high-resolution local database that uses precise algorithms to easily record data in local files within a project with persistent JSON and YML

Shuruhatik 5 Dec 28, 2022
Web3.js provider to interact with the VeChain Thor protocol

web3-providers-connex Web3.js provider implemented using Connex.js. It makes it possible to use web3.js and ethers.js to interact with VeChain Thor pr

null 13 Dec 26, 2022
🌱 Ethereum provider solution for Dapp&Wallets, 🏷 If you have good suggestions, please submit issues

English | 简体中文 | 日本 ETH Wallet Modal An Ethereum Provider Solution for Integrated Wallets and Dapps ⚠️ Notice If you need to reduce unnecessary import

Dan Xu 35 Dec 19, 2022
☁️ Application using Node.js, AdonisJs, Adonis ACL, Adonis Kue Provider, Adonis Mail, Adonis Lucid Slugify, Adonis Validator, AdonisJs Redis, ESLint and pg

Node.js - SaaS ☁️ Application using Node.js, AdonisJs, Adonis ACL, Adonis Kue Provider, Adonis Mail, Adonis Lucid Slugify, Adonis Validator, AdonisJs

null 4 Aug 19, 2022
Cross provider map drawing library, supporting Mapbox, Google Maps and Leaflet out the box

Terra Draw Frictionless map drawing across mapping providers. TerraDraw centralises map drawing logic and provides a host of out the box drawing modes

James Milner 106 Dec 31, 2022
Mercure Provider - Real-time Made Easy

@setten/mercure is a Mercure client for AdonisJS. Mercure allows you to use Server Sent Events to push data to your clients using Http. Note You must

Setten 32 Nov 29, 2022
📊🌍 Super small, light, privacy-focused, self-hostable web statistics provider

femtostats Update: It turns out Fathom Lite does everything I wanted to do except custom events for free, so I'm going to stop working on this for now

Ian Langworth ☠ 12 Nov 17, 2022
A local-first personal finance system

Note from maintainer: don't expect responses or PR merges until May 16th. ??️ I (@jlongster) am currently away on vacation and not checking this. I am

null 5.7k Dec 31, 2022
Actual, a local-first personal finance tool

This is the main project to run Actual, a local-first personal finance tool. It comes with the latest version of Actual, and a server to persist chang

null 1.3k Jan 3, 2023
Gatsby-Formik-contact-form-with-backend-panel - Full working contact form with backend GUI panel.

Gatsby minimal starter ?? Quick start Create a Gatsby site. Use the Gatsby CLI to create a new site, specifying the minimal starter. # create a new Ga

Bart 1 Jan 2, 2022
Venni backend - The backend of the Venni client apps implementing the credit card payments, matching algorithms, bank transfers, trip rating system, and more.

Cloud Functions Description This repository contains the cloud functions used in the Firebase backend of the Venni apps. Local Development Setup For t

Abrantes 1 Jan 3, 2022