Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
549ab76b42 |
@@ -36,4 +36,17 @@ Y({
|
|||||||
});
|
});
|
||||||
// bind quill to richtext type
|
// bind quill to richtext type
|
||||||
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 })
|
||||||
22
README.md
22
README.md
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
# 
|
# 
|
||||||
|
|
||||||
Yjs is a framework for offline-first p2p shared editing on structured data like text, richtext, json, or XML.
|
Yjs is a framework for p2p shared editing on structured data like (rich-)text, json, and XML.
|
||||||
It is fairly easy to get started, as Yjs hides most of the complexity of concurrent editing.
|
It is similar to [ShareJs] and [OpenCoweb], but easy to use.
|
||||||
For additional information, demos, and tutorials visit [y-js.org](http://y-js.org/).
|
For additional information, demos, and tutorials visit [y-js.org](http://y-js.org/).
|
||||||
|
|
||||||
### Extensions
|
### Extensions
|
||||||
@@ -42,7 +42,7 @@ Connectors, Databases, and Types are available as modules that extend Yjs. Here
|
|||||||
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to the [Quill Rich Text Editor](http://quilljs.com/)|
|
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to the [Quill Rich Text Editor](http://quilljs.com/)|
|
||||||
|
|
||||||
## Use it!
|
## 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).
|
||||||
|
|
||||||
### Bower
|
### Bower
|
||||||
```
|
```
|
||||||
@@ -91,17 +91,17 @@ bower i yjs y-memory y-webrtc y-array y-text
|
|||||||
```
|
```
|
||||||
|
|
||||||
Here is a simple example of a shared textarea
|
Here is a simple example of a shared textarea
|
||||||
```HTML
|
```
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<script src="./bower_components/yjs/y.js"></script>
|
<script src="./bower_components/yjs/y.js"></script>
|
||||||
<!-- Yjs automatically includes all missing dependencies (browser only) -->
|
|
||||||
<script>
|
<script>
|
||||||
Y({
|
Y({
|
||||||
db: {
|
db: {
|
||||||
name: 'memory' // use memory database adapter.
|
name: 'memory' // use memory database adapter.
|
||||||
// name: 'indexeddb' // use indexeddb database adapter instead for offline apps
|
// name: 'indexeddb'
|
||||||
|
// name: 'leveldb'
|
||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
name: 'webrtc', // use webrtc connector
|
name: 'webrtc', // use webrtc connector
|
||||||
@@ -117,9 +117,9 @@ Here is a simple example of a shared textarea
|
|||||||
// The Yjs instance `y` is available
|
// The Yjs instance `y` is available
|
||||||
// y.share.* contains the shared types
|
// y.share.* contains the shared types
|
||||||
|
|
||||||
// Bind `y.share.textarea` to `<textarea/>`
|
// Bind the textarea to y.share.textarea
|
||||||
y.share.textarea.bind(document.querySelector('textarea'))
|
y.share.textarea.bind(document.querySelector('textarea'))
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
<textarea></textarea>
|
<textarea></textarea>
|
||||||
</body>
|
</body>
|
||||||
@@ -134,9 +134,6 @@ Report _any_ issues to the [Github issue page](https://github.com/y-js/yjs/issue
|
|||||||
# API
|
# API
|
||||||
|
|
||||||
### Y(options)
|
### 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
|
* options.db
|
||||||
* Will be forwarded to the database adapter. Specify the database adaper on `options.db.name`.
|
* Will be forwarded to the database adapter. Specify the database adaper on `options.db.name`.
|
||||||
* Have a look at the used database adapter repository to see all available options.
|
* Have a look at the used database adapter repository to see all available options.
|
||||||
@@ -262,3 +259,6 @@ Yjs is licensed under the [MIT License](./LICENSE).
|
|||||||
|
|
||||||
<yjs@dbis.rwth-aachen.de>
|
<yjs@dbis.rwth-aachen.de>
|
||||||
|
|
||||||
|
[ShareJs]: https://github.com/share/ShareJS
|
||||||
|
[OpenCoweb]: https://github.com/opencoweb/coweb/wiki
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "12.1.0",
|
"version": "12.0.4",
|
||||||
"homepage": "y-js.org",
|
"homepage": "y-js.org",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
||||||
|
|||||||
273
y.es6
273
y.es6
@@ -2,9 +2,6 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
function canRead (auth) { return auth === 'read' || auth === 'write' }
|
|
||||||
function canWrite (auth) { return auth === 'write' }
|
|
||||||
|
|
||||||
module.exports = function (Y/* :any */) {
|
module.exports = function (Y/* :any */) {
|
||||||
class AbstractConnector {
|
class AbstractConnector {
|
||||||
/* ::
|
/* ::
|
||||||
@@ -58,8 +55,6 @@ module.exports = function (Y/* :any */) {
|
|||||||
this.syncStep2 = Promise.resolve()
|
this.syncStep2 = Promise.resolve()
|
||||||
this.broadcastOpBuffer = []
|
this.broadcastOpBuffer = []
|
||||||
this.protocolVersion = 11
|
this.protocolVersion = 11
|
||||||
this.authInfo = opts.auth || null
|
|
||||||
this.checkAuth = opts.checkAuth || function () { return Promise.resolve('write') } // default is everyone has write access
|
|
||||||
}
|
}
|
||||||
reconnect () {
|
reconnect () {
|
||||||
}
|
}
|
||||||
@@ -93,9 +88,6 @@ module.exports = function (Y/* :any */) {
|
|||||||
onUserEvent (f) {
|
onUserEvent (f) {
|
||||||
this.userEventListeners.push(f)
|
this.userEventListeners.push(f)
|
||||||
}
|
}
|
||||||
removeUserEventListener (f) {
|
|
||||||
this.userEventListeners = this.userEventListeners.filter(g => { f !== g })
|
|
||||||
}
|
|
||||||
userLeft (user) {
|
userLeft (user) {
|
||||||
if (this.connections[user] != null) {
|
if (this.connections[user] != null) {
|
||||||
delete this.connections[user]
|
delete this.connections[user]
|
||||||
@@ -172,8 +164,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
type: 'sync step 1',
|
type: 'sync step 1',
|
||||||
stateSet: stateSet,
|
stateSet: stateSet,
|
||||||
deleteSet: deleteSet,
|
deleteSet: deleteSet,
|
||||||
protocolVersion: conn.protocolVersion,
|
protocolVersion: conn.protocolVersion
|
||||||
auth: conn.authInfo
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -227,7 +218,7 @@ module.exports = function (Y/* :any */) {
|
|||||||
*/
|
*/
|
||||||
receiveMessage (sender/* :UserId */, message/* :Message */) {
|
receiveMessage (sender/* :UserId */, message/* :Message */) {
|
||||||
if (sender === this.userId) {
|
if (sender === this.userId) {
|
||||||
return Promise.resolve()
|
return
|
||||||
}
|
}
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.log(`receive ${sender} -> ${this.userId}: ${message.type}`, JSON.parse(JSON.stringify(message))) // eslint-disable-line
|
console.log(`receive ${sender} -> ${this.userId}: ${message.type}`, JSON.parse(JSON.stringify(message))) // eslint-disable-line
|
||||||
@@ -242,118 +233,91 @@ module.exports = function (Y/* :any */) {
|
|||||||
type: 'sync stop',
|
type: 'sync stop',
|
||||||
protocolVersion: this.protocolVersion
|
protocolVersion: this.protocolVersion
|
||||||
})
|
})
|
||||||
return Promise.reject('Incompatible protocol version')
|
return
|
||||||
}
|
}
|
||||||
if (message.auth != null && this.connections[sender] != null) {
|
if (message.type === 'sync step 1') {
|
||||||
// authenticate using auth in message
|
let conn = this
|
||||||
var auth = this.checkAuth(message.auth, this.y)
|
let m = message
|
||||||
this.connections[sender].auth = auth
|
this.y.db.requestTransaction(function *() {
|
||||||
auth.then(auth => {
|
var currentStateSet = yield* this.getStateSet()
|
||||||
for (var f of this.userEventListeners) {
|
yield* this.applyDeleteSet(m.deleteSet)
|
||||||
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
|
|
||||||
|
|
||||||
this.y.db.requestTransaction(function *() {
|
var ds = yield* this.getDeleteSet()
|
||||||
var currentStateSet = yield* this.getStateSet()
|
var ops = yield* this.getOperations(m.stateSet)
|
||||||
if (canWrite(auth)) {
|
conn.send(sender, {
|
||||||
yield* this.applyDeleteSet(m.deleteSet)
|
type: 'sync step 2',
|
||||||
}
|
os: ops,
|
||||||
|
stateSet: currentStateSet,
|
||||||
var ds = yield* this.getDeleteSet()
|
deleteSet: ds,
|
||||||
var ops = yield* this.getOperations(m.stateSet)
|
protocolVersion: this.protocolVersion
|
||||||
|
})
|
||||||
|
if (this.forwardToSyncingClients) {
|
||||||
|
conn.syncingClients.push(sender)
|
||||||
|
setTimeout(function () {
|
||||||
|
conn.syncingClients = conn.syncingClients.filter(function (cli) {
|
||||||
|
return cli !== sender
|
||||||
|
})
|
||||||
conn.send(sender, {
|
conn.send(sender, {
|
||||||
type: 'sync step 2',
|
type: 'sync done'
|
||||||
os: ops,
|
|
||||||
stateSet: currentStateSet,
|
|
||||||
deleteSet: ds,
|
|
||||||
protocolVersion: this.protocolVersion,
|
|
||||||
auth: this.authInfo
|
|
||||||
})
|
})
|
||||||
if (this.forwardToSyncingClients) {
|
}, 5000) // TODO: conn.syncingClientDuration)
|
||||||
conn.syncingClients.push(sender)
|
} else {
|
||||||
setTimeout(function () {
|
conn.send(sender, {
|
||||||
conn.syncingClients = conn.syncingClients.filter(function (cli) {
|
type: 'sync done'
|
||||||
return cli !== sender
|
|
||||||
})
|
|
||||||
conn.send(sender, {
|
|
||||||
type: 'sync done'
|
|
||||||
})
|
|
||||||
}, 5000) // TODO: conn.syncingClientDuration)
|
|
||||||
} else {
|
|
||||||
conn.send(sender, {
|
|
||||||
type: 'sync done'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
conn._setSyncedWith(sender)
|
||||||
})
|
})
|
||||||
} else {
|
} else if (message.type === 'sync step 2') {
|
||||||
return Promise.reject('Unable to deliver message')
|
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') {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_setSyncedWith (user) {
|
_setSyncedWith (user) {
|
||||||
@@ -487,21 +451,11 @@ module.exports = function (Y) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
whenTransactionsFinished: function () {
|
whenTransactionsFinished: function () {
|
||||||
var self = this
|
var ps = []
|
||||||
return new Promise(function (resolve, reject) {
|
for (var name in this.users) {
|
||||||
// The connector first has to send the messages to the db.
|
ps.push(this.users[name].y.db.whenTransactionsFinished())
|
||||||
// Wait for the checkAuth-function to resolve
|
}
|
||||||
// The test lib only has a simple checkAuth function: `() => Promise.resolve()`
|
return Promise.all(ps)
|
||||||
// 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)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
flushOne: function flushOne () {
|
flushOne: function flushOne () {
|
||||||
var bufs = []
|
var bufs = []
|
||||||
@@ -527,9 +481,8 @@ module.exports = function (Y) {
|
|||||||
delete buff[sender]
|
delete buff[sender]
|
||||||
}
|
}
|
||||||
var user = globalRoom.users[userId]
|
var user = globalRoom.users[userId]
|
||||||
return user.receiveMessage(m[0], m[1]).then(function () {
|
user.receiveMessage(m[0], m[1])
|
||||||
return user.y.db.whenTransactionsFinished()
|
return user.y.db.whenTransactionsFinished()
|
||||||
}, function () {})
|
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -546,14 +499,16 @@ module.exports = function (Y) {
|
|||||||
}
|
}
|
||||||
globalRoom.whenTransactionsFinished().then(nextFlush)
|
globalRoom.whenTransactionsFinished().then(nextFlush)
|
||||||
} else {
|
} else {
|
||||||
c = globalRoom.flushOne()
|
setTimeout(function () {
|
||||||
if (c) {
|
var c = globalRoom.flushOne()
|
||||||
c.then(function () {
|
if (c) {
|
||||||
globalRoom.whenTransactionsFinished().then(nextFlush)
|
c.then(function () {
|
||||||
})
|
globalRoom.whenTransactionsFinished().then(nextFlush)
|
||||||
} else {
|
})
|
||||||
resolve()
|
} else {
|
||||||
}
|
resolve()
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalRoom.whenTransactionsFinished().then(nextFlush)
|
globalRoom.whenTransactionsFinished().then(nextFlush)
|
||||||
@@ -579,7 +534,7 @@ module.exports = function (Y) {
|
|||||||
this.syncingClientDuration = 0
|
this.syncingClientDuration = 0
|
||||||
}
|
}
|
||||||
receiveMessage (sender, m) {
|
receiveMessage (sender, m) {
|
||||||
return super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
|
super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
|
||||||
}
|
}
|
||||||
send (userId, message) {
|
send (userId, message) {
|
||||||
var buffer = globalRoom.buffers[userId]
|
var buffer = globalRoom.buffers[userId]
|
||||||
@@ -626,7 +581,7 @@ module.exports = function (Y) {
|
|||||||
if (buff[sender].length === 0) {
|
if (buff[sender].length === 0) {
|
||||||
delete buff[sender]
|
delete buff[sender]
|
||||||
}
|
}
|
||||||
yield this.receiveMessage(m[0], m[1])
|
this.receiveMessage(m[0], m[1])
|
||||||
}
|
}
|
||||||
yield self.whenTransactionsFinished()
|
yield self.whenTransactionsFinished()
|
||||||
})
|
})
|
||||||
@@ -2741,7 +2696,7 @@ module.exports = function (Y /* : any*/) {
|
|||||||
try {
|
try {
|
||||||
this.eventListeners[i](event)
|
this.eventListeners[i](event)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
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)
|
console.error('User events must not throw Errors!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3559,28 +3514,24 @@ function Y (opts/* :YOptions */) /* :Promise<YConfig> */ {
|
|||||||
modules.push(opts.share[name])
|
modules.push(opts.share[name])
|
||||||
}
|
}
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
if (opts == null) reject('An options object is expected! ')
|
setTimeout(function () {
|
||||||
else if (opts.connector == null) reject('You must specify a connector! (missing connector property)')
|
Y.requestModules(modules).then(function () {
|
||||||
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
if (opts == null) reject('An options object is expected! ')
|
||||||
else if (opts.db == null) reject('You must specify a database! (missing db 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 db name! (missing db.name property)')
|
else if (opts.connector.name == null) reject('You must specify connector name! (missing connector.name property)')
|
||||||
else if (opts.share == null) reject('You must specify a set of shared types!')
|
else if (opts.db == null) reject('You must specify a database! (missing db property)')
|
||||||
else {
|
else if (opts.connector.name == null) reject('You must specify db name! (missing db.name property)')
|
||||||
opts = Y.utils.copyObject(opts)
|
else if (opts.share == null) reject('You must specify a set of shared types!')
|
||||||
opts.connector = Y.utils.copyObject(opts.connector)
|
else {
|
||||||
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)
|
var yconfig = new YConfig(opts)
|
||||||
yconfig.db.whenUserIdSet(function () {
|
yconfig.db.whenUserIdSet(function () {
|
||||||
yconfig.init(function () {
|
yconfig.init(function () {
|
||||||
resolve(yconfig)
|
resolve(yconfig)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).catch(reject)
|
}
|
||||||
}, 0)
|
}).catch(reject)
|
||||||
}
|
}, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user