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 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
- if (this.connection.canSend) {
429
- var isDuplicate = this.connection.sendFetch(this);
430
- pushActionCallback(this.inflightFetch, isDuplicate, callback);
431
- return;
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.fetch(function() {
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.3.1",
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.1",
8
- "async": "^2.6.3",
9
- "fast-deep-equal": "^2.0.1",
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.1"
11
+ "ot-json0": "^1.1.0"
12
12
  },
13
13
  "devDependencies": {
14
- "chai": "^4.2.0",
15
- "coveralls": "^3.0.7",
16
- "eslint": "^7.32.0",
14
+ "chai": "^4.3.7",
15
+ "coveralls": "^3.1.1",
16
+ "eslint": "^8.47.0",
17
17
  "eslint-config-google": "^0.14.0",
18
- "lolex": "^5.1.1",
19
- "mocha": "^8.2.1",
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.3.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": "^9.2.4"
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
+ });
@@ -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 = lolex.install({now: day1});
20
+ clock = sinon.useFakeTimers(day1);
22
21
  backend = new Backend();
23
22
  });
24
23
 
@@ -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);
@@ -734,11 +734,11 @@ describe('middleware', function() {
734
734
  next();
735
735
  });
736
736
 
737
- sinon.spy(doc, 'fetch');
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.fetch.calledOnce).to.be.true;
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