Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Store client protocol on Agent #684

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var logger = require('./logger');
var ACTIONS = require('./message-actions').ACTIONS;
var types = require('./types');
var util = require('./util');
var protocol = require('./protocol');

var ERROR_CODE = ShareDBError.CODES;

Expand Down Expand Up @@ -62,6 +63,8 @@ function Agent(backend, stream) {
// active, and it is passed to each middleware call
this.custom = Object.create(null);

this.protocol = Object.create(null);

// The first message received over the connection. Stored to warn if messages
// are being sent before the handshake.
this._firstReceivedMessage = null;
Expand Down Expand Up @@ -437,6 +440,7 @@ Agent.prototype._handleMessage = function(request, callback) {
switch (request.a) {
case ACTIONS.handshake:
if (request.id) this.src = request.id;
this._setProtocol(request);
return callback(null, this._initMessage(ACTIONS.handshake));
case ACTIONS.queryFetch:
return this._queryFetch(request.id, request.c, request.q, getQueryOptions(request), callback);
Expand Down Expand Up @@ -788,8 +792,8 @@ Agent.prototype._fetchSnapshotByTimestamp = function(collection, id, timestamp,
Agent.prototype._initMessage = function(action) {
return {
a: action,
protocol: 1,
protocolMinor: 1,
protocol: protocol.major,
protocolMinor: protocol.minor,
id: this._src(),
type: types.defaultType.uri
};
Expand Down Expand Up @@ -973,6 +977,11 @@ Agent.prototype._checkFirstMessage = function(request) {
}
};

Agent.prototype._setProtocol = function(request) {
this.protocol.major = request.protocol;
this.protocol.minor = request.protocolMinor;
};

function createClientOp(request, clientId) {
// src can be provided if it is not the same as the current agent,
// such as a resubmission after a reconnect, but it usually isn't needed
Expand Down
14 changes: 10 additions & 4 deletions lib/client/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var types = require('../types');
var util = require('../util');
var logger = require('../logger');
var DocPresenceEmitter = require('./presence/doc-presence-emitter');
var protocol = require('../protocol');

var ERROR_CODE = ShareDBError.CODES;

Expand Down Expand Up @@ -728,16 +729,21 @@ Connection.prototype._handleSnapshotFetch = function(error, message) {
};

Connection.prototype._handleLegacyInit = function(message) {
// If the minor protocol version has been set, we want to use the
// If the protocol is at least 1.1, we want to use the
// new handshake protocol. Let's send a handshake initialize, because
// we now know the server is ready. If we've already sent it, we'll
// just ignore the response anyway.
if (message.protocolMinor) return this._initializeHandshake();
if (protocol.checkAtLeast(message, '1.1')) return this._initializeHandshake();
this._initialize(message);
};

Connection.prototype._initializeHandshake = function() {
this.send({a: ACTIONS.handshake, id: this.id});
this.send({
a: ACTIONS.handshake,
id: this.id,
protocol: protocol.major,
protocolMinor: protocol.minor
});
};

Connection.prototype._handleHandshake = function(error, message) {
Expand All @@ -753,7 +759,7 @@ Connection.prototype._handlePingPong = function(error) {
Connection.prototype._initialize = function(message) {
if (this.state !== 'connecting') return;

if (message.protocol !== 1) {
if (message.protocol !== protocol.major) {
ericyhwang marked this conversation as resolved.
Show resolved Hide resolved
return this.emit('error', new ShareDBError(
ERROR_CODE.ERR_PROTOCOL_VERSION_NOT_SUPPORTED,
'Unsupported protocol version: ' + message.protocol
Expand Down
28 changes: 28 additions & 0 deletions lib/protocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
major: 1,
minor: 1,
checkAtLeast: checkAtLeast
};

function checkAtLeast(toCheck, checkAgainst) {
toCheck = normalizedProtocol(toCheck);
checkAgainst = normalizedProtocol(checkAgainst);
if (toCheck.major > checkAgainst.major) return true;
return toCheck.major === checkAgainst.major &&
toCheck.minor >= checkAgainst.minor;
}

function normalizedProtocol(protocol) {
if (typeof protocol === 'string') {
var segments = protocol.split('.');
protocol = {
major: segments[0],
minor: segments[1]
};
}

return {
major: +(protocol.protocol || protocol.major || 0),
minor: +(protocol.protocolMinor || protocol.minor || 0)
};
}
12 changes: 12 additions & 0 deletions test/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var StreamSocket = require('../lib/stream-socket');
var expect = require('chai').expect;
var ACTIONS = require('../lib/message-actions').ACTIONS;
var Connection = require('../lib/client/connection');
var protocol = require('../lib/protocol');
var LegacyConnection = require('sharedb-legacy/lib/client').Connection;

describe('Agent', function() {
Expand Down Expand Up @@ -70,5 +71,16 @@ describe('Agent', function() {
done();
});
});

it('records the client protocol on the agent', function(done) {
var connection = backend.connect();
connection.once('connected', function() {
expect(connection.agent.protocol).to.eql({
major: protocol.major,
minor: protocol.minor
});
done();
});
});
});
});
31 changes: 31 additions & 0 deletions test/protocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var protocol = require('../lib/protocol');
var expect = require('chai').expect;

describe('protocol', function() {
describe('checkAtLeast', function() {
var FIXTURES = [
['1.0', '1.0', true],
['1.1', '1.0', true],
['1.0', '1.1', false],
['1.0', '1', true],
['1.10', '1.3', true],
['2.0', '1.3', true],
[{major: 1, minor: 0}, {major: 1, minor: 0}, true],
[{major: 1, minor: 1}, {major: 1, minor: 0}, true],
[{major: 1, minor: 0}, {major: 1, minor: 1}, false],
[{protocol: 1, protocolMinor: 0}, {protocol: 1, protocolMinor: 0}, true],
[{protocol: 1, protocolMinor: 1}, {protocol: 1, protocolMinor: 0}, true],
[{protocol: 1, protocolMinor: 0}, {protocol: 1, protocolMinor: 1}, false],
[{}, '1.0', false],
['', '1.0', false]
];

FIXTURES.forEach(function(fixture) {
var is = fixture[2] ? ' is ' : ' is not ';
var name = 'checks ' + JSON.stringify(fixture[0]) + is + 'at least ' + JSON.stringify(fixture[1]);
it(name, function() {
expect(protocol.checkAtLeast(fixture[0], fixture[1])).to.equal(fixture[2]);
});
});
});
});
Loading