sharedb 2.1.0 → 2.2.3
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/README.md +3 -4
- package/lib/agent.js +15 -5
- package/lib/backend.js +18 -0
- package/lib/client/doc.js +26 -11
- package/lib/client/presence/local-doc-presence.js +15 -0
- package/lib/ot.js +13 -1
- package/package.json +1 -1
- package/test/backend.js +36 -14
- package/test/client/presence/doc-presence.js +46 -0
- package/test/client/presence/presence.js +98 -0
- package/test/client/snapshot-version-request.js +46 -0
package/README.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
_This README is for `sharedb@1.x`. For `sharedb@1.x-beta`, see [the 1.x-beta branch](https://github.com/share/sharedb/tree/1.x-beta). To upgrade, see [the upgrade guide](https://github.com/share/sharedb/wiki/Upgrading-to-sharedb@1.0.0-from-1.0.0-beta)._
|
|
2
|
-
|
|
3
1
|
# ShareDB
|
|
4
2
|
|
|
5
3
|
[](https://npmjs.org/package/sharedb)
|
|
@@ -11,8 +9,9 @@ ShareDB is a realtime database backend based on [Operational Transformation
|
|
|
11
9
|
documents. It is the realtime backend for the [DerbyJS web application
|
|
12
10
|
framework](http://derbyjs.com/).
|
|
13
11
|
|
|
14
|
-
For questions, discussion and announcements, join the [ShareJS mailing
|
|
15
|
-
list](https://groups.google.com/forum/?fromgroups#!forum/sharejs) or [
|
|
12
|
+
For help, questions, discussion and announcements, join the [ShareJS mailing
|
|
13
|
+
list](https://groups.google.com/forum/?fromgroups#!forum/sharejs) or [read the documentation](https://share.github.io/sharedb/
|
|
14
|
+
).
|
|
16
15
|
|
|
17
16
|
Please report any bugs you find to the [issue
|
|
18
17
|
tracker](https://github.com/share/sharedb/issues).
|
package/lib/agent.js
CHANGED
|
@@ -736,17 +736,25 @@ Agent.prototype._src = function() {
|
|
|
736
736
|
|
|
737
737
|
Agent.prototype._broadcastPresence = function(presence, callback) {
|
|
738
738
|
var agent = this;
|
|
739
|
+
var backend = this.backend;
|
|
739
740
|
var requests = this.presenceRequests[presence.ch] || (this.presenceRequests[presence.ch] = {});
|
|
740
741
|
var previousRequest = requests[presence.id];
|
|
741
742
|
if (!previousRequest || previousRequest.pv < presence.pv) {
|
|
742
743
|
this.presenceRequests[presence.ch][presence.id] = presence;
|
|
743
744
|
}
|
|
744
|
-
|
|
745
|
+
var context = {
|
|
746
|
+
presence: presence,
|
|
747
|
+
collection: presence.c
|
|
748
|
+
};
|
|
749
|
+
backend.trigger(backend.MIDDLEWARE_ACTIONS.receivePresence, this, context, function(error) {
|
|
745
750
|
if (error) return callback(error);
|
|
746
|
-
|
|
747
|
-
agent.backend.pubsub.publish([channel], presence, function(error) {
|
|
751
|
+
backend.transformPresenceToLatestVersion(agent, presence, function(error, presence) {
|
|
748
752
|
if (error) return callback(error);
|
|
749
|
-
|
|
753
|
+
var channel = agent._getPresenceChannel(presence.ch);
|
|
754
|
+
agent.backend.pubsub.publish([channel], presence, function(error) {
|
|
755
|
+
if (error) return callback(error);
|
|
756
|
+
callback(null, presence);
|
|
757
|
+
});
|
|
750
758
|
});
|
|
751
759
|
});
|
|
752
760
|
};
|
|
@@ -815,7 +823,9 @@ Agent.prototype._handlePresenceData = function(presence) {
|
|
|
815
823
|
var agent = this;
|
|
816
824
|
backend.trigger(backend.MIDDLEWARE_ACTIONS.sendPresence, this, context, function(error) {
|
|
817
825
|
if (error) {
|
|
818
|
-
|
|
826
|
+
if (backend.doNotForwardSendPresenceErrorsToClient) backend.errorHandler(error, {agent: agent});
|
|
827
|
+
else agent.send({a: 'p', ch: presence.ch, id: presence.id, error: getReplyErrorObject(error)});
|
|
828
|
+
return;
|
|
819
829
|
}
|
|
820
830
|
agent.send(presence);
|
|
821
831
|
});
|
package/lib/backend.js
CHANGED
|
@@ -14,6 +14,7 @@ var StreamSocket = require('./stream-socket');
|
|
|
14
14
|
var SubmitRequest = require('./submit-request');
|
|
15
15
|
var ReadSnapshotsRequest = require('./read-snapshots-request');
|
|
16
16
|
var util = require('./util');
|
|
17
|
+
var logger = require('./logger');
|
|
17
18
|
|
|
18
19
|
var ERROR_CODE = ShareDBError.CODES;
|
|
19
20
|
|
|
@@ -34,6 +35,14 @@ function Backend(options) {
|
|
|
34
35
|
this.suppressPublish = !!options.suppressPublish;
|
|
35
36
|
this.maxSubmitRetries = options.maxSubmitRetries || null;
|
|
36
37
|
this.presenceEnabled = !!options.presence;
|
|
38
|
+
this.doNotForwardSendPresenceErrorsToClient = !!options.doNotForwardSendPresenceErrorsToClient;
|
|
39
|
+
if (this.presenceEnabled && !this.doNotForwardSendPresenceErrorsToClient) {
|
|
40
|
+
logger.warn(
|
|
41
|
+
'Broadcasting "sendPresence" middleware errors to clients is deprecated ' +
|
|
42
|
+
'and will be removed in a future release. Disable this behaviour with:\n\n' +
|
|
43
|
+
'new Backend({doNotForwardSendPresenceErrorsToClient: true})\n\n'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
37
46
|
|
|
38
47
|
// Map from event name to a list of middleware
|
|
39
48
|
this.middleware = {};
|
|
@@ -41,6 +50,13 @@ function Backend(options) {
|
|
|
41
50
|
// The number of open agents for monitoring and testing memory leaks
|
|
42
51
|
this.agentsCount = 0;
|
|
43
52
|
this.remoteAgentsCount = 0;
|
|
53
|
+
|
|
54
|
+
this.errorHandler = typeof options.errorHandler === 'function' ?
|
|
55
|
+
options.errorHandler :
|
|
56
|
+
// eslint-disable-next-line no-unused-vars
|
|
57
|
+
function(error, context) {
|
|
58
|
+
logger.error(error);
|
|
59
|
+
};
|
|
44
60
|
}
|
|
45
61
|
module.exports = Backend;
|
|
46
62
|
emitter.mixin(Backend);
|
|
@@ -68,6 +84,8 @@ Backend.prototype.MIDDLEWARE_ACTIONS = {
|
|
|
68
84
|
// by design, changing existing reply properties can cause weird bugs, since
|
|
69
85
|
// the rest of ShareDB would be unaware of those changes.
|
|
70
86
|
reply: 'reply',
|
|
87
|
+
// The server received presence information
|
|
88
|
+
receivePresence: 'receivePresence',
|
|
71
89
|
// About to send presence information to a client
|
|
72
90
|
sendPresence: 'sendPresence',
|
|
73
91
|
// An operation is about to be submitted to the database
|
package/lib/client/doc.js
CHANGED
|
@@ -114,6 +114,10 @@ function Doc(connection, collection, id) {
|
|
|
114
114
|
// ops are still received. Should be toggled through the pause() and
|
|
115
115
|
// resume() methods to correctly flush on resume.
|
|
116
116
|
this.paused = false;
|
|
117
|
+
|
|
118
|
+
// Internal counter that gets incremented every time doc.data is updated.
|
|
119
|
+
// Used as a cheap way to check if doc.data has changed.
|
|
120
|
+
this._dataStateVersion = 0;
|
|
117
121
|
}
|
|
118
122
|
emitter.mixin(Doc);
|
|
119
123
|
|
|
@@ -156,13 +160,18 @@ Doc.prototype._setType = function(newType) {
|
|
|
156
160
|
} else if (newType === null) {
|
|
157
161
|
this.type = newType;
|
|
158
162
|
// If we removed the type from the object, also remove its data
|
|
159
|
-
this.
|
|
163
|
+
this._setData(undefined);
|
|
160
164
|
} else {
|
|
161
165
|
var err = new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Missing type ' + newType);
|
|
162
166
|
return this.emit('error', err);
|
|
163
167
|
}
|
|
164
168
|
};
|
|
165
169
|
|
|
170
|
+
Doc.prototype._setData = function(data) {
|
|
171
|
+
this.data = data;
|
|
172
|
+
this._dataStateVersion++;
|
|
173
|
+
};
|
|
174
|
+
|
|
166
175
|
// Ingest snapshot data. This data must include a version, snapshot and type.
|
|
167
176
|
// This is used both to ingest data that was exported with a webpage and data
|
|
168
177
|
// that was received from the server during a fetch.
|
|
@@ -218,9 +227,11 @@ Doc.prototype.ingestSnapshot = function(snapshot, callback) {
|
|
|
218
227
|
this.version = snapshot.v;
|
|
219
228
|
var type = (snapshot.type === undefined) ? types.defaultType : snapshot.type;
|
|
220
229
|
this._setType(type);
|
|
221
|
-
this.
|
|
222
|
-
this.type.deserialize
|
|
223
|
-
|
|
230
|
+
this._setData(
|
|
231
|
+
(this.type && this.type.deserialize) ?
|
|
232
|
+
this.type.deserialize(snapshot.data) :
|
|
233
|
+
snapshot.data
|
|
234
|
+
);
|
|
224
235
|
this.emit('load');
|
|
225
236
|
callback && callback();
|
|
226
237
|
};
|
|
@@ -622,7 +633,7 @@ Doc.prototype._otApply = function(op, source) {
|
|
|
622
633
|
}
|
|
623
634
|
// Apply the individual op component
|
|
624
635
|
this.emit('before op', componentOp.op, source, op.src);
|
|
625
|
-
this.
|
|
636
|
+
this._setData(this.type.apply(this.data, componentOp.op));
|
|
626
637
|
this.emit('op', componentOp.op, source, op.src);
|
|
627
638
|
}
|
|
628
639
|
this.emit('op batch', op.op, source);
|
|
@@ -635,7 +646,7 @@ Doc.prototype._otApply = function(op, source) {
|
|
|
635
646
|
// the snapshot before it gets changed
|
|
636
647
|
this.emit('before op', op.op, source, op.src);
|
|
637
648
|
// Apply the operation to the local data, mutating it in place
|
|
638
|
-
this.
|
|
649
|
+
this._setData(this.type.apply(this.data, op.op));
|
|
639
650
|
// Emit an 'op' event once the local data includes the changes from the
|
|
640
651
|
// op. For locally submitted ops, this will be synchronously with
|
|
641
652
|
// submission and before the server or other clients have received the op.
|
|
@@ -648,11 +659,15 @@ Doc.prototype._otApply = function(op, source) {
|
|
|
648
659
|
|
|
649
660
|
if (op.create) {
|
|
650
661
|
this._setType(op.create.type);
|
|
651
|
-
|
|
652
|
-
(this.type.createDeserialized)
|
|
653
|
-
this.type.createDeserialized(op.create.data)
|
|
654
|
-
|
|
655
|
-
|
|
662
|
+
if (this.type.deserialize) {
|
|
663
|
+
if (this.type.createDeserialized) {
|
|
664
|
+
this._setData(this.type.createDeserialized(op.create.data));
|
|
665
|
+
} else {
|
|
666
|
+
this._setData(this.type.deserialize(this.type.create(op.create.data)));
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
this._setData(this.type.create(op.create.data));
|
|
670
|
+
}
|
|
656
671
|
this.emit('create', source);
|
|
657
672
|
return;
|
|
658
673
|
}
|
|
@@ -12,6 +12,7 @@ function LocalDocPresence(presence, presenceId) {
|
|
|
12
12
|
|
|
13
13
|
this._doc = this.connection.get(this.collection, this.id);
|
|
14
14
|
this._isSending = false;
|
|
15
|
+
this._docDataVersionByPresenceVersion = {};
|
|
15
16
|
|
|
16
17
|
this._opHandler = this._transformAgainstOp.bind(this);
|
|
17
18
|
this._createOrDelHandler = this._handleCreateOrDel.bind(this);
|
|
@@ -35,6 +36,9 @@ LocalDocPresence.prototype.submit = function(value, callback) {
|
|
|
35
36
|
return this._callbackOrEmit(error, callback);
|
|
36
37
|
};
|
|
37
38
|
|
|
39
|
+
// Record the current data state version to check if we need to transform
|
|
40
|
+
// the presence later
|
|
41
|
+
this._docDataVersionByPresenceVersion[this.presenceVersion] = this._doc._dataStateVersion;
|
|
38
42
|
LocalPresence.prototype.submit.call(this, value, callback);
|
|
39
43
|
};
|
|
40
44
|
|
|
@@ -63,6 +67,7 @@ LocalDocPresence.prototype._sendPending = function() {
|
|
|
63
67
|
});
|
|
64
68
|
|
|
65
69
|
presence._pendingMessages = [];
|
|
70
|
+
presence._docDataVersionByPresenceVersion = {};
|
|
66
71
|
});
|
|
67
72
|
};
|
|
68
73
|
|
|
@@ -76,9 +81,18 @@ LocalDocPresence.prototype._registerWithDoc = function() {
|
|
|
76
81
|
|
|
77
82
|
LocalDocPresence.prototype._transformAgainstOp = function(op, source) {
|
|
78
83
|
var presence = this;
|
|
84
|
+
var docDataVersion = this._doc._dataStateVersion;
|
|
85
|
+
|
|
79
86
|
this._pendingMessages.forEach(function(message) {
|
|
87
|
+
// Check if the presence needs transforming against the op - this is to check against
|
|
88
|
+
// edge cases where presence is submitted from an 'op' event
|
|
89
|
+
var messageDocDataVersion = presence._docDataVersionByPresenceVersion[message.pv];
|
|
90
|
+
if (messageDocDataVersion >= docDataVersion) return;
|
|
80
91
|
try {
|
|
81
92
|
message.p = presence._transformPresence(message.p, op, source);
|
|
93
|
+
// Ensure the presence's data version is kept consistent to deal with "deep" op
|
|
94
|
+
// submissions
|
|
95
|
+
presence._docDataVersionByPresenceVersion[message.pv] = docDataVersion;
|
|
82
96
|
} catch (error) {
|
|
83
97
|
var callback = presence._getCallback(message.pv);
|
|
84
98
|
presence._callbackOrEmit(error, callback);
|
|
@@ -103,6 +117,7 @@ LocalDocPresence.prototype._handleCreateOrDel = function() {
|
|
|
103
117
|
LocalDocPresence.prototype._handleLoad = function() {
|
|
104
118
|
this.value = null;
|
|
105
119
|
this._pendingMessages = [];
|
|
120
|
+
this._docDataVersionByPresenceVersion = {};
|
|
106
121
|
};
|
|
107
122
|
|
|
108
123
|
LocalDocPresence.prototype._message = function() {
|
package/lib/ot.js
CHANGED
|
@@ -221,11 +221,18 @@ function normalizeLegacyJson0Ops(snapshot, json0Op) {
|
|
|
221
221
|
if (snapshot.type !== types.defaultType.uri) return;
|
|
222
222
|
var components = json0Op.op;
|
|
223
223
|
if (!components) return;
|
|
224
|
+
var data = snapshot.data;
|
|
225
|
+
|
|
226
|
+
// type.apply() makes no guarantees about mutating the original data, so
|
|
227
|
+
// we need to clone. However, we only need to apply() if we have multiple
|
|
228
|
+
// components, so avoid cloning if we don't have to.
|
|
229
|
+
if (components.length > 1) data = util.clone(data);
|
|
230
|
+
|
|
224
231
|
for (var i = 0; i < components.length; i++) {
|
|
225
232
|
var component = components[i];
|
|
226
233
|
if (typeof component.lm === 'string') component.lm = +component.lm;
|
|
227
234
|
var path = component.p;
|
|
228
|
-
var element =
|
|
235
|
+
var element = data;
|
|
229
236
|
for (var j = 0; j < path.length; j++) {
|
|
230
237
|
var key = path[j];
|
|
231
238
|
// https://github.com/ottypes/json0/blob/73db17e86adc5d801951d1a69453b01382e66c7d/lib/json0.js#L21
|
|
@@ -234,5 +241,10 @@ function normalizeLegacyJson0Ops(snapshot, json0Op) {
|
|
|
234
241
|
else if (element.constructor === Object) path[j] = key.toString();
|
|
235
242
|
element = element[key];
|
|
236
243
|
}
|
|
244
|
+
|
|
245
|
+
// Apply to update the snapshot, so we can correctly check the path for
|
|
246
|
+
// the next component. We don't need to do this on the final iteration,
|
|
247
|
+
// since there's no more ops.
|
|
248
|
+
if (i < components.length - 1) data = types.defaultType.apply(data, [component]);
|
|
237
249
|
}
|
|
238
250
|
}
|
package/package.json
CHANGED
package/test/backend.js
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
var Backend = require('../lib/backend');
|
|
2
2
|
var expect = require('chai').expect;
|
|
3
3
|
var sinon = require('sinon');
|
|
4
|
+
var logger = require('../lib/logger');
|
|
4
5
|
|
|
5
6
|
describe('Backend', function() {
|
|
6
7
|
var backend;
|
|
7
|
-
var agent = {
|
|
8
|
-
custom: {
|
|
9
|
-
foo: 'bar'
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
var fetchOptions = {
|
|
13
|
-
snapshotOptions: {
|
|
14
|
-
fizz: 'buzz'
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
beforeEach(function() {
|
|
19
|
-
backend = new Backend();
|
|
20
|
-
});
|
|
21
8
|
|
|
22
9
|
afterEach(function(done) {
|
|
23
10
|
backend.close(done);
|
|
24
11
|
});
|
|
25
12
|
|
|
13
|
+
describe('options', function() {
|
|
14
|
+
describe('errorHandler', function() {
|
|
15
|
+
it('logs by default', function() {
|
|
16
|
+
backend = new Backend();
|
|
17
|
+
var error = new Error('foo');
|
|
18
|
+
sinon.spy(logger, 'error');
|
|
19
|
+
backend.errorHandler(error);
|
|
20
|
+
expect(logger.error.callCount).to.equal(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('overrides with another function', function() {
|
|
24
|
+
var handler = sinon.spy();
|
|
25
|
+
backend = new Backend({errorHandler: handler});
|
|
26
|
+
var error = new Error('foo');
|
|
27
|
+
backend.errorHandler(error);
|
|
28
|
+
expect(handler.callCount).to.equal(1);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
26
33
|
describe('a simple document', function() {
|
|
34
|
+
var agent = {
|
|
35
|
+
custom: {
|
|
36
|
+
foo: 'bar'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var fetchOptions = {
|
|
40
|
+
snapshotOptions: {
|
|
41
|
+
fizz: 'buzz'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
beforeEach(function() {
|
|
46
|
+
backend = new Backend();
|
|
47
|
+
});
|
|
48
|
+
|
|
27
49
|
beforeEach(function(done) {
|
|
28
50
|
var doc = backend.connect().get('books', '1984');
|
|
29
51
|
doc.create({title: '1984'}, function(error) {
|
|
@@ -979,4 +979,50 @@ describe('DocPresence', function() {
|
|
|
979
979
|
}
|
|
980
980
|
], done);
|
|
981
981
|
});
|
|
982
|
+
|
|
983
|
+
it('does not transform presence submitted in an op event when the presence was created late', function(done) {
|
|
984
|
+
var localPresence1;
|
|
985
|
+
|
|
986
|
+
doc1.on('op', function() {
|
|
987
|
+
localPresence1.submit({index: 7});
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
localPresence1 = presence1.create('presence-1');
|
|
991
|
+
|
|
992
|
+
async.series([
|
|
993
|
+
presence2.subscribe.bind(presence2),
|
|
994
|
+
function(next) {
|
|
995
|
+
doc1.submitOp({index: 5, value: 'ern'});
|
|
996
|
+
presence2.on('receive', function(id, value) {
|
|
997
|
+
expect(value).to.eql({index: 7});
|
|
998
|
+
next();
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
], done);
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
it('does transform late-created presence submitted in an op event by a deep second op', function(done) {
|
|
1005
|
+
var localPresence1;
|
|
1006
|
+
|
|
1007
|
+
var submitted = false;
|
|
1008
|
+
doc1.on('op', function() {
|
|
1009
|
+
if (submitted) return;
|
|
1010
|
+
submitted = true;
|
|
1011
|
+
localPresence1.submit({index: 7});
|
|
1012
|
+
doc1.submitOp({index: 5, value: 'akg'});
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
localPresence1 = presence1.create('presence-1');
|
|
1016
|
+
|
|
1017
|
+
async.series([
|
|
1018
|
+
presence2.subscribe.bind(presence2),
|
|
1019
|
+
function(next) {
|
|
1020
|
+
doc1.submitOp({index: 5, value: 'ern'});
|
|
1021
|
+
presence2.on('receive', function(id, value) {
|
|
1022
|
+
expect(value).to.eql({index: 10});
|
|
1023
|
+
next();
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
], done);
|
|
1027
|
+
});
|
|
982
1028
|
});
|
|
@@ -487,4 +487,102 @@ describe('Presence', function() {
|
|
|
487
487
|
}
|
|
488
488
|
], done);
|
|
489
489
|
});
|
|
490
|
+
|
|
491
|
+
describe('middleware', function() {
|
|
492
|
+
describe('receivePresence', function() {
|
|
493
|
+
it('provides the presence in the middleware', function(done) {
|
|
494
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.receivePresence, function(context) {
|
|
495
|
+
expect(context.presence.p).to.eql({index: 5});
|
|
496
|
+
done();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
var localPresence1 = presence1.create('presence-1');
|
|
500
|
+
localPresence1.submit({index: 5}, errorHandler(done));
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('can mutate the presence in the middleware', function(done) {
|
|
504
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.receivePresence, function(context, next) {
|
|
505
|
+
context.presence.p.index++;
|
|
506
|
+
next();
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
var localPresence1 = presence1.create('presence-1');
|
|
510
|
+
|
|
511
|
+
async.series([
|
|
512
|
+
presence2.subscribe.bind(presence2),
|
|
513
|
+
function(next) {
|
|
514
|
+
presence2.on('receive', function(id, value) {
|
|
515
|
+
expect(value).to.eql({index: 6});
|
|
516
|
+
next();
|
|
517
|
+
});
|
|
518
|
+
localPresence1.submit({index: 5}, errorHandler(done));
|
|
519
|
+
}
|
|
520
|
+
], done);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('can cancel a presence broadcast by erroring', function(done) {
|
|
524
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.receivePresence, function(context, next) {
|
|
525
|
+
next(new Error('bad!'));
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
var localPresence1 = presence1.create('presence-1');
|
|
529
|
+
|
|
530
|
+
async.series([
|
|
531
|
+
presence2.subscribe.bind(presence2),
|
|
532
|
+
function(next) {
|
|
533
|
+
presence2.on('receive', function() {
|
|
534
|
+
done(new Error('should not have received presence'));
|
|
535
|
+
});
|
|
536
|
+
localPresence1.submit({index: 5}, function(error) {
|
|
537
|
+
expect(error.message).to.contain('bad!');
|
|
538
|
+
next();
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
], done);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
describe('sendPresence', function() {
|
|
546
|
+
// TODO: This functionality is deprecated
|
|
547
|
+
it('sends an error to a subscribed client', function(done) {
|
|
548
|
+
var localPresence1 = presence1.create('presence-1');
|
|
549
|
+
|
|
550
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.sendPresence, function(context, next) {
|
|
551
|
+
next(new Error('uh-oh!'));
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
async.series([
|
|
555
|
+
presence2.subscribe.bind(presence2),
|
|
556
|
+
function(next) {
|
|
557
|
+
localPresence1.submit({index: 3}, errorHandler(done));
|
|
558
|
+
presence2.once('error', function(error) {
|
|
559
|
+
expect(error.message).to.equal('uh-oh!');
|
|
560
|
+
next();
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
], done);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it('emits errors on the server instead of sending to the client', function(done) {
|
|
567
|
+
var localPresence1 = presence1.create('presence-1');
|
|
568
|
+
|
|
569
|
+
backend.doNotForwardSendPresenceErrorsToClient = true;
|
|
570
|
+
backend.use(backend.MIDDLEWARE_ACTIONS.sendPresence, function(context, next) {
|
|
571
|
+
next(new Error('uh-oh!'));
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
async.series([
|
|
575
|
+
presence2.subscribe.bind(presence2),
|
|
576
|
+
function(next) {
|
|
577
|
+
localPresence1.submit({index: 3}, errorHandler(done));
|
|
578
|
+
presence2.on('error', errorHandler(done));
|
|
579
|
+
backend.errorHandler = function(error) {
|
|
580
|
+
expect(error.message).to.equal('uh-oh!');
|
|
581
|
+
next();
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
], done);
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
});
|
|
490
588
|
});
|
|
@@ -496,4 +496,50 @@ describe('SnapshotVersionRequest', function() {
|
|
|
496
496
|
});
|
|
497
497
|
});
|
|
498
498
|
});
|
|
499
|
+
|
|
500
|
+
describe('invalid json0v2 path with multiple components', function() {
|
|
501
|
+
beforeEach(function(done) {
|
|
502
|
+
var doc = backend.connect().get('series', 'his-dark-materials');
|
|
503
|
+
async.series([
|
|
504
|
+
doc.create.bind(doc, [{}]),
|
|
505
|
+
doc.submitOp.bind(doc, [
|
|
506
|
+
{p: ['0', 'title'], oi: ''},
|
|
507
|
+
{p: ['0', 'title', 0], si: 'Northern Lights'}
|
|
508
|
+
])
|
|
509
|
+
], done);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe('json0v1', function() {
|
|
513
|
+
it('fetches v2 with json0v1', function(done) {
|
|
514
|
+
backend.connect().fetchSnapshot('series', 'his-dark-materials', 2, function(error, snapshot) {
|
|
515
|
+
if (error) return done(error);
|
|
516
|
+
expect(snapshot.data).to.eql([{title: 'Northern Lights'}]);
|
|
517
|
+
done();
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe('json0v2', function() {
|
|
523
|
+
var defaultType;
|
|
524
|
+
|
|
525
|
+
beforeEach(function() {
|
|
526
|
+
defaultType = types.defaultType;
|
|
527
|
+
types.defaultType = json0v2;
|
|
528
|
+
types.register(json0v2);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
afterEach(function() {
|
|
532
|
+
types.defaultType = defaultType;
|
|
533
|
+
types.register(defaultType);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('fetches v2 with json0v2', function(done) {
|
|
537
|
+
backend.connect().fetchSnapshot('series', 'his-dark-materials', 2, function(error, snapshot) {
|
|
538
|
+
if (error) return done(error);
|
|
539
|
+
expect(snapshot.data).to.eql([{title: 'Northern Lights'}]);
|
|
540
|
+
done();
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
});
|
|
499
545
|
});
|