sharedb 3.3.1 → 4.0.1
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 +13 -13
- package/test/agent.js +74 -0
- package/test/client/doc.js +18 -0
- package/test/client/snapshot-timestamp-request.js +1 -2
- package/test/client/subscribe.js +0 -40
- 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,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharedb",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "JSON OT database backend",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"arraydiff": "^0.1.
|
|
8
|
-
"async": "^2.
|
|
9
|
-
"fast-deep-equal": "^
|
|
7
|
+
"arraydiff": "^0.1.3",
|
|
8
|
+
"async": "^3.2.4",
|
|
9
|
+
"fast-deep-equal": "^3.1.3",
|
|
10
10
|
"hat": "0.0.3",
|
|
11
|
-
"ot-json0": "^1.0
|
|
11
|
+
"ot-json0": "^1.1.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"chai": "^4.
|
|
15
|
-
"coveralls": "^3.
|
|
16
|
-
"eslint": "^
|
|
14
|
+
"chai": "^4.3.7",
|
|
15
|
+
"coveralls": "^3.1.1",
|
|
16
|
+
"eslint": "^8.47.0",
|
|
17
17
|
"eslint-config-google": "^0.14.0",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"nyc": "^14.1.1",
|
|
18
|
+
"mocha": "^10.2.0",
|
|
19
|
+
"nyc": "^15.1.0",
|
|
21
20
|
"ot-json0-v2": "https://github.com/ottypes/json0#90a3ae26364c4fa3b19b6df34dad46707a704421",
|
|
22
|
-
"ot-json1": "^0.
|
|
21
|
+
"ot-json1": "^1.0.2",
|
|
23
22
|
"rich-text": "^4.1.0",
|
|
24
23
|
"sharedb-legacy": "npm:sharedb@=1.1.0",
|
|
25
|
-
"sinon": "^
|
|
24
|
+
"sinon": "^15.2.0",
|
|
25
|
+
"sinon-chai": "^3.7.0"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"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}];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
var Backend = require('../../lib/backend');
|
|
2
2
|
var expect = require('chai').expect;
|
|
3
|
-
var lolex = require('lolex');
|
|
4
3
|
var MemoryDb = require('../../lib/db/memory');
|
|
5
4
|
var MemoryMilestoneDb = require('../../lib/milestone-db/memory');
|
|
6
5
|
var sinon = require('sinon');
|
|
@@ -18,7 +17,7 @@ describe('SnapshotTimestampRequest', function() {
|
|
|
18
17
|
var ONE_DAY = 1000 * 60 * 60 * 24;
|
|
19
18
|
|
|
20
19
|
beforeEach(function() {
|
|
21
|
-
clock =
|
|
20
|
+
clock = sinon.useFakeTimers(day1);
|
|
22
21
|
backend = new Backend();
|
|
23
22
|
});
|
|
24
23
|
|
package/test/client/subscribe.js
CHANGED
|
@@ -838,46 +838,6 @@ module.exports = function() {
|
|
|
838
838
|
});
|
|
839
839
|
});
|
|
840
840
|
|
|
841
|
-
it('doc fetches ops to catch up if it receives multiple future ops', function(done) {
|
|
842
|
-
var backend = this.backend;
|
|
843
|
-
var doc = this.backend.connect().get('dogs', 'fido').on('error', done);
|
|
844
|
-
var doc2 = this.backend.connect().get('dogs', 'fido').on('error', done);
|
|
845
|
-
// Delaying op replies will cause multiple future ops to be received
|
|
846
|
-
// before the fetch to catch up completes
|
|
847
|
-
backend.use('op', function(request, next) {
|
|
848
|
-
setTimeout(next, 10 * Math.random());
|
|
849
|
-
});
|
|
850
|
-
doc.create({age: 3}, function(err) {
|
|
851
|
-
if (err) return done(err);
|
|
852
|
-
doc2.subscribe(function(err) {
|
|
853
|
-
if (err) return done(err);
|
|
854
|
-
var wait = 4;
|
|
855
|
-
doc2.on('op', function() {
|
|
856
|
-
if (--wait) return;
|
|
857
|
-
expect(doc2.version).eql(5);
|
|
858
|
-
expect(doc2.data).eql({age: 122});
|
|
859
|
-
// Wait for whenNothingPending, because the doc might have kicked
|
|
860
|
-
// off multiple fetches, and some could be pending still. We want to
|
|
861
|
-
// resolve all inflight requests of the database before closing and
|
|
862
|
-
// proceeding to the next test
|
|
863
|
-
doc2.whenNothingPending(done);
|
|
864
|
-
});
|
|
865
|
-
backend.suppressPublish = true;
|
|
866
|
-
doc.submitOp({p: ['age'], na: 1}, function(err) {
|
|
867
|
-
if (err) return done(err);
|
|
868
|
-
backend.suppressPublish = false;
|
|
869
|
-
doc.submitOp({p: ['age'], na: 5}, function(err) {
|
|
870
|
-
if (err) return done(err);
|
|
871
|
-
doc.submitOp({p: ['age'], na: 13}, function(err) {
|
|
872
|
-
if (err) return done(err);
|
|
873
|
-
doc.submitOp({p: ['age'], na: 100});
|
|
874
|
-
});
|
|
875
|
-
});
|
|
876
|
-
});
|
|
877
|
-
});
|
|
878
|
-
});
|
|
879
|
-
});
|
|
880
|
-
|
|
881
841
|
describe('doc.subscribed', function() {
|
|
882
842
|
it('is set to false initially', function(done) {
|
|
883
843
|
var doc = this.backend.connect().get('dogs', 'fido').on('error', done);
|
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
|