Skip to content

Commit fc351fa

Browse files
committed
Introduce enablePresence option. Closes share#128
1 parent d41c961 commit fc351fa

File tree

4 files changed

+67
-26
lines changed

4 files changed

+67
-26
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ default, ShareDB stores all operations forever - nothing is truly deleted.
7676

7777
## User presence synchronization
7878

79+
ShareDB supports synchronization of user presence data. This feature is opt-in, not enabled by default. To enable this feature, pass the `enablePresence: true` option to the ShareDB constructor (e.g. `var share = new ShareDB({ enablePresence: true })`).
80+
7981
Presence data represents a user and is automatically synchronized between all clients subscribed to the same document. Its format is defined by the document's [OT Type](https://github.com/ottypes/docs), for example it may contain a user ID and a cursor position in a text document. All clients can modify their own presence data and receive a read-only version of other client's data. Presence data is automatically cleared when a client unsubscribes from the document or disconnects. It is also automatically transformed against applied operations, so that it still makes sense in the context of a modified document, for example a cursor position may be automatically advanced when a user types at the beginning of a text document.
8082

8183
## Server API
@@ -96,6 +98,8 @@ __Options__
9698
* `options.pubsub` _(instance of `ShareDB.PubSub`)_
9799
Notify other ShareDB processes when data changes
98100
through this pub/sub adapter. Defaults to `ShareDB.MemoryPubSub()`.
101+
* `options.enablePresence` _(optional boolean)_
102+
Enable user presence synchronization.
99103

100104
#### Database Adapters
101105
* `ShareDB.MemoryDB`, backed by a non-persistent database with no queries

lib/backend.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ function Backend(options) {
4848
if (!options.disableSpaceDelimitedActions) {
4949
this._shimAfterSubmit();
5050
}
51+
52+
this.enablePresence = options.enablePresence;
5153
}
5254
module.exports = Backend;
5355
emitter.mixin(Backend);
@@ -155,6 +157,11 @@ Backend.prototype.connect = function(connection, req) {
155157
// not used internal to ShareDB, but it is handy for server-side only user
156158
// code that may cache state on the agent and read it in middleware
157159
connection.agent = agent;
160+
161+
// Pass through information on whether or not presence is enabled,
162+
// so that Doc instances can use it.
163+
connection.enablePresence = this.enablePresence;
164+
158165
return connection;
159166
};
160167

lib/client/doc.js

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function Doc(connection, collection, id) {
6868
this.data = undefined;
6969

7070
// Properties related to presence are grouped within this object.
71-
this.presence = {
71+
this.presence = connection.enablePresence && {
7272

7373
// The current presence data.
7474
// Map of src -> presence data
@@ -158,14 +158,18 @@ Doc.prototype.destroy = function(callback) {
158158
if (callback) return callback(err);
159159
return doc.emit('error', err);
160160
}
161-
doc.presence.received = {};
162-
doc.presence.cachedOps.length = 0;
161+
if (doc.presence) {
162+
doc.presence.received = {};
163+
doc.presence.cachedOps.length = 0;
164+
}
163165
doc.connection._destroyDoc(doc);
164166
if (callback) callback();
165167
});
166168
} else {
167-
doc.presence.received = {};
168-
doc.presence.cachedOps.length = 0;
169+
if (doc.presence) {
170+
doc.presence.received = {};
171+
doc.presence.cachedOps.length = 0;
172+
}
169173
doc.connection._destroyDoc(doc);
170174
if (callback) callback();
171175
}
@@ -246,7 +250,11 @@ Doc.prototype.ingestSnapshot = function(snapshot, callback) {
246250
if (this.version > snapshot.v) return callback && callback();
247251

248252
this.version = snapshot.v;
249-
this.presence.cachedOps.length = 0;
253+
254+
if (this.presence) {
255+
this.presence.cachedOps.length = 0;
256+
}
257+
250258
var type = (snapshot.type === undefined) ? types.defaultType : snapshot.type;
251259
this._setType(type);
252260
this.data = (this.type && this.type.deserialize) ?
@@ -276,8 +284,7 @@ Doc.prototype.hasPending = function() {
276284
this.inflightSubscribe.length ||
277285
this.inflightUnsubscribe.length ||
278286
this.pendingFetch.length ||
279-
this.presence.inflight ||
280-
this.presence.pending
287+
this.presence && (this.presence.inflight || this.presence.pending)
281288
);
282289
};
283290

@@ -522,6 +529,8 @@ Doc.prototype.flush = function() {
522529
this._sendOp();
523530
}
524531

