implemented experimental websockets-connector
This commit is contained in:
parent
684d38d6c8
commit
cccc0e1015
@ -1,9 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
</head>
|
||||
<script src="../../y.js"></script>
|
||||
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="./index.js"></script>
|
||||
<script src="./index.mjs" type="module"></script>
|
||||
</head>
|
||||
<body contenteditable="true">
|
||||
</body>
|
||||
|
@ -1,29 +0,0 @@
|
||||
/* global Y */
|
||||
|
||||
window.onload = function () {
|
||||
window.domBinding = new Y.DomBinding(window.yXmlType, document.body, { scrollingElement: document.scrollingElement })
|
||||
}
|
||||
|
||||
let y = new Y('htmleditor', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
})
|
||||
|
||||
window.y = y
|
||||
window.yXmlType = y.define('xml', Y.XmlFragment)
|
||||
window.undoManager = new Y.utils.UndoManager(window.yXmlType, {
|
||||
captureTimeout: 500
|
||||
})
|
||||
|
||||
document.onkeydown = function interceptUndoRedo (e) {
|
||||
if (e.keyCode === 90 && (e.metaKey || e.ctrlKey)) {
|
||||
if (!e.shiftKey) {
|
||||
window.undoManager.undo()
|
||||
} else {
|
||||
window.undoManager.redo()
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
31
examples/html-editor/index.mjs
Normal file
31
examples/html-editor/index.mjs
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
import YWebsocketsConnector from '../../src/Connectors/WebsocketsConnector/WebsocketsConnector.mjs'
|
||||
import Y from '../../src/Y.mjs'
|
||||
import DomBinding from '../../src/Bindings/DomBinding/DomBinding.mjs'
|
||||
import UndoManager from '../../src/Util/UndoManager.mjs'
|
||||
import XmlFragment from '../../src/Types/YXml/YXmlFragment.mjs'
|
||||
|
||||
const connector = new YWebsocketsConnector()
|
||||
const y = new Y('html-editor')
|
||||
connector.connectY('html-editor', y)
|
||||
|
||||
window.onload = function () {
|
||||
window.domBinding = new DomBinding(window.yXmlType, document.body, { scrollingElement: document.scrollingElement })
|
||||
}
|
||||
|
||||
window.y = y
|
||||
window.yXmlType = y.define('xml', XmlFragment)
|
||||
window.undoManager = new UndoManager(window.yXmlType, {
|
||||
captureTimeout: 500
|
||||
})
|
||||
|
||||
document.onkeydown = function interceptUndoRedo (e) {
|
||||
if (e.keyCode === 90 && (e.metaKey || e.ctrlKey)) {
|
||||
if (!e.shiftKey) {
|
||||
window.undoManager.undo()
|
||||
} else {
|
||||
window.undoManager.redo()
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
|
||||
import Y from '../src/Y.js'
|
||||
import yWebsocketsClient from '../../y-websockets-client/src/y-websockets-client.js'
|
||||
import extendYIndexedDBPersistence from '../../y-indexeddb/src/y-indexeddb.js'
|
||||
import Y from '../src/Y..mjs'
|
||||
import yWebsocketsClient from '../../y-websockets-client/src/y-websockets-client.mjs'
|
||||
import extendYIndexedDBPersistence from '../../y-indexeddb/src/y-indexeddb.mjs'
|
||||
|
||||
Y.extend(yWebsocketsClient)
|
||||
extendYIndexedDBPersistence(Y)
|
||||
|
1349
package-lock.json
generated
1349
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"test": "npm run lint",
|
||||
"debug": "concurrently 'rollup -wc rollup.test.js' 'cutest-serve y.test.js -o'",
|
||||
"lint": "standard",
|
||||
"lint": "standard src/**/*.mjs test/**/*.mjs tests-lib/**/*.mjs",
|
||||
"docs": "esdoc",
|
||||
"serve-docs": "npm run docs && serve ./docs/",
|
||||
"dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js",
|
||||
@ -55,6 +55,7 @@
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-latest": "^6.24.1",
|
||||
"chance": "^1.0.9",
|
||||
"codemirror": "^5.37.0",
|
||||
"concurrently": "^3.4.0",
|
||||
"cutest": "^0.1.9",
|
||||
"esdoc": "^1.0.4",
|
||||
@ -70,7 +71,8 @@
|
||||
"rollup-plugin-uglify": "^1.0.2",
|
||||
"rollup-regenerator-runtime": "^6.23.1",
|
||||
"rollup-watch": "^3.2.2",
|
||||
"standard": "^10.0.2",
|
||||
"tag-dist-files": "^0.1.6"
|
||||
"standard": "^11.0.1",
|
||||
"tag-dist-files": "^0.1.6",
|
||||
"uws": "^10.148.0"
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
var pkg = require('./package.json')
|
||||
|
||||
export default {
|
||||
input: 'src/Y.dist.js',
|
||||
input: 'src/Y.dist.mjs',
|
||||
name: 'Y',
|
||||
sourcemap: true,
|
||||
output: {
|
||||
|
@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
var pkg = require('./package.json')
|
||||
|
||||
export default {
|
||||
input: 'src/Y.dist.js',
|
||||
input: 'src/Y.dist.mjs',
|
||||
nameame: 'Y',
|
||||
sourcemap: true,
|
||||
output: {
|
||||
|
@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
import multiEntry from 'rollup-plugin-multi-entry'
|
||||
|
||||
export default {
|
||||
input: 'test/index.js',
|
||||
input: 'test/index.mjs',
|
||||
name: 'y-tests',
|
||||
sourcemap: true,
|
||||
output: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
import { createMutualExclude } from '../Util/mutualExclude.js'
|
||||
import { createMutualExclude } from '../Util/mutualExclude.mjs'
|
||||
|
||||
/**
|
||||
* Abstract class for bindings.
|
@ -1,7 +1,7 @@
|
||||
|
||||
import Binding from '../Binding.js'
|
||||
import simpleDiff from '../../Util/simpleDiff.js'
|
||||
import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.js'
|
||||
import Binding from '../Binding.mjs'
|
||||
import simpleDiff from '../../Util/simpleDiff.mjs'
|
||||
import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.mjs'
|
||||
|
||||
function typeObserver () {
|
||||
this._mutualExclude(() => {
|
@ -1,11 +1,11 @@
|
||||
/* global MutationObserver */
|
||||
|
||||
import Binding from '../Binding.js'
|
||||
import { createAssociation, removeAssociation } from './util.js'
|
||||
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.js'
|
||||
import { defaultFilter, applyFilterOnType } from './filter.js'
|
||||
import typeObserver from './typeObserver.js'
|
||||
import domObserver from './domObserver.js'
|
||||
import Binding from '../Binding.mjs'
|
||||
import { createAssociation, removeAssociation } from './util.mjs'
|
||||
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.mjs'
|
||||
import { defaultFilter, applyFilterOnType } from './filter.mjs'
|
||||
import typeObserver from './typeObserver.mjs'
|
||||
import domObserver from './domObserver.mjs'
|
||||
|
||||
/**
|
||||
* A binding that binds the children of a YXmlFragment to a DOM element.
|
@ -1,11 +1,11 @@
|
||||
|
||||
import YXmlHook from '../../Types/YXml/YXmlHook.js'
|
||||
import YXmlHook from '../../Types/YXml/YXmlHook.mjs'
|
||||
import {
|
||||
iterateUntilUndeleted,
|
||||
removeAssociation,
|
||||
insertNodeHelper } from './util.js'
|
||||
import diff from '../../Util/simpleDiff.js'
|
||||
import YXmlFragment from '../../Types/YXml/YXmlFragment.js'
|
||||
insertNodeHelper } from './util.mjs'
|
||||
import diff from '../../Util/simpleDiff.mjs'
|
||||
import YXmlFragment from '../../Types/YXml/YXmlFragment.mjs'
|
||||
|
||||
/**
|
||||
* 1. Check if any of the nodes was deleted
|
@ -1,9 +1,9 @@
|
||||
|
||||
import YXmlText from '../../Types/YXml/YXmlText.js'
|
||||
import YXmlHook from '../../Types/YXml/YXmlHook.js'
|
||||
import YXmlElement from '../../Types/YXml/YXmlElement.js'
|
||||
import { createAssociation, domsToTypes } from './util.js'
|
||||
import { filterDomAttributes, defaultFilter } from './filter.js'
|
||||
import YXmlText from '../../Types/YXml/YXmlText.mjs'
|
||||
import YXmlHook from '../../Types/YXml/YXmlHook.mjs'
|
||||
import YXmlElement from '../../Types/YXml/YXmlElement.mjs'
|
||||
import { createAssociation, domsToTypes } from './util.mjs'
|
||||
import { filterDomAttributes, defaultFilter } from './filter.mjs'
|
||||
|
||||
/**
|
||||
* Creates a Yjs type (YXml) based on the contents of a DOM Element.
|
@ -1,4 +1,4 @@
|
||||
import isParentOf from '../../Util/isParentOf.js'
|
||||
import isParentOf from '../../Util/isParentOf.mjs'
|
||||
|
||||
/**
|
||||
* Default filter method (does nothing).
|
@ -1,6 +1,6 @@
|
||||
/* globals getSelection */
|
||||
|
||||
import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.js'
|
||||
import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.mjs'
|
||||
|
||||
let browserSelection = null
|
||||
let relativeSelection = null
|
@ -1,8 +1,8 @@
|
||||
/* global getSelection */
|
||||
|
||||
import YXmlText from '../../Types/YXml/YXmlText.js'
|
||||
import YXmlHook from '../../Types/YXml/YXmlHook.js'
|
||||
import { removeDomChildrenUntilElementFound } from './util.js'
|
||||
import YXmlText from '../../Types/YXml/YXmlText.mjs'
|
||||
import YXmlHook from '../../Types/YXml/YXmlHook.mjs'
|
||||
import { removeDomChildrenUntilElementFound } from './util.mjs'
|
||||
|
||||
function findScrollReference (scrollingElement) {
|
||||
if (scrollingElement !== null) {
|
@ -1,5 +1,5 @@
|
||||
|
||||
import domToType from './domToType.js'
|
||||
import domToType from './domToType.mjs'
|
||||
|
||||
/**
|
||||
* Iterates items until an undeleted item is found.
|
@ -1,4 +1,4 @@
|
||||
import Binding from '../Binding.js'
|
||||
import Binding from '../Binding.mjs'
|
||||
|
||||
function typeObserver (event) {
|
||||
const quill = this.target
|
56
src/Bindings/TextareaBinding/TextareaBinding.mjs
Normal file
56
src/Bindings/TextareaBinding/TextareaBinding.mjs
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
import Binding from '../Binding.mjs'
|
||||
import simpleDiff from '../../Util/simpleDiff.mjs'
|
||||
import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.mjs'
|
||||
|
||||
function typeObserver () {
|
||||
this._mutualExclude(() => {
|
||||
const textarea = this.target
|
||||
const textType = this.type
|
||||
const relativeStart = getRelativePosition(textType, textarea.selectionStart)
|
||||
const relativeEnd = getRelativePosition(textType, textarea.selectionEnd)
|
||||
textarea.value = textType.toString()
|
||||
const start = fromRelativePosition(textType._y, relativeStart)
|
||||
const end = fromRelativePosition(textType._y, relativeEnd)
|
||||
textarea.setSelectionRange(start, end)
|
||||
})
|
||||
}
|
||||
|
||||
function domObserver () {
|
||||
this._mutualExclude(() => {
|
||||
let diff = simpleDiff(this.type.toString(), this.target.value)
|
||||
this.type.delete(diff.pos, diff.remove)
|
||||
this.type.insert(diff.pos, diff.insert)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A binding that binds a YText to a dom textarea.
|
||||
*
|
||||
* This binding is automatically destroyed when its parent is deleted.
|
||||
*
|
||||
* @example
|
||||
* const textare = document.createElement('textarea')
|
||||
* const type = y.define('textarea', Y.Text)
|
||||
* const binding = new Y.QuillBinding(type, textarea)
|
||||
*
|
||||
*/
|
||||
export default class TextareaBinding extends Binding {
|
||||
constructor (textType, domTextarea) {
|
||||
// Binding handles textType as this.type and domTextarea as this.target
|
||||
super(textType, domTextarea)
|
||||
// set initial value
|
||||
domTextarea.value = textType.toString()
|
||||
// Observers are handled by this class
|
||||
this._typeObserver = typeObserver.bind(this)
|
||||
this._domObserver = domObserver.bind(this)
|
||||
textType.observe(this._typeObserver)
|
||||
domTextarea.addEventListener('input', this._domObserver)
|
||||
}
|
||||
destroy () {
|
||||
// Remove everything that is handled by this class
|
||||
this.type.unobserve(this._typeObserver)
|
||||
this.target.unobserve(this._domObserver)
|
||||
super.destroy()
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import BinaryEncoder from './Util/Binary/Encoder.js'
|
||||
import BinaryDecoder from './Util/Binary/Decoder.js'
|
||||
import BinaryEncoder from './Util/Binary/Encoder.mjs'
|
||||
import BinaryDecoder from './Util/Binary/Decoder.mjs'
|
||||
|
||||
import { sendSyncStep1, readSyncStep1 } from './MessageHandler/syncStep1.js'
|
||||
import { readSyncStep2 } from './MessageHandler/syncStep2.js'
|
||||
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js'
|
||||
import { sendSyncStep1, readSyncStep1 } from './MessageHandler/syncStep1.mjs'
|
||||
import { readSyncStep2 } from './MessageHandler/syncStep2.mjs'
|
||||
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.mjs'
|
||||
|
||||
import debug from 'debug'
|
||||
|
114
src/Connectors/WebsocketsConnector/WebsocketsConnector.mjs
Normal file
114
src/Connectors/WebsocketsConnector/WebsocketsConnector.mjs
Normal file
@ -0,0 +1,114 @@
|
||||
import BinaryEncoder from '../../Util/Binary/Encoder.mjs'
|
||||
/* global WebSocket */
|
||||
import NamedEventHandler from '../../Util/NamedEventHandler.mjs'
|
||||
import decodeMessage, { messageSS, messageSubscribe, messageStructs, messageGetSS } from './decodeMessage.mjs'
|
||||
import { createMutualExclude } from '../../Util/mutualExclude.mjs'
|
||||
|
||||
export const STATE_CONNECTING = 0
|
||||
export const STATE_SYNCING = 1
|
||||
export const STATE_SYNCED = 2
|
||||
export const STATE_DISCONNECTED = 3
|
||||
|
||||
export default class WebsocketsConnector extends NamedEventHandler {
|
||||
constructor (url = 'ws://localhost:1234') {
|
||||
super()
|
||||
this.url = url
|
||||
this._state = STATE_DISCONNECTED
|
||||
this._socket = null
|
||||
this._rooms = new Map()
|
||||
this._connectToServer = true
|
||||
this._reconnectTimeout = 300
|
||||
this._mutualExclude = createMutualExclude()
|
||||
this.connect()
|
||||
}
|
||||
|
||||
getRoom (roomName) {
|
||||
return this._rooms.get(roomName)
|
||||
}
|
||||
|
||||
connectY (roomName, y) {
|
||||
let room = this._rooms.get(roomName)
|
||||
if (room !== undefined) {
|
||||
throw new Error('Room is already taken! There can be only one Yjs instance per roomName!')
|
||||
}
|
||||
this._rooms.set(roomName, {
|
||||
roomName,
|
||||
y
|
||||
})
|
||||
y.on('afterTransaction', (y, transaction) => {
|
||||
this._mutualExclude(() => {
|
||||
if (transaction.encodedStructsLen > 0) {
|
||||
const encoder = new BinaryEncoder()
|
||||
messageStructs(roomName, y, encoder, transaction.encodedStructs)
|
||||
this.send(encoder)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_setState (state) {
|
||||
this.emit('stateChanged', {
|
||||
state
|
||||
})
|
||||
this._state = state
|
||||
}
|
||||
|
||||
get state () {
|
||||
return this._state
|
||||
}
|
||||
|
||||
_onOpen () {
|
||||
const encoder = new BinaryEncoder()
|
||||
for (const [roomName, room] of this._rooms) {
|
||||
const y = room.y
|
||||
messageGetSS(roomName, y, encoder)
|
||||
messageSS(roomName, y, encoder)
|
||||
messageSubscribe(roomName, y, encoder)
|
||||
}
|
||||
this.send(encoder)
|
||||
}
|
||||
|
||||
send (encoder) {
|
||||
if (encoder.length > 0) {
|
||||
this._socket.send(encoder.createBuffer())
|
||||
}
|
||||
}
|
||||
|
||||
_onClose () {
|
||||
this._socket = null
|
||||
if (this._connectToServer) {
|
||||
setTimeout(() => {
|
||||
if (this._connectToServer) {
|
||||
this.connect()
|
||||
}
|
||||
}, this._reconnectTimeout)
|
||||
this.connect()
|
||||
}
|
||||
}
|
||||
|
||||
_onMessage (message) {
|
||||
if (message.data.byteLength > 0) {
|
||||
const reply = decodeMessage(this, message.data, null)
|
||||
this.send(reply)
|
||||
}
|
||||
}
|
||||
|
||||
disconnect (code = 1000, reason = 'Client manually disconnected') {
|
||||
const socket = this._socket
|
||||
this._connectToServer = false
|
||||
socket.close(code, reason)
|
||||
}
|
||||
|
||||
connect () {
|
||||
if (this._socket === null) {
|
||||
const socket = new WebSocket(this.url)
|
||||
socket.binaryType = 'arraybuffer'
|
||||
this._socket = socket
|
||||
this._connectToServer = true
|
||||
// Connection opened
|
||||
socket.addEventListener('open', this._onOpen.bind(this))
|
||||
socket.addEventListener('close', this._onClose.bind(this))
|
||||
socket.addEventListener('message', this._onMessage.bind(this))
|
||||
}
|
||||
}
|
||||
}
|
104
src/Connectors/WebsocketsConnector/decodeMessage.mjs
Normal file
104
src/Connectors/WebsocketsConnector/decodeMessage.mjs
Normal file
@ -0,0 +1,104 @@
|
||||
import BinaryDecoder from '../../Util/Binary/Decoder.mjs'
|
||||
import BinaryEncoder from '../../Util/Binary/Encoder.mjs'
|
||||
import { readStateSet, writeStateSet } from '../../MessageHandler/stateSet.mjs'
|
||||
import { writeStructs } from '../../MessageHandler/syncStep1.mjs'
|
||||
import { writeDeleteSet, readDeleteSet } from '../../MessageHandler/deleteSet.mjs'
|
||||
import { integrateRemoteStructs } from '../../MessageHandler/integrateRemoteStructs.mjs'
|
||||
|
||||
const CONTENT_GET_SS = 4
|
||||
export function messageGetSS (roomName, y, encoder) {
|
||||
encoder.writeVarString(roomName)
|
||||
encoder.writeVarUint(CONTENT_GET_SS)
|
||||
}
|
||||
|
||||
const CONTENT_SUBSCRIBE = 3
|
||||
export function messageSubscribe (roomName, y, encoder) {
|
||||
encoder.writeVarString(roomName)
|
||||
encoder.writeVarUint(CONTENT_SUBSCRIBE)
|
||||
}
|
||||
|
||||
const CONTENT_SS = 0
|
||||
/**
|
||||
* Message the current state set. The other side must respond with CONTENT_STRUCTS_DSS
|
||||
*/
|
||||
export function messageSS (roomName, y, encoder) {
|
||||
encoder.writeVarString(roomName)
|
||||
encoder.writeVarUint(CONTENT_SS)
|
||||
writeStateSet(y, encoder)
|
||||
}
|
||||
|
||||
const CONTENT_STRUCTS_DSS = 2
|
||||
export function messageStructsDSS (roomName, y, encoder, ss) {
|
||||
encoder.writeVarString(roomName)
|
||||
encoder.writeVarUint(CONTENT_STRUCTS_DSS)
|
||||
writeStructs(y, encoder, ss)
|
||||
writeDeleteSet(y, encoder)
|
||||
}
|
||||
|
||||
const CONTENT_STRUCTS = 5
|
||||
export function messageStructs (roomName, y, encoder, structsBinaryEncoder) {
|
||||
encoder.writeVarString(roomName)
|
||||
encoder.writeVarUint(CONTENT_STRUCTS)
|
||||
encoder.writeBinaryEncoder(structsBinaryEncoder)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a client-message.
|
||||
*
|
||||
* A client-message consists of multiple message-elements that are concatenated without delimiter.
|
||||
* Each has the following structure:
|
||||
* - roomName
|
||||
* - content_type
|
||||
* - content (additional info that is encoded based on the value of content_type)
|
||||
*
|
||||
* The message is encoded until no more message-elements are available.
|
||||
*
|
||||
* @param {*} connector The connector that handles the connections
|
||||
* @param {*} message The binary encoded message
|
||||
* @param {*} ws The connection object
|
||||
*/
|
||||
export default function decodeMessage (connector, message, ws) {
|
||||
const decoder = new BinaryDecoder(message)
|
||||
const encoder = new BinaryEncoder()
|
||||
while (decoder.hasContent()) {
|
||||
const roomName = decoder.readVarString()
|
||||
const contentType = decoder.readVarUint()
|
||||
const room = connector.getRoom(roomName)
|
||||
const y = room.y
|
||||
switch (contentType) {
|
||||
case CONTENT_STRUCTS:
|
||||
connector._mutualExclude(() => {
|
||||
y.transact(() => {
|
||||
integrateRemoteStructs(y, decoder)
|
||||
}, true)
|
||||
})
|
||||
break
|
||||
case CONTENT_GET_SS:
|
||||
messageSS(roomName, y, encoder)
|
||||
break
|
||||
case CONTENT_SUBSCRIBE:
|
||||
room.connections.add(ws)
|
||||
break
|
||||
case CONTENT_SS:
|
||||
// received state set
|
||||
// reply with missing content
|
||||
const ss = readStateSet(decoder)
|
||||
messageStructsDSS(roomName, y, encoder, ss)
|
||||
break
|
||||
case CONTENT_STRUCTS_DSS:
|
||||
connector._mutualExclude(() => {
|
||||
y.transact(() => {
|
||||
integrateRemoteStructs(y, decoder)
|
||||
readDeleteSet(y, decoder)
|
||||
}, true)
|
||||
})
|
||||
break
|
||||
default:
|
||||
console.error('Unexpected content type!')
|
||||
if (ws !== null) {
|
||||
ws.close() // TODO: specify reason
|
||||
}
|
||||
}
|
||||
}
|
||||
return encoder
|
||||
}
|
99
src/Connectors/WebsocketsConnector/server.mjs
Normal file
99
src/Connectors/WebsocketsConnector/server.mjs
Normal file
@ -0,0 +1,99 @@
|
||||
import Y from '../../Y.mjs'
|
||||
import uws from 'uws'
|
||||
import BinaryEncoder from '../../Util/Binary/Encoder.mjs'
|
||||
import decodeMessage, { messageStructs } from './decodeMessage.mjs'
|
||||
|
||||
const WebsocketsServer = uws.Server
|
||||
|
||||
/**
|
||||
* Maps from room-name to ..
|
||||
* {
|
||||
* connections, // Set of ws-clients that listen to the room
|
||||
* y // Yjs instance that handles the room
|
||||
* }
|
||||
*/
|
||||
const rooms = new Map()
|
||||
/**
|
||||
* Maps from ws-connection to Set<roomName> - the set of connected roomNames
|
||||
*/
|
||||
const connections = new Map()
|
||||
const wss = new WebsocketsServer({ port: 1234 })
|
||||
|
||||
/**
|
||||
* Set of room names that are scheduled to be sweeped (destroyed because they don't have a connection anymore)
|
||||
*/
|
||||
const scheduledSweeps = new Set()
|
||||
setInterval(function sweepRoomes () {
|
||||
scheduledSweeps.forEach(roomName => {
|
||||
const room = rooms.get(roomName)
|
||||
if (room !== undefined) {
|
||||
if (room.connections.size === 0) {
|
||||
room.y.destroy()
|
||||
}
|
||||
rooms.delete(roomName)
|
||||
}
|
||||
})
|
||||
}, 5000)
|
||||
|
||||
const wsConnector = {
|
||||
_mutualExclude: f => { f() },
|
||||
subscribe: function subscribe (roomName, ws) {
|
||||
let roomNames = connections.get(ws)
|
||||
if (roomNames === undefined) {
|
||||
roomNames = new Set()
|
||||
connections.set(ws, roomNames)
|
||||
}
|
||||
roomNames.add(roomName)
|
||||
},
|
||||
getRoom: function getRoom (roomName) {
|
||||
let room = rooms.get(roomName)
|
||||
if (room === undefined) {
|
||||
const y = new Y(roomName)
|
||||
y.on('afterTransaction', (y, transaction) => {
|
||||
if (transaction.encodedStructsLen > 0) {
|
||||
const encoder = new BinaryEncoder()
|
||||
messageStructs(roomName, y, encoder, transaction.encodedStructs)
|
||||
const message = encoder.createBuffer()
|
||||
// when changed, broakcast update to all connections
|
||||
room.connections.forEach(conn => {
|
||||
conn.send(message)
|
||||
})
|
||||
}
|
||||
})
|
||||
room = {
|
||||
name: roomName,
|
||||
connections: new Set(),
|
||||
y
|
||||
}
|
||||
rooms.set(roomName, room)
|
||||
}
|
||||
return room
|
||||
}
|
||||
}
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
ws.on('message', function onWSMessage (message) {
|
||||
if (message.byteLength > 0) {
|
||||
const reply = decodeMessage(wsConnector, message, ws)
|
||||
if (reply.length > 0) {
|
||||
ws.send(reply.createBuffer())
|
||||
}
|
||||
}
|
||||
})
|
||||
ws.on('close', function onWSClose () {
|
||||
const roomNames = connections.get(ws)
|
||||
if (roomNames !== undefined) {
|
||||
roomNames.forEach(roomName => {
|
||||
const room = rooms.get(roomName)
|
||||
if (room !== undefined) {
|
||||
const connections = room.connections
|
||||
connections.delete(ws)
|
||||
if (connections.size === 0) {
|
||||
scheduledSweeps.add(roomName)
|
||||
}
|
||||
}
|
||||
})
|
||||
connections.delete(ws)
|
||||
}
|
||||
})
|
||||
})
|
@ -1,8 +1,8 @@
|
||||
|
||||
import { writeStructs } from './syncStep1.js'
|
||||
import { integrateRemoteStructs } from './integrateRemoteStructs.js'
|
||||
import { readDeleteSet, writeDeleteSet } from './deleteSet.js'
|
||||
import BinaryEncoder from '../Util/Binary/Encoder.js'
|
||||
import { writeStructs } from './syncStep1.mjs'
|
||||
import { integrateRemoteStructs } from './integrateRemoteStructs.mjs'
|
||||
import { readDeleteSet, writeDeleteSet } from './deleteSet.mjs'
|
||||
import BinaryEncoder from '../Util/Binary/Encoder.mjs'
|
||||
|
||||
/**
|
||||
* Read the Decoder and fill the Yjs instance with data in the decoder.
|
@ -1,5 +1,5 @@
|
||||
import { deleteItemRange } from '../Struct/Delete.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import { deleteItemRange } from '../Struct/Delete.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
|
||||
export function stringifyDeleteSet (y, decoder, strBuilder) {
|
||||
let dsLength = decoder.readUint32()
|
@ -1,7 +1,7 @@
|
||||
import { getStruct } from '../Util/structReferences.js'
|
||||
import BinaryDecoder from '../Util/Binary/Decoder.js'
|
||||
import { logID } from './messageToString.js'
|
||||
import GC from '../Struct/GC.js'
|
||||
import { getStruct } from '../Util/structReferences.mjs'
|
||||
import BinaryDecoder from '../Util/Binary/Decoder.mjs'
|
||||
import { logID } from './messageToString.mjs'
|
||||
import GC from '../Struct/GC.mjs'
|
||||
|
||||
class MissingEntry {
|
||||
constructor (decoder, missing, struct) {
|
@ -1,10 +1,10 @@
|
||||
import BinaryDecoder from '../Util/Binary/Decoder.js'
|
||||
import { stringifyStructs } from './integrateRemoteStructs.js'
|
||||
import { stringifySyncStep1 } from './syncStep1.js'
|
||||
import { stringifySyncStep2 } from './syncStep2.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import RootID from '../Util/ID/RootID.js'
|
||||
import Y from '../Y.js'
|
||||
import BinaryDecoder from '../Util/Binary/Decoder.mjs'
|
||||
import { stringifyStructs } from './integrateRemoteStructs.mjs'
|
||||
import { stringifySyncStep1 } from './syncStep1.mjs'
|
||||
import { stringifySyncStep2 } from './syncStep2.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
import RootID from '../Util/ID/RootID.mjs'
|
||||
import Y from '../Y.mjs'
|
||||
|
||||
export function messageToString ([y, buffer]) {
|
||||
let decoder = new BinaryDecoder(buffer)
|
@ -1,8 +1,8 @@
|
||||
import BinaryEncoder from '../Util/Binary/Encoder.js'
|
||||
import { readStateSet, writeStateSet } from './stateSet.js'
|
||||
import { writeDeleteSet } from './deleteSet.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import { RootFakeUserID } from '../Util/ID/RootID.js'
|
||||
import BinaryEncoder from '../Util/Binary/Encoder.mjs'
|
||||
import { readStateSet, writeStateSet } from './stateSet.mjs'
|
||||
import { writeDeleteSet } from './deleteSet.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
import { RootFakeUserID } from '../Util/ID/RootID.mjs'
|
||||
|
||||
export function stringifySyncStep1 (y, decoder, strBuilder) {
|
||||
let auth = decoder.readVarString()
|
@ -1,5 +1,5 @@
|
||||
import { stringifyStructs, integrateRemoteStructs } from './integrateRemoteStructs.js'
|
||||
import { readDeleteSet } from './deleteSet.js'
|
||||
import { stringifyStructs, integrateRemoteStructs } from './integrateRemoteStructs.mjs'
|
||||
import { readDeleteSet } from './deleteSet.mjs'
|
||||
|
||||
export function stringifySyncStep2 (y, decoder, strBuilder) {
|
||||
strBuilder.push(' - auth: ' + decoder.readVarString())
|
@ -1,8 +1,8 @@
|
||||
import BinaryEncoder from './Util/Binary/Encoder.js'
|
||||
import BinaryDecoder from './Util/Binary/Decoder.js'
|
||||
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
|
||||
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js'
|
||||
import { createMutualExclude } from './Util/mutualExclude.js'
|
||||
import BinaryEncoder from './Util/Binary/Encoder.mjs'
|
||||
import BinaryDecoder from './Util/Binary/Decoder.mjs'
|
||||
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.mjs'
|
||||
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.mjs'
|
||||
import { createMutualExclude } from './Util/mutualExclude.mjs'
|
||||
|
||||
function getFreshCnf () {
|
||||
let buffer = new BinaryEncoder()
|
2
src/Persistences/AbstractPersistence.mjs
Normal file
2
src/Persistences/AbstractPersistence.mjs
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export default class AbstractPersistence {}
|
1
src/Persistences/FilePersistence.mjs
Normal file
1
src/Persistences/FilePersistence.mjs
Normal file
@ -0,0 +1 @@
|
||||
|
174
src/Persistences/IndexeddbPersistence.mjs
Normal file
174
src/Persistences/IndexeddbPersistence.mjs
Normal file
@ -0,0 +1,174 @@
|
||||
/* global indexedDB, location, BroadcastChannel */
|
||||
|
||||
import Y from '../Y.mjs'
|
||||
|
||||
/*
|
||||
* Request to Promise transformer
|
||||
*/
|
||||
function rtop (request) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
request.onerror = function (event) {
|
||||
reject(new Error(event.target.error))
|
||||
}
|
||||
request.onblocked = function () {
|
||||
location.reload()
|
||||
}
|
||||
request.onsuccess = function (event) {
|
||||
resolve(event.target.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function openDB (room) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let request = indexedDB.open(room)
|
||||
window.r1 = request
|
||||
request.onupgradeneeded = function (event) {
|
||||
const db = event.target.result
|
||||
if (db.objectStoreNames.contains('model')) {
|
||||
db.deleteObjectStore('updates')
|
||||
db.deleteObjectStore('model')
|
||||
db.deleteObjectStore('custom')
|
||||
}
|
||||
db.createObjectStore('updates', {autoIncrement: true})
|
||||
db.createObjectStore('model')
|
||||
db.createObjectStore('custom')
|
||||
}
|
||||
request.onerror = function (event) {
|
||||
reject(new Error(event.target.error))
|
||||
}
|
||||
request.onblocked = function () {
|
||||
location.reload()
|
||||
}
|
||||
request.onsuccess = function (event) {
|
||||
const db = event.target.result
|
||||
db.onversionchange = function () { db.close() }
|
||||
resolve(db)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const PREFERRED_TRIM_SIZE = 500
|
||||
|
||||
export default class IndexedDBPersistence extends Y.AbstractPersistence {
|
||||
constructor (opts) {
|
||||
super(opts)
|
||||
window.addEventListener('unload', () => {
|
||||
this.ys.forEach(function (cnf, y) {
|
||||
if (cnf.db !== null) {
|
||||
cnf.db.close()
|
||||
} else {
|
||||
cnf._db.then(db => db.close())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
init (y) {
|
||||
let cnf = this.ys.get(y)
|
||||
let room = y.room
|
||||
cnf.db = null
|
||||
const dbOpened = openDB(room)
|
||||
dbOpened.then(db => {
|
||||
cnf.db = db
|
||||
})
|
||||
if (typeof BroadcastChannel !== 'undefined') {
|
||||
cnf.channel = new BroadcastChannel('__yjs__' + room)
|
||||
cnf.channel.addEventListener('message', e => {
|
||||
cnf.mutualExclude(function () {
|
||||
y.transact(function () {
|
||||
Y.utils.integrateRemoteStructs(y, new Y.utils.BinaryDecoder(e.data))
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
cnf.channel = null
|
||||
}
|
||||
return dbOpened
|
||||
}
|
||||
|
||||
deinit (y) {
|
||||
let cnf = this.ys.get(y)
|
||||
cnf.db.close()
|
||||
super.deinit(y)
|
||||
}
|
||||
|
||||
set (y, key, value) {
|
||||
const cnf = this.ys.get(y)
|
||||
const t = cnf.db.transaction(['custom'], 'readwrite')
|
||||
const customStore = t.objectStore('custom')
|
||||
return rtop(customStore.put(value, key))
|
||||
}
|
||||
|
||||
get (y, key) {
|
||||
const cnf = this.ys.get(y)
|
||||
const t = cnf.db.transaction(['custom'], 'readwrite')
|
||||
const customStore = t.objectStore('custom')
|
||||
return rtop(customStore.get(key))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all persisted data that belongs to a room.
|
||||
* Automatically destroys all Yjs all Yjs instances that persist to
|
||||
* the room. If `destroyYjsInstances = false` the persistence functionality
|
||||
* will be removed from the Yjs instances.
|
||||
*/
|
||||
removePersistedData (room, destroyYjsInstances = true) {
|
||||
super.removePersistedData(room, destroyYjsInstances)
|
||||
return rtop(indexedDB.deleteDatabase(room))
|
||||
}
|
||||
|
||||
saveUpdate (y, update) {
|
||||
let cnf = this.ys.get(y)
|
||||
if (cnf.channel !== null) {
|
||||
cnf.channel.postMessage(update)
|
||||
}
|
||||
let t = cnf.db.transaction(['updates'], 'readwrite')
|
||||
let updatesStore = t.objectStore('updates')
|
||||
updatesStore.put(update)
|
||||
let cntP = rtop(updatesStore.count())
|
||||
cntP.then(cnt => {
|
||||
if (cnt >= PREFERRED_TRIM_SIZE) {
|
||||
this.persist(y)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
saveStruct (y, struct) {
|
||||
super.saveStruct(y, struct)
|
||||
}
|
||||
|
||||
retrieve (y) {
|
||||
let cnf = this.ys.get(y)
|
||||
let t = cnf.db.transaction(['updates', 'model'], 'readonly')
|
||||
let modelStore = t.objectStore('model')
|
||||
let updatesStore = t.objectStore('updates')
|
||||
return Promise.all([rtop(modelStore.get(0)), rtop(updatesStore.getAll())])
|
||||
.then(([model, updates]) => {
|
||||
super.retrieve(y, model, updates)
|
||||
})
|
||||
}
|
||||
|
||||
persist (y) {
|
||||
let cnf = this.ys.get(y)
|
||||
let db = cnf.db
|
||||
let t = db.transaction(['updates', 'model'], 'readwrite')
|
||||
let updatesStore = t.objectStore('updates')
|
||||
return rtop(updatesStore.getAll())
|
||||
.then(updates => {
|
||||
// apply pending updates before deleting them
|
||||
Y.AbstractPersistence.prototype.retrieve.call(this, y, null, updates)
|
||||
// get binary model
|
||||
let binaryModel = Y.AbstractPersistence.prototype.persist.call(this, y)
|
||||
// delete all pending updates
|
||||
if (updates.length > 0) {
|
||||
let modelStore = t.objectStore('model')
|
||||
modelStore.put(binaryModel, 0)
|
||||
updatesStore.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof Y !== 'undefined') {
|
||||
extendYIndexedDBPersistence(Y) // eslint-disable-line
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
|
||||
import Tree from '../Util/Tree.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import Tree from '../Util/Tree.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
|
||||
class DSNode {
|
||||
constructor (id, len, gc) {
|
@ -1,8 +1,8 @@
|
||||
import Tree from '../Util/Tree.js'
|
||||
import RootID from '../Util/ID/RootID.js'
|
||||
import { getStruct } from '../Util/structReferences.js'
|
||||
import { logID } from '../MessageHandler/messageToString.js'
|
||||
import GC from '../Struct/GC.js'
|
||||
import Tree from '../Util/Tree.mjs'
|
||||
import RootID from '../Util/ID/RootID.mjs'
|
||||
import { getStruct } from '../Util/structReferences.mjs'
|
||||
import { logID } from '../MessageHandler/messageToString.mjs'
|
||||
import GC from '../Struct/GC.mjs'
|
||||
|
||||
export default class OperationStore extends Tree {
|
||||
constructor (y) {
|
@ -1,4 +1,4 @@
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
|
||||
export default class StateStore {
|
||||
constructor (y) {
|
@ -1,6 +1,7 @@
|
||||
import { getStructReference } from '../Util/structReferences.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import { logID } from '../MessageHandler/messageToString.js'
|
||||
import { getStructReference } from '../Util/structReferences.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
import { logID } from '../MessageHandler/messageToString.mjs'
|
||||
import { writeStructToTransaction } from '../Transaction.mjs'
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -108,6 +109,7 @@ export default class Delete {
|
||||
if (y.persistence !== null) {
|
||||
y.persistence.saveStruct(y, this)
|
||||
}
|
||||
writeStructToTransaction(y._transaction, this)
|
||||
}
|
||||
|
||||
/**
|
@ -1,6 +1,7 @@
|
||||
import { getStructReference } from '../Util/structReferences.js'
|
||||
import { RootFakeUserID } from '../Util/ID/RootID.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import { getStructReference } from '../Util/structReferences.mjs'
|
||||
import { RootFakeUserID } from '../Util/ID/RootID.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
import { writeStructToTransaction } from '../Transaction.mjs'
|
||||
|
||||
// TODO should have the same base class as Item
|
||||
export default class GC {
|
||||
@ -43,6 +44,7 @@ export default class GC {
|
||||
if (y.persistence !== null) {
|
||||
y.persistence.saveStruct(y, this)
|
||||
}
|
||||
writeStructToTransaction(y._transaction, this)
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { getStructReference } from '../Util/structReferences.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import { default as RootID, RootFakeUserID } from '../Util/ID/RootID.js'
|
||||
import Delete from './Delete.js'
|
||||
import { transactionTypeChanged } from '../Transaction.js'
|
||||
import GC from './GC.js'
|
||||
import { getStructReference } from '../Util/structReferences.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
import { default as RootID, RootFakeUserID } from '../Util/ID/RootID.mjs'
|
||||
import Delete from './Delete.mjs'
|
||||
import { transactionTypeChanged, writeStructToTransaction } from '../Transaction.mjs'
|
||||
import GC from './GC.mjs'
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -385,6 +385,7 @@ export default class Item {
|
||||
if (y.persistence !== null) {
|
||||
y.persistence.saveStruct(y, this)
|
||||
}
|
||||
writeStructToTransaction(y._transaction, this)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { default as Item } from './Item.js'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.js'
|
||||
import Item from './Item.mjs'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.mjs'
|
||||
|
||||
export default class ItemEmbed extends Item {
|
||||
constructor () {
|
@ -1,5 +1,5 @@
|
||||
import { default as Item } from './Item.js'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.js'
|
||||
import Item from './Item.mjs'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.mjs'
|
||||
|
||||
export default class ItemFormat extends Item {
|
||||
constructor () {
|
@ -1,5 +1,5 @@
|
||||
import { splitHelper, default as Item } from './Item.js'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.js'
|
||||
import Item, { splitHelper } from './Item.mjs'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.mjs'
|
||||
|
||||
export default class ItemJSON extends Item {
|
||||
constructor () {
|
@ -1,5 +1,5 @@
|
||||
import { splitHelper, default as Item } from './Item.js'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.js'
|
||||
import Item, { splitHelper } from './Item.mjs'
|
||||
import { logItemHelper } from '../MessageHandler/messageToString.mjs'
|
||||
|
||||
export default class ItemString extends Item {
|
||||
constructor () {
|
@ -1,6 +1,6 @@
|
||||
import Item from './Item.js'
|
||||
import EventHandler from '../Util/EventHandler.js'
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import Item from './Item.mjs'
|
||||
import EventHandler from '../Util/EventHandler.mjs'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
|
||||
// restructure children as if they were inserted one after another
|
||||
function integrateChildren (y, start) {
|
@ -1,3 +1,4 @@
|
||||
import BinaryEncoder from './Util/Binary/Encoder.mjs'
|
||||
|
||||
/**
|
||||
* A transaction is created for every change on the Yjs model. It is possible
|
||||
@ -58,7 +59,19 @@ export default class Transaction {
|
||||
* @type {Map<YType,Array<YEvent>>}
|
||||
*/
|
||||
this.changedParentTypes = new Map()
|
||||
this.encodedStructsLen = 0
|
||||
this._encodedStructs = new BinaryEncoder()
|
||||
this._encodedStructs.writeUint32(0)
|
||||
}
|
||||
get encodedStructs () {
|
||||
this._encodedStructs.setUint32(0, this.encodedStructsLen)
|
||||
return this._encodedStructs
|
||||
}
|
||||
}
|
||||
|
||||
export function writeStructToTransaction (transaction, struct) {
|
||||
transaction.encodedStructsLen++
|
||||
struct._toBinary(transaction._encodedStructs)
|
||||
}
|
||||
|
||||
/**
|
@ -1,8 +1,8 @@
|
||||
import Type from '../../Struct/Type.js'
|
||||
import ItemJSON from '../../Struct/ItemJSON.js'
|
||||
import ItemString from '../../Struct/ItemString.js'
|
||||
import { logID, logItemHelper } from '../../MessageHandler/messageToString.js'
|
||||
import YEvent from '../../Util/YEvent.js'
|
||||
import Type from '../../Struct/Type.mjs'
|
||||
import ItemJSON from '../../Struct/ItemJSON.mjs'
|
||||
import ItemString from '../../Struct/ItemString.mjs'
|
||||
import { logID, logItemHelper } from '../../MessageHandler/messageToString.mjs'
|
||||
import YEvent from '../../Util/YEvent.mjs'
|
||||
|
||||
/**
|
||||
* Event that describes the changes on a YArray
|
@ -1,8 +1,8 @@
|
||||
import Type from '../../Struct/Type.js'
|
||||
import Item from '../../Struct/Item.js'
|
||||
import ItemJSON from '../../Struct/ItemJSON.js'
|
||||
import { logItemHelper } from '../../MessageHandler/messageToString.js'
|
||||
import YEvent from '../../Util/YEvent.js'
|
||||
import Item from '../../Struct/Item.mjs'
|
||||
import Type from '../../Struct/Type.mjs'
|
||||
import ItemJSON from '../../Struct/ItemJSON.mjs'
|
||||
import { logItemHelper } from '../../MessageHandler/messageToString.mjs'
|
||||
import YEvent from '../../Util/YEvent.mjs'
|
||||
|
||||
/**
|
||||
* Event that describes the changes on a YMap.
|
@ -1,8 +1,8 @@
|
||||
import ItemString from '../../Struct/ItemString.js'
|
||||
import ItemEmbed from '../../Struct/ItemEmbed.js'
|
||||
import ItemFormat from '../../Struct/ItemFormat.js'
|
||||
import { logItemHelper } from '../../MessageHandler/messageToString.js'
|
||||
import { YArrayEvent, default as YArray } from '../YArray/YArray.js'
|
||||
import ItemEmbed from '../../Struct/ItemEmbed.mjs'
|
||||
import ItemString from '../../Struct/ItemString.mjs'
|
||||
import ItemFormat from '../../Struct/ItemFormat.mjs'
|
||||
import { logItemHelper } from '../../MessageHandler/messageToString.mjs'
|
||||
import { YArrayEvent, default as YArray } from '../YArray/YArray.mjs'
|
||||
|
||||
/**
|
||||
* @private
|
@ -1,6 +1,6 @@
|
||||
import YMap from '../YMap/YMap.js'
|
||||
import YXmlFragment from './YXmlFragment.js'
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.js'
|
||||
import YMap from '../YMap/YMap.mjs'
|
||||
import YXmlFragment from './YXmlFragment.mjs'
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.mjs'
|
||||
|
||||
/**
|
||||
* An YXmlElement imitates the behavior of a
|
@ -1,4 +1,4 @@
|
||||
import YEvent from '../../Util/YEvent.js'
|
||||
import YEvent from '../../Util/YEvent.mjs'
|
||||
|
||||
/**
|
||||
* An Event that describes changes on a YXml Element or Yxml Fragment
|
@ -1,9 +1,9 @@
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.js'
|
||||
import YXmlTreeWalker from './YXmlTreeWalker.js'
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.mjs'
|
||||
import YXmlTreeWalker from './YXmlTreeWalker.mjs'
|
||||
|
||||
import YArray from '../YArray/YArray.js'
|
||||
import YXmlEvent from './YXmlEvent.js'
|
||||
import { logItemHelper } from '../../MessageHandler/messageToString.js'
|
||||
import YArray from '../YArray/YArray.mjs'
|
||||
import YXmlEvent from './YXmlEvent.mjs'
|
||||
import { logItemHelper } from '../../MessageHandler/messageToString.mjs'
|
||||
|
||||
/**
|
||||
* Dom filter function.
|
@ -1,5 +1,5 @@
|
||||
import YMap from '../YMap/YMap.js'
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.js'
|
||||
import YMap from '../YMap/YMap.mjs'
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.mjs'
|
||||
|
||||
/**
|
||||
* You can manage binding to a custom type with YXmlHook.
|
@ -1,5 +1,5 @@
|
||||
import YText from '../YText/YText.js'
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.js'
|
||||
import YText from '../YText/YText.mjs'
|
||||
import { createAssociation } from '../../Bindings/DomBinding/util.mjs'
|
||||
|
||||
/**
|
||||
* Represents text in a Dom Element. In the future this type will also handle
|
@ -1,4 +1,4 @@
|
||||
import YXmlFragment from './YXmlFragment.js'
|
||||
import YXmlFragment from './YXmlFragment.mjs'
|
||||
|
||||
/**
|
||||
* Define the elements to which a set of CSS queries apply.
|
@ -1,5 +1,5 @@
|
||||
import ID from '../ID/ID.js'
|
||||
import { default as RootID, RootFakeUserID } from '../ID/RootID.js'
|
||||
import ID from '../ID/ID.mjs'
|
||||
import { default as RootID, RootFakeUserID } from '../ID/RootID.mjs'
|
||||
|
||||
/**
|
||||
* A BinaryDecoder handles the decoding of an ArrayBuffer.
|
||||
@ -25,6 +25,10 @@ export default class BinaryDecoder {
|
||||
this.pos = 0
|
||||
}
|
||||
|
||||
hasContent () {
|
||||
return this.pos !== this.uint8arr.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this decoder instance.
|
||||
* Optionally set a new position parameter.
|
@ -1,4 +1,4 @@
|
||||
import { RootFakeUserID } from '../ID/RootID.js'
|
||||
import { RootFakeUserID } from '../ID/RootID.mjs'
|
||||
|
||||
const bits7 = 0b1111111
|
||||
const bits8 = 0b11111111
|
||||
@ -127,6 +127,15 @@ export default class BinaryEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the content of another binary encoder.
|
||||
*
|
||||
* @param encoder The BinaryEncoder to be written.
|
||||
*/
|
||||
writeBinaryEncoder (encoder) {
|
||||
this.data = this.data.concat(encoder.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an ID at the current position.
|
||||
*
|
@ -1,4 +1,4 @@
|
||||
import { getStructReference } from '../structReferences.js'
|
||||
import { getStructReference } from '../structReferences.mjs'
|
||||
|
||||
export const RootFakeUserID = 0xFFFFFF
|
||||
|
@ -1,5 +1,5 @@
|
||||
import ID from './ID/ID.js'
|
||||
import isParentOf from './isParentOf.js'
|
||||
import ID from './ID/ID.mjs'
|
||||
import isParentOf from './isParentOf.mjs'
|
||||
|
||||
class ReverseOperation {
|
||||
constructor (y, transaction) {
|
@ -1,7 +1,7 @@
|
||||
|
||||
import ID from '../Util/ID/ID.js'
|
||||
import ItemJSON from '../Struct/ItemJSON.js'
|
||||
import ItemString from '../Struct/ItemString.js'
|
||||
import ID from '../Util/ID/ID.mjs'
|
||||
import ItemJSON from '../Struct/ItemJSON.mjs'
|
||||
import ItemString from '../Struct/ItemString.mjs'
|
||||
|
||||
/**
|
||||
* Try to merge all items in os with their successors.
|
@ -1,6 +1,6 @@
|
||||
import ID from './ID/ID.js'
|
||||
import RootID from './ID/RootID.js'
|
||||
import GC from '../Struct/GC.js'
|
||||
import ID from './ID/ID.mjs'
|
||||
import RootID from './ID/RootID.mjs'
|
||||
import GC from '../Struct/GC.mjs'
|
||||
|
||||
// TODO: Implement function to describe ranges
|
||||
|
@ -1,17 +1,17 @@
|
||||
import YArray from '../Types/YArray/YArray.js'
|
||||
import YMap from '../Types/YMap/YMap.js'
|
||||
import YText from '../Types/YText/YText.js'
|
||||
import YXmlText from '../Types/YXml/YXmlText.js'
|
||||
import YXmlHook from '../Types/YXml/YXmlHook.js'
|
||||
import YXmlFragment from '../Types/YXml/YXmlFragment.js'
|
||||
import YXmlElement from '../Types/YXml/YXmlElement.js'
|
||||
import Delete from '../Struct/Delete.mjs'
|
||||
import ItemJSON from '../Struct/ItemJSON.mjs'
|
||||
import ItemString from '../Struct/ItemString.mjs'
|
||||
import ItemFormat from '../Struct/ItemFormat.mjs'
|
||||
import ItemEmbed from '../Struct/ItemEmbed.mjs'
|
||||
import GC from '../Struct/GC.mjs'
|
||||
|
||||
import Delete from '../Struct/Delete.js'
|
||||
import ItemJSON from '../Struct/ItemJSON.js'
|
||||
import ItemString from '../Struct/ItemString.js'
|
||||
import ItemFormat from '../Struct/ItemFormat.js'
|
||||
import ItemEmbed from '../Struct/ItemEmbed.js'
|
||||
import GC from '../Struct/GC.js'
|
||||
import YArray from '../Types/YArray/YArray.mjs'
|
||||
import YMap from '../Types/YMap/YMap.mjs'
|
||||
import YText from '../Types/YText/YText.mjs'
|
||||
import YXmlText from '../Types/YXml/YXmlText.mjs'
|
||||
import YXmlHook from '../Types/YXml/YXmlHook.mjs'
|
||||
import YXmlFragment from '../Types/YXml/YXmlFragment.mjs'
|
||||
import YXmlElement from '../Types/YXml/YXmlElement.mjs'
|
||||
|
||||
const structs = new Map()
|
||||
const references = new Map()
|
@ -1,30 +1,30 @@
|
||||
|
||||
import Y from './Y.js'
|
||||
import UndoManager from './Util/UndoManager.js'
|
||||
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js'
|
||||
import Y from './Y.mjs'
|
||||
import UndoManager from './Util/UndoManager.mjs'
|
||||
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.mjs'
|
||||
|
||||
import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js'
|
||||
import { messageToString, messageToRoomname } from './MessageHandler/messageToString.mjs'
|
||||
|
||||
import Connector from './Connector.js'
|
||||
import Persistence from './Persistence.js'
|
||||
import YArray from './Types/YArray/YArray.js'
|
||||
import YMap from './Types/YMap/YMap.js'
|
||||
import YText from './Types/YText/YText.js'
|
||||
import YXmlText from './Types/YXml/YXmlText.js'
|
||||
import YXmlHook from './Types/YXml/YXmlHook.js'
|
||||
import YXmlFragment from './Types/YXml/YXmlFragment.js'
|
||||
import YXmlElement from './Types/YXml/YXmlElement.js'
|
||||
import BinaryDecoder from './Util/Binary/Decoder.js'
|
||||
import { getRelativePosition, fromRelativePosition } from './Util/relativePosition.js'
|
||||
import { registerStruct } from './Util/structReferences.js'
|
||||
import TextareaBinding from './Bindings/TextareaBinding/TextareaBinding.js'
|
||||
import QuillBinding from './Bindings/QuillBinding/QuillBinding.js'
|
||||
import DomBinding from './Bindings/DomBinding/DomBinding.js'
|
||||
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
|
||||
import Connector from './Connector.mjs'
|
||||
import Persistence from './Persistence.mjs'
|
||||
import YArray from './Types/YArray/YArray.mjs'
|
||||
import YMap from './Types/YMap/YMap.mjs'
|
||||
import YText from './Types/YText/YText.mjs'
|
||||
import YXmlText from './Types/YXml/YXmlText.mjs'
|
||||
import YXmlHook from './Types/YXml/YXmlHook.mjs'
|
||||
import YXmlFragment from './Types/YXml/YXmlFragment.mjs'
|
||||
import YXmlElement from './Types/YXml/YXmlElement.mjs'
|
||||
import BinaryDecoder from './Util/Binary/Decoder.mjs'
|
||||
import { getRelativePosition, fromRelativePosition } from './Util/relativePosition.mjs'
|
||||
import { registerStruct } from './Util/structReferences.mjs'
|
||||
import TextareaBinding from './Bindings/TextareaBinding/TextareaBinding.mjs'
|
||||
import QuillBinding from './Bindings/QuillBinding/QuillBinding.mjs'
|
||||
import DomBinding from './Bindings/DomBinding/DomBinding.mjs'
|
||||
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.mjs'
|
||||
|
||||
import debug from 'debug'
|
||||
import domToType from './Bindings/DomBinding/domToType.js'
|
||||
import { domsToTypes, switchAssociation } from './Bindings/DomBinding/util.js'
|
||||
import domToType from './Bindings/DomBinding/domToType.mjs'
|
||||
import { domsToTypes, switchAssociation } from './Bindings/DomBinding/util.mjs'
|
||||
|
||||
// TODO: The following assignments should be moved to yjs-dist
|
||||
Y.AbstractConnector = Connector
|
@ -1,12 +1,12 @@
|
||||
import DeleteStore from './Store/DeleteStore.js'
|
||||
import OperationStore from './Store/OperationStore.js'
|
||||
import StateStore from './Store/StateStore.js'
|
||||
import { generateRandomUint32 } from './Util/generateRandomUint32.js'
|
||||
import RootID from './Util/ID/RootID.js'
|
||||
import NamedEventHandler from './Util/NamedEventHandler.js'
|
||||
import Transaction from './Transaction.js'
|
||||
import DeleteStore from './Store/DeleteStore.mjs'
|
||||
import OperationStore from './Store/OperationStore.mjs'
|
||||
import StateStore from './Store/StateStore.mjs'
|
||||
import { generateRandomUint32 } from './Util/generateRandomUint32.mjs'
|
||||
import RootID from './Util/ID/RootID.mjs'
|
||||
import NamedEventHandler from './Util/NamedEventHandler.mjs'
|
||||
import Transaction from './Transaction.mjs'
|
||||
|
||||
export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js'
|
||||
export { default as DomBinding } from './Bindings/DomBinding/DomBinding.mjs'
|
||||
|
||||
/**
|
||||
* Anything that can be encoded with `JSON.stringify` and can be decoded with
|
||||
@ -28,7 +28,7 @@ export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js'
|
||||
* @param {AbstractPersistence} persistence Persistence adapter instance
|
||||
*/
|
||||
export default class Y extends NamedEventHandler {
|
||||
constructor (room, opts, persistence, conf = {}) {
|
||||
constructor (room, connector, persistence, conf = {}) {
|
||||
super()
|
||||
this.gcEnabled = conf.gc || false
|
||||
/**
|
||||
@ -36,16 +36,8 @@ export default class Y extends NamedEventHandler {
|
||||
* @type {String}
|
||||
*/
|
||||
this.room = room
|
||||
if (opts != null) {
|
||||
opts.connector.room = room
|
||||
}
|
||||
this._contentReady = false
|
||||
this._opts = opts
|
||||
if (typeof opts.userID !== 'number') {
|
||||
this.userID = generateRandomUint32()
|
||||
} else {
|
||||
this.userID = opts.userID
|
||||
}
|
||||
this.userID = generateRandomUint32()
|
||||
// TODO: This should be a Map so we can use encodables as keys
|
||||
this.share = {}
|
||||
this.ds = new DeleteStore(this)
|
||||
@ -61,10 +53,13 @@ export default class Y extends NamedEventHandler {
|
||||
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 (connector != null) {
|
||||
if (connector.constructor === Object) {
|
||||
connector.connector.room = room
|
||||
this.connector = new Y[connector.connector.name](this, connector.connector)
|
||||
this.connected = true
|
||||
this.emit('connectorReady')
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
@ -1,7 +1,7 @@
|
||||
import { test } from '../node_modules/cutest/cutest.mjs'
|
||||
import Chance from 'chance'
|
||||
import DeleteStore from '../src/Store/DeleteStore.js'
|
||||
import ID from '../src/Util/ID/ID.js'
|
||||
import DeleteStore from '../src/Store/DeleteStore.mjs'
|
||||
import ID from '../src/Util/ID/ID.mjs'
|
||||
|
||||
/**
|
||||
* Converts a DS to an array of length 10.
|
@ -1,5 +1,5 @@
|
||||
import { test } from '../node_modules/cutest/cutest.mjs'
|
||||
import simpleDiff from '../src/Util/simpleDiff.js'
|
||||
import simpleDiff from '../src/Util/simpleDiff.mjs'
|
||||
import Chance from 'chance'
|
||||
|
||||
function runDiffTest (t, a, b, expected) {
|
@ -1,7 +1,7 @@
|
||||
import { test } from '../node_modules/cutest/cutest.mjs'
|
||||
import BinaryEncoder from '../src/Util/Binary/Encoder.js'
|
||||
import BinaryDecoder from '../src/Util/Binary/Decoder.js'
|
||||
import { generateRandomUint32 } from '../src/Util/generateRandomUint32.js'
|
||||
import BinaryEncoder from '../src/Util/Binary/Encoder.mjs'
|
||||
import BinaryDecoder from '../src/Util/Binary/Decoder.mjs'
|
||||
import { generateRandomUint32 } from '../src/Util/generateRandomUint32.mjs'
|
||||
import Chance from 'chance'
|
||||
|
||||
function testEncoding (t, write, read, val) {
|
@ -1,7 +0,0 @@
|
||||
import './red-black-tree.js'
|
||||
import './y-array.tests.js'
|
||||
import './y-text.tests.js'
|
||||
import './y-map.tests.js'
|
||||
import './y-xml.tests.js'
|
||||
import './encode-decode.tests.js'
|
||||
import './diff.tests.js'
|
7
test/index.mjs
Normal file
7
test/index.mjs
Normal file
@ -0,0 +1,7 @@
|
||||
import './red-black-tree.mjs'
|
||||
import './y-array.tests.mjs'
|
||||
import './y-text.tests.mjs'
|
||||
import './y-map.tests.mjs'
|
||||
import './y-xml.tests.mjs'
|
||||
import './encode-decode.tests.mjs'
|
||||
import './diff.tests.mjs'
|
@ -1,5 +1,5 @@
|
||||
import RedBlackTree from '../src/Util/Tree.js'
|
||||
import ID from '../src/Util/ID/ID.js'
|
||||
import RedBlackTree from '../src/Util/Tree.mjs'
|
||||
import ID from '../src/Util/ID/ID.mjs'
|
||||
import Chance from 'chance'
|
||||
import { test, proxyConsole } from 'cutest'
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { wait, initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../tests-lib/helper.js'
|
||||
import { wait, initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../tests-lib/helper.mjs'
|
||||
import { test, proxyConsole } from 'cutest'
|
||||
|
||||
proxyConsole()
|
@ -1,4 +1,4 @@
|
||||
import { initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../tests-lib/helper.js'
|
||||
import { initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../tests-lib/helper.mjs'
|
||||
import { test, proxyConsole } from 'cutest'
|
||||
|
||||
proxyConsole()
|
@ -1,4 +1,4 @@
|
||||
import { initArrays, compareUsers, flushAll } from '../tests-lib/helper.js'
|
||||
import { initArrays, compareUsers, flushAll } from '../tests-lib/helper.mjs'
|
||||
import { test, proxyConsole } from 'cutest'
|
||||
|
||||
proxyConsole()
|
@ -1,4 +1,4 @@
|
||||
import { wait, initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../../yjs/tests-lib/helper.js'
|
||||
import { wait, initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../../yjs/tests-lib/helper.mjs'
|
||||
import { test } from 'cutest'
|
||||
|
||||
test('set property', async function xml0 (t) {
|
@ -1,14 +1,14 @@
|
||||
|
||||
import _Y from '../src/Y.dist.js'
|
||||
import { DomBinding } from '../src/Y.js'
|
||||
import TestConnector from './test-connector.js'
|
||||
import _Y from '../src/Y.dist.mjs'
|
||||
import { DomBinding } from '../src/Y.mjs'
|
||||
import TestConnector from './test-connector.mjs'
|
||||
|
||||
import Chance from 'chance'
|
||||
import ItemJSON from '../src/Struct/ItemJSON.js'
|
||||
import ItemString from '../src/Struct/ItemString.js'
|
||||
import { defragmentItemContent } from '../src/Util/defragmentItemContent.js'
|
||||
import ItemJSON from '../src/Struct/ItemJSON.mjs'
|
||||
import ItemString from '../src/Struct/ItemString.mjs'
|
||||
import { defragmentItemContent } from '../src/Util/defragmentItemContent.mjs'
|
||||
import Quill from 'quill'
|
||||
import GC from '../src/Struct/GC.js'
|
||||
import GC from '../src/Struct/GC.mjs'
|
||||
|
||||
export const Y = _Y
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { wait } from './helper'
|
||||
import { messageToString } from '../src/MessageHandler/messageToString'
|
||||
import AbstractConnector from '../src/Connector.js'
|
||||
import AbstractConnector from '../src/Connector.mjs'
|
||||
|
||||
var rooms = {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user