sharedb 5.0.3 → 5.0.4
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/agent.js +86 -23
- package/package.json +1 -1
- package/test/client/presence/doc-presence.js +18 -0
package/lib/agent.js
CHANGED
|
@@ -48,6 +48,10 @@ function Agent(backend, stream) {
|
|
|
48
48
|
// request if the client disconnects ungracefully. This is a
|
|
49
49
|
// map of channel -> id -> request
|
|
50
50
|
this.presenceRequests = Object.create(null);
|
|
51
|
+
// Keep track of the latest known Doc version, so that we can avoid fetching
|
|
52
|
+
// ops to transform presence if not needed
|
|
53
|
+
this.latestDocVersionStreams = Object.create(null);
|
|
54
|
+
this.latestDocVersions = Object.create(null);
|
|
51
55
|
|
|
52
56
|
// We need to track this manually to make sure we don't reply to messages
|
|
53
57
|
// after the stream was closed.
|
|
@@ -108,6 +112,12 @@ Agent.prototype._cleanup = function() {
|
|
|
108
112
|
emitter.destroy();
|
|
109
113
|
}
|
|
110
114
|
this.subscribedQueries = Object.create(null);
|
|
115
|
+
|
|
116
|
+
for (var collection in this.latestDocVersionStreams) {
|
|
117
|
+
var streams = this.latestDocVersionStreams[collection];
|
|
118
|
+
for (var id in streams) streams[id].destroy();
|
|
119
|
+
}
|
|
120
|
+
this.latestDocVersionStreams = Object.create(null);
|
|
111
121
|
};
|
|
112
122
|
|
|
113
123
|
/**
|
|
@@ -115,17 +125,8 @@ Agent.prototype._cleanup = function() {
|
|
|
115
125
|
* _sendOp()
|
|
116
126
|
*/
|
|
117
127
|
Agent.prototype._subscribeToStream = function(collection, id, stream) {
|
|
118
|
-
if (this.closed) return stream.destroy();
|
|
119
|
-
|
|
120
|
-
var streams = this.subscribedDocs[collection] || (this.subscribedDocs[collection] = Object.create(null));
|
|
121
|
-
|
|
122
|
-
// If already subscribed to this document, destroy the previously subscribed stream
|
|
123
|
-
var previous = streams[id];
|
|
124
|
-
if (previous) previous.destroy();
|
|
125
|
-
streams[id] = stream;
|
|
126
|
-
|
|
127
128
|
var agent = this;
|
|
128
|
-
|
|
129
|
+
this._subscribeMapToStream(this.subscribedDocs, collection, id, stream, function(data) {
|
|
129
130
|
if (data.error) {
|
|
130
131
|
// Log then silently ignore errors in a subscription stream, since these
|
|
131
132
|
// may not be the client's fault, and they were not the result of a
|
|
@@ -135,13 +136,26 @@ Agent.prototype._subscribeToStream = function(collection, id, stream) {
|
|
|
135
136
|
}
|
|
136
137
|
agent._onOp(collection, id, data);
|
|
137
138
|
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
Agent.prototype._subscribeMapToStream = function(map, collection, id, stream, dataHandler) {
|
|
142
|
+
if (this.closed) return stream.destroy();
|
|
143
|
+
|
|
144
|
+
var streams = map[collection] || (map[collection] = Object.create(null));
|
|
145
|
+
|
|
146
|
+
// If already subscribed to this document, destroy the previously subscribed stream
|
|
147
|
+
var previous = streams[id];
|
|
148
|
+
if (previous) previous.destroy();
|
|
149
|
+
streams[id] = stream;
|
|
150
|
+
|
|
151
|
+
stream.on('data', dataHandler);
|
|
138
152
|
stream.on('end', function() {
|
|
139
153
|
// The op stream is done sending, so release its reference
|
|
140
|
-
var streams =
|
|
154
|
+
var streams = map[collection];
|
|
141
155
|
if (!streams || streams[id] !== stream) return;
|
|
142
156
|
delete streams[id];
|
|
143
157
|
if (util.hasKeys(streams)) return;
|
|
144
|
-
delete
|
|
158
|
+
delete map[collection];
|
|
145
159
|
});
|
|
146
160
|
};
|
|
147
161
|
|
|
@@ -794,25 +808,74 @@ Agent.prototype._broadcastPresence = function(presence, callback) {
|
|
|
794
808
|
collection: presence.c
|
|
795
809
|
};
|
|
796
810
|
var start = Date.now();
|
|
797
|
-
|
|
811
|
+
|
|
812
|
+
var subscriptionUpdater = presence.p === null ?
|
|
813
|
+
this._unsubscribeDocVersion.bind(this) :
|
|
814
|
+
this._subscribeDocVersion.bind(this);
|
|
815
|
+
|
|
816
|
+
subscriptionUpdater(presence.c, presence.d, function(error) {
|
|
798
817
|
if (error) return callback(error);
|
|
799
|
-
|
|
800
|
-
var previousRequest = requests[presence.id];
|
|
801
|
-
if (!previousRequest || previousRequest.pv < presence.pv) {
|
|
802
|
-
presenceRequests[presence.ch][presence.id] = presence;
|
|
803
|
-
}
|
|
804
|
-
backend.transformPresenceToLatestVersion(agent, presence, function(error, presence) {
|
|
818
|
+
backend.trigger(backend.MIDDLEWARE_ACTIONS.receivePresence, agent, context, function(error) {
|
|
805
819
|
if (error) return callback(error);
|
|
806
|
-
var
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
820
|
+
var requests = presenceRequests[presence.ch] || (presenceRequests[presence.ch] = Object.create(null));
|
|
821
|
+
var previousRequest = requests[presence.id];
|
|
822
|
+
if (!previousRequest || previousRequest.pv < presence.pv) {
|
|
823
|
+
presenceRequests[presence.ch][presence.id] = presence;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
var transformer = function(agent, presence, callback) {
|
|
810
827
|
callback(null, presence);
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
var latestDocVersion = util.dig(agent.latestDocVersions, presence.c, presence.d);
|
|
831
|
+
var presenceIsUpToDate = presence.v === latestDocVersion;
|
|
832
|
+
if (!presenceIsUpToDate) {
|
|
833
|
+
transformer = backend.transformPresenceToLatestVersion.bind(backend);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
transformer(agent, presence, function(error, presence) {
|
|
837
|
+
if (error) return callback(error);
|
|
838
|
+
var channel = agent._getPresenceChannel(presence.ch);
|
|
839
|
+
agent.backend.pubsub.publish([channel], presence, function(error) {
|
|
840
|
+
if (error) return callback(error);
|
|
841
|
+
backend.emit('timing', 'presence.broadcast', Date.now() - start, context);
|
|
842
|
+
callback(null, presence);
|
|
843
|
+
});
|
|
811
844
|
});
|
|
812
845
|
});
|
|
813
846
|
});
|
|
814
847
|
};
|
|
815
848
|
|
|
849
|
+
Agent.prototype._subscribeDocVersion = function(collection, id, callback) {
|
|
850
|
+
if (!collection || !id) return callback();
|
|
851
|
+
|
|
852
|
+
var latestDocVersions = this.latestDocVersions;
|
|
853
|
+
var isSubscribed = util.dig(latestDocVersions, collection, id) !== undefined;
|
|
854
|
+
if (isSubscribed) return callback();
|
|
855
|
+
|
|
856
|
+
var agent = this;
|
|
857
|
+
this.backend.subscribe(this, collection, id, null, function(error, stream, snapshot) {
|
|
858
|
+
if (error) return callback(error);
|
|
859
|
+
|
|
860
|
+
var versions = latestDocVersions[collection] || (latestDocVersions[collection] = Object.create(null));
|
|
861
|
+
versions[id] = snapshot.v;
|
|
862
|
+
|
|
863
|
+
agent._subscribeMapToStream(agent.latestDocVersionStreams, collection, id, stream, function(op) {
|
|
864
|
+
// op.v behind snapshot.v by 1
|
|
865
|
+
latestDocVersions[collection][id] = op.v + 1;
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
callback();
|
|
869
|
+
});
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
Agent.prototype._unsubscribeDocVersion = function(collection, id, callback) {
|
|
873
|
+
var stream = util.dig(this.latestDocVersionStreams, collection, id);
|
|
874
|
+
if (stream) stream.destroy();
|
|
875
|
+
util.digAndRemove(this.latestDocVersions, collection, id);
|
|
876
|
+
util.nextTick(callback);
|
|
877
|
+
};
|
|
878
|
+
|
|
816
879
|
Agent.prototype._createPresence = function(request) {
|
|
817
880
|
return {
|
|
818
881
|
a: ACTIONS.presence,
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ var types = require('../../../lib/types');
|
|
|
5
5
|
var presenceTestType = require('./presence-test-type');
|
|
6
6
|
var errorHandler = require('../../util').errorHandler;
|
|
7
7
|
var PresencePauser = require('./presence-pauser');
|
|
8
|
+
var sinon = require('sinon');
|
|
8
9
|
types.register(presenceTestType.type);
|
|
9
10
|
|
|
10
11
|
describe('DocPresence', function() {
|
|
@@ -297,6 +298,23 @@ describe('DocPresence', function() {
|
|
|
297
298
|
], done);
|
|
298
299
|
});
|
|
299
300
|
|
|
301
|
+
it('does not call getOps() when presence is already up-to-date', function(done) {
|
|
302
|
+
var localPresence1 = presence1.create('presence-1');
|
|
303
|
+
|
|
304
|
+
async.series([
|
|
305
|
+
doc1.fetch.bind(doc1), // Ensure up-to-date
|
|
306
|
+
function(next) {
|
|
307
|
+
sinon.spy(Backend.prototype, 'getOps');
|
|
308
|
+
next();
|
|
309
|
+
},
|
|
310
|
+
localPresence1.submit.bind(localPresence1, {index: 1}),
|
|
311
|
+
function(next) {
|
|
312
|
+
expect(Backend.prototype.getOps).not.to.have.been.called;
|
|
313
|
+
next();
|
|
314
|
+
}
|
|
315
|
+
], done);
|
|
316
|
+
});
|
|
317
|
+
|
|
300
318
|
// This test case attempts to force us into a tight race condition corner case:
|
|
301
319
|
// 1. doc1 sends presence, as well as submits an op
|
|
302
320
|
// 2. doc2 receives the op first, followed by the presence, which is now out-of-date
|