open-collaboration-yjs
Version:
Open Collaboration Yjs integration, part of the Open Collaboration Tools project
98 lines • 4.54 kB
JavaScript
// ******************************************************************************
// Copyright 2024 TypeFox GmbH
// This program and the accompanying materials are made available under the
// terms of the MIT License, which is available in the project root.
// ******************************************************************************
import * as syncProtocol from 'y-protocols/sync';
import * as awarenessProtocol from 'y-protocols/awareness';
import * as encoding from 'lib0/encoding';
import * as decoding from 'lib0/decoding';
import { ObservableV2 } from 'lib0/observable';
export const LOCAL_ORIGIN = 'local';
export class OpenCollaborationYjsProvider extends ObservableV2 {
connection;
doc;
awareness;
constructor(connection, doc, awareness, options) {
super();
this.connection = connection;
this.doc = doc;
this.awareness = awareness;
this.doc.on('update', this.yjsUpdateHandler.bind(this));
this.awareness.on('update', this.yjsAwarenessUpdateHandler.bind(this));
connection.sync.onDataUpdate(this.ocpDataUpdateHandler.bind(this));
connection.sync.onAwarenessUpdate(this.ocpAwarenessUpdateHandler.bind(this));
connection.sync.onAwarenessQuery(this.ocpAwarenessQueryHandler.bind(this));
if (options?.resyncTimer && options.resyncTimer > 0) {
this.setResyncInterval(options.resyncTimer);
}
}
setResyncInterval(timeout) {
const interval = setInterval(() => {
const encoder = encoding.createEncoder();
syncProtocol.writeSyncStep1(encoder, this.doc);
this.connection.sync.dataUpdate(this.encode(encoder));
}, timeout);
this.doc.on('destroy', () => {
clearInterval(interval);
});
}
ocpDataUpdateHandler(origin, update) {
const decoder = this.decode(update);
const encoder = encoding.createEncoder();
const syncMessageType = syncProtocol.readSyncMessage(decoder, encoder, this.doc, origin);
if (syncMessageType === syncProtocol.messageYjsSyncStep1) {
this.connection.sync.dataUpdate(origin, this.encode(encoder));
}
}
ocpAwarenessUpdateHandler(origin, update) {
const decoder = this.decode(update);
awarenessProtocol.applyAwarenessUpdate(this.awareness, decoding.readVarUint8Array(decoder), origin);
}
ocpAwarenessQueryHandler(origin) {
const encoder = encoding.createEncoder();
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, Array.from(this.awareness.getStates().keys())));
this.connection.sync.awarenessUpdate(origin, this.encode(encoder));
}
yjsUpdateHandler(update, origin) {
if (origin !== this) {
const encoder = encoding.createEncoder();
syncProtocol.writeUpdate(encoder, update);
this.connection.sync.dataUpdate(this.encode(encoder));
}
}
yjsAwarenessUpdateHandler(changed) {
const changedClients = changed.added.concat(changed.updated).concat(changed.removed);
const encoder = encoding.createEncoder();
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients));
this.connection.sync.awarenessUpdate(this.encode(encoder));
}
connect() {
// write sync step 1
const encoderSync = encoding.createEncoder();
syncProtocol.writeSyncStep1(encoderSync, this.doc);
this.connection.sync.dataUpdate(this.encode(encoderSync));
// broadcast local state
const encoderState = encoding.createEncoder();
syncProtocol.writeSyncStep2(encoderState, this.doc);
this.connection.sync.dataUpdate(this.encode(encoderState));
// query awareness info
this.connection.sync.awarenessQuery();
// broadcast local awareness info
const encoderAwareness = encoding.createEncoder();
encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
this.doc.clientID
]));
this.connection.sync.awarenessUpdate(this.encode(encoderAwareness));
}
dispose() {
awarenessProtocol.removeAwarenessStates(this.awareness, [this.doc.clientID], 'client disconnected');
}
encode(encoder) {
return encoding.toUint8Array(encoder);
}
decode(data) {
return decoding.createDecoder(data);
}
}
//# sourceMappingURL=yjs-provider.js.map