Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
919dfb5e16 | ||
|
|
3b8e148d8f | ||
|
|
a77eb39218 | ||
|
|
9902da470b | ||
|
|
0ec83aa431 | ||
|
|
ff006c92d7 | ||
|
|
15a472df44 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
Examples/bower_components
|
bower_components
|
||||||
.directory
|
.directory
|
||||||
.codio
|
.codio
|
||||||
.settings
|
.settings
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Y({
|
|||||||
name: 'memory'
|
name: 'memory'
|
||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
name: 'webrtc',
|
name: 'websockets-client',
|
||||||
room: 'richtext-example18',
|
room: 'richtext-example18',
|
||||||
debug: true
|
debug: true
|
||||||
//url: 'http://127.0.0.1:2345'
|
//url: 'http://127.0.0.1:2345'
|
||||||
|
|||||||
@@ -9,7 +9,15 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"ignore": [],
|
"ignore": [],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yjs": "../",
|
"yjs": "~0.7.6",
|
||||||
"y-webrtc": "~0.6.4"
|
"y-array": "~0.7.5",
|
||||||
|
"y-map": "~0.7.2",
|
||||||
|
"y-memory": "~0.7.0",
|
||||||
|
"y-richtext": "~0.7.5",
|
||||||
|
"y-webrtc": "~0.7.1",
|
||||||
|
"y-websockets-client": "~0.7.10",
|
||||||
|
"y-text": "~0.7.1",
|
||||||
|
"y-indexeddb": "~0.7.1",
|
||||||
|
"quill": "~0.20.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "0.7.1",
|
"version": "0.8.5",
|
||||||
"homepage": "y-js.org",
|
"homepage": "y-js.org",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
||||||
|
|||||||
76
package.json
76
package.json
@@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "yjs",
|
|
||||||
"version": "0.7.1",
|
|
||||||
"description": "A framework for real-time p2p shared editing on arbitrary complex data types",
|
|
||||||
"main": "y.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "node --harmony ./node_modules/.bin/gulp test",
|
|
||||||
"lint": "./node_modules/.bin/standard"
|
|
||||||
},
|
|
||||||
"pre-commit": [
|
|
||||||
"lint",
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"standard": {
|
|
||||||
"parser": "babel-eslint",
|
|
||||||
"ignore": [
|
|
||||||
"build/**",
|
|
||||||
"dist/**",
|
|
||||||
"declarations/**",
|
|
||||||
"./y.js",
|
|
||||||
"./y.js.map"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/y-js/yjs.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"OT",
|
|
||||||
"Operational Transformation",
|
|
||||||
"collaboration",
|
|
||||||
"synchronization",
|
|
||||||
"ShareJs",
|
|
||||||
"OpenCoweb",
|
|
||||||
"concurrency"
|
|
||||||
],
|
|
||||||
"author": "Kevin Jahns",
|
|
||||||
"email": "kevin.jahns@rwth-aachen.de",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/y-js/yjs/issues"
|
|
||||||
},
|
|
||||||
"homepage": "http://y-js.org",
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-plugin-transform-runtime": "^6.1.18",
|
|
||||||
"babel-preset-es2015": "^6.1.18",
|
|
||||||
"babelify": "^7.2.0",
|
|
||||||
"browserify": "^12.0.1",
|
|
||||||
"eslint": "^1.10.2",
|
|
||||||
"gulp": "^3.9.0",
|
|
||||||
"gulp-bump": "^1.0.0",
|
|
||||||
"gulp-concat": "^2.6.0",
|
|
||||||
"gulp-filter": "^3.0.1",
|
|
||||||
"gulp-git": "^1.6.0",
|
|
||||||
"gulp-if": "^2.0.0",
|
|
||||||
"gulp-jasmine": "^2.0.1",
|
|
||||||
"gulp-jasmine-browser": "^0.2.3",
|
|
||||||
"gulp-load-plugins": "^1.0.0",
|
|
||||||
"gulp-prompt": "^0.1.2",
|
|
||||||
"gulp-rename": "^1.2.2",
|
|
||||||
"gulp-serve": "^1.2.0",
|
|
||||||
"gulp-shell": "^0.5.1",
|
|
||||||
"gulp-sourcemaps": "^1.5.2",
|
|
||||||
"gulp-tag-version": "^1.3.0",
|
|
||||||
"gulp-uglify": "^1.5.1",
|
|
||||||
"gulp-util": "^3.0.6",
|
|
||||||
"gulp-watch": "^4.3.5",
|
|
||||||
"minimist": "^1.2.0",
|
|
||||||
"pre-commit": "^1.1.1",
|
|
||||||
"regenerator": "^0.8.42",
|
|
||||||
"run-sequence": "^1.1.4",
|
|
||||||
"standard": "^5.2.2",
|
|
||||||
"vinyl-buffer": "^1.0.0",
|
|
||||||
"vinyl-source-stream": "^1.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
180
y.es6
180
y.es6
@@ -51,6 +51,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
this.debug = opts.debug === true
|
this.debug = opts.debug === true
|
||||||
this.broadcastedHB = false
|
this.broadcastedHB = false
|
||||||
this.syncStep2 = Promise.resolve()
|
this.syncStep2 = Promise.resolve()
|
||||||
|
this.broadcastOpBuffer = []
|
||||||
}
|
}
|
||||||
reconnect () {
|
reconnect () {
|
||||||
}
|
}
|
||||||
@@ -167,6 +168,31 @@ module.exports = function (Y/* :any */) {
|
|||||||
console.log(`send ${this.userId} -> ${uid}: ${message.type}`, message) // eslint-disable-line
|
console.log(`send ${this.userId} -> ${uid}: ${message.type}`, message) // eslint-disable-line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Buffer operations, and broadcast them when ready.
|
||||||
|
*/
|
||||||
|
broadcastOps (ops) {
|
||||||
|
var self = this
|
||||||
|
function broadcastOperations () {
|
||||||
|
if (self.broadcastOpBuffer.length > 0) {
|
||||||
|
self.broadcast({
|
||||||
|
type: 'update',
|
||||||
|
ops: self.broadcastOpBuffer
|
||||||
|
})
|
||||||
|
self.broadcastOpBuffer = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.broadcastOpBuffer.length === 0) {
|
||||||
|
this.broadcastOpBuffer = ops
|
||||||
|
if (this.y.db.transactionInProgress) {
|
||||||
|
this.y.db.whenTransactionsFinished().then(broadcastOperations)
|
||||||
|
} else {
|
||||||
|
setTimeout(broadcastOperations, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
You received a raw message, and you know that it is intended for Yjs. Then call this function.
|
You received a raw message, and you know that it is intended for Yjs. Then call this function.
|
||||||
*/
|
*/
|
||||||
@@ -227,15 +253,14 @@ module.exports = function (Y/* :any */) {
|
|||||||
db.requestTransaction(function * () {
|
db.requestTransaction(function * () {
|
||||||
var ops = yield* this.getOperations(m.stateSet)
|
var ops = yield* this.getOperations(m.stateSet)
|
||||||
if (ops.length > 0) {
|
if (ops.length > 0) {
|
||||||
var update /* :MessageUpdate */ = {
|
|
||||||
type: 'update',
|
|
||||||
ops: ops
|
|
||||||
}
|
|
||||||
if (!broadcastHB) { // TODO: consider to broadcast here..
|
if (!broadcastHB) { // TODO: consider to broadcast here..
|
||||||
conn.send(sender, update)
|
conn.send(sender, {
|
||||||
|
type: 'update',
|
||||||
|
ops: ops
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// broadcast only once!
|
// broadcast only once!
|
||||||
conn.broadcast(update)
|
conn.broadcastOps(ops)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer.resolve()
|
defer.resolve()
|
||||||
@@ -257,10 +282,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
return o.struct === 'Delete'
|
return o.struct === 'Delete'
|
||||||
})
|
})
|
||||||
if (delops.length > 0) {
|
if (delops.length > 0) {
|
||||||
this.broadcast({
|
this.broadcastOps(delops)
|
||||||
type: 'update',
|
|
||||||
ops: delops
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.y.db.apply(message.ops)
|
this.y.db.apply(message.ops)
|
||||||
@@ -1406,10 +1428,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
}
|
}
|
||||||
if (!this.store.y.connector.isDisconnected() && send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops)
|
if (!this.store.y.connector.isDisconnected() && send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops)
|
||||||
// is connected, and this is not going to be send in addOperation
|
// is connected, and this is not going to be send in addOperation
|
||||||
this.store.y.connector.broadcast({
|
this.store.y.connector.broadcastOps(send)
|
||||||
type: 'update',
|
|
||||||
ops: send
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1805,10 +1824,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
var ops = deletions.map(function (d) {
|
var ops = deletions.map(function (d) {
|
||||||
return {struct: 'Delete', target: [d[0], d[1]]}
|
return {struct: 'Delete', target: [d[0], d[1]]}
|
||||||
})
|
})
|
||||||
this.store.y.connector.broadcast({
|
this.store.y.connector.broadcastOps(ops)
|
||||||
type: 'update',
|
|
||||||
ops: ops
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* isGarbageCollected (id) {
|
* isGarbageCollected (id) {
|
||||||
@@ -1846,10 +1862,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
yield* this.os.put(op)
|
yield* this.os.put(op)
|
||||||
if (!this.store.y.connector.isDisconnected() && this.store.forwardAppliedOperations && op.id[0] !== '_') {
|
if (!this.store.y.connector.isDisconnected() && this.store.forwardAppliedOperations && op.id[0] !== '_') {
|
||||||
// is connected, and this is not going to be send in addOperation
|
// is connected, and this is not going to be send in addOperation
|
||||||
this.store.y.connector.broadcast({
|
this.store.y.connector.broadcastOps([op])
|
||||||
type: 'update',
|
|
||||||
ops: [op]
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
* getOperation (id/* :any */)/* :Transaction<any> */ {
|
* getOperation (id/* :any */)/* :Transaction<any> */ {
|
||||||
@@ -1909,34 +1922,8 @@ module.exports = function (Y/* :any */) {
|
|||||||
})
|
})
|
||||||
return ss
|
return ss
|
||||||
}
|
}
|
||||||
* getOperations (startSS) {
|
|
||||||
// TODO: use bounds here!
|
|
||||||
if (startSS == null) {
|
|
||||||
startSS = {}
|
|
||||||
}
|
|
||||||
var ops = []
|
|
||||||
|
|
||||||
var endSV = yield* this.getStateVector()
|
|
||||||
for (var endState of endSV) {
|
|
||||||
var user = endState.user
|
|
||||||
if (user === '_') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var startPos = startSS[user] || 0
|
|
||||||
|
|
||||||
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
|
||||||
ops.push(op)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var res = []
|
|
||||||
for (var op of ops) {
|
|
||||||
var o = yield* this.makeOperationReady(startSS, op)
|
|
||||||
res.push(o)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
Here, we make op executable for the receiving user.
|
Here, we make all missing operations executable for the receiving user.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
startSS: denotes to the SV that the remote user sent
|
startSS: denotes to the SV that the remote user sent
|
||||||
@@ -1971,7 +1958,92 @@ module.exports = function (Y/* :any */) {
|
|||||||
(startSS or currSS.. ?)
|
(startSS or currSS.. ?)
|
||||||
-> Could be necessary when I turn GC again.
|
-> Could be necessary when I turn GC again.
|
||||||
-> Is a bad(ish) idea because it requires more computation
|
-> Is a bad(ish) idea because it requires more computation
|
||||||
|
|
||||||
|
What we do:
|
||||||
|
* Iterate over all missing operations.
|
||||||
|
* When there is an operation, where the right op is known, send this op all missing ops to the left to the user
|
||||||
|
* I explained above what we have to do with each operation. Here is how we do it efficiently:
|
||||||
|
1. Go to the left until you find either op.origin, or a known operation (let o denote current operation in the iteration)
|
||||||
|
2. Found a known operation -> set op.left = o, and send it to the user. stop
|
||||||
|
3. Found o = op.origin -> set op.left = op.origin, and send it to the user. start again from 1. (set op = o)
|
||||||
|
4. Found some o -> set o.right = op, o.left = o.origin, send it to the user, continue
|
||||||
*/
|
*/
|
||||||
|
* getOperations (startSS) {
|
||||||
|
// TODO: use bounds here!
|
||||||
|
if (startSS == null) {
|
||||||
|
startSS = {}
|
||||||
|
}
|
||||||
|
var send = []
|
||||||
|
|
||||||
|
var endSV = yield* this.getStateVector()
|
||||||
|
for (var endState of endSV) {
|
||||||
|
var user = endState.user
|
||||||
|
if (user === '_') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var startPos = startSS[user] || 0
|
||||||
|
|
||||||
|
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
||||||
|
op = Y.Struct[op.struct].encode(op)
|
||||||
|
if (op.struct !== 'Insert') {
|
||||||
|
send.push(op)
|
||||||
|
} else if (op.right == null || op.right[1] < (startSS[op.right[0]] || 0)) {
|
||||||
|
// case 1. op.right is known
|
||||||
|
var o = op
|
||||||
|
// Remember: ?
|
||||||
|
// -> set op.right
|
||||||
|
// 1. to the first operation that is known (according to startSS)
|
||||||
|
// 2. or to the first operation that has an origin that is not to the
|
||||||
|
// right of op.
|
||||||
|
// For this we maintain a list of ops which origins are not found yet.
|
||||||
|
var missing_origins = [op]
|
||||||
|
var newright = op.right
|
||||||
|
while (true) {
|
||||||
|
if (o.left == null) {
|
||||||
|
op.left = null
|
||||||
|
send.push(op)
|
||||||
|
if (!Y.utils.compareIds(o.id, op.id)) {
|
||||||
|
o = Y.Struct[op.struct].encode(o)
|
||||||
|
o.right = missing_origins[missing_origins.length - 1].id
|
||||||
|
send.push(o)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
o = yield* this.getOperation(o.left)
|
||||||
|
// we set another o, check if we can reduce $missing_origins
|
||||||
|
while (missing_origins.length > 0 && Y.utils.compareIds(missing_origins[missing_origins.length - 1].origin, o.id)) {
|
||||||
|
missing_origins.pop()
|
||||||
|
}
|
||||||
|
if (o.id[1] < (startSS[o.id[0]] || 0)) {
|
||||||
|
// case 2. o is known
|
||||||
|
op.left = o.id
|
||||||
|
send.push(op)
|
||||||
|
break
|
||||||
|
} else if (Y.utils.compareIds(o.id, op.origin)) {
|
||||||
|
// case 3. o is op.origin
|
||||||
|
op.left = op.origin
|
||||||
|
send.push(op)
|
||||||
|
op = Y.Struct[op.struct].encode(o)
|
||||||
|
op.right = newright
|
||||||
|
if (missing_origins.length > 0) {
|
||||||
|
console.log('This should not happen .. :( please report this')
|
||||||
|
}
|
||||||
|
missing_origins = [op]
|
||||||
|
} else {
|
||||||
|
// case 4. send o, continue to find op.origin
|
||||||
|
var s = Y.Struct[op.struct].encode(o)
|
||||||
|
s.right = missing_origins[missing_origins.length - 1].id
|
||||||
|
s.left = s.origin
|
||||||
|
send.push(s)
|
||||||
|
missing_origins.push(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return send.reverse()
|
||||||
|
}
|
||||||
|
/* this is what we used before.. use this as a reference..
|
||||||
* makeOperationReady (startSS, op) {
|
* makeOperationReady (startSS, op) {
|
||||||
op = Y.Struct[op.struct].encode(op)
|
op = Y.Struct[op.struct].encode(op)
|
||||||
op = Y.utils.copyObject(op)
|
op = Y.utils.copyObject(op)
|
||||||
@@ -1995,6 +2067,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
op.left = op.origin
|
op.left = op.origin
|
||||||
return op
|
return op
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
Y.Transaction = TransactionInterface
|
Y.Transaction = TransactionInterface
|
||||||
}
|
}
|
||||||
@@ -2325,8 +2398,9 @@ function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
|||||||
Y.sourceDir = opts.sourceDir
|
Y.sourceDir = opts.sourceDir
|
||||||
return Y.requestModules(modules).then(function () {
|
return Y.requestModules(modules).then(function () {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
var yconfig = new YConfig(opts, function () {
|
var yconfig = new YConfig(opts)
|
||||||
yconfig.db.whenUserIdSet(function () {
|
yconfig.db.whenUserIdSet(function () {
|
||||||
|
yconfig.init(function () {
|
||||||
resolve(yconfig)
|
resolve(yconfig)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -2343,6 +2417,10 @@ class YConfig {
|
|||||||
constructor (opts, callback) {
|
constructor (opts, callback) {
|
||||||
this.db = new Y[opts.db.name](this, opts.db)
|
this.db = new Y[opts.db.name](this, opts.db)
|
||||||
this.connector = new Y[opts.connector.name](this, opts.connector)
|
this.connector = new Y[opts.connector.name](this, opts.connector)
|
||||||
|
this.options = opts
|
||||||
|
}
|
||||||
|
init (callback) {
|
||||||
|
var opts = this.options
|
||||||
var share = {}
|
var share = {}
|
||||||
this.share = share
|
this.share = share
|
||||||
this.db.requestTransaction(function * requestTransaction () {
|
this.db.requestTransaction(function * requestTransaction () {
|
||||||
|
|||||||
Reference in New Issue
Block a user