sharedb 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client/connection.js +11 -2
- package/lib/client/doc.js +3 -2
- package/lib/client/presence/doc-presence-emitter.js +75 -0
- package/lib/client/presence/local-doc-presence.js +11 -10
- package/lib/client/presence/presence.js +16 -2
- package/lib/client/presence/remote-doc-presence.js +9 -8
- package/package.json +1 -1
- package/test/client/doc.js +21 -1
- package/test/client/presence/doc-presence-emitter.js +100 -0
- package/test/client/presence/doc-presence.js +10 -0
- package/test/client/presence/presence.js +112 -0
- package/CHANGELOG.md +0 -44
package/lib/client/connection.js
CHANGED
|
@@ -10,6 +10,7 @@ var ACTIONS = require('../message-actions').ACTIONS;
|
|
|
10
10
|
var types = require('../types');
|
|
11
11
|
var util = require('../util');
|
|
12
12
|
var logger = require('../logger');
|
|
13
|
+
var DocPresenceEmitter = require('./presence/doc-presence-emitter');
|
|
13
14
|
|
|
14
15
|
var ERROR_CODE = ShareDBError.CODES;
|
|
15
16
|
|
|
@@ -51,6 +52,7 @@ function Connection(socket) {
|
|
|
51
52
|
|
|
52
53
|
// Maps from channel -> presence objects
|
|
53
54
|
this._presences = {};
|
|
55
|
+
this._docPresenceEmitter = new DocPresenceEmitter();
|
|
54
56
|
|
|
55
57
|
// Map from snapshot request ID -> snapshot request
|
|
56
58
|
this._snapshotRequests = {};
|
|
@@ -521,6 +523,7 @@ Connection.prototype.get = function(collection, id) {
|
|
|
521
523
|
this.emit('doc', doc);
|
|
522
524
|
}
|
|
523
525
|
|
|
526
|
+
doc._wantsDestroy = false;
|
|
524
527
|
return doc;
|
|
525
528
|
};
|
|
526
529
|
|
|
@@ -531,7 +534,9 @@ Connection.prototype.get = function(collection, id) {
|
|
|
531
534
|
* @private
|
|
532
535
|
*/
|
|
533
536
|
Connection.prototype._destroyDoc = function(doc) {
|
|
537
|
+
if (!doc._wantsDestroy) return;
|
|
534
538
|
util.digAndRemove(this.collections, doc.collection, doc.id);
|
|
539
|
+
doc.emit('destroy');
|
|
535
540
|
};
|
|
536
541
|
|
|
537
542
|
Connection.prototype._addDoc = function(doc) {
|
|
@@ -773,17 +778,21 @@ Connection.prototype._initialize = function(message) {
|
|
|
773
778
|
|
|
774
779
|
Connection.prototype.getPresence = function(channel) {
|
|
775
780
|
var connection = this;
|
|
776
|
-
|
|
781
|
+
var presence = util.digOrCreate(this._presences, channel, function() {
|
|
777
782
|
return new Presence(connection, channel);
|
|
778
783
|
});
|
|
784
|
+
presence._wantsDestroy = false;
|
|
785
|
+
return presence;
|
|
779
786
|
};
|
|
780
787
|
|
|
781
788
|
Connection.prototype.getDocPresence = function(collection, id) {
|
|
782
789
|
var channel = DocPresence.channel(collection, id);
|
|
783
790
|
var connection = this;
|
|
784
|
-
|
|
791
|
+
var presence = util.digOrCreate(this._presences, channel, function() {
|
|
785
792
|
return new DocPresence(connection, collection, id);
|
|
786
793
|
});
|
|
794
|
+
presence._wantsDestroy = false;
|
|
795
|
+
return presence;
|
|
787
796
|
};
|
|
788
797
|
|
|
789
798
|
Connection.prototype._sendPresenceAction = function(action, seq, presence) {
|
package/lib/client/doc.js
CHANGED
|
@@ -76,6 +76,8 @@ function Doc(connection, collection, id) {
|
|
|
76
76
|
// Whether to re-establish the subscription on reconnect
|
|
77
77
|
this.wantSubscribe = false;
|
|
78
78
|
|
|
79
|
+
this._wantsDestroy = false;
|
|
80
|
+
|
|
79
81
|
// The op that is currently roundtripping to the server, or null.
|
|
80
82
|
//
|
|
81
83
|
// When the connection reconnects, the inflight op is resubmitted.
|
|
@@ -122,6 +124,7 @@ function Doc(connection, collection, id) {
|
|
|
122
124
|
emitter.mixin(Doc);
|
|
123
125
|
|
|
124
126
|
Doc.prototype.destroy = function(callback) {
|
|
127
|
+
this._wantsDestroy = true;
|
|
125
128
|
var doc = this;
|
|
126
129
|
doc.whenNothingPending(function() {
|
|
127
130
|
if (doc.wantSubscribe) {
|
|
@@ -131,12 +134,10 @@ Doc.prototype.destroy = function(callback) {
|
|
|
131
134
|
return doc.emit('error', err);
|
|
132
135
|
}
|
|
133
136
|
doc.connection._destroyDoc(doc);
|
|
134
|
-
doc.emit('destroy');
|
|
135
137
|
if (callback) callback();
|
|
136
138
|
});
|
|
137
139
|
} else {
|
|
138
140
|
doc.connection._destroyDoc(doc);
|
|
139
|
-
doc.emit('destroy');
|
|
140
141
|
if (callback) callback();
|
|
141
142
|
}
|
|
142
143
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
var util = require('../../util');
|
|
2
|
+
var EventEmitter = require('events').EventEmitter;
|
|
3
|
+
|
|
4
|
+
var EVENTS = [
|
|
5
|
+
'create',
|
|
6
|
+
'del',
|
|
7
|
+
'destroy',
|
|
8
|
+
'load',
|
|
9
|
+
'op'
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
module.exports = DocPresenceEmitter;
|
|
13
|
+
|
|
14
|
+
function DocPresenceEmitter() {
|
|
15
|
+
this._docs = {};
|
|
16
|
+
this._forwarders = {};
|
|
17
|
+
this._emitters = {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
DocPresenceEmitter.prototype.addEventListener = function(doc, event, listener) {
|
|
21
|
+
this._registerDoc(doc);
|
|
22
|
+
var emitter = util.dig(this._emitters, doc.collection, doc.id);
|
|
23
|
+
emitter.on(event, listener);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
DocPresenceEmitter.prototype.removeEventListener = function(doc, event, listener) {
|
|
27
|
+
var emitter = util.dig(this._emitters, doc.collection, doc.id);
|
|
28
|
+
if (!emitter) return;
|
|
29
|
+
emitter.off(event, listener);
|
|
30
|
+
// We'll always have at least one, because of the destroy listener
|
|
31
|
+
if (emitter._eventsCount === 1) this._unregisterDoc(doc);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
DocPresenceEmitter.prototype._registerDoc = function(doc) {
|
|
35
|
+
var alreadyRegistered = true;
|
|
36
|
+
util.digOrCreate(this._docs, doc.collection, doc.id, function() {
|
|
37
|
+
alreadyRegistered = false;
|
|
38
|
+
return doc;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (alreadyRegistered) return;
|
|
42
|
+
|
|
43
|
+
var emitter = util.digOrCreate(this._emitters, doc.collection, doc.id, function() {
|
|
44
|
+
var e = new EventEmitter();
|
|
45
|
+
// Set a high limit to avoid unnecessary warnings, but still
|
|
46
|
+
// retain some degree of memory leak detection
|
|
47
|
+
e.setMaxListeners(1000);
|
|
48
|
+
return e;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
var self = this;
|
|
52
|
+
EVENTS.forEach(function(event) {
|
|
53
|
+
var forwarder = util.digOrCreate(self._forwarders, doc.collection, doc.id, event, function() {
|
|
54
|
+
return emitter.emit.bind(emitter, event);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
doc.on(event, forwarder);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.addEventListener(doc, 'destroy', this._unregisterDoc.bind(this, doc));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
DocPresenceEmitter.prototype._unregisterDoc = function(doc) {
|
|
64
|
+
var forwarders = util.dig(this._forwarders, doc.collection, doc.id);
|
|
65
|
+
for (var event in forwarders) {
|
|
66
|
+
doc.off(event, forwarders[event]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var emitter = util.dig(this._emitters, doc.collection, doc.id);
|
|
70
|
+
emitter.removeAllListeners();
|
|
71
|
+
|
|
72
|
+
util.digAndRemove(this._forwarders, doc.collection, doc.id);
|
|
73
|
+
util.digAndRemove(this._emitters, doc.collection, doc.id);
|
|
74
|
+
util.digAndRemove(this._docs, doc.collection, doc.id);
|
|
75
|
+
};
|
|
@@ -11,6 +11,7 @@ function LocalDocPresence(presence, presenceId) {
|
|
|
11
11
|
this.id = this.presence.id;
|
|
12
12
|
|
|
13
13
|
this._doc = this.connection.get(this.collection, this.id);
|
|
14
|
+
this._emitter = this.connection._docPresenceEmitter;
|
|
14
15
|
this._isSending = false;
|
|
15
16
|
this._docDataVersionByPresenceVersion = {};
|
|
16
17
|
|
|
@@ -43,11 +44,11 @@ LocalDocPresence.prototype.submit = function(value, callback) {
|
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
LocalDocPresence.prototype.destroy = function(callback) {
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
50
|
-
this.
|
|
47
|
+
this._emitter.removeEventListener(this._doc, 'op', this._opHandler);
|
|
48
|
+
this._emitter.removeEventListener(this._doc, 'create', this._createOrDelHandler);
|
|
49
|
+
this._emitter.removeEventListener(this._doc, 'del', this._createOrDelHandler);
|
|
50
|
+
this._emitter.removeEventListener(this._doc, 'load', this._loadHandler);
|
|
51
|
+
this._emitter.removeEventListener(this._doc, 'destroy', this._destroyHandler);
|
|
51
52
|
|
|
52
53
|
LocalPresence.prototype.destroy.call(this, callback);
|
|
53
54
|
};
|
|
@@ -72,11 +73,11 @@ LocalDocPresence.prototype._sendPending = function() {
|
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
LocalDocPresence.prototype._registerWithDoc = function() {
|
|
75
|
-
this.
|
|
76
|
-
this.
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
76
|
+
this._emitter.addEventListener(this._doc, 'op', this._opHandler);
|
|
77
|
+
this._emitter.addEventListener(this._doc, 'create', this._createOrDelHandler);
|
|
78
|
+
this._emitter.addEventListener(this._doc, 'del', this._createOrDelHandler);
|
|
79
|
+
this._emitter.addEventListener(this._doc, 'load', this._loadHandler);
|
|
80
|
+
this._emitter.addEventListener(this._doc, 'destroy', this._destroyHandler);
|
|
80
81
|
};
|
|
81
82
|
|
|
82
83
|
LocalDocPresence.prototype._transformAgainstOp = function(op, source) {
|
|
@@ -24,6 +24,7 @@ function Presence(connection, channel) {
|
|
|
24
24
|
|
|
25
25
|
this._remotePresenceInstances = {};
|
|
26
26
|
this._subscriptionCallbacksBySeq = {};
|
|
27
|
+
this._wantsDestroy = false;
|
|
27
28
|
}
|
|
28
29
|
emitter.mixin(Presence);
|
|
29
30
|
|
|
@@ -36,6 +37,9 @@ Presence.prototype.unsubscribe = function(callback) {
|
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
Presence.prototype.create = function(id) {
|
|
40
|
+
if (this._wantsDestroy) {
|
|
41
|
+
throw new Error('Presence is being destroyed');
|
|
42
|
+
}
|
|
39
43
|
id = id || hat();
|
|
40
44
|
var localPresence = this._createLocalPresence(id);
|
|
41
45
|
this.localPresences[id] = localPresence;
|
|
@@ -43,10 +47,15 @@ Presence.prototype.create = function(id) {
|
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
Presence.prototype.destroy = function(callback) {
|
|
50
|
+
this._wantsDestroy = true;
|
|
46
51
|
var presence = this;
|
|
52
|
+
// Store these at the time of destruction: any LocalPresence on this
|
|
53
|
+
// instance at this time will be destroyed, but if the destroy is
|
|
54
|
+
// cancelled, any future LocalPresence objects will be kept.
|
|
55
|
+
// See: https://github.com/share/sharedb/pull/579
|
|
56
|
+
var localIds = Object.keys(presence.localPresences);
|
|
47
57
|
this.unsubscribe(function(error) {
|
|
48
58
|
if (error) return presence._callbackOrEmit(error, callback);
|
|
49
|
-
var localIds = Object.keys(presence.localPresences);
|
|
50
59
|
var remoteIds = Object.keys(presence._remotePresenceInstances);
|
|
51
60
|
async.parallel(
|
|
52
61
|
[
|
|
@@ -56,13 +65,18 @@ Presence.prototype.destroy = function(callback) {
|
|
|
56
65
|
}, next);
|
|
57
66
|
},
|
|
58
67
|
function(next) {
|
|
68
|
+
// We don't bother stashing the RemotePresence instances because
|
|
69
|
+
// they're not really bound to our local state: if we want to
|
|
70
|
+
// destroy, we destroy them all, but if we cancel the destroy,
|
|
71
|
+
// we'll want to keep them all
|
|
72
|
+
if (!presence._wantsDestroy) return next();
|
|
59
73
|
async.each(remoteIds, function(presenceId, next) {
|
|
60
74
|
presence._remotePresenceInstances[presenceId].destroy(next);
|
|
61
75
|
}, next);
|
|
62
76
|
}
|
|
63
77
|
],
|
|
64
78
|
function(error) {
|
|
65
|
-
delete presence.connection._presences[presence.channel];
|
|
79
|
+
if (presence._wantsDestroy) delete presence.connection._presences[presence.channel];
|
|
66
80
|
presence._callbackOrEmit(error, callback);
|
|
67
81
|
}
|
|
68
82
|
);
|
|
@@ -11,6 +11,7 @@ function RemoteDocPresence(presence, presenceId) {
|
|
|
11
11
|
this.presenceVersion = null;
|
|
12
12
|
|
|
13
13
|
this._doc = this.connection.get(this.collection, this.id);
|
|
14
|
+
this._emitter = this.connection._docPresenceEmitter;
|
|
14
15
|
this._pending = null;
|
|
15
16
|
this._opCache = null;
|
|
16
17
|
this._pendingSetPending = false;
|
|
@@ -31,19 +32,19 @@ RemoteDocPresence.prototype.receiveUpdate = function(message) {
|
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
RemoteDocPresence.prototype.destroy = function(callback) {
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
35
|
+
this._emitter.removeEventListener(this._doc, 'op', this._opHandler);
|
|
36
|
+
this._emitter.removeEventListener(this._doc, 'create', this._createDelHandler);
|
|
37
|
+
this._emitter.removeEventListener(this._doc, 'del', this._createDelHandler);
|
|
38
|
+
this._emitter.removeEventListener(this._doc, 'load', this._loadHandler);
|
|
38
39
|
|
|
39
40
|
RemotePresence.prototype.destroy.call(this, callback);
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
RemoteDocPresence.prototype._registerWithDoc = function() {
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
44
|
+
this._emitter.addEventListener(this._doc, 'op', this._opHandler);
|
|
45
|
+
this._emitter.addEventListener(this._doc, 'create', this._createDelHandler);
|
|
46
|
+
this._emitter.addEventListener(this._doc, 'del', this._createDelHandler);
|
|
47
|
+
this._emitter.addEventListener(this._doc, 'load', this._loadHandler);
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
RemoteDocPresence.prototype._setPendingPresence = function() {
|
package/package.json
CHANGED
package/test/client/doc.js
CHANGED
|
@@ -43,11 +43,31 @@ describe('Doc', function() {
|
|
|
43
43
|
if (err) return done(err);
|
|
44
44
|
var doc2 = connection.get('dogs', 'fido');
|
|
45
45
|
expect(doc).not.equal(doc2);
|
|
46
|
-
expect(doc).eql(doc2);
|
|
47
46
|
done();
|
|
48
47
|
});
|
|
49
48
|
});
|
|
50
49
|
|
|
50
|
+
it('destroying then getting synchronously does not destroy the new doc', function(done) {
|
|
51
|
+
var connection = this.connection;
|
|
52
|
+
var doc = connection.get('dogs', 'fido');
|
|
53
|
+
var doc2;
|
|
54
|
+
|
|
55
|
+
doc.create({name: 'fido'}, function(error) {
|
|
56
|
+
if (error) return done(error);
|
|
57
|
+
|
|
58
|
+
doc.destroy(function(error) {
|
|
59
|
+
if (error) return done(error);
|
|
60
|
+
var doc3 = connection.get('dogs', 'fido');
|
|
61
|
+
async.parallel([
|
|
62
|
+
doc2.submitOp.bind(doc2, [{p: ['snacks'], oi: true}]),
|
|
63
|
+
doc3.submitOp.bind(doc3, [{p: ['color'], oi: 'gray'}])
|
|
64
|
+
], done);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
doc2 = connection.get('dogs', 'fido');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
51
71
|
it('doc.destroy() works without a callback', function() {
|
|
52
72
|
var doc = this.connection.get('dogs', 'fido');
|
|
53
73
|
doc.destroy();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
var Backend = require('../../../lib/backend');
|
|
2
|
+
var errorHandler = require('../../util').errorHandler;
|
|
3
|
+
var expect = require('chai').expect;
|
|
4
|
+
|
|
5
|
+
describe('DocPresenceEmitter', function() {
|
|
6
|
+
var backend;
|
|
7
|
+
var connection;
|
|
8
|
+
var doc;
|
|
9
|
+
var emitter;
|
|
10
|
+
|
|
11
|
+
beforeEach(function(done) {
|
|
12
|
+
backend = new Backend();
|
|
13
|
+
connection = backend.connect();
|
|
14
|
+
doc = connection.get('books', 'northern-lights');
|
|
15
|
+
doc.create({title: 'Northern Lights'}, done);
|
|
16
|
+
emitter = connection._docPresenceEmitter;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('listens to an op event', function(done) {
|
|
20
|
+
emitter.addEventListener(doc, 'op', function(op) {
|
|
21
|
+
expect(op).to.eql([{p: ['author'], oi: 'Philip Pullman'}]);
|
|
22
|
+
done();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
doc.submitOp([{p: ['author'], oi: 'Philip Pullman'}], errorHandler(done));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('stops listening to events', function(done) {
|
|
29
|
+
var listener = function() {
|
|
30
|
+
done(new Error('should not reach'));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
emitter.addEventListener(doc, 'op', listener);
|
|
34
|
+
emitter.removeEventListener(doc, 'op', listener);
|
|
35
|
+
|
|
36
|
+
doc.submitOp([{p: ['author'], oi: 'Philip Pullman'}], done);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('removes the listener from the doc if there are no more listeners', function() {
|
|
40
|
+
expect(doc._eventsCount).to.equal(0);
|
|
41
|
+
var listener = function() {};
|
|
42
|
+
|
|
43
|
+
emitter.addEventListener(doc, 'op', listener);
|
|
44
|
+
|
|
45
|
+
expect(doc._eventsCount).to.be.greaterThan(0);
|
|
46
|
+
expect(emitter._docs).not.to.be.empty;
|
|
47
|
+
expect(emitter._emitters).not.to.be.empty;
|
|
48
|
+
expect(emitter._forwarders).not.to.be.empty;
|
|
49
|
+
|
|
50
|
+
emitter.removeEventListener(doc, 'op', listener);
|
|
51
|
+
|
|
52
|
+
expect(doc._eventsCount).to.equal(0);
|
|
53
|
+
expect(emitter._docs).to.be.empty;
|
|
54
|
+
expect(emitter._emitters).to.be.empty;
|
|
55
|
+
expect(emitter._forwarders).to.be.empty;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('only registers a single listener on the doc', function() {
|
|
59
|
+
expect(doc._eventsCount).to.equal(0);
|
|
60
|
+
var listener = function() { };
|
|
61
|
+
emitter.addEventListener(doc, 'op', listener);
|
|
62
|
+
var count = doc._eventsCount;
|
|
63
|
+
emitter.addEventListener(doc, 'op', listener);
|
|
64
|
+
expect(doc._eventsCount).to.equal(count);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('only triggers the given event', function(done) {
|
|
68
|
+
emitter.addEventListener(doc, 'op', function(op) {
|
|
69
|
+
expect(op).to.eql([{p: ['author'], oi: 'Philip Pullman'}]);
|
|
70
|
+
done();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
emitter.addEventListener(doc, 'del', function() {
|
|
74
|
+
done(new Error('should not reach'));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
doc.submitOp([{p: ['author'], oi: 'Philip Pullman'}], errorHandler(done));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('removes listeners on destroy', function(done) {
|
|
81
|
+
expect(doc._eventsCount).to.equal(0);
|
|
82
|
+
var listener = function() { };
|
|
83
|
+
|
|
84
|
+
emitter.addEventListener(doc, 'op', listener);
|
|
85
|
+
|
|
86
|
+
expect(doc._eventsCount).to.be.greaterThan(0);
|
|
87
|
+
expect(emitter._docs).not.to.be.empty;
|
|
88
|
+
expect(emitter._emitters).not.to.be.empty;
|
|
89
|
+
expect(emitter._forwarders).not.to.be.empty;
|
|
90
|
+
|
|
91
|
+
doc.destroy(function(error) {
|
|
92
|
+
if (error) return done(error);
|
|
93
|
+
expect(doc._eventsCount).to.equal(0);
|
|
94
|
+
expect(emitter._docs).to.be.empty;
|
|
95
|
+
expect(emitter._emitters).to.be.empty;
|
|
96
|
+
expect(emitter._forwarders).to.be.empty;
|
|
97
|
+
done();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -1025,4 +1025,14 @@ describe('DocPresence', function() {
|
|
|
1025
1025
|
}
|
|
1026
1026
|
], done);
|
|
1027
1027
|
});
|
|
1028
|
+
|
|
1029
|
+
it('does not trigger EventEmitter memory leak warnings', function() {
|
|
1030
|
+
for (var i = 0; i < 100; i++) {
|
|
1031
|
+
presence1.create();
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
expect(doc1._events.op.warned).not.to.be.ok;
|
|
1035
|
+
var emitter = connection1._docPresenceEmitter._emitters[doc1.collection][doc1.id];
|
|
1036
|
+
expect(emitter._events.op.warned).not.to.be.ok;
|
|
1037
|
+
});
|
|
1028
1038
|
});
|
|
@@ -98,6 +98,118 @@ describe('Presence', function() {
|
|
|
98
98
|
], done);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
it('gets presence during a destroy', function(done) {
|
|
102
|
+
var localPresence1 = presence1.create('presence-1');
|
|
103
|
+
var presence2a;
|
|
104
|
+
|
|
105
|
+
async.series([
|
|
106
|
+
presence2.subscribe.bind(presence2),
|
|
107
|
+
function(next) {
|
|
108
|
+
presence2.destroy(errorHandler(done));
|
|
109
|
+
next();
|
|
110
|
+
},
|
|
111
|
+
function(next) {
|
|
112
|
+
presence2a = connection2.getPresence('test-channel');
|
|
113
|
+
presence2a.subscribe(function(error) {
|
|
114
|
+
next(error);
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
function(next) {
|
|
118
|
+
localPresence1.submit({index: 5}, errorHandler(done));
|
|
119
|
+
presence2a.once('receive', function() {
|
|
120
|
+
next();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
], done);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('destroys old local presence but keeps new local presence when getting during destroy', function(done) {
|
|
127
|
+
presence2.create('presence-2');
|
|
128
|
+
var presence2a;
|
|
129
|
+
|
|
130
|
+
async.series([
|
|
131
|
+
presence2.subscribe.bind(presence2),
|
|
132
|
+
function(next) {
|
|
133
|
+
presence2.destroy(function() {
|
|
134
|
+
expect(presence2).to.equal(presence2a);
|
|
135
|
+
expect(Object.keys(presence2.localPresences)).to.eql(['presence-2a']);
|
|
136
|
+
done();
|
|
137
|
+
});
|
|
138
|
+
next();
|
|
139
|
+
},
|
|
140
|
+
function(next) {
|
|
141
|
+
presence2a = connection2.getPresence('test-channel');
|
|
142
|
+
presence2a.create('presence-2a');
|
|
143
|
+
presence2a.subscribe(function(error) {
|
|
144
|
+
next(error);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
], errorHandler(done));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('destroys old local presence but keeps new local presence when getting during destroy', function(done) {
|
|
151
|
+
presence2.create('presence-2');
|
|
152
|
+
var presence2a;
|
|
153
|
+
|
|
154
|
+
async.series([
|
|
155
|
+
presence2.subscribe.bind(presence2),
|
|
156
|
+
function(next) {
|
|
157
|
+
presence2.destroy(function() {
|
|
158
|
+
expect(presence2).to.equal(presence2a);
|
|
159
|
+
expect(Object.keys(presence2.localPresences)).to.eql(['presence-2a']);
|
|
160
|
+
done();
|
|
161
|
+
});
|
|
162
|
+
next();
|
|
163
|
+
},
|
|
164
|
+
function(next) {
|
|
165
|
+
presence2a = connection2.getPresence('test-channel');
|
|
166
|
+
presence2a.create('presence-2a');
|
|
167
|
+
presence2a.subscribe(function(error) {
|
|
168
|
+
next(error);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
], errorHandler(done));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('throws if trying to create local presence when wanting destroy', function(done) {
|
|
175
|
+
presence2.destroy(errorHandler(done));
|
|
176
|
+
expect(function() {
|
|
177
|
+
presence2.create('presence-2');
|
|
178
|
+
}).to.throw('Presence is being destroyed');
|
|
179
|
+
done();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('gets presence after destroy unsubscribe', function(done) {
|
|
183
|
+
var localPresence2 = presence2.create('presence-2');
|
|
184
|
+
var presence2a;
|
|
185
|
+
|
|
186
|
+
var flushLocalPresence2Destroy;
|
|
187
|
+
sinon.stub(localPresence2, 'destroy').callsFake(function(callback) {
|
|
188
|
+
flushLocalPresence2Destroy = callback;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
async.series([
|
|
192
|
+
presence2.subscribe.bind(presence2),
|
|
193
|
+
function(next) {
|
|
194
|
+
presence2.destroy(function() {
|
|
195
|
+
expect(connection2.getPresence('test-channel')).to.equal(presence2a);
|
|
196
|
+
done();
|
|
197
|
+
});
|
|
198
|
+
next();
|
|
199
|
+
},
|
|
200
|
+
// Wait for the destroy unsubscribe callback to start, where we check
|
|
201
|
+
// _wantsDestroy for the first time
|
|
202
|
+
presence2.unsubscribe.bind(presence2),
|
|
203
|
+
function(next) {
|
|
204
|
+
presence2a = connection2.getPresence('test-channel');
|
|
205
|
+
presence2a.subscribe(function(error) {
|
|
206
|
+
next(error);
|
|
207
|
+
});
|
|
208
|
+
flushLocalPresence2Destroy();
|
|
209
|
+
}
|
|
210
|
+
], errorHandler(done));
|
|
211
|
+
});
|
|
212
|
+
|
|
101
213
|
it('requests existing presence from other subscribed clients when subscribing', function(done) {
|
|
102
214
|
var localPresence1 = presence1.create('presence-1');
|
|
103
215
|
async.series([
|
package/CHANGELOG.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
## v2.0
|
|
2
|
-
|
|
3
|
-
### Breaking changes
|
|
4
|
-
|
|
5
|
-
* Drop Node.js v10 support
|
|
6
|
-
|
|
7
|
-
## v1.0-beta
|
|
8
|
-
|
|
9
|
-
### Breaking changes
|
|
10
|
-
|
|
11
|
-
* Add options argument to all public database adapter methods that read
|
|
12
|
-
or write from snapshots or ops.
|
|
13
|
-
|
|
14
|
-
* DB methods that get snapshots or ops no longer return metadata unless
|
|
15
|
-
`{metadata: true}` option is passed.
|
|
16
|
-
|
|
17
|
-
* Replace `source` argument with `options` in doc methods. Use `options.source`
|
|
18
|
-
instead.
|
|
19
|
-
|
|
20
|
-
* Backend streams now write objects intead of strings.
|
|
21
|
-
|
|
22
|
-
* MemoryDB.prototype._querySync now returns `{snapshots: ..., extra: ...}`
|
|
23
|
-
instead of just an array of snapshots.
|
|
24
|
-
|
|
25
|
-
### Non-breaking changes
|
|
26
|
-
|
|
27
|
-
* Add options argument to backend.submit.
|
|
28
|
-
|
|
29
|
-
* Add error codes to all errors.
|
|
30
|
-
|
|
31
|
-
* Add `'updated'` event on queries which fires on all query result changes.
|
|
32
|
-
|
|
33
|
-
* In clients, wrap errors in Error objects to they get passed through event
|
|
34
|
-
emitters.
|
|
35
|
-
|
|
36
|
-
* Sanitize stack traces when sending errors to client, but log them on the
|
|
37
|
-
server.
|
|
38
|
-
|
|
39
|
-
## v0.11.37
|
|
40
|
-
|
|
41
|
-
Beginning of changelog.
|
|
42
|
-
|
|
43
|
-
If you're upgrading from ShareJS 0.7 or earlier,
|
|
44
|
-
take a look at the [ShareJS upgrade guide](docs/upgrading-from-sharejs.md).
|