Skip to content

Commit

Permalink
Store custom client ID on Agent/Connection ID
Browse files Browse the repository at this point in the history
It can be useful to identify which connection submitted what operation.
This is already possible to some extent by tracking the ID randomly
generated when instantiating `Agent`.

However, this ID obviously changes on every connection, and bears no
relation to the actual client that is connecting.

This change allows the client to specify some ID when connecting, which
will be concatenated on to the agent's ID like this:

`<randomId>:<clientId>`

Keeping the `randomId` ensures that the IDs remain unique, and
concatenating the `clientId` allows the consumer to later determine
which operations were submitted by the client that matches that ID, by
checking the `src` field stored on an operation.
  • Loading branch information
Alec Gibson committed Jul 16, 2018
1 parent 762e05d commit feeb831
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 13 deletions.
5 changes: 3 additions & 2 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ var types = require('./types');
* @param {Backend} backend
* @param {Duplex} stream connection to a client
*/
function Agent(backend, stream) {
function Agent(backend, stream, clientIdSuffix) {
this.backend = backend;
this.stream = stream;

this.clientId = hat();
clientIdSuffix = typeof clientIdSuffix === 'string' ? (':' + clientIdSuffix) : '';
this.clientId = hat() + clientIdSuffix;
this.connectTime = Date.now();

// We need to track which documents are subscribed by the client. This is a
Expand Down
25 changes: 14 additions & 11 deletions lib/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,32 +109,35 @@ Backend.prototype.close = function(callback) {
finish();
};

Backend.prototype.connect = function(connection, req) {
Backend.prototype.connect = function(connection, req, clientId) {
var socket = new StreamSocket();
if (connection) {
connection.bindToSocket(socket);
} else {
connection = new Connection(socket);
}
socket._open();
var agent = this.listen(socket.stream, req);
var agent = this.listen(socket.stream, req, clientId);
// Store a reference to the agent on the connection for convenience. This is
// not used internal to ShareDB, but it is handy for server-side only user
// code that may cache state on the agent and read it in middleware
connection.agent = agent;
return connection;
};

/** A client has connected through the specified stream. Listen for messages.
*
* The optional second argument (req) is an initial request which is passed
* through to any connect() middleware. This is useful for inspecting cookies
* or an express session or whatever on the request object in your middleware.
*
* (The agent is available through all middleware)
/**
* @param stream - the stream to listen to for messages from the client
* @param req (optional) - an initial request which is passed through to any connect()
* middleware(). This is useful for inspecting cookies or an express
* session or whatever on the request object in your middleware
* @param clientId (optional) - an identifier for the connecting client. This will be
* appended to the connection ID, and can be used to identify the source client
* of an operation. It will look like: <randomId>:<clientId>
* @returns agent - the instance of the connected agent, which is available through all
* middleware
*/
Backend.prototype.listen = function(stream, req) {
var agent = new Agent(this, stream);
Backend.prototype.listen = function(stream, req, clientId) {
var agent = new Agent(this, stream, clientId);
this.trigger(this.MIDDLEWARE_ACTIONS.connect, agent, {stream: stream, req: req}, function(err) {
if (err) return agent.close(err);
agent._open();
Expand Down
32 changes: 32 additions & 0 deletions test/client/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,38 @@ describe('client connection', function() {
connection.socket.onerror({message: 'Test'});
});

describe('specifying a custom client ID suffix', function () {
it('can have a custom client ID suffix specified', function (done) {
this.backend.use('connect', function (request, next) {
var idSegments = request.agent.clientId.split(':');
expect(idSegments[1]).equal('abc');
done();
});

this.backend.connect(undefined, undefined, 'abc');
});

it('ignores an empty client ID suffix', function (done) {
this.backend.use('connect', function (request, next) {
var idSegments = request.agent.clientId.split(':');
expect(idSegments.length).equal(1);
done();
});

this.backend.connect();
});

it('ignores a non-string client ID suffix', function (done) {
this.backend.use('connect', function (request, next) {
var idSegments = request.agent.clientId.split(':');
expect(idSegments.length).equal(1);
done();
});

this.backend.connect(undefined, undefined, 123);
});
});

describe('backend.agentsCount', function() {
it('updates after connect and connection.close()', function(done) {
var backend = this.backend;
Expand Down

0 comments on commit feeb831

Please sign in to comment.