Spaces:
Sleeping
Sleeping
import {EventEmitter} from "events"; | |
import sha1 from "sha1"; | |
import * as diff from "diff"; | |
export default class RemoteFile extends EventEmitter { | |
autoReconnect: boolean; | |
socket: WebSocket; | |
connected: boolean = false; | |
filePath: string; | |
timestamp: number; | |
_content: string; | |
constructor ({autoReconnect = false} = {}) { | |
super(); | |
this.autoReconnect = autoReconnect; | |
} | |
get hash (): string { | |
return sha1(this._content) as string; | |
} | |
get content (): string { | |
return this._content; | |
} | |
set content (value: string) { | |
const timestamp = Date.now(); | |
const patch = diff.createPatch(this.filePath, this._content, value); | |
this.socket.send(JSON.stringify({ | |
command: "increase", | |
timestamp, | |
fromHash: this.hash, | |
toHash: sha1(value), | |
patch, | |
})); | |
this.timestamp = timestamp; | |
this._content = value; | |
} | |
connect (host: string, filePath: string) { | |
if (this.socket) | |
this.socket.close(); | |
this.socket = new WebSocket(host, "editor-frontend"); | |
this.socket.onopen = () => { | |
console.debug("[RemoteFile] socket open."); | |
this.connected = true; | |
this.emit("connected"); | |
this.socket.send(JSON.stringify({ command: "bindFile", filePath })); | |
}; | |
this.socket.onclose = event => { | |
console.debug("Synchronizer service socket closed:", event.code, event.reason); | |
this.connected = false; | |
this.emit("disconnected"); | |
if (this.autoReconnect && event.code === 1006) { | |
console.log("[RemoteFile] try to reconnect..."); | |
setTimeout(() => this.connect(host, filePath), 100); | |
} | |
}; | |
this.socket.onmessage = event => { | |
const message = JSON.parse(event.data); | |
switch (message.command) { | |
case "failure": | |
console.warn("service failure:", message.description); | |
this.close(); | |
break; | |
case "fullSync": | |
this.timestamp = message.timestamp; | |
this._content = message.content; | |
console.assert(this.hash === message.hash, "[RemoteFile] verify failed:", this.hash, message.hash); | |
this.emit("sync", {timestamp: this.timestamp}); | |
break; | |
case "increase": | |
//console.log("increase:", this.hash, message); | |
// already consistent with remote, update timestemp only | |
if (this.hash === message.toHash) { | |
this.timestamp = Math.max(this.timestamp, message.timestamp); | |
break; | |
} | |
if (this.hash !== message.fromHash) { | |
if (message.timestamp < this.timestamp) | |
break; | |
console.warn("hash mismatched:", this.hash, message.fromHash); | |
this.socket.send(JSON.stringify({ command: "requestFullSync", timestamp: this.timestamp})); | |
} | |
else { | |
this.timestamp = message.timestamp; | |
this._content = diff.applyPatch(this._content, message.patch); | |
console.assert(this.hash === message.toHash, "[RemoteFile] verify failed:", this.hash, message.toHash); | |
this.emit("sync", {timestamp: this.timestamp}); | |
} | |
break; | |
default: | |
console.warn("[RemoteFile] unexpected command:", message); | |
} | |
}; | |
this.filePath = filePath; | |
} | |
close () { | |
this.socket.close(); | |
} | |
}; | |