|
|
|
@ -1,6 +1,12 @@
|
|
|
|
|
import { ChangeSet, Text } from '@codemirror/state';
|
|
|
|
|
import {EditorView} from "codemirror"
|
|
|
|
|
import { Update, collab, getSyncedVersion, receiveUpdates, sendableUpdates } from '@codemirror/collab';
|
|
|
|
|
import { EditorView } from 'codemirror';
|
|
|
|
|
import {
|
|
|
|
|
Update,
|
|
|
|
|
collab,
|
|
|
|
|
getSyncedVersion,
|
|
|
|
|
receiveUpdates,
|
|
|
|
|
sendableUpdates,
|
|
|
|
|
} from '@codemirror/collab';
|
|
|
|
|
import { ViewPlugin, ViewUpdate } from '@codemirror/view';
|
|
|
|
|
|
|
|
|
|
export class Connection {
|
|
|
|
@ -15,18 +21,23 @@ export class Connection {
|
|
|
|
|
if (resolve) {
|
|
|
|
|
resolve(response.payload);
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Received response for unknown or already used request', response._request);
|
|
|
|
|
console.error(
|
|
|
|
|
'Received response for unknown or already used request',
|
|
|
|
|
response._request
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Received invalid response', response._request);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
request(body: Record<string, unknown>): Promise<any> {
|
|
|
|
|
body['_request'] = this.requestId;
|
|
|
|
|
this.client.send(JSON.stringify(body));
|
|
|
|
|
return new Promise((resolve) => this.resolves[this.requestId++] = resolve);
|
|
|
|
|
return new Promise(
|
|
|
|
|
(resolve) => (this.resolves[this.requestId++] = resolve)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -36,69 +47,74 @@ function pushUpdates(
|
|
|
|
|
fullUpdates: readonly Update[]
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
// Strip off transaction data
|
|
|
|
|
let updates = fullUpdates.map(u => ({
|
|
|
|
|
let updates = fullUpdates.map((u) => ({
|
|
|
|
|
clientID: u.clientID,
|
|
|
|
|
changes: u.changes.toJSON()
|
|
|
|
|
changes: u.changes.toJSON(),
|
|
|
|
|
}));
|
|
|
|
|
return connection.request({type: "pushUpdates", version, updates});
|
|
|
|
|
return connection.request({ type: 'pushUpdates', version, updates });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pullUpdates(
|
|
|
|
|
connection: Connection,
|
|
|
|
|
version: number
|
|
|
|
|
): Promise<readonly Update[]> {
|
|
|
|
|
|
|
|
|
|
return connection.request({type: "pullUpdates", version})
|
|
|
|
|
.then(updates => updates.map((u: any) => ({
|
|
|
|
|
return connection.request({ type: 'pullUpdates', version }).then((updates) =>
|
|
|
|
|
updates.map((u: any) => ({
|
|
|
|
|
changes: ChangeSet.fromJSON(u.changes),
|
|
|
|
|
clientID: u.clientID
|
|
|
|
|
})));
|
|
|
|
|
clientID: u.clientID,
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getDocument(
|
|
|
|
|
connection: Connection
|
|
|
|
|
): Promise<{version: number, doc: Text}> {
|
|
|
|
|
|
|
|
|
|
return connection.request({ type: "getDocument" }).then(data => ({
|
|
|
|
|
): Promise<{ version: number; doc: Text }> {
|
|
|
|
|
return connection.request({ type: 'getDocument' }).then((data) => ({
|
|
|
|
|
version: data.version,
|
|
|
|
|
doc: Text.of(data.doc.split("\n"))
|
|
|
|
|
doc: Text.of(data.doc.split('\n')),
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function peerExtension(startVersion: number, connection: Connection) {
|
|
|
|
|
let plugin = ViewPlugin.fromClass(class {
|
|
|
|
|
private pushing = false
|
|
|
|
|
private done = false
|
|
|
|
|
let plugin = ViewPlugin.fromClass(
|
|
|
|
|
class {
|
|
|
|
|
private pushing = false;
|
|
|
|
|
private done = false;
|
|
|
|
|
|
|
|
|
|
constructor(private view: EditorView) { this.pull() }
|
|
|
|
|
constructor(private view: EditorView) {
|
|
|
|
|
this.pull();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update(update: ViewUpdate) {
|
|
|
|
|
if (update.docChanged) this.push()
|
|
|
|
|
}
|
|
|
|
|
update(update: ViewUpdate) {
|
|
|
|
|
if (update.docChanged) this.push();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async push() {
|
|
|
|
|
let updates = sendableUpdates(this.view.state)
|
|
|
|
|
async push() {
|
|
|
|
|
let updates = sendableUpdates(this.view.state);
|
|
|
|
|
|
|
|
|
|
if (this.pushing || !updates.length) return
|
|
|
|
|
this.pushing = true
|
|
|
|
|
let version = getSyncedVersion(this.view.state)
|
|
|
|
|
await pushUpdates(connection, version, updates)
|
|
|
|
|
this.pushing = false
|
|
|
|
|
// Regardless of whether the push failed or new updates came in
|
|
|
|
|
// while it was running, try again if there's updates remaining
|
|
|
|
|
if (sendableUpdates(this.view.state).length)
|
|
|
|
|
setTimeout(() => this.push(), 100)
|
|
|
|
|
}
|
|
|
|
|
if (this.pushing || !updates.length) return;
|
|
|
|
|
this.pushing = true;
|
|
|
|
|
let version = getSyncedVersion(this.view.state);
|
|
|
|
|
await pushUpdates(connection, version, updates);
|
|
|
|
|
this.pushing = false;
|
|
|
|
|
// Regardless of whether the push failed or new updates came in
|
|
|
|
|
// while it was running, try again if there's updates remaining
|
|
|
|
|
if (sendableUpdates(this.view.state).length)
|
|
|
|
|
setTimeout(() => this.push(), 100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async pull() {
|
|
|
|
|
while (!this.done) {
|
|
|
|
|
let version = getSyncedVersion(this.view.state)
|
|
|
|
|
let updates = await pullUpdates(connection, version)
|
|
|
|
|
this.view.dispatch(receiveUpdates(this.view.state, updates))
|
|
|
|
|
async pull() {
|
|
|
|
|
while (!this.done) {
|
|
|
|
|
let version = getSyncedVersion(this.view.state);
|
|
|
|
|
let updates = await pullUpdates(connection, version);
|
|
|
|
|
this.view.dispatch(receiveUpdates(this.view.state, updates));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroy() { this.done = true }
|
|
|
|
|
})
|
|
|
|
|
return [collab({startVersion}), plugin]
|
|
|
|
|
destroy() {
|
|
|
|
|
this.done = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return [collab({ startVersion }), plugin];
|
|
|
|
|
}
|