collaboration

the @cyxth/colab provides you with APIs to easily add real time collaboration to your application in minutes.

prerequisites

first make sure you have created a cyxth account and created a cyxth instance, and make sure to check out authorization and concepts.

next download @cyxth/core and @cyxth/colab packages

# cyxth core
npm install @cyxth/core
# cyxth colab
npm install @cyxth/colab
# add types 
npm install --save-dev @cyxth/types

colab also requires the colab-wasm package. you can download this from npm too and add it locally to your project or use the cdn to load it on connect

This guide gives a high level overview and colab concepts, feel free to check the colab api reference and colab examples after this guide. for a deeper understanding of various topics.

initialization

colab is a cyxth core plugin and is instantiated like the other plugins to get the colab use colab() function from cyxth core module.

import Cyxth from '@cyxth/core';
import Colab from '@cyxth/colab';

// initialize cyxth
const cyxth = new Cyxth("YOUR_APP_URL");

cyxth.register([
    Colab,
    // ... other modules
]);

// authorize user
await cyxth.connect(USER_TOKEN);

// use the colab wasm package from cdn
const wasmUrl = "https://cdn.cyxth.com/[email protected]";
const colab: Colab = await cyxth.colab(wasmUrl);

the user has to be connected to cyxth or else cyxth.colab() will fail.

channels

colab channels extends the functionality of cyxth channels. all actions that can be done on a cyxth channel can be done on colab channels. users can create() , join() , manage and configure channels. we went through this in concepts. check out channel concepts for more info.

colab channels adds the load() functionality to sync state for users who have been offline and have enabled offline storage functionality.

data types

cyxth colab uses CRDTs (Conflict free Replicated Data Types) under the hood. CRDTs are data types that allow for independent mutation of shared state across multiple users while automatically resolving any inconsistency that may occur during any change. although users may have different states at a given time they are always guranteed to eventually converge on the same state. you can read more about crdts here.

cyxth colab implements various CRDTs in the colab-wasm package allowing you to change any state to a collaborative state.

  • values - a simple value (number, string, boolean)
  • maps - key value pair
  • lists - an ordered sequence of elements
  • text - collaborative text editing type
  • counter - a simple counter with increment & decrement operations

values, maps and lists can be deeply nested to represent any state as a schemaless JSON document. Text nodes and counters are standalone crdts that can only exist on the top level.

Colab strictly requires that your top most data type is a map and other values, lists, maps, etc are nested within the top most map.

for example

{
    tasks: [
        {
            id: "task_id",
            title: "write the docs",
            done: false,
            tags: ["docs","colab"]
        },
        // ... other tasks
    ]
    // ... other key value pairs
}

the provided data types are schemaless (except text and counter) allowing the state to evolve with your application. a user can change a list to a map or value and vice versa

// state is schemaless
{ position: [23,56] }

{
    position: {
        x: 23,
        y: 56,
        z: 68.9
    }
}

lets go through the various operations supported by these data types in the change interface

change

the main way to interface with the underlying CRDT is through colab’s change() function. change() returns a ChangeContext which provides various methods to easily change state.

let ctx = colab.change("tasks");
// insert a task to task list

await ctx.getList().insert(0, {
    id: "task_id",
    title: "write the docs",
    done: false,
    tags: ["docs","colab"]
});

// update task title
await ctx.getList()
    .index(0)
    .update("title", "update the docs")

the change context methods are chained to update nested elements

here are all the change methods. with some examples on usage check the reference for more in depth info.

delete

delete element at key

await colab.change("elements rect-0").delete();

key

do changes in a nested map key. this returns change context to nest changes

await colab.change("elements").key("rect-0").delete()

update

update an element at key. if the element does not exist it will be created

await colab.change("elements rect-0").update({
    postion: {
        x: 78,
        y: 32
    },
    color: "#fff"
});

getList

returns a List for list specific changes, will create a list at key if the element did not exist. list methods are insert, push, pop, index and delete.

await ctx.getList().insert(0, {
    id: "task_id",
    title: "write the docs",
    done: false,
    tags: ["docs","colab"]
});

getText

returns Text for text specific changes. text provides two main methods insert(index, text) and delete(index, length) to easily manipulate collaborative text. methods in Text can not be nested.

let ctx = colab.change("doc");
// add text hello world
await ctx.getText().insert(0, "hello world");
// delete world
await ctx.getText().delete(6,5)

getCounter

returns Counter for counter specific changes. counter also has two methods increment and decrement.

let ctx = colab.change("clicks");
await ctx.getCounter().increment();

presence

Intent is key for a fluid collaboration experience, letting other users know which action a user is about to take immensly improves the overall user experience while making every thing predictable to users collaborating on the same piece of state.

examples include showing mouse cursor positions and object selections in a design app, user position in a text document or text selection

using the colab presence() method you can send any user intent to all connected users and listen for on presence event to handle it. You can take this a step further with cyxth calls voice chat or integrate chat for an even better experience.

here is an example from the tasks demo

// send task selection in the presence channel
colab.presence({
    ty: "task-selection",
    indices: [1,2,4]
});

// listen on the presence channel
colab.on("presence",(pr: PresenceData) => {
    let {data, userId} = pr;

    if (data.ty === "task-selection"){
        // ...show selected tasks by user in ui
    }
})

events

all events on cyxth channels such as user:join and user:leave are also available for cyxth colab channels. colab adds presence and change events. presence is for user intent sharing as discussed above and change is for the data changes in real time. you have to listen for change event to get collaboration going.

all local changes are instant and are shown to the user as they are applied. remote changes streaming in are applied to the local state and the change event is fired. users can be temporarily out of sync for example if a user goes offline or then etwork changes. since cyxth colab is based on CRDTs they will be eventually consitent when the user comes back online and state will be as if the user never left.

colab.on("change", (changeSet: ChangeSet) => {
    let {changes, userId} = changeSet;
    console.log(change.userId);
    changes.apply(localState);
    // ... update ui
})

check out ChangeSet for more info on the change event.