Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8dd7d1862 | ||
|
|
9e9f238b12 | ||
|
|
723fc77627 |
@@ -8,7 +8,7 @@ Y({
|
|||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
name: 'websockets-client',
|
name: 'websockets-client',
|
||||||
room: 'richtext-example-quill-1.0'
|
room: 'richtext-example-quill-1.0-test'
|
||||||
},
|
},
|
||||||
sourceDir: '/bower_components',
|
sourceDir: '/bower_components',
|
||||||
share: {
|
share: {
|
||||||
@@ -38,3 +38,15 @@ Y({
|
|||||||
y.share.richtext.bind(window.quill)
|
y.share.richtext.bind(window.quill)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Y({
|
||||||
|
db: {
|
||||||
|
name: 'indexeddb'
|
||||||
|
},
|
||||||
|
connector: {
|
||||||
|
name: 'websockets-client',
|
||||||
|
room: 'test42'
|
||||||
|
},
|
||||||
|
share: {
|
||||||
|
state : 'Map'
|
||||||
|
}
|
||||||
|
}).then((y) => { window.y = y })
|
||||||
15
README.md
15
README.md
@@ -71,6 +71,19 @@ require('y-text')(Y)
|
|||||||
// do the same for all modules you want to use
|
// do the same for all modules you want to use
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ES6 Syntax
|
||||||
|
```
|
||||||
|
import Y from 'yjs'
|
||||||
|
import yArray from 'y-array'
|
||||||
|
import yWebsocketsClient from 'y-webrtc'
|
||||||
|
import yMemory from 'y-memory'
|
||||||
|
import yArray from 'y-array'
|
||||||
|
import yMap from 'y-map'
|
||||||
|
import yText from 'y-text'
|
||||||
|
// ..
|
||||||
|
Y.extend(yArray, yWebsocketsClient, yMemory, yArray, yMap, yText /*, .. */)
|
||||||
|
```
|
||||||
|
|
||||||
# Text editing example
|
# Text editing example
|
||||||
Install dependencies
|
Install dependencies
|
||||||
```
|
```
|
||||||
@@ -242,7 +255,7 @@ This is a complete rewrite of the 0.5 version of Yjs. Since Yjs 0.6.0 it is poss
|
|||||||
I created this framework during my bachelor thesis at the chair of computer science 5 [(i5)](http://dbis.rwth-aachen.de/cms), RWTH University. Since December 2014 I'm working on Yjs as a part of my student worker job at the i5.
|
I created this framework during my bachelor thesis at the chair of computer science 5 [(i5)](http://dbis.rwth-aachen.de/cms), RWTH University. Since December 2014 I'm working on Yjs as a part of my student worker job at the i5.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Yjs is licensed under the [MIT License](./LICENSE.txt).
|
Yjs is licensed under the [MIT License](./LICENSE).
|
||||||
|
|
||||||
<yjs@dbis.rwth-aachen.de>
|
<yjs@dbis.rwth-aachen.de>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "12.0.0",
|
"version": "12.0.3",
|
||||||
"homepage": "y-js.org",
|
"homepage": "y-js.org",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
||||||
|
|||||||
83
y.es6
83
y.es6
@@ -67,6 +67,16 @@ module.exports = function (Y/* :any */) {
|
|||||||
this.whenSyncedListeners = []
|
this.whenSyncedListeners = []
|
||||||
return this.y.db.stopGarbageCollector()
|
return this.y.db.stopGarbageCollector()
|
||||||
}
|
}
|
||||||
|
repair () {
|
||||||
|
console.info('Repairing the state of Yjs. This can happen if messages get lost, and Yjs detects that something is wrong. If this happens often, please report an issue here: https://github.com/y-js/yjs/issues')
|
||||||
|
for (var name in this.connections) {
|
||||||
|
this.connections[name].isSynced = false
|
||||||
|
}
|
||||||
|
this.isSynced = false
|
||||||
|
this.currentSyncTarget = null
|
||||||
|
this.broadcastedHB = false
|
||||||
|
this.findNextSyncTarget()
|
||||||
|
}
|
||||||
setUserId (userId) {
|
setUserId (userId) {
|
||||||
if (this.userId == null) {
|
if (this.userId == null) {
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
@@ -696,6 +706,41 @@ module.exports = function (Y /* :any */) {
|
|||||||
if (this.gcTimeout > 0) {
|
if (this.gcTimeout > 0) {
|
||||||
garbageCollect()
|
garbageCollect()
|
||||||
}
|
}
|
||||||
|
this.repairCheckInterval = !opts.repairCheckInterval ? 6000 : opts.repairCheckInterval
|
||||||
|
this.opsReceivedTimestamp = new Date()
|
||||||
|
this.startRepairCheck()
|
||||||
|
}
|
||||||
|
startRepairCheck () {
|
||||||
|
var os = this
|
||||||
|
if (this.repairCheckInterval > 0) {
|
||||||
|
this.repairCheckIntervalHandler = setInterval(function repairOnMissingOperations () {
|
||||||
|
/*
|
||||||
|
Case 1. No ops have been received in a while (new Date() - os.opsReceivedTimestamp > os.repairCheckInterval)
|
||||||
|
- 1.1 os.listenersById is empty. Then the state was correct the whole time. -> Nothing to do (nor to update)
|
||||||
|
- 1.2 os.listenersById is not empty.
|
||||||
|
* Then the state was incorrect for at least {os.repairCheckInterval} seconds.
|
||||||
|
* -> Remove everything in os.listenersById and sync again (connector.repair())
|
||||||
|
Case 2. An op has been received in the last {os.repairCheckInterval } seconds.
|
||||||
|
It is not yet necessary to check for faulty behavior. Everything can still resolve itself. Wait for more messages.
|
||||||
|
If nothing was received for a while and os.listenersById is still not emty, we are in case 1.2
|
||||||
|
-> Do nothing
|
||||||
|
|
||||||
|
Baseline here is: we really only have to catch case 1.2..
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
new Date() - os.opsReceivedTimestamp > os.repairCheckInterval &&
|
||||||
|
Object.keys(os.listenersById).length > 0 // os.listenersById is not empty
|
||||||
|
) {
|
||||||
|
// haven't received operations for over {os.repairCheckInterval} seconds, resend state vector
|
||||||
|
os.listenersById = {}
|
||||||
|
os.opsReceivedTimestamp = new Date() // update so you don't send repair several times in a row
|
||||||
|
os.y.connector.repair()
|
||||||
|
}
|
||||||
|
}, this.repairCheckInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stopRepairCheck () {
|
||||||
|
clearInterval(this.repairCheckIntervalHandler)
|
||||||
}
|
}
|
||||||
queueGarbageCollector (id) {
|
queueGarbageCollector (id) {
|
||||||
if (this.y.isConnected()) {
|
if (this.y.isConnected()) {
|
||||||
@@ -791,6 +836,7 @@ module.exports = function (Y /* :any */) {
|
|||||||
* destroy () {
|
* destroy () {
|
||||||
clearInterval(this.gcInterval)
|
clearInterval(this.gcInterval)
|
||||||
this.gcInterval = null
|
this.gcInterval = null
|
||||||
|
this.stopRepairCheck()
|
||||||
for (var key in this.initializedTypes) {
|
for (var key in this.initializedTypes) {
|
||||||
var type = this.initializedTypes[key]
|
var type = this.initializedTypes[key]
|
||||||
if (type._destroy != null) {
|
if (type._destroy != null) {
|
||||||
@@ -830,12 +876,14 @@ module.exports = function (Y /* :any */) {
|
|||||||
/*
|
/*
|
||||||
Apply a list of operations.
|
Apply a list of operations.
|
||||||
|
|
||||||
|
* we save a timestamp, because we received new operations that could resolve ops in this.listenersById (see this.startRepairCheck)
|
||||||
* get a transaction
|
* get a transaction
|
||||||
* check whether all Struct.*.requiredOps are in the OS
|
* check whether all Struct.*.requiredOps are in the OS
|
||||||
* check if it is an expected op (otherwise wait for it)
|
* check if it is an expected op (otherwise wait for it)
|
||||||
* check if was deleted, apply a delete operation after op was applied
|
* check if was deleted, apply a delete operation after op was applied
|
||||||
*/
|
*/
|
||||||
apply (ops) {
|
apply (ops) {
|
||||||
|
this.opsReceivedTimestamp = new Date()
|
||||||
for (var i = 0; i < ops.length; i++) {
|
for (var i = 0; i < ops.length; i++) {
|
||||||
var o = ops[i]
|
var o = ops[i]
|
||||||
if (o.id == null || o.id[0] !== this.y.connector.userId) {
|
if (o.id == null || o.id[0] !== this.y.connector.userId) {
|
||||||
@@ -2225,7 +2273,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
if (this.store.forwardAppliedOperations) {
|
if (this.store.forwardAppliedOperations) {
|
||||||
var ops = []
|
var ops = []
|
||||||
ops.push({struct: 'Delete', target: [d[0], d[1]], length: del[2]})
|
ops.push({struct: 'Delete', target: [del[0], del[1]], length: del[2]})
|
||||||
this.store.y.connector.broadcastOps(ops)
|
this.store.y.connector.broadcastOps(ops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3368,19 +3416,31 @@ module.exports = Y
|
|||||||
Y.requiringModules = requiringModules
|
Y.requiringModules = requiringModules
|
||||||
|
|
||||||
Y.extend = function (name, value) {
|
Y.extend = function (name, value) {
|
||||||
if (value instanceof Y.utils.CustomTypeDefinition) {
|
if (arguments.length === 2 && typeof name === 'string') {
|
||||||
Y[name] = value.parseArguments
|
if (value instanceof Y.utils.CustomTypeDefinition) {
|
||||||
|
Y[name] = value.parseArguments
|
||||||
|
} else {
|
||||||
|
Y[name] = value
|
||||||
|
}
|
||||||
|
if (requiringModules[name] != null) {
|
||||||
|
requiringModules[name].resolve()
|
||||||
|
delete requiringModules[name]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Y[name] = value
|
for (var i = 0; i < arguments.length; i++) {
|
||||||
}
|
var f = arguments[i]
|
||||||
if (requiringModules[name] != null) {
|
if (typeof f === 'function') {
|
||||||
requiringModules[name].resolve()
|
f(Y)
|
||||||
delete requiringModules[name]
|
} else {
|
||||||
|
throw new Error('Expected function!')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Y.requestModules = requestModules
|
Y.requestModules = requestModules
|
||||||
function requestModules (modules) {
|
function requestModules (modules, sourceDir) {
|
||||||
|
sourceDir = sourceDir || '/bower_components'
|
||||||
// determine if this module was compiled for es5 or es6 (y.js vs. y.es6)
|
// determine if this module was compiled for es5 or es6 (y.js vs. y.es6)
|
||||||
// if Insert.execute is a Function, then it isnt a generator..
|
// if Insert.execute is a Function, then it isnt a generator..
|
||||||
// then load the es5(.js) files..
|
// then load the es5(.js) files..
|
||||||
@@ -3394,7 +3454,7 @@ function requestModules (modules) {
|
|||||||
// module does not exist
|
// module does not exist
|
||||||
if (typeof window !== 'undefined' && window.Y !== 'undefined') {
|
if (typeof window !== 'undefined' && window.Y !== 'undefined') {
|
||||||
var imported = document.createElement('script')
|
var imported = document.createElement('script')
|
||||||
imported.src = Y.sourceDir + '/' + modulename + '/' + modulename + extention
|
imported.src = sourceDir + '/' + modulename + '/' + modulename + extention
|
||||||
document.head.appendChild(imported)
|
document.head.appendChild(imported)
|
||||||
|
|
||||||
let requireModule = {}
|
let requireModule = {}
|
||||||
@@ -3450,10 +3510,9 @@ function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
|||||||
for (var name in opts.share) {
|
for (var name in opts.share) {
|
||||||
modules.push(opts.share[name])
|
modules.push(opts.share[name])
|
||||||
}
|
}
|
||||||
Y.sourceDir = opts.sourceDir
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
Y.requestModules(modules).then(function () {
|
Y.requestModules(modules, opts.sourceDir).then(function () {
|
||||||
if (opts == null) reject('An options object is expected! ')
|
if (opts == null) reject('An options object is expected! ')
|
||||||
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
||||||
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
||||||
|
|||||||
Reference in New Issue
Block a user