sharedb 3.3.1 → 4.0.0
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 +20 -0
- package/lib/client/doc.js +11 -18
- package/package.json +3 -2
- package/test/agent.js +74 -0
- package/test/client/doc.js +18 -0
- package/test/middleware.js +2 -2
- package/test/setup.js +4 -0
package/lib/agent.js
CHANGED
|
@@ -58,6 +58,11 @@ function Agent(backend, stream) {
|
|
|
58
58
|
// active, and it is passed to each middleware call
|
|
59
59
|
this.custom = {};
|
|
60
60
|
|
|
61
|
+
// The first message received over the connection. Stored to warn if messages
|
|
62
|
+
// are being sent before the handshake.
|
|
63
|
+
this._firstReceivedMessage = null;
|
|
64
|
+
this._handshakeReceived = false;
|
|
65
|
+
|
|
61
66
|
// Send the legacy message to initialize old clients with the random agent Id
|
|
62
67
|
this.send(this._initMessage(ACTIONS.initLegacy));
|
|
63
68
|
}
|
|
@@ -394,6 +399,7 @@ Agent.prototype._handleMessage = function(request, callback) {
|
|
|
394
399
|
try {
|
|
395
400
|
var errMessage = this._checkRequest(request);
|
|
396
401
|
if (errMessage) return callback(new ShareDBError(ERROR_CODE.ERR_MESSAGE_BADLY_FORMED, errMessage));
|
|
402
|
+
this._checkFirstMessage(request);
|
|
397
403
|
|
|
398
404
|
switch (request.a) {
|
|
399
405
|
case ACTIONS.handshake:
|
|
@@ -855,6 +861,20 @@ Agent.prototype._handlePresenceData = function(presence) {
|
|
|
855
861
|
});
|
|
856
862
|
};
|
|
857
863
|
|
|
864
|
+
Agent.prototype._checkFirstMessage = function(request) {
|
|
865
|
+
if (this._handshakeReceived) return;
|
|
866
|
+
if (!this._firstReceivedMessage) this._firstReceivedMessage = request;
|
|
867
|
+
|
|
868
|
+
if (request.a === ACTIONS.handshake) {
|
|
869
|
+
this._handshakeReceived = true;
|
|
870
|
+
if (this._firstReceivedMessage.a !== ACTIONS.handshake) {
|
|
871
|
+
logger.warn('Unexpected message received before handshake', this._firstReceivedMessage);
|
|
872
|
+
}
|
|
873
|
+
// Release memory
|
|
874
|
+
this._firstReceivedMessage = null;
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
|
|
858
878
|
function createClientOp(request, clientId) {
|
|
859
879
|
// src can be provided if it is not the same as the current agent,
|
|
860
880
|
// such as a resubmission after a reconnect, but it usually isn't needed
|
package/lib/client/doc.js
CHANGED
|
@@ -425,12 +425,17 @@ Doc.prototype._resubscribe = function() {
|
|
|
425
425
|
|
|
426
426
|
// Request the current document snapshot or ops that bring us up to date
|
|
427
427
|
Doc.prototype.fetch = function(callback) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
428
|
+
this._fetch({}, callback);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
Doc.prototype._fetch = function(options, callback) {
|
|
433
432
|
this.pendingFetch.push(callback);
|
|
433
|
+
var shouldSend = this.connection.canSend && (
|
|
434
|
+
options.force || !this.inflightFetch.length
|
|
435
|
+
);
|
|
436
|
+
if (!shouldSend) return;
|
|
437
|
+
this.inflightFetch.push(this.pendingFetch.shift());
|
|
438
|
+
this.connection.sendFetch(this);
|
|
434
439
|
};
|
|
435
440
|
|
|
436
441
|
// Fetch the initial document and keep receiving updates
|
|
@@ -490,18 +495,6 @@ Doc.prototype._flushSubscribe = function() {
|
|
|
490
495
|
}
|
|
491
496
|
};
|
|
492
497
|
|
|
493
|
-
function pushActionCallback(inflight, isDuplicate, callback) {
|
|
494
|
-
if (isDuplicate) {
|
|
495
|
-
var lastCallback = inflight.pop();
|
|
496
|
-
inflight.push(function(err) {
|
|
497
|
-
lastCallback && lastCallback(err);
|
|
498
|
-
callback && callback(err);
|
|
499
|
-
});
|
|
500
|
-
} else {
|
|
501
|
-
inflight.push(callback);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
498
|
function combineCallbacks(callbacks) {
|
|
506
499
|
callbacks = callbacks.filter(util.truthy);
|
|
507
500
|
if (!callbacks.length) return null;
|
|
@@ -1043,7 +1036,7 @@ Doc.prototype._hardRollback = function(err) {
|
|
|
1043
1036
|
|
|
1044
1037
|
// Fetch the latest version from the server to get us back into a working state
|
|
1045
1038
|
var doc = this;
|
|
1046
|
-
this.
|
|
1039
|
+
this._fetch({force: true}, function() {
|
|
1047
1040
|
// We want to check that no errors are swallowed, so we check that:
|
|
1048
1041
|
// - there are callbacks to call, and
|
|
1049
1042
|
// - that every single pending op called a callback
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharedb",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "JSON OT database backend",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"ot-json1": "^0.3.0",
|
|
23
23
|
"rich-text": "^4.1.0",
|
|
24
24
|
"sharedb-legacy": "npm:sharedb@=1.1.0",
|
|
25
|
-
"sinon": "^9.2.4"
|
|
25
|
+
"sinon": "^9.2.4",
|
|
26
|
+
"sinon-chai": "^3.7.0"
|
|
26
27
|
},
|
|
27
28
|
"files": [
|
|
28
29
|
"lib/",
|
package/test/agent.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
var Backend = require('../lib/backend');
|
|
2
|
+
var logger = require('../lib/logger');
|
|
3
|
+
var sinon = require('sinon');
|
|
4
|
+
var StreamSocket = require('../lib/stream-socket');
|
|
5
|
+
var expect = require('chai').expect;
|
|
6
|
+
var ACTIONS = require('../lib/message-actions').ACTIONS;
|
|
7
|
+
var Connection = require('../lib/client/connection');
|
|
8
|
+
var LegacyConnection = require('sharedb-legacy/lib/client').Connection;
|
|
9
|
+
|
|
10
|
+
describe('Agent', function() {
|
|
11
|
+
var backend;
|
|
12
|
+
|
|
13
|
+
beforeEach(function() {
|
|
14
|
+
backend = new Backend();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(function(done) {
|
|
18
|
+
backend.close(done);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('handshake', function() {
|
|
22
|
+
it('warns when messages are sent before the handshake', function(done) {
|
|
23
|
+
var socket = new StreamSocket();
|
|
24
|
+
var stream = socket.stream;
|
|
25
|
+
backend.listen(stream);
|
|
26
|
+
sinon.spy(logger, 'warn');
|
|
27
|
+
socket.send(JSON.stringify({a: ACTIONS.subscribe, c: 'dogs', d: 'fido'}));
|
|
28
|
+
var connection = new Connection(socket);
|
|
29
|
+
socket._open();
|
|
30
|
+
connection.once('connected', function() {
|
|
31
|
+
expect(logger.warn).to.have.been.calledOnceWithExactly(
|
|
32
|
+
'Unexpected message received before handshake',
|
|
33
|
+
{a: ACTIONS.subscribe, c: 'dogs', d: 'fido'}
|
|
34
|
+
);
|
|
35
|
+
done();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not warn when messages are sent after the handshake', function(done) {
|
|
40
|
+
var socket = new StreamSocket();
|
|
41
|
+
var stream = socket.stream;
|
|
42
|
+
var agent = backend.listen(stream);
|
|
43
|
+
sinon.spy(logger, 'warn');
|
|
44
|
+
var connection = new Connection(socket);
|
|
45
|
+
socket._open();
|
|
46
|
+
connection.once('connected', function() {
|
|
47
|
+
socket.send(JSON.stringify({a: ACTIONS.subscribe, c: 'dogs', d: 'fido'}));
|
|
48
|
+
expect(logger.warn).not.to.have.been.called;
|
|
49
|
+
expect(agent._firstReceivedMessage).to.be.null;
|
|
50
|
+
done();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does not warn for clients on protocol v1.0', function(done) {
|
|
55
|
+
backend.use('receive', function(request, next) {
|
|
56
|
+
var error = null;
|
|
57
|
+
if (request.data.a === ACTIONS.handshake) error = new Error('Unexpected handshake');
|
|
58
|
+
next(error);
|
|
59
|
+
});
|
|
60
|
+
var socket = new StreamSocket();
|
|
61
|
+
var stream = socket.stream;
|
|
62
|
+
backend.listen(stream);
|
|
63
|
+
sinon.spy(logger, 'warn');
|
|
64
|
+
socket.send(JSON.stringify({a: ACTIONS.subscribe, c: 'dogs', d: 'fido'}));
|
|
65
|
+
var connection = new LegacyConnection(socket);
|
|
66
|
+
socket._open();
|
|
67
|
+
connection.get('dogs', 'fido').fetch(function(error) {
|
|
68
|
+
if (error) return done(error);
|
|
69
|
+
expect(logger.warn).not.to.have.been.called;
|
|
70
|
+
done();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
package/test/client/doc.js
CHANGED
|
@@ -5,6 +5,7 @@ var json0 = require('ot-json0').type;
|
|
|
5
5
|
var richText = require('rich-text').type;
|
|
6
6
|
var ShareDBError = require('../../lib/error');
|
|
7
7
|
var errorHandler = require('../util').errorHandler;
|
|
8
|
+
var sinon = require('sinon');
|
|
8
9
|
|
|
9
10
|
describe('Doc', function() {
|
|
10
11
|
beforeEach(function() {
|
|
@@ -84,6 +85,23 @@ describe('Doc', function() {
|
|
|
84
85
|
});
|
|
85
86
|
});
|
|
86
87
|
|
|
88
|
+
describe('fetch', function() {
|
|
89
|
+
it('only fetches once when calling in quick succession', function(done) {
|
|
90
|
+
var connection = this.connection;
|
|
91
|
+
var doc = connection.get('dogs', 'fido');
|
|
92
|
+
sinon.spy(connection, 'sendFetch');
|
|
93
|
+
var count = 0;
|
|
94
|
+
var finish = function() {
|
|
95
|
+
count++;
|
|
96
|
+
expect(connection.sendFetch).to.have.been.calledOnce;
|
|
97
|
+
if (count === 3) done();
|
|
98
|
+
};
|
|
99
|
+
doc.fetch(finish);
|
|
100
|
+
doc.fetch(finish);
|
|
101
|
+
doc.fetch(finish);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
87
105
|
describe('when connection closed', function() {
|
|
88
106
|
beforeEach(function(done) {
|
|
89
107
|
this.op1 = [{p: ['snacks'], oi: true}];
|
package/test/middleware.js
CHANGED
|
@@ -734,11 +734,11 @@ describe('middleware', function() {
|
|
|
734
734
|
next();
|
|
735
735
|
});
|
|
736
736
|
|
|
737
|
-
sinon.spy(doc, '
|
|
737
|
+
sinon.spy(doc, '_fetch');
|
|
738
738
|
|
|
739
739
|
doc.submitOp([{p: ['fetch'], oi: true}], function(error) {
|
|
740
740
|
expect(error).to.be.ok;
|
|
741
|
-
expect(doc.
|
|
741
|
+
expect(doc._fetch.calledOnce).to.be.true;
|
|
742
742
|
done();
|
|
743
743
|
});
|
|
744
744
|
});
|
package/test/setup.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
var logger = require('../lib/logger');
|
|
2
2
|
var sinon = require('sinon');
|
|
3
|
+
var sinonChai = require('sinon-chai');
|
|
4
|
+
var chai = require('chai');
|
|
5
|
+
|
|
6
|
+
chai.use(sinonChai);
|
|
3
7
|
|
|
4
8
|
if (process.env.LOGGING !== 'true') {
|
|
5
9
|
// Silence the logger for tests by setting all its methods to no-ops
|