532+
if (!this.presence) return;
533+
525534
if (this.subscribed && !this.presence.inflight && this.presence.pending && !this.hasWritePending()) {
526535
this.presence.inflight = this.presence.pending;
527536
this.presence.inflightSeq = this.connection.seq;
@@ -923,7 +932,10 @@ Doc.prototype.resume = function() {
923932
Doc.prototype._opAcknowledged = function(message) {
924933
if (this.inflightOp.create) {
925934
this.version = message.v;
926-
this.presence.cachedOps.length = 0;
935+
936+
if (this.presence) {
937+
this.presence.cachedOps.length = 0;
938+
}
927939

928940
} else if (message.v !== this.version) {
929941
// We should already be at the same version, because the server should
@@ -995,8 +1007,10 @@ Doc.prototype._hardRollback = function(err) {
9951007

9961008
// Apply the same technique for presence, cleaning up as we go.
9971009
var pendingPresence = [];
998-
if (this.presence.inflight) pendingPresence.push(this.presence.inflight);
999-
if (this.presence.pending) pendingPresence.push(this.presence.pending);
1010+
if (this.presence) {
1011+
if (this.presence.inflight) pendingPresence.push(this.presence.inflight);
1012+
if (this.presence.pending) pendingPresence.push(this.presence.pending);
1013+
}
10001014

10011015
// Cancel all pending ops and reset if we can't invert
10021016
this._setType(null);
@@ -1005,22 +1019,24 @@ Doc.prototype._hardRollback = function(err) {
10051019
this.pendingOps = [];
10061020

10071021
// Reset presence-related properties.
1008-
this.presence.inflight = null;
1009-
this.presence.inflightSeq = 0;
1010-
this.presence.pending = null;
1011-
this.presence.cachedOps.length = 0;
1012-
this.presence.received = {};
1013-
this.presence.requestReply = true;
1014-
1015-
var srcList = Object.keys(this.presence.current);
1016-
var changedSrcList = [];
1017-
for (var i = 0; i < srcList.length; i++) {
1018-
var src = srcList[i];
1019-
if (this._setPresence(src, null)) {
1020-
changedSrcList.push(src);
1022+
if (this.presence) {
1023+
this.presence.inflight = null;
1024+
this.presence.inflightSeq = 0;
1025+
this.presence.pending = null;
1026+
this.presence.cachedOps.length = 0;
1027+
this.presence.received = {};
1028+
this.presence.requestReply = true;
1029+
1030+
var srcList = Object.keys(this.presence.current);
1031+
var changedSrcList = [];
1032+
for (var i = 0; i < srcList.length; i++) {
1033+
var src = srcList[i];
1034+
if (this._setPresence(src, null)) {
1035+
changedSrcList.push(src);
1036+
}
10211037
}
1038+
this._emitPresence(changedSrcList, false);
10221039
}
1023-
this._emitPresence(changedSrcList, false);
10241040

10251041
// Fetch the latest version from the server to get us back into a working state
10261042
var doc = this;
@@ -1247,6 +1263,7 @@ Doc.prototype._processReceivedPresence = function(src, emit) {
12471263
};
12481264

12491265
Doc.prototype._processAllReceivedPresence = function() {
1266+
if (!this.presence) return;
12501267
var srcList = Object.keys(this.presence.received);
12511268
var changedSrcList = [];
12521269
for (var i = 0; i < srcList.length; i++) {
@@ -1270,6 +1287,7 @@ Doc.prototype._transformPresence = function(src, op) {
12701287
};
12711288

12721289
Doc.prototype._transformAllPresence = function(op) {
1290+
if (!this.presence) return;
12731291
var srcList = Object.keys(this.presence.current);
12741292
var changedSrcList = [];
12751293
for (var i = 0; i < srcList.length; i++) {
@@ -1282,6 +1300,8 @@ Doc.prototype._transformAllPresence = function(op) {
12821300
};
12831301

12841302
Doc.prototype._pausePresence = function() {
1303+
if (!this.presence) return;
1304+
12851305
if (this.presence.inflight) {
12861306
this.presence.pending = this.presence.pending
12871307
? this.presence.inflight.concat(this.presence.pending)
@@ -1331,6 +1351,7 @@ Doc.prototype._emitPresence = function(srcList, submitted) {
13311351
};
13321352

13331353
Doc.prototype._cacheOp = function(op) {
1354+
if (!this.presence) return;
13341355
// Remove the old ops.
13351356
var oldOpTime = Date.now() - this.presence.cachedOpsTimeout;
13361357
var i;

test/client/presence.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ types.register(presenceType.type);
1111
types.register(presenceType.type2);
1212
types.register(presenceType.type3);
1313

14+
describe('client presence', function() {
15+
it('does not expose doc.presence if enablePresence is false', function() {
16+
var backend = new Backend();
17+
var connection = backend.connect();
18+
var doc = connection.get('dogs', 'fido');
19+
expect(typeof doc.presence).to.equal('undefined');
20+
});
21+
});
22+
1423
[
1524
'wrapped-presence-no-compare',
1625
'wrapped-presence-with-compare',
@@ -22,7 +31,7 @@ types.register(presenceType.type3);
2231

2332
describe('client presence (' + typeName + ')', function() {
2433
beforeEach(function() {
25-
this.backend = new Backend();
34+
this.backend = new Backend({ enablePresence: true });
2635
this.connection = this.backend.connect();
2736
this.connection2 = this.backend.connect();
2837
this.doc = this.connection.get('dogs', 'fido');

0 commit comments

Comments
 (0)