Comparing version 12.0.4 to 12.1.0
{ | ||
"name": "yjs", | ||
"version": "12.0.4", | ||
"version": "12.1.0", | ||
"homepage": "y-js.org", | ||
@@ -5,0 +5,0 @@ "authors": [ |
@@ -39,15 +39,2 @@ /* global Y, 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 }) | ||
}) |
# ![Yjs](http://y-js.org/images/yjs.png) | ||
Yjs is a framework for p2p shared editing on structured data like (rich-)text, json, and XML. | ||
It is similar to [ShareJs] and [OpenCoweb], but easy to use. | ||
Yjs is a framework for offline-first p2p shared editing on structured data like text, richtext, json, or XML. | ||
It is fairly easy to get started, as Yjs hides most of the complexity of concurrent editing. | ||
For additional information, demos, and tutorials visit [y-js.org](http://y-js.org/). | ||
@@ -45,3 +45,3 @@ | ||
## Use it! | ||
Install Yjs, and its modules with [bower](http://bower.io/), or [npm](https://www.npmjs.org/package/yjs). | ||
Install Yjs, and its modules with [bower](http://bower.io/), or [npm](https://www.npmjs.org/package/yjs). | ||
@@ -95,3 +95,3 @@ ### Bower | ||
Here is a simple example of a shared textarea | ||
``` | ||
```HTML | ||
<!DOCTYPE html> | ||
@@ -101,2 +101,3 @@ <html> | ||
<script src="./bower_components/yjs/y.js"></script> | ||
<!-- Yjs automatically includes all missing dependencies (browser only) --> | ||
<script> | ||
@@ -106,4 +107,3 @@ Y({ | ||
name: 'memory' // use memory database adapter. | ||
// name: 'indexeddb' | ||
// name: 'leveldb' | ||
// name: 'indexeddb' // use indexeddb database adapter instead for offline apps | ||
}, | ||
@@ -124,5 +124,5 @@ connector: { | ||
// Bind the textarea to y.share.textarea | ||
// Bind `y.share.textarea` to `<textarea/>` | ||
y.share.textarea.bind(document.querySelector('textarea')) | ||
} | ||
}) | ||
</script> | ||
@@ -142,2 +142,5 @@ <textarea></textarea> | ||
### Y(options) | ||
* Y.extend(module1, module2, ..) | ||
* Add extensions to Y | ||
* `Y.extend(require('y-webrtc'))` has the same semantics as `require('y-webrtc')(Y)` | ||
* options.db | ||
@@ -268,4 +271,1 @@ * Will be forwarded to the database adapter. Specify the database adaper on `options.db.name`. | ||
[ShareJs]: https://github.com/share/ShareJS | ||
[OpenCoweb]: https://github.com/opencoweb/coweb/wiki | ||
{ | ||
"name": "yjs", | ||
"version": "12.0.4", | ||
"version": "12.1.0", | ||
"description": "A framework for real-time p2p shared editing on any data", | ||
@@ -5,0 +5,0 @@ "main": "./src/y.js", |
# ![Yjs](http://y-js.org/images/yjs.png) | ||
Yjs is a framework for p2p shared editing on structured data like (rich-)text, json, and XML. | ||
It is similar to [ShareJs] and [OpenCoweb], but easy to use. | ||
Yjs is a framework for offline-first p2p shared editing on structured data like text, richtext, json, or XML. | ||
It is fairly easy to get started, as Yjs hides most of the complexity of concurrent editing. | ||
For additional information, demos, and tutorials visit [y-js.org](http://y-js.org/). | ||
@@ -45,3 +45,3 @@ | ||
## Use it! | ||
Install Yjs, and its modules with [bower](http://bower.io/), or [npm](https://www.npmjs.org/package/yjs). | ||
Install Yjs, and its modules with [bower](http://bower.io/), or [npm](https://www.npmjs.org/package/yjs). | ||
@@ -95,3 +95,3 @@ ### Bower | ||
Here is a simple example of a shared textarea | ||
``` | ||
```HTML | ||
<!DOCTYPE html> | ||
@@ -101,2 +101,3 @@ <html> | ||
<script src="./bower_components/yjs/y.js"></script> | ||
<!-- Yjs automatically includes all missing dependencies (browser only) --> | ||
<script> | ||
@@ -106,4 +107,3 @@ Y({ | ||
name: 'memory' // use memory database adapter. | ||
// name: 'indexeddb' | ||
// name: 'leveldb' | ||
// name: 'indexeddb' // use indexeddb database adapter instead for offline apps | ||
}, | ||
@@ -124,5 +124,5 @@ connector: { | ||
// Bind the textarea to y.share.textarea | ||
// Bind `y.share.textarea` to `<textarea/>` | ||
y.share.textarea.bind(document.querySelector('textarea')) | ||
} | ||
}) | ||
</script> | ||
@@ -142,2 +142,5 @@ <textarea></textarea> | ||
### Y(options) | ||
* Y.extend(module1, module2, ..) | ||
* Add extensions to Y | ||
* `Y.extend(require('y-webrtc'))` has the same semantics as `require('y-webrtc')(Y)` | ||
* options.db | ||
@@ -268,4 +271,1 @@ * Will be forwarded to the database adapter. Specify the database adaper on `options.db.name`. | ||
[ShareJs]: https://github.com/share/ShareJS | ||
[OpenCoweb]: https://github.com/opencoweb/coweb/wiki | ||
/* @flow */ | ||
'use strict' | ||
function canRead (auth) { return auth === 'read' || auth === 'write' } | ||
function canWrite (auth) { return auth === 'write' } | ||
module.exports = function (Y/* :any */) { | ||
@@ -57,2 +60,4 @@ class AbstractConnector { | ||
this.protocolVersion = 11 | ||
this.authInfo = opts.auth || null | ||
this.checkAuth = opts.checkAuth || function () { return Promise.resolve('write') } // default is everyone has write access | ||
} | ||
@@ -91,2 +96,5 @@ reconnect () { | ||
} | ||
removeUserEventListener (f) { | ||
this.userEventListeners = this.userEventListeners.filter(g => { f !== g }) | ||
} | ||
userLeft (user) { | ||
@@ -168,3 +176,4 @@ if (this.connections[user] != null) { | ||
deleteSet: deleteSet, | ||
protocolVersion: conn.protocolVersion | ||
protocolVersion: conn.protocolVersion, | ||
auth: conn.authInfo | ||
}) | ||
@@ -223,3 +232,3 @@ }) | ||
if (sender === this.userId) { | ||
return | ||
return Promise.resolve() | ||
} | ||
@@ -239,87 +248,114 @@ if (this.debug) { | ||
}) | ||
return | ||
return Promise.reject('Incompatible protocol version') | ||
} | ||
if (message.type === 'sync step 1') { | ||
let conn = this | ||
let m = message | ||
this.y.db.requestTransaction(function *() { | ||
var currentStateSet = yield* this.getStateSet() | ||
yield* this.applyDeleteSet(m.deleteSet) | ||
if (message.auth != null && this.connections[sender] != null) { | ||
// authenticate using auth in message | ||
var auth = this.checkAuth(message.auth, this.y) | ||
this.connections[sender].auth = auth | ||
auth.then(auth => { | ||
for (var f of this.userEventListeners) { | ||
f({ | ||
action: 'userAuthenticated', | ||
user: sender, | ||
auth: auth | ||
}) | ||
} | ||
}) | ||
} else if (this.connections[sender] != null && this.connections[sender].auth == null) { | ||
// authenticate without otherwise | ||
this.connections[sender].auth = this.checkAuth(null, this.y) | ||
} | ||
if (this.connections[sender] != null && this.connections[sender].auth != null) { | ||
return this.connections[sender].auth.then((auth) => { | ||
if (message.type === 'sync step 1' && canRead(auth)) { | ||
let conn = this | ||
let m = message | ||
var ds = yield* this.getDeleteSet() | ||
var ops = yield* this.getOperations(m.stateSet) | ||
conn.send(sender, { | ||
type: 'sync step 2', | ||
os: ops, | ||
stateSet: currentStateSet, | ||
deleteSet: ds, | ||
protocolVersion: this.protocolVersion | ||
}) | ||
if (this.forwardToSyncingClients) { | ||
conn.syncingClients.push(sender) | ||
setTimeout(function () { | ||
conn.syncingClients = conn.syncingClients.filter(function (cli) { | ||
return cli !== sender | ||
}) | ||
this.y.db.requestTransaction(function *() { | ||
var currentStateSet = yield* this.getStateSet() | ||
if (canWrite(auth)) { | ||
yield* this.applyDeleteSet(m.deleteSet) | ||
} | ||
var ds = yield* this.getDeleteSet() | ||
var ops = yield* this.getOperations(m.stateSet) | ||
conn.send(sender, { | ||
type: 'sync done' | ||
type: 'sync step 2', | ||
os: ops, | ||
stateSet: currentStateSet, | ||
deleteSet: ds, | ||
protocolVersion: this.protocolVersion, | ||
auth: this.authInfo | ||
}) | ||
}, 5000) // TODO: conn.syncingClientDuration) | ||
} else { | ||
conn.send(sender, { | ||
type: 'sync done' | ||
}) | ||
} | ||
conn._setSyncedWith(sender) | ||
}) | ||
} else if (message.type === 'sync step 2') { | ||
let conn = this | ||
var broadcastHB = !this.broadcastedHB | ||
this.broadcastedHB = true | ||
var db = this.y.db | ||
var defer = {} | ||
defer.promise = new Promise(function (resolve) { | ||
defer.resolve = resolve | ||
}) | ||
this.syncStep2 = defer.promise | ||
let m /* :MessageSyncStep2 */ = message | ||
db.requestTransaction(function * () { | ||
yield* this.applyDeleteSet(m.deleteSet) | ||
this.store.apply(m.os) | ||
db.requestTransaction(function * () { | ||
var ops = yield* this.getOperations(m.stateSet) | ||
if (ops.length > 0) { | ||
if (!broadcastHB) { // TODO: consider to broadcast here.. | ||
if (this.forwardToSyncingClients) { | ||
conn.syncingClients.push(sender) | ||
setTimeout(function () { | ||
conn.syncingClients = conn.syncingClients.filter(function (cli) { | ||
return cli !== sender | ||
}) | ||
conn.send(sender, { | ||
type: 'sync done' | ||
}) | ||
}, 5000) // TODO: conn.syncingClientDuration) | ||
} else { | ||
conn.send(sender, { | ||
type: 'update', | ||
ops: ops | ||
type: 'sync done' | ||
}) | ||
} else { | ||
// broadcast only once! | ||
conn.broadcastOps(ops) | ||
} | ||
conn._setSyncedWith(sender) | ||
}) | ||
} else if (message.type === 'sync step 2' && canWrite(auth)) { | ||
let conn = this | ||
var broadcastHB = !this.broadcastedHB | ||
this.broadcastedHB = true | ||
var db = this.y.db | ||
var defer = {} | ||
defer.promise = new Promise(function (resolve) { | ||
defer.resolve = resolve | ||
}) | ||
this.syncStep2 = defer.promise | ||
let m /* :MessageSyncStep2 */ = message | ||
db.requestTransaction(function * () { | ||
yield* this.applyDeleteSet(m.deleteSet) | ||
this.store.apply(m.os) | ||
db.requestTransaction(function * () { | ||
var ops = yield* this.getOperations(m.stateSet) | ||
if (ops.length > 0) { | ||
if (!broadcastHB) { // TODO: consider to broadcast here.. | ||
conn.send(sender, { | ||
type: 'update', | ||
ops: ops | ||
}) | ||
} else { | ||
// broadcast only once! | ||
conn.broadcastOps(ops) | ||
} | ||
} | ||
defer.resolve() | ||
}) | ||
}) | ||
} else if (message.type === 'sync done') { | ||
var self = this | ||
this.syncStep2.then(function () { | ||
self._setSyncedWith(sender) | ||
}) | ||
} else if (message.type === 'update' && canWrite(auth)) { | ||
if (this.forwardToSyncingClients) { | ||
for (var client of this.syncingClients) { | ||
this.send(client, message) | ||
} | ||
} | ||
defer.resolve() | ||
}) | ||
if (this.y.db.forwardAppliedOperations) { | ||
var delops = message.ops.filter(function (o) { | ||
return o.struct === 'Delete' | ||
}) | ||
if (delops.length > 0) { | ||
this.broadcastOps(delops) | ||
} | ||
} | ||
this.y.db.apply(message.ops) | ||
} | ||
}) | ||
} else if (message.type === 'sync done') { | ||
var self = this | ||
this.syncStep2.then(function () { | ||
self._setSyncedWith(sender) | ||
}) | ||
} else if (message.type === 'update') { | ||
if (this.forwardToSyncingClients) { | ||
for (var client of this.syncingClients) { | ||
this.send(client, message) | ||
} | ||
} | ||
if (this.y.db.forwardAppliedOperations) { | ||
var delops = message.ops.filter(function (o) { | ||
return o.struct === 'Delete' | ||
}) | ||
if (delops.length > 0) { | ||
this.broadcastOps(delops) | ||
} | ||
} | ||
this.y.db.apply(message.ops) | ||
} else { | ||
return Promise.reject('Unable to deliver message') | ||
} | ||
@@ -326,0 +362,0 @@ } |
@@ -27,7 +27,17 @@ /* global getRandom, async */ | ||
whenTransactionsFinished: function () { | ||
var ps = [] | ||
for (var name in this.users) { | ||
ps.push(this.users[name].y.db.whenTransactionsFinished()) | ||
} | ||
return Promise.all(ps) | ||
var self = this | ||
return new Promise(function (resolve, reject) { | ||
// The connector first has to send the messages to the db. | ||
// Wait for the checkAuth-function to resolve | ||
// The test lib only has a simple checkAuth function: `() => Promise.resolve()` | ||
// Just add a function to the event-queue, in order to wait for the event. | ||
// TODO: this may be buggy in test applications (but it isn't be for real-life apps) | ||
setTimeout(function () { | ||
var ps = [] | ||
for (var name in self.users) { | ||
ps.push(self.users[name].y.db.whenTransactionsFinished()) | ||
} | ||
Promise.all(ps).then(resolve, reject) | ||
}, 0) | ||
}) | ||
}, | ||
@@ -58,4 +68,5 @@ flushOne: function flushOne () { | ||
var user = globalRoom.users[userId] | ||
user.receiveMessage(m[0], m[1]) | ||
return user.y.db.whenTransactionsFinished() | ||
return user.receiveMessage(m[0], m[1]).then(function () { | ||
return user.y.db.whenTransactionsFinished() | ||
}, function () {}) | ||
} else { | ||
@@ -77,12 +88,10 @@ return false | ||
} else { | ||
setTimeout(function () { | ||
var c = globalRoom.flushOne() | ||
if (c) { | ||
c.then(function () { | ||
globalRoom.whenTransactionsFinished().then(nextFlush) | ||
}) | ||
} else { | ||
resolve() | ||
} | ||
}, 0) | ||
c = globalRoom.flushOne() | ||
if (c) { | ||
c.then(function () { | ||
globalRoom.whenTransactionsFinished().then(nextFlush) | ||
}) | ||
} else { | ||
resolve() | ||
} | ||
} | ||
@@ -113,3 +122,3 @@ } | ||
receiveMessage (sender, m) { | ||
super.receiveMessage(sender, JSON.parse(JSON.stringify(m))) | ||
return super.receiveMessage(sender, JSON.parse(JSON.stringify(m))) | ||
} | ||
@@ -161,3 +170,3 @@ send (userId, message) { | ||
} | ||
this.receiveMessage(m[0], m[1]) | ||
yield this.receiveMessage(m[0], m[1]) | ||
} | ||
@@ -164,0 +173,0 @@ yield self.whenTransactionsFinished() |
@@ -55,3 +55,3 @@ /* @flow */ | ||
} catch (e) { | ||
console.error('User events must not throw Errors!') | ||
console.error('Your observer threw an error. This error was caught so that Yjs still can ensure data consistency! In order to debug this error you have to check "Pause On Caught Exceptions"', e) | ||
} | ||
@@ -58,0 +58,0 @@ } |
28
src/y.js
@@ -115,11 +115,15 @@ /* @flow */ | ||
return new Promise(function (resolve, reject) { | ||
setTimeout(function () { | ||
Y.requestModules(modules).then(function () { | ||
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.name == null) reject('You must specify connector name! (missing connector.name property)') | ||
else if (opts.db == null) reject('You must specify a database! (missing db property)') | ||
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)') | ||
else if (opts.share == null) reject('You must specify a set of shared types!') | ||
else { | ||
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.name == null) reject('You must specify connector name! (missing connector.name property)') | ||
else if (opts.db == null) reject('You must specify a database! (missing db property)') | ||
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)') | ||
else if (opts.share == null) reject('You must specify a set of shared types!') | ||
else { | ||
opts = Y.utils.copyObject(opts) | ||
opts.connector = Y.utils.copyObject(opts.connector) | ||
opts.db = Y.utils.copyObject(opts.db) | ||
opts.share = Y.utils.copyObject(opts.share) | ||
setTimeout(function () { | ||
Y.requestModules(modules).then(function () { | ||
var yconfig = new YConfig(opts) | ||
@@ -131,5 +135,5 @@ yconfig.db.whenUserIdSet(function () { | ||
}) | ||
} | ||
}).catch(reject) | ||
}, 0) | ||
}).catch(reject) | ||
}, 0) | ||
} | ||
}) | ||
@@ -136,0 +140,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 3 instances in 1 package
837555
5293
0
44