sharedb 4.1.0 → 4.1.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/agent.js +54 -30
- package/lib/backend.js +3 -3
- package/lib/client/connection.js +16 -12
- package/lib/client/presence/doc-presence-emitter.js +3 -3
- package/lib/client/presence/local-doc-presence.js +3 -3
- package/lib/client/presence/local-presence.js +1 -1
- package/lib/client/presence/presence.js +14 -15
- package/lib/client/presence/remote-doc-presence.js +1 -3
- package/lib/db/index.js +3 -3
- package/lib/db/memory.js +4 -4
- package/lib/logger/logger.js +1 -1
- package/lib/milestone-db/memory.js +3 -2
- package/lib/ot.js +14 -0
- package/lib/projections.js +2 -1
- package/lib/pubsub/index.js +3 -3
- package/lib/read-snapshots-request.js +1 -1
- package/lib/submit-request.js +1 -1
- package/lib/types.js +1 -1
- package/lib/util.js +16 -4
- package/package.json +1 -1
- package/test/client/doc.js +100 -0
- package/test/client/presence/doc-presence.js +4 -10
- package/test/client/presence/presence.js +21 -6
package/lib/agent.js
CHANGED
|
@@ -31,23 +31,23 @@ function Agent(backend, stream) {
|
|
|
31
31
|
|
|
32
32
|
// We need to track which documents are subscribed by the client. This is a
|
|
33
33
|
// map of collection -> id -> stream
|
|
34
|
-
this.subscribedDocs =
|
|
34
|
+
this.subscribedDocs = Object.create(null);
|
|
35
35
|
|
|
36
36
|
// Map from queryId -> emitter
|
|
37
|
-
this.subscribedQueries =
|
|
37
|
+
this.subscribedQueries = Object.create(null);
|
|
38
38
|
|
|
39
39
|
// Track which documents are subscribed to presence by the client. This is a
|
|
40
40
|
// map of channel -> stream
|
|
41
|
-
this.subscribedPresences =
|
|
41
|
+
this.subscribedPresences = Object.create(null);
|
|
42
42
|
// Highest seq received for a subscription request. Any seq lower than this
|
|
43
43
|
// value is stale, and should be ignored. Used for keeping the subscription
|
|
44
44
|
// state in sync with the client's desired state. Map of channel -> seq
|
|
45
|
-
this.presenceSubscriptionSeq =
|
|
45
|
+
this.presenceSubscriptionSeq = Object.create(null);
|
|
46
46
|
// Keep track of the last request that has been sent by each local presence
|
|
47
47
|
// belonging to this agent. This is used to generate a new disconnection
|
|
48
48
|
// request if the client disconnects ungracefully. This is a
|
|
49
49
|
// map of channel -> id -> request
|
|
50
|
-
this.presenceRequests =
|
|
50
|
+
this.presenceRequests = Object.create(null);
|
|
51
51
|
|
|
52
52
|
// We need to track this manually to make sure we don't reply to messages
|
|
53
53
|
// after the stream was closed.
|
|
@@ -56,7 +56,7 @@ function Agent(backend, stream) {
|
|
|
56
56
|
// For custom use in middleware. The agent is a convenient place to cache
|
|
57
57
|
// session state in memory. It is in memory only as long as the session is
|
|
58
58
|
// active, and it is passed to each middleware call
|
|
59
|
-
this.custom =
|
|
59
|
+
this.custom = Object.create(null);
|
|
60
60
|
|
|
61
61
|
// The first message received over the connection. Stored to warn if messages
|
|
62
62
|
// are being sent before the handshake.
|
|
@@ -95,19 +95,19 @@ Agent.prototype._cleanup = function() {
|
|
|
95
95
|
stream.destroy();
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
this.subscribedDocs =
|
|
98
|
+
this.subscribedDocs = Object.create(null);
|
|
99
99
|
|
|
100
100
|
for (var channel in this.subscribedPresences) {
|
|
101
101
|
this.subscribedPresences[channel].destroy();
|
|
102
102
|
}
|
|
103
|
-
this.subscribedPresences =
|
|
103
|
+
this.subscribedPresences = Object.create(null);
|
|
104
104
|
|
|
105
105
|
// Clean up query subscription streams
|
|
106
106
|
for (var id in this.subscribedQueries) {
|
|
107
107
|
var emitter = this.subscribedQueries[id];
|
|
108
108
|
emitter.destroy();
|
|
109
109
|
}
|
|
110
|
-
this.subscribedQueries =
|
|
110
|
+
this.subscribedQueries = Object.create(null);
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
/**
|
|
@@ -117,7 +117,7 @@ Agent.prototype._cleanup = function() {
|
|
|
117
117
|
Agent.prototype._subscribeToStream = function(collection, id, stream) {
|
|
118
118
|
if (this.closed) return stream.destroy();
|
|
119
119
|
|
|
120
|
-
var streams = this.subscribedDocs[collection] || (this.subscribedDocs[collection] =
|
|
120
|
+
var streams = this.subscribedDocs[collection] || (this.subscribedDocs[collection] = Object.create(null));
|
|
121
121
|
|
|
122
122
|
// If already subscribed to this document, destroy the previously subscribed stream
|
|
123
123
|
var previous = streams[id];
|
|
@@ -373,15 +373,25 @@ Agent.prototype._checkRequest = function(request) {
|
|
|
373
373
|
request.a === ACTIONS.unsubscribe ||
|
|
374
374
|
request.a === ACTIONS.presence) {
|
|
375
375
|
// Doc-based request.
|
|
376
|
-
if (request.c != null
|
|
377
|
-
|
|
376
|
+
if (request.c != null) {
|
|
377
|
+
if (typeof request.c !== 'string' || util.isDangerousProperty(request.c)) {
|
|
378
|
+
return 'Invalid collection';
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (request.d != null) {
|
|
382
|
+
if (typeof request.d !== 'string' || util.isDangerousProperty(request.d)) {
|
|
383
|
+
return 'Invalid id';
|
|
384
|
+
}
|
|
385
|
+
}
|
|
378
386
|
|
|
379
387
|
if (request.a === ACTIONS.op || request.a === ACTIONS.presence) {
|
|
380
388
|
if (request.v != null && (typeof request.v !== 'number' || request.v < 0)) return 'Invalid version';
|
|
381
389
|
}
|
|
382
390
|
|
|
383
391
|
if (request.a === ACTIONS.presence) {
|
|
384
|
-
if (typeof request.id !== 'string'
|
|
392
|
+
if (typeof request.id !== 'string' || util.isDangerousProperty(request.id)) {
|
|
393
|
+
return 'Invalid presence ID';
|
|
394
|
+
}
|
|
385
395
|
}
|
|
386
396
|
} else if (
|
|
387
397
|
request.a === ACTIONS.bulkFetch ||
|
|
@@ -389,9 +399,18 @@ Agent.prototype._checkRequest = function(request) {
|
|
|
389
399
|
request.a === ACTIONS.bulkUnsubscribe
|
|
390
400
|
) {
|
|
391
401
|
// Bulk request
|
|
392
|
-
if (request.c != null
|
|
402
|
+
if (request.c != null) {
|
|
403
|
+
if (typeof request.c !== 'string' || util.isDangerousProperty(request.c)) {
|
|
404
|
+
return 'Invalid collection';
|
|
405
|
+
}
|
|
406
|
+
}
|
|
393
407
|
if (typeof request.b !== 'object') return 'Invalid bulk subscribe data';
|
|
394
408
|
}
|
|
409
|
+
if (request.ch != null) {
|
|
410
|
+
if (typeof request.ch !== 'string' || util.isDangerousProperty(request.ch)) {
|
|
411
|
+
return 'Invalid presence channel';
|
|
412
|
+
}
|
|
413
|
+
}
|
|
395
414
|
};
|
|
396
415
|
|
|
397
416
|
// Handle an incoming message from the client
|
|
@@ -453,6 +472,8 @@ Agent.prototype._handleMessage = function(request, callback) {
|
|
|
453
472
|
return this._subscribePresence(request.ch, request.seq, callback);
|
|
454
473
|
case ACTIONS.presenceUnsubscribe:
|
|
455
474
|
return this._unsubscribePresence(request.ch, request.seq, callback);
|
|
475
|
+
case ACTIONS.presenceRequest:
|
|
476
|
+
return this._requestPresence(request.ch, callback);
|
|
456
477
|
case ACTIONS.pingPong:
|
|
457
478
|
return this._pingPong(callback);
|
|
458
479
|
default:
|
|
@@ -481,7 +502,7 @@ function getQueryOptions(request) {
|
|
|
481
502
|
fetch = [id];
|
|
482
503
|
}
|
|
483
504
|
} else {
|
|
484
|
-
if (!fetchOps) fetchOps =
|
|
505
|
+
if (!fetchOps) fetchOps = Object.create(null);
|
|
485
506
|
fetchOps[id] = version;
|
|
486
507
|
}
|
|
487
508
|
}
|
|
@@ -568,7 +589,7 @@ function getResultsData(results) {
|
|
|
568
589
|
}
|
|
569
590
|
|
|
570
591
|
function getMapResult(snapshotMap) {
|
|
571
|
-
var data =
|
|
592
|
+
var data = Object.create(null);
|
|
572
593
|
for (var id in snapshotMap) {
|
|
573
594
|
var mapValue = snapshotMap[id];
|
|
574
595
|
// fetchBulk / subscribeBulk map data can have either a Snapshot or an object
|
|
@@ -767,11 +788,7 @@ Agent.prototype._src = function() {
|
|
|
767
788
|
Agent.prototype._broadcastPresence = function(presence, callback) {
|
|
768
789
|
var agent = this;
|
|
769
790
|
var backend = this.backend;
|
|
770
|
-
var
|
|
771
|
-
var previousRequest = requests[presence.id];
|
|
772
|
-
if (!previousRequest || previousRequest.pv < presence.pv) {
|
|
773
|
-
this.presenceRequests[presence.ch][presence.id] = presence;
|
|
774
|
-
}
|
|
791
|
+
var presenceRequests = this.presenceRequests;
|
|
775
792
|
var context = {
|
|
776
793
|
presence: presence,
|
|
777
794
|
collection: presence.c
|
|
@@ -779,6 +796,11 @@ Agent.prototype._broadcastPresence = function(presence, callback) {
|
|
|
779
796
|
var start = Date.now();
|
|
780
797
|
backend.trigger(backend.MIDDLEWARE_ACTIONS.receivePresence, this, context, function(error) {
|
|
781
798
|
if (error) return callback(error);
|
|
799
|
+
var requests = presenceRequests[presence.ch] || (presenceRequests[presence.ch] = Object.create(null));
|
|
800
|
+
var previousRequest = requests[presence.id];
|
|
801
|
+
if (!previousRequest || previousRequest.pv < presence.pv) {
|
|
802
|
+
presenceRequests[presence.ch][presence.id] = presence;
|
|
803
|
+
}
|
|
782
804
|
backend.transformPresenceToLatestVersion(agent, presence, function(error, presence) {
|
|
783
805
|
if (error) return callback(error);
|
|
784
806
|
var channel = agent._getPresenceChannel(presence.ch);
|
|
@@ -807,19 +829,17 @@ Agent.prototype._createPresence = function(request) {
|
|
|
807
829
|
};
|
|
808
830
|
};
|
|
809
831
|
|
|
810
|
-
Agent.prototype._subscribePresence = function(channel, seq,
|
|
832
|
+
Agent.prototype._subscribePresence = function(channel, seq, cb) {
|
|
811
833
|
var agent = this;
|
|
812
834
|
|
|
813
|
-
function
|
|
814
|
-
|
|
815
|
-
callback(error, {ch: channel, seq: seq});
|
|
816
|
-
});
|
|
835
|
+
function callback(error) {
|
|
836
|
+
cb(error, {ch: channel, seq: seq});
|
|
817
837
|
}
|
|
818
838
|
|
|
819
839
|
var existingStream = agent.subscribedPresences[channel];
|
|
820
840
|
if (existingStream) {
|
|
821
841
|
agent.presenceSubscriptionSeq[channel] = seq;
|
|
822
|
-
return
|
|
842
|
+
return callback();
|
|
823
843
|
}
|
|
824
844
|
|
|
825
845
|
var presenceChannel = this._getPresenceChannel(channel);
|
|
@@ -827,12 +847,14 @@ Agent.prototype._subscribePresence = function(channel, seq, callback) {
|
|
|
827
847
|
if (error) return callback(error);
|
|
828
848
|
if (seq < agent.presenceSubscriptionSeq[channel]) {
|
|
829
849
|
stream.destroy();
|
|
830
|
-
return callback(
|
|
850
|
+
return callback();
|
|
831
851
|
}
|
|
832
852
|
agent.presenceSubscriptionSeq[channel] = seq;
|
|
833
853
|
agent.subscribedPresences[channel] = stream;
|
|
834
854
|
agent._subscribeToPresenceStream(channel, stream);
|
|
835
|
-
|
|
855
|
+
agent._requestPresence(channel, function(error) {
|
|
856
|
+
callback(error);
|
|
857
|
+
});
|
|
836
858
|
});
|
|
837
859
|
};
|
|
838
860
|
|
|
@@ -902,7 +924,9 @@ function createClientOp(request, clientId) {
|
|
|
902
924
|
function shallowCopy(object) {
|
|
903
925
|
var out = {};
|
|
904
926
|
for (var key in object) {
|
|
905
|
-
|
|
927
|
+
if (util.hasOwn(object, key)) {
|
|
928
|
+
out[key] = object[key];
|
|
929
|
+
}
|
|
906
930
|
}
|
|
907
931
|
return out;
|
|
908
932
|
}
|
package/lib/backend.js
CHANGED
|
@@ -30,7 +30,7 @@ function Backend(options) {
|
|
|
30
30
|
this.milestoneDb = options.milestoneDb || new NoOpMilestoneDB();
|
|
31
31
|
|
|
32
32
|
// Map from projected collection -> {type, fields}
|
|
33
|
-
this.projections =
|
|
33
|
+
this.projections = Object.create(null);
|
|
34
34
|
|
|
35
35
|
this.suppressPublish = !!options.suppressPublish;
|
|
36
36
|
this.maxSubmitRetries = options.maxSubmitRetries || null;
|
|
@@ -45,7 +45,7 @@ function Backend(options) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Map from event name to a list of middleware
|
|
48
|
-
this.middleware =
|
|
48
|
+
this.middleware = Object.create(null);
|
|
49
49
|
|
|
50
50
|
// The number of open agents for monitoring and testing memory leaks
|
|
51
51
|
this.agentsCount = 0;
|
|
@@ -569,7 +569,7 @@ Backend.prototype.subscribeBulk = function(agent, index, versions, callback) {
|
|
|
569
569
|
var projection = this.projections[index];
|
|
570
570
|
var collection = (projection) ? projection.target : index;
|
|
571
571
|
var backend = this;
|
|
572
|
-
var streams =
|
|
572
|
+
var streams = Object.create(null);
|
|
573
573
|
var doFetch = Array.isArray(versions);
|
|
574
574
|
var ids = (doFetch) ? versions : Object.keys(versions);
|
|
575
575
|
var request = {
|
package/lib/client/connection.js
CHANGED
|
@@ -40,7 +40,7 @@ function Connection(socket) {
|
|
|
40
40
|
|
|
41
41
|
// Map of collection -> id -> doc object for created documents.
|
|
42
42
|
// (created documents MUST BE UNIQUE)
|
|
43
|
-
this.collections =
|
|
43
|
+
this.collections = Object.create(null);
|
|
44
44
|
|
|
45
45
|
// Each query and snapshot request is created with an id that the server uses when it sends us
|
|
46
46
|
// info about the request (updates, etc)
|
|
@@ -48,14 +48,14 @@ function Connection(socket) {
|
|
|
48
48
|
this.nextSnapshotRequestId = 1;
|
|
49
49
|
|
|
50
50
|
// Map from query ID -> query object.
|
|
51
|
-
this.queries =
|
|
51
|
+
this.queries = Object.create(null);
|
|
52
52
|
|
|
53
53
|
// Maps from channel -> presence objects
|
|
54
|
-
this._presences =
|
|
54
|
+
this._presences = Object.create(null);
|
|
55
55
|
this._docPresenceEmitter = new DocPresenceEmitter();
|
|
56
56
|
|
|
57
57
|
// Map from snapshot request ID -> snapshot request
|
|
58
|
-
this._snapshotRequests =
|
|
58
|
+
this._snapshotRequests = Object.create(null);
|
|
59
59
|
|
|
60
60
|
// A unique message number for the given id
|
|
61
61
|
this.seq = 1;
|
|
@@ -221,7 +221,7 @@ Connection.prototype.handleMessage = function(message) {
|
|
|
221
221
|
if (!query) return;
|
|
222
222
|
if (err) return query._handleError(err);
|
|
223
223
|
if (message.diff) query._handleDiff(message.diff);
|
|
224
|
-
if (
|
|
224
|
+
if (util.hasOwn(message, 'extra')) query._handleExtra(message.extra);
|
|
225
225
|
return;
|
|
226
226
|
|
|
227
227
|
case ACTIONS.bulkFetch:
|
|
@@ -376,7 +376,7 @@ Connection.prototype._setState = function(newState, reason) {
|
|
|
376
376
|
};
|
|
377
377
|
|
|
378
378
|
Connection.prototype.startBulk = function() {
|
|
379
|
-
if (!this.bulk) this.bulk =
|
|
379
|
+
if (!this.bulk) this.bulk = Object.create(null);
|
|
380
380
|
};
|
|
381
381
|
|
|
382
382
|
Connection.prototype.endBulk = function() {
|
|
@@ -394,7 +394,7 @@ Connection.prototype.endBulk = function() {
|
|
|
394
394
|
Connection.prototype._sendBulk = function(action, collection, values) {
|
|
395
395
|
if (!values) return;
|
|
396
396
|
var ids = [];
|
|
397
|
-
var versions =
|
|
397
|
+
var versions = Object.create(null);
|
|
398
398
|
var versionsCount = 0;
|
|
399
399
|
var versionId;
|
|
400
400
|
for (var id in values) {
|
|
@@ -426,9 +426,9 @@ Connection.prototype._sendActions = function(action, doc, version) {
|
|
|
426
426
|
this._addDoc(doc);
|
|
427
427
|
if (this.bulk) {
|
|
428
428
|
// Bulk subscribe
|
|
429
|
-
var actions = this.bulk[doc.collection] || (this.bulk[doc.collection] =
|
|
430
|
-
var versions = actions[action] || (actions[action] =
|
|
431
|
-
var isDuplicate =
|
|
429
|
+
var actions = this.bulk[doc.collection] || (this.bulk[doc.collection] = Object.create(null));
|
|
430
|
+
var versions = actions[action] || (actions[action] = Object.create(null));
|
|
431
|
+
var isDuplicate = util.hasOwn(versions, doc.id);
|
|
432
432
|
versions[doc.id] = version;
|
|
433
433
|
return isDuplicate;
|
|
434
434
|
} else {
|
|
@@ -515,7 +515,7 @@ Connection.prototype.getExisting = function(collection, id) {
|
|
|
515
515
|
*/
|
|
516
516
|
Connection.prototype.get = function(collection, id) {
|
|
517
517
|
var docs = this.collections[collection] ||
|
|
518
|
-
(this.collections[collection] =
|
|
518
|
+
(this.collections[collection] = Object.create(null));
|
|
519
519
|
|
|
520
520
|
var doc = docs[id];
|
|
521
521
|
if (!doc) {
|
|
@@ -542,7 +542,7 @@ Connection.prototype._destroyDoc = function(doc) {
|
|
|
542
542
|
Connection.prototype._addDoc = function(doc) {
|
|
543
543
|
var docs = this.collections[doc.collection];
|
|
544
544
|
if (!docs) {
|
|
545
|
-
docs = this.collections[doc.collection] =
|
|
545
|
+
docs = this.collections[doc.collection] = Object.create(null);
|
|
546
546
|
}
|
|
547
547
|
if (docs[doc.id] !== doc) {
|
|
548
548
|
docs[doc.id] = doc;
|
|
@@ -809,6 +809,10 @@ Connection.prototype._addPresence = function(presence) {
|
|
|
809
809
|
});
|
|
810
810
|
};
|
|
811
811
|
|
|
812
|
+
Connection.prototype._requestRemotePresence = function(channel) {
|
|
813
|
+
this.send({a: ACTIONS.presenceRequest, ch: channel});
|
|
814
|
+
};
|
|
815
|
+
|
|
812
816
|
Connection.prototype._handlePresenceSubscribe = function(error, message) {
|
|
813
817
|
var presence = util.dig(this._presences, message.ch);
|
|
814
818
|
if (presence) presence._handleSubscribe(error, message.seq);
|
|
@@ -12,9 +12,9 @@ var EVENTS = [
|
|
|
12
12
|
module.exports = DocPresenceEmitter;
|
|
13
13
|
|
|
14
14
|
function DocPresenceEmitter() {
|
|
15
|
-
this._docs =
|
|
16
|
-
this._forwarders =
|
|
17
|
-
this._emitters =
|
|
15
|
+
this._docs = Object.create(null);
|
|
16
|
+
this._forwarders = Object.create(null);
|
|
17
|
+
this._emitters = Object.create(null);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
DocPresenceEmitter.prototype.addEventListener = function(doc, event, listener) {
|
|
@@ -13,7 +13,7 @@ function LocalDocPresence(presence, presenceId) {
|
|
|
13
13
|
this._doc = this.connection.get(this.collection, this.id);
|
|
14
14
|
this._emitter = this.connection._docPresenceEmitter;
|
|
15
15
|
this._isSending = false;
|
|
16
|
-
this._docDataVersionByPresenceVersion =
|
|
16
|
+
this._docDataVersionByPresenceVersion = Object.create(null);
|
|
17
17
|
|
|
18
18
|
this._opHandler = this._transformAgainstOp.bind(this);
|
|
19
19
|
this._createOrDelHandler = this._handleCreateOrDel.bind(this);
|
|
@@ -68,7 +68,7 @@ LocalDocPresence.prototype._sendPending = function() {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
presence._pendingMessages = [];
|
|
71
|
-
presence._docDataVersionByPresenceVersion =
|
|
71
|
+
presence._docDataVersionByPresenceVersion = Object.create(null);
|
|
72
72
|
});
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -118,7 +118,7 @@ LocalDocPresence.prototype._handleCreateOrDel = function() {
|
|
|
118
118
|
LocalDocPresence.prototype._handleLoad = function() {
|
|
119
119
|
this.value = null;
|
|
120
120
|
this._pendingMessages = [];
|
|
121
|
-
this._docDataVersionByPresenceVersion =
|
|
121
|
+
this._docDataVersionByPresenceVersion = Object.create(null);
|
|
122
122
|
};
|
|
123
123
|
|
|
124
124
|
LocalDocPresence.prototype._message = function() {
|
|
@@ -19,21 +19,21 @@ function Presence(connection, channel) {
|
|
|
19
19
|
|
|
20
20
|
this.wantSubscribe = false;
|
|
21
21
|
this.subscribed = false;
|
|
22
|
-
this.remotePresences =
|
|
23
|
-
this.localPresences =
|
|
22
|
+
this.remotePresences = Object.create(null);
|
|
23
|
+
this.localPresences = Object.create(null);
|
|
24
24
|
|
|
25
|
-
this._remotePresenceInstances =
|
|
26
|
-
this._subscriptionCallbacksBySeq =
|
|
25
|
+
this._remotePresenceInstances = Object.create(null);
|
|
26
|
+
this._subscriptionCallbacksBySeq = Object.create(null);
|
|
27
27
|
this._wantsDestroy = false;
|
|
28
28
|
}
|
|
29
29
|
emitter.mixin(Presence);
|
|
30
30
|
|
|
31
|
-
Presence.prototype.subscribe = function(
|
|
32
|
-
this._sendSubscriptionAction(true,
|
|
31
|
+
Presence.prototype.subscribe = function(callback) {
|
|
32
|
+
this._sendSubscriptionAction(true, callback);
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
Presence.prototype.unsubscribe = function(
|
|
36
|
-
this._sendSubscriptionAction(false,
|
|
35
|
+
Presence.prototype.unsubscribe = function(callback) {
|
|
36
|
+
this._sendSubscriptionAction(false, callback);
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
Presence.prototype.create = function(id) {
|
|
@@ -83,14 +83,9 @@ Presence.prototype.destroy = function(callback) {
|
|
|
83
83
|
});
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
Presence.prototype._sendSubscriptionAction = function(wantSubscribe,
|
|
87
|
-
if (typeof options === 'function') {
|
|
88
|
-
callback = options;
|
|
89
|
-
options = {};
|
|
90
|
-
}
|
|
91
|
-
options = options || {};
|
|
86
|
+
Presence.prototype._sendSubscriptionAction = function(wantSubscribe, callback) {
|
|
92
87
|
wantSubscribe = !!wantSubscribe;
|
|
93
|
-
if (
|
|
88
|
+
if (wantSubscribe === this.wantSubscribe) {
|
|
94
89
|
if (!callback) return;
|
|
95
90
|
if (wantSubscribe === this.subscribed) return util.nextTick(callback);
|
|
96
91
|
if (Object.keys(this._subscriptionCallbacksBySeq).length) {
|
|
@@ -106,6 +101,10 @@ Presence.prototype._sendSubscriptionAction = function(wantSubscribe, options, ca
|
|
|
106
101
|
}
|
|
107
102
|
};
|
|
108
103
|
|
|
104
|
+
Presence.prototype._requestRemotePresence = function() {
|
|
105
|
+
this.connection._requestRemotePresence(this.channel);
|
|
106
|
+
};
|
|
107
|
+
|
|
109
108
|
Presence.prototype._handleSubscribe = function(error, seq) {
|
|
110
109
|
if (this.wantSubscribe) this.subscribed = true;
|
|
111
110
|
var callback = this._subscriptionCallback(seq);
|
|
@@ -105,9 +105,7 @@ RemoteDocPresence.prototype._catchUpStalePresence = function() {
|
|
|
105
105
|
if (!this._opCache) {
|
|
106
106
|
this._startCachingOps();
|
|
107
107
|
this._doc.fetch();
|
|
108
|
-
|
|
109
|
-
// to force presence updates from other clients
|
|
110
|
-
this.presence.subscribe({_force: true});
|
|
108
|
+
this.presence._requestRemotePresence();
|
|
111
109
|
return false;
|
|
112
110
|
}
|
|
113
111
|
|
package/lib/db/index.js
CHANGED
|
@@ -26,7 +26,7 @@ DB.prototype.getSnapshot = function(collection, id, fields, options, callback) {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
DB.prototype.getSnapshotBulk = function(collection, ids, fields, options, callback) {
|
|
29
|
-
var results =
|
|
29
|
+
var results = Object.create(null);
|
|
30
30
|
var db = this;
|
|
31
31
|
async.each(ids, function(id, eachCb) {
|
|
32
32
|
db.getSnapshot(collection, id, fields, options, function(err, snapshot) {
|
|
@@ -50,7 +50,7 @@ DB.prototype.getOpsToSnapshot = function(collection, id, from, snapshot, options
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
DB.prototype.getOpsBulk = function(collection, fromMap, toMap, options, callback) {
|
|
53
|
-
var results =
|
|
53
|
+
var results = Object.create(null);
|
|
54
54
|
var db = this;
|
|
55
55
|
async.forEachOf(fromMap, function(from, id, eachCb) {
|
|
56
56
|
var to = toMap && toMap[id];
|
|
@@ -83,7 +83,7 @@ DB.prototype.query = function(collection, query, fields, options, callback) {
|
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
DB.prototype.queryPoll = function(collection, query, options, callback) {
|
|
86
|
-
var fields =
|
|
86
|
+
var fields = Object.create(null);
|
|
87
87
|
this.query(collection, query, fields, options, function(err, snapshots, extra) {
|
|
88
88
|
if (err) return callback(err);
|
|
89
89
|
var ids = [];
|
package/lib/db/memory.js
CHANGED
|
@@ -17,12 +17,12 @@ function MemoryDB(options) {
|
|
|
17
17
|
DB.call(this, options);
|
|
18
18
|
|
|
19
19
|
// Map from collection name -> doc id -> doc snapshot ({v:, type:, data:})
|
|
20
|
-
this.docs =
|
|
20
|
+
this.docs = Object.create(null);
|
|
21
21
|
|
|
22
22
|
// Map from collection name -> doc id -> list of operations. Operations
|
|
23
23
|
// don't store their version - instead their version is simply the index in
|
|
24
24
|
// the list.
|
|
25
|
-
this.ops =
|
|
25
|
+
this.ops = Object.create(null);
|
|
26
26
|
|
|
27
27
|
this.closed = false;
|
|
28
28
|
};
|
|
@@ -139,7 +139,7 @@ MemoryDB.prototype._writeOpSync = function(collection, id, op) {
|
|
|
139
139
|
// object will be passed in with a type property. If there is no type property,
|
|
140
140
|
// it should be considered a delete
|
|
141
141
|
MemoryDB.prototype._writeSnapshotSync = function(collection, id, snapshot) {
|
|
142
|
-
var collectionDocs = this.docs[collection] || (this.docs[collection] =
|
|
142
|
+
var collectionDocs = this.docs[collection] || (this.docs[collection] = Object.create(null));
|
|
143
143
|
if (!snapshot.type) {
|
|
144
144
|
delete collectionDocs[id];
|
|
145
145
|
} else {
|
|
@@ -165,7 +165,7 @@ MemoryDB.prototype._getSnapshotSync = function(collection, id, includeMetadata)
|
|
|
165
165
|
};
|
|
166
166
|
|
|
167
167
|
MemoryDB.prototype._getOpLogSync = function(collection, id) {
|
|
168
|
-
var collectionOps = this.ops[collection] || (this.ops[collection] =
|
|
168
|
+
var collectionOps = this.ops[collection] || (this.ops[collection] = Object.create(null));
|
|
169
169
|
return collectionOps[id] || (collectionOps[id] = []);
|
|
170
170
|
};
|
|
171
171
|
|
package/lib/logger/logger.js
CHANGED
|
@@ -5,7 +5,7 @@ var SUPPORTED_METHODS = [
|
|
|
5
5
|
];
|
|
6
6
|
|
|
7
7
|
function Logger() {
|
|
8
|
-
var defaultMethods =
|
|
8
|
+
var defaultMethods = Object.create(null);
|
|
9
9
|
SUPPORTED_METHODS.forEach(function(method) {
|
|
10
10
|
// Deal with Chrome issue: https://bugs.chromium.org/p/chromium/issues/detail?id=179628
|
|
11
11
|
defaultMethods[method] = console[method].bind(console);
|
|
@@ -20,7 +20,7 @@ function MemoryMilestoneDB(options) {
|
|
|
20
20
|
MilestoneDB.call(this, options);
|
|
21
21
|
|
|
22
22
|
// Map from collection name -> doc id -> array of milestone snapshots
|
|
23
|
-
this._milestoneSnapshots =
|
|
23
|
+
this._milestoneSnapshots = Object.create(null);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
MemoryMilestoneDB.prototype = Object.create(MilestoneDB.prototype);
|
|
@@ -103,7 +103,8 @@ MemoryMilestoneDB.prototype._findMilestoneSnapshot = function(collection, id, br
|
|
|
103
103
|
};
|
|
104
104
|
|
|
105
105
|
MemoryMilestoneDB.prototype._getMilestoneSnapshotsSync = function(collection, id) {
|
|
106
|
-
var collectionSnapshots = this._milestoneSnapshots[collection] ||
|
|
106
|
+
var collectionSnapshots = this._milestoneSnapshots[collection] ||
|
|
107
|
+
(this._milestoneSnapshots[collection] = Object.create(null));
|
|
107
108
|
return collectionSnapshots[id] || (collectionSnapshots[id] = []);
|
|
108
109
|
};
|
|
109
110
|
|
package/lib/ot.js
CHANGED
|
@@ -108,6 +108,20 @@ function applyOpEdit(snapshot, edit) {
|
|
|
108
108
|
var type = types.map[snapshot.type];
|
|
109
109
|
if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
|
|
110
110
|
|
|
111
|
+
if (type.name === 'json0' && Array.isArray(edit)) {
|
|
112
|
+
for (var i = 0; i < edit.length; i++) {
|
|
113
|
+
var opComponent = edit[i];
|
|
114
|
+
if (Array.isArray(opComponent.p)) {
|
|
115
|
+
for (var j = 0; j < opComponent.p.length; j++) {
|
|
116
|
+
var pathSegment = opComponent.p[j];
|
|
117
|
+
if (util.isDangerousProperty(pathSegment)) {
|
|
118
|
+
return new ShareDBError(ERROR_CODE.ERR_OT_OP_NOT_APPLIED, 'Invalid path segment');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
111
125
|
try {
|
|
112
126
|
snapshot.data = type.apply(snapshot.data, edit);
|
|
113
127
|
} catch (err) {
|
package/lib/projections.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
var json0 = require('ot-json0').type;
|
|
2
2
|
var ShareDBError = require('./error');
|
|
3
|
+
var util = require('./util');
|
|
3
4
|
|
|
4
5
|
var ERROR_CODE = ShareDBError.CODES;
|
|
5
6
|
|
|
@@ -119,7 +120,7 @@ function projectData(fields, data) {
|
|
|
119
120
|
// Shallow copy of each field
|
|
120
121
|
var result = {};
|
|
121
122
|
for (var key in fields) {
|
|
122
|
-
if (
|
|
123
|
+
if (util.hasOwn(data, key)) {
|
|
123
124
|
result[key] = data[key];
|
|
124
125
|
}
|
|
125
126
|
}
|
package/lib/pubsub/index.js
CHANGED
|
@@ -13,12 +13,12 @@ function PubSub(options) {
|
|
|
13
13
|
this.nextStreamId = 1;
|
|
14
14
|
this.streamsCount = 0;
|
|
15
15
|
// Maps channel -> id -> stream
|
|
16
|
-
this.streams =
|
|
16
|
+
this.streams = Object.create(null);
|
|
17
17
|
// State for tracking subscriptions. We track this.subscribed separately from
|
|
18
18
|
// the streams, since the stream gets added synchronously, and the subscribe
|
|
19
19
|
// isn't complete until the callback returns from Redis
|
|
20
20
|
// Maps channel -> true
|
|
21
|
-
this.subscribed =
|
|
21
|
+
this.subscribed = Object.create(null);
|
|
22
22
|
|
|
23
23
|
var pubsub = this;
|
|
24
24
|
this._defaultCallback = function(err) {
|
|
@@ -111,7 +111,7 @@ PubSub.prototype._createStream = function(channel) {
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
this.streamsCount++;
|
|
114
|
-
var map = this.streams[channel] || (this.streams[channel] =
|
|
114
|
+
var map = this.streams[channel] || (this.streams[channel] = Object.create(null));
|
|
115
115
|
stream.id = this.nextStreamId++;
|
|
116
116
|
map[stream.id] = stream;
|
|
117
117
|
|
|
@@ -43,7 +43,7 @@ function ReadSnapshotsRequest(collection, snapshots, snapshotType) {
|
|
|
43
43
|
*/
|
|
44
44
|
ReadSnapshotsRequest.prototype.rejectSnapshotRead = function(snapshot, error) {
|
|
45
45
|
if (!this._idToError) {
|
|
46
|
-
this._idToError =
|
|
46
|
+
this._idToError = Object.create(null);
|
|
47
47
|
}
|
|
48
48
|
this._idToError[snapshot.id] = error;
|
|
49
49
|
};
|
package/lib/submit-request.js
CHANGED
|
@@ -26,7 +26,7 @@ function SubmitRequest(backend, agent, index, id, op, options) {
|
|
|
26
26
|
// Set as this request is sent through middleware
|
|
27
27
|
this.action = null;
|
|
28
28
|
// For custom use in middleware
|
|
29
|
-
this.custom =
|
|
29
|
+
this.custom = Object.create(null);
|
|
30
30
|
|
|
31
31
|
// Whether or not to store a milestone snapshot. If left as null, the milestone
|
|
32
32
|
// snapshots are saved according to the interval provided to the milestone db
|
package/lib/types.js
CHANGED
package/lib/util.js
CHANGED
|
@@ -7,6 +7,9 @@ exports.hasKeys = function(object) {
|
|
|
7
7
|
return false;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
var hasOwn;
|
|
11
|
+
exports.hasOwn = hasOwn = Object.hasOwn || Object.prototype.hasOwnProperty.call;
|
|
12
|
+
|
|
10
13
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger#Polyfill
|
|
11
14
|
exports.isInteger = Number.isInteger || function(value) {
|
|
12
15
|
return typeof value === 'number' &&
|
|
@@ -29,7 +32,7 @@ exports.dig = function() {
|
|
|
29
32
|
var obj = arguments[0];
|
|
30
33
|
for (var i = 1; i < arguments.length; i++) {
|
|
31
34
|
var key = arguments[i];
|
|
32
|
-
obj = obj[key]
|
|
35
|
+
obj = hasOwn(obj, key) ? obj[key] : (i === arguments.length - 1 ? undefined : Object.create(null));
|
|
33
36
|
}
|
|
34
37
|
return obj;
|
|
35
38
|
};
|
|
@@ -39,8 +42,8 @@ exports.digOrCreate = function() {
|
|
|
39
42
|
var createCallback = arguments[arguments.length - 1];
|
|
40
43
|
for (var i = 1; i < arguments.length - 1; i++) {
|
|
41
44
|
var key = arguments[i];
|
|
42
|
-
obj = obj[key]
|
|
43
|
-
(obj[key] = i === arguments.length - 2 ? createCallback() :
|
|
45
|
+
obj = hasOwn(obj, key) ? obj[key] :
|
|
46
|
+
(obj[key] = i === arguments.length - 2 ? createCallback() : Object.create(null));
|
|
44
47
|
}
|
|
45
48
|
return obj;
|
|
46
49
|
};
|
|
@@ -50,7 +53,7 @@ exports.digAndRemove = function() {
|
|
|
50
53
|
var objects = [obj];
|
|
51
54
|
for (var i = 1; i < arguments.length - 1; i++) {
|
|
52
55
|
var key = arguments[i];
|
|
53
|
-
if (!obj
|
|
56
|
+
if (!hasOwn(obj, key)) break;
|
|
54
57
|
obj = obj[key];
|
|
55
58
|
objects.push(obj);
|
|
56
59
|
};
|
|
@@ -101,3 +104,12 @@ exports.clone = function(obj) {
|
|
|
101
104
|
return (obj === undefined) ? undefined : JSON.parse(JSON.stringify(obj));
|
|
102
105
|
};
|
|
103
106
|
|
|
107
|
+
var objectProtoPropNames = Object.create(null);
|
|
108
|
+
Object.getOwnPropertyNames(Object.prototype).forEach(function(prop) {
|
|
109
|
+
if (prop !== '__proto__') {
|
|
110
|
+
objectProtoPropNames[prop] = true;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
exports.isDangerousProperty = function(propName) {
|
|
114
|
+
return propName === '__proto__' || objectProtoPropNames[propName];
|
|
115
|
+
};
|
package/package.json
CHANGED
package/test/client/doc.js
CHANGED
|
@@ -617,6 +617,106 @@ describe('Doc', function() {
|
|
|
617
617
|
});
|
|
618
618
|
});
|
|
619
619
|
|
|
620
|
+
describe('errors on ops that could cause prototype corruption', function() {
|
|
621
|
+
function expectReceiveError(
|
|
622
|
+
connection,
|
|
623
|
+
collectionName,
|
|
624
|
+
docId,
|
|
625
|
+
expectedError,
|
|
626
|
+
done
|
|
627
|
+
) {
|
|
628
|
+
connection.on('receive', function(request) {
|
|
629
|
+
var message = request.data;
|
|
630
|
+
if (message.c === collectionName && message.d === docId) {
|
|
631
|
+
if ('error' in message) {
|
|
632
|
+
request.data = null; // Stop further processing of the message
|
|
633
|
+
if (message.error.message === expectedError) {
|
|
634
|
+
return done();
|
|
635
|
+
} else {
|
|
636
|
+
return done('Unexpected ShareDB error: ' + message.error.message);
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
return done('Expected error on ' + collectionName + '.' + docId + ' but got no error');
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
['__proto__', 'constructor'].forEach(function(badProp) {
|
|
646
|
+
it('Rejects ops with collection ' + badProp, function(done) {
|
|
647
|
+
var collectionName = badProp;
|
|
648
|
+
var docId = 'test-doc';
|
|
649
|
+
expectReceiveError(this.connection, collectionName, docId, 'Invalid collection', done);
|
|
650
|
+
this.connection.send({
|
|
651
|
+
a: 'op',
|
|
652
|
+
c: collectionName,
|
|
653
|
+
d: docId,
|
|
654
|
+
v: 0,
|
|
655
|
+
seq: this.connection.seq++,
|
|
656
|
+
x: {},
|
|
657
|
+
create: {type: 'http://sharejs.org/types/JSONv0', data: {name: 'Test doc'}}
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('Rejects ops with doc id ' + badProp, function(done) {
|
|
662
|
+
var collectionName = 'test-collection';
|
|
663
|
+
var docId = badProp;
|
|
664
|
+
expectReceiveError(this.connection, collectionName, docId, 'Invalid id', done);
|
|
665
|
+
this.connection.send({
|
|
666
|
+
a: 'op',
|
|
667
|
+
c: collectionName,
|
|
668
|
+
d: docId,
|
|
669
|
+
v: 0,
|
|
670
|
+
seq: this.connection.seq++,
|
|
671
|
+
x: {},
|
|
672
|
+
create: {type: 'http://sharejs.org/types/JSONv0', data: {name: 'Some doc'}}
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('Rejects ops with ' + badProp + ' as first path segment', function(done) {
|
|
677
|
+
var connection = this.connection;
|
|
678
|
+
var collectionName = 'test-collection';
|
|
679
|
+
var docId = 'test-doc';
|
|
680
|
+
connection.get(collectionName, docId).create({id: docId}, function(err) {
|
|
681
|
+
if (err) {
|
|
682
|
+
return done(err);
|
|
683
|
+
}
|
|
684
|
+
expectReceiveError(connection, collectionName, docId, 'Invalid path segment', done);
|
|
685
|
+
connection.send({
|
|
686
|
+
a: 'op',
|
|
687
|
+
c: collectionName,
|
|
688
|
+
d: docId,
|
|
689
|
+
v: 1,
|
|
690
|
+
seq: connection.seq++,
|
|
691
|
+
x: {},
|
|
692
|
+
op: [{p: [badProp, 'toString'], oi: 'oops'}]
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('Rejects ops with ' + badProp + ' as later path segment', function(done) {
|
|
698
|
+
var connection = this.connection;
|
|
699
|
+
var collectionName = 'test-collection';
|
|
700
|
+
var docId = 'test-doc';
|
|
701
|
+
connection.get(collectionName, docId).create({id: docId}, function(err) {
|
|
702
|
+
if (err) {
|
|
703
|
+
return done(err);
|
|
704
|
+
}
|
|
705
|
+
expectReceiveError(connection, collectionName, docId, 'Invalid path segment', done);
|
|
706
|
+
connection.send({
|
|
707
|
+
a: 'op',
|
|
708
|
+
c: collectionName,
|
|
709
|
+
d: docId,
|
|
710
|
+
v: 1,
|
|
711
|
+
seq: connection.seq++,
|
|
712
|
+
x: {},
|
|
713
|
+
op: [{p: ['foo', badProp], oi: 'oops'}]
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
|
|
620
720
|
describe('toSnapshot', function() {
|
|
621
721
|
var doc;
|
|
622
722
|
beforeEach(function(done) {
|
|
@@ -645,20 +645,14 @@ describe('DocPresence', function() {
|
|
|
645
645
|
presence1.subscribe.bind(presence1),
|
|
646
646
|
presence2.subscribe.bind(presence2),
|
|
647
647
|
function(next) {
|
|
648
|
-
connection1.
|
|
649
|
-
next();
|
|
650
|
-
},
|
|
651
|
-
function(next) {
|
|
652
|
-
localPresence2.submit({index: 0}, errorHandler(done));
|
|
653
|
-
// We've not _actually_ disconnected the connection, so this
|
|
654
|
-
// event will still fire.
|
|
655
|
-
presence1.once('receive', function() {
|
|
648
|
+
connection1.once('closed', function() {
|
|
656
649
|
next();
|
|
657
650
|
});
|
|
651
|
+
connection1.close();
|
|
658
652
|
},
|
|
653
|
+
localPresence2.submit.bind(localPresence2, {index: 0}),
|
|
659
654
|
function(next) {
|
|
660
|
-
|
|
661
|
-
connection1._setState('connected');
|
|
655
|
+
backend.connect(connection1);
|
|
662
656
|
presence1.once('receive', function(id, presence) {
|
|
663
657
|
expect(presence).to.eql({index: 0});
|
|
664
658
|
next();
|
|
@@ -344,9 +344,11 @@ describe('Presence', function() {
|
|
|
344
344
|
presence1.subscribe.bind(presence1),
|
|
345
345
|
presence2.subscribe.bind(presence2),
|
|
346
346
|
function(next) {
|
|
347
|
-
connection2.
|
|
347
|
+
connection2.once('closed', function() {
|
|
348
|
+
next();
|
|
349
|
+
});
|
|
350
|
+
connection2.close();
|
|
348
351
|
expect(connection2.canSend).to.be.false;
|
|
349
|
-
next();
|
|
350
352
|
},
|
|
351
353
|
localPresence1.submit.bind(localPresence1, {index: 1}),
|
|
352
354
|
function(next) {
|
|
@@ -355,8 +357,7 @@ describe('Presence', function() {
|
|
|
355
357
|
expect(presence).to.eql({index: 1});
|
|
356
358
|
next();
|
|
357
359
|
});
|
|
358
|
-
|
|
359
|
-
connection2._setState('connected');
|
|
360
|
+
backend.connect(connection2);
|
|
360
361
|
}
|
|
361
362
|
], done);
|
|
362
363
|
});
|
|
@@ -444,12 +445,14 @@ describe('Presence', function() {
|
|
|
444
445
|
it('does not leak Streams when subscribing the same presence multiple times', function(done) {
|
|
445
446
|
var streamsCount;
|
|
446
447
|
async.series([
|
|
447
|
-
presence1.subscribe.bind(presence1
|
|
448
|
+
presence1.subscribe.bind(presence1),
|
|
448
449
|
function(next) {
|
|
449
450
|
streamsCount = backend.pubsub.streamsCount;
|
|
451
|
+
// Trick it into sending a duplicate request
|
|
452
|
+
presence1.wantSubscribe = false;
|
|
450
453
|
next();
|
|
451
454
|
},
|
|
452
|
-
presence1.subscribe.bind(presence1
|
|
455
|
+
presence1.subscribe.bind(presence1),
|
|
453
456
|
function(next) {
|
|
454
457
|
expect(backend.pubsub.streamsCount).to.equal(streamsCount);
|
|
455
458
|
next();
|
|
@@ -463,6 +466,16 @@ describe('Presence', function() {
|
|
|
463
466
|
}).to.throw();
|
|
464
467
|
});
|
|
465
468
|
|
|
469
|
+
['__proto__', 'constructor'].forEach(function(badProp) {
|
|
470
|
+
it('Rejects presence with channel ' + badProp, function(done) {
|
|
471
|
+
var presence = connection1.getPresence(badProp);
|
|
472
|
+
presence.subscribe(function(err) {
|
|
473
|
+
expect(err).to.be.an('error').to.haveOwnProperty('message', 'Invalid presence channel');
|
|
474
|
+
done();
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
466
479
|
it('assigns an ID if one is not provided', function() {
|
|
467
480
|
var localPresence = presence1.create();
|
|
468
481
|
expect(localPresence.presenceId).to.be.ok;
|
|
@@ -656,6 +669,7 @@ describe('Presence', function() {
|
|
|
656
669
|
function(next) {
|
|
657
670
|
presence2.on('receive', function(id, value) {
|
|
658
671
|
expect(value).to.eql({index: 6});
|
|
672
|
+
expect(connection1.agent.presenceRequests['test-channel']['presence-1'].p).to.eql({index: 6});
|
|
659
673
|
next();
|
|
660
674
|
});
|
|
661
675
|
localPresence1.submit({index: 5}, errorHandler(done));
|
|
@@ -678,6 +692,7 @@ describe('Presence', function() {
|
|
|
678
692
|
});
|
|
679
693
|
localPresence1.submit({index: 5}, function(error) {
|
|
680
694
|
expect(error.message).to.contain('bad!');
|
|
695
|
+
expect(connection1.agent.presenceRequests['test-channel']).not.to.be.ok;
|
|
681
696
|
next();
|
|
682
697
|
});
|
|
683
698
|
}
|