Docs
Getting started

Getting started

Technologies

Secsync is defining a contract between a central backend service and clients. There are helpers to setup secsync using the following technologies:

  • Client/server communication: Websocket
  • CRDT: Yjs or Automerge
  • Server language: JavaScript
  • Client rendering: React

Secsync is not bound to these technologies and all utitlies are exposed to implement different technologies. If you are looking for a different combination please reach out.

Installation

npm install secsync

Tutorial

Let's build an end-to-end encrypted to-do list.

We start from a Yjs To-Dos application that currently only manages a To-Do list locally in a Yjs document. The whole application is built in one component.

import React, { useRef, useState } from "react";
import { useY } from "react-yjs";
import * as Yjs from "yjs";
 
export const YjsUnsyncedTodosExample: React.FC = () => {
  // initialize Yjs document
  const yDocRef = useRef<Yjs.Doc>(new Yjs.Doc());
  // get/define the array in the Yjs document
  const yTodos: Yjs.Array<string> = yDocRef.current.getArray("todos");
  // the useY hook ensures React re-renders once
  // the array changes and returns the array
  const todos = useY(yTodos);
  // local state for the text of a new to-do
  const [newTodoText, setNewTodoText] = useState("");
 
  return (
    <>
      <div className="todoapp">
        <form
          onSubmit={(event) => {
            event.preventDefault();
            yTodos.push([newTodoText]);
            setNewTodoText("");
          }}
        >
          <input
            placeholder="What needs to be done?"
            onChange={(event) => setNewTodoText(event.target.value)}
            value={newTodoText}
            className="new-todo"
          />
          <button className="add">Add</button>
        </form>
 
        <ul className="todo-list">
          {todos.map((entry, index) => {
            return (
              <li key={`${index}-${entry}`}>
                <div className="edit">{entry}</div>
                <button
                  className="destroy"
                  onClick={() => {
                    yTodos.delete(index, 1);
                  }}
                />
              </li>
            );
          })}
        </ul>
      </div>
    </>
  );
};

You can try it out here:

Example App

    Setup useYjsSync

    Now let's setup the end-to-end encrypted data sync. For now we will use the backend service used by the documentation.

    const websocketEndpoint = "wss://secsync.fly.dev";

    Next we want to setup the keys. While in Secsync every Snapshot (and related Updates and EphemeralMessages) can each have their own encryption keys we use a stable documentKey. In addition we create signing keys for the client. In this tutorial we won't validate them and therefor we can generate some.

    import sodium, { KeyPair } from "libsodium-wrappers";
     
    export const YjsTodosExample: React.FC = () => {
      // Can be created using sodium.randombytes_buf(sodium.crypto_aead_chacha20poly1305_IETF_KEYBYTES)
      // Sodium is only used inside the component since sodium can only be used after sodium.ready
      // resolved which is recommended to be checked before even mounting this component.
      const documentKey = sodium.from_base64(
        "MTcyipWZ6Kiibd5fATw55i9wyEU7KbdDoTE_MRgDR98"
      );
     
      const [authorKeyPair] = useState<KeyPair>(() => {
        return sodium.crypto_sign_keypair();
      });
    
    };

    Next up we adding the useYjsSync hook. Each parameter is explained by it's code comment.

    const [state, send] = useYjsSync({
      // The Yjs document
      yDoc: yDocRef.current,
      // A unique ID identifying the document
      documentId,
      // The current client's signing keyPair
      signatureKeyPair: authorKeyPair,
      // The backend service to connect to
      websocketEndpoint,
      // Used to authenticate the client with the server (not checked by the documentation backend)
      websocketSessionKey: "your-secret-session-key",
      // Callback to return the key for the Snapshot. In this case we are going to use
      // one documentKey for all Snapshots.
      getSnapshotKey: async (snapshotInfo) => {
        return documentKey;
      },
      // Callback invoked to return the necessary values to create a Snapshot.
      getNewSnapshotData: async ({ id }) => {
        return {
          // A Snapshot of the CRDT data
          data: Yjs.encodeStateAsUpdateV2(yDocRef.current),
          // Encryption key for the snapshot
          key: documentKey,
          // Custom data that will not be encrypted, but cryptographically attached
          // to the Snapshot as public data. In cryptography also referred to as
          // additional authenticated data (AAD).
          publicData: {},
        };
      },
      // Callback invoked for ever snapshot, update, ephemeralMessage to validate if
      // it was signed by a valid client for this document. While the encryption key
      // is securing confidentiality this can be used to verify the author and possibly
      // reject changes from clients that are not valid anymore e.g. a removed member.
      isValidClient: async (signingPublicKey: string) => {
        return true;
      },
      // The libsodium-wrappers API implementation. This design was chosen to allow to pass in
      // other implementations e.g. react-native-libsodium for ReactNative
      sodium,
    });

    You can try it out here by adding To-Dos and you they will load if you refresh the page since the documentId is part of the URL. Once you refresh the last snapshot with all related updates will be downloaded.

    Add Secsync DevTools

    In order to make development more transparent we created a DevTool component visualizing relevant information from the document.

    import { DevTool } from "secsync-react-devtool";
    // `state` and `send` from the useYjsSync hook
    <DevTool state={state} send={send} />

    Example App

    Setup your own server

    In order to setup your own server please refer to the server setup documentation.