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 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,6 +1,6 @@
1
1
  {
2
2
  "name": "sharedb",
3
- "version": "3.3.1",
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
+ });
@@ -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}];
@@ -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