import DeleteStore from './Store/DeleteStore.js' import OperationStore from './Store/OperationStore.js' import StateStore from './Store/StateStore.js' import { generateUserID } from './Util/generateUserID.js' import RootID from './Util/RootID.js' import NamedEventHandler from './Util/NamedEventHandler.js' import UndoManager from './Util/UndoManager.js' import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js' import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js' import Connector from './Connector.js' import Persistence from './Persistence.js' import YArray from './Type/YArray.js' import YMap from './Type/YMap.js' import YText from './Type/YText.js' import { YXmlFragment, YXmlElement, YXmlText, YXmlHook } from './Type/y-xml/y-xml.js' import BinaryDecoder from './Binary/Decoder.js' import { getRelativePosition, fromRelativePosition } from './Util/relativePosition.js' import { addStruct as addType } from './Util/structReferences.js' import debug from 'debug' import Transaction from './Transaction.js' export default class Y extends NamedEventHandler { constructor (room, opts, persistence) { super() this.room = room if (opts != null) { opts.connector.room = room } this._contentReady = false this._opts = opts this.userID = generateUserID() this.share = {} this.ds = new DeleteStore(this) this.os = new OperationStore(this) this.ss = new StateStore(this) this._missingStructs = new Map() this._readyToIntegrate = [] this._transaction = null this.connector = null this.connected = false let initConnection = () => { if (opts != null) { this.connector = new Y[opts.connector.name](this, opts.connector) this.connected = true this.emit('connectorReady') } } if (persistence != null) { this.persistence = persistence persistence._init(this).then(initConnection) } else { this.persistence = null initConnection() } } _setContentReady () { if (!this._contentReady) { this._contentReady = true this.emit('content') } } whenContentReady () { if (this._contentReady) { return Promise.resolve() } else { return new Promise(resolve => { this.once('content', resolve) }) } } _beforeChange () {} transact (f, remote = false) { let initialCall = this._transaction === null if (initialCall) { this._transaction = new Transaction(this) this.emit('beforeTransaction', this, this._transaction, remote) } try { f(this) } catch (e) { console.error(e) } if (initialCall) { this.emit('beforeObserverCalls', this, this._transaction, remote) const transaction = this._transaction this._transaction = null // emit change events on changed types transaction.changedTypes.forEach(function (subs, type) { if (!type._deleted) { type._callObserver(transaction, subs, remote) } }) transaction.changedParentTypes.forEach(function (events, type) { if (!type._deleted) { events = events .filter(event => !event.target._deleted ) events .forEach(event => { event.currentTarget = type }) // we don't have to check for events.length // because there is no way events is empty.. type._deepEventHandler.callEventListeners(transaction, events) } }) // when all changes & events are processed, emit afterTransaction event this.emit('afterTransaction', this, transaction, remote) } } // fake _start for root properties (y.set('name', type)) get _start () { return null } set _start (start) { return null } define (name, TypeConstructor) { let id = new RootID(name, TypeConstructor) let type = this.os.get(id) if (this.share[name] === undefined) { this.share[name] = type } else if (this.share[name] !== type) { throw new Error('Type is already defined with a different constructor') } return type } get (name) { return this.share[name] } disconnect () { if (this.connected) { this.connected = false return this.connector.disconnect() } else { return Promise.resolve() } } reconnect () { if (!this.connected) { this.connected = true return this.connector.reconnect() } else { return Promise.resolve() } } destroy () { super.destroy() this.share = null if (this.connector != null) { if (this.connector.destroy != null) { this.connector.destroy() } else { this.connector.disconnect() } } if (this.persistence !== null) { this.persistence.deinit(this) this.persistence = null } this.os = null this.ds = null this.ss = null } whenSynced () { return new Promise(resolve => { this.once('synced', () => { resolve() }) }) } } Y.extend = function extendYjs () { for (var i = 0; i < arguments.length; i++) { var f = arguments[i] if (typeof f === 'function') { f(Y) } else { throw new Error('Expected a function!') } } } // TODO: The following assignments should be moved to yjs-dist Y.AbstractConnector = Connector Y.AbstractPersistence = Persistence Y.Array = YArray Y.Map = YMap Y.Text = YText Y.XmlElement = YXmlElement Y.XmlFragment = YXmlFragment Y.XmlText = YXmlText Y.XmlHook = YXmlHook Y.utils = { BinaryDecoder, UndoManager, getRelativePosition, fromRelativePosition, addType, integrateRemoteStructs } Y.debug = debug debug.formatters.Y = messageToString debug.formatters.y = messageToRoomname