<commit_before>/*
 * Copyright (c) 2013 OmniTI Computer Consulting, Inc.
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var uuid = require('node-uuid');
var net = require('net');
var async = require('async');

var FqCommand = require('./command');
var FqMessage = require('./message');
var EnhancedBuffer = require('./enhancedBuffer');

var FQ_PROTO = {
    CMD_MODE: 0xcc50cafe,
    DATA_MODE: 0xcc50face,
    PEER_MODE: 0xcc50fade
};

function Client (connOpts, options){
    var self = this;
    connOpts = connOpts ? connOpts : {};
    options = options ? options : {};
    self.connOpts = {
        host: connOpts.host || 'localhost',
        port: connOpts.port || 8765,
        user: connOpts.user || 'node-fq',
        pass: connOpts.pass || 'pass',
        queue: connOpts.queue || uuid(),
        queueType: connOpts.queueType || "mem"
    };

    self.connection = {
        cmdConnected: false,
        cmdConnecting: false,
        dataConnected: false,
        dataConnecting: false,
        shuttingDown: false,
        dataReady: false,
        dataSocket: null,
        cmdSocket: null,
    };
    self.cmdHeartbeat = {
        ms: options.heartbeat || 0,
        timer: null,
        last: process.hrtime(),
        hb: new FqCommand.Heartbeat()
    };

    self.clientKey = null;

    self.cmdQueue = async.queue(function(fqCmd, asyncCallback){
        fqCmd.send(self, function(err){
            if(err){
                return asyncCallback();
            }
            fqCmd.process(self, function(err, res){
                return asyncCallback(err, res);
            });
        });
    },1);
}
util.inherits(Client, EventEmitter);

Client.prototype.setHeartbeat = function(cmdHeartbeatMs){
    var self = this;
    clearInterval(self.cmdHeartbeat.timer);
    if(cmdHeartbeatMs !== undefined){
        self.cmdHeartbeat.ms = cmdHeartbeatMs;
    }
    var ms = self.cmdHeartbeat.ms;
    if(!ms){
        return;
    }
    var hbReq = new FqCommand.HeartbeatRequest(ms);
    self.sendCmd(hbReq);
    var hb = self.cmdHeartbeat.hb;
    self.cmdHeartbeat.timer =
        setInterval(function(){
            // Send a heartbeat. No in band response, so don't wait.
            self.sendCmd(hb);
            // Check when the last time we received a heartbeat
            var hrDiff = process.hrtime(self.cmdHeartbeat.last);
            var nsDiff = hrDiff[0] * 1e9 + hrDiff[1]; // nanoseconds
            var nsHBInterval = self.cmdHeartbeat.ms * 1e6; //ms to ns
            if(nsDiff > (nsHBInterval * 3)){
                var error =
                    new Error('Have not received heartbeat in too long');
                self.emit('error', error);
            }
        }, self.cmdHeartbeat.ms);
};

Client.prototype.receiveHeartbeat = function(){
    this.cmdHeartbeat.last = process.hrtime();
};

Client.prototype.connect = function(){
    var self = this;
    self.reset();

    var errorEmitted = false;

    var cmdSocket = self.connectCmd();
    cmdSocket.on('auth', function(success){
        if(!success){
            return self.emit('error', new Error('Unsuccessful auth'));
        }
        var dataSocket = self.connectData();
        dataSocket.on('dataReady', function(){
            self.connection.dataReady = true;
            self.emit('ready');
        });

        dataSocket.on('error', function(e){
            self.reset();
            if(!errorEmitted){
                self.emit('error', e);
            }
            errorEmitted = true;
        });
    });
    cmdSocket.on('error', function(e){
        self.reset();
        if(!errorEmitted){
            self.emit('error', e);
        }
        errorEmitted = true;
    });
    cmdSocket.on('readable', function(){
        // Commands waiting for in band responses will accept heartbeats
        // If there are none waiting when data comes in, go and check for hb
        if((self.cmdQueue.length() + self.cmdQueue.running()) === 0){
            self.cmdHeartbeat.hb.process(self, function(){});
        }
    });
};

Client.prototype.connectData = function(){
    var self = this;
    if(self.connection.dataConnecting){
        return;
    }
    self.connection.dataConnecting = true;

    var params = {
        host: self.connOpts.host,
        port: self.connOpts.port,
        highWaterMark: 262144 // 2x max payload size (2 x 128kb)
    };
    var socket = net.connect( params, function(){
        self.connection.dataConnecting = false;
        self.connection.dataConnected = true;
        var buff = new EnhancedBuffer(4 + 2 + self.clientKey.length);
        buff.writeUInt32BE(FQ_PROTO.DATA_MODE);
        buff.writeUInt16BE(self.clientKey.length);
        buff.write(self.clientKey);
        self.dataWrite(buff.buffer);
        socket.emit('dataReady');
    });
    socket.on('error', function(e){
        console.error('Data Socket: Error',e);
    });
    socket.on('close', function(){
        console.log('Data Socket: Close');
    });
    socket.on('end', function(){
        console.log('Data Socket: End');
    });
    self.connection.dataSocket = socket;
    return socket;
};

Client.prototype.connectCmd = function(){
    var self = this;
    if(self.connection.cmdConnecting){
        return;
    }
    self.connection.cmdConnecting = true;
    var host = self.connOpts.host;
    var port = self.connOpts.port;
    var socket = net.connect( {host: host, port: port}, function(){
        self.connection.cmdConnected = true;
        self.connection.cmdConnecting = false;
        var buff = new Buffer(4);
        buff.writeUInt32BE(FQ_PROTO.CMD_MODE,0);
        self.cmdWrite(buff);
        self.doAuth(function(err, success){
            if(err){
                return socket.emit('error', err);
            }
            self.setHeartbeat();
            return socket.emit('auth', success);
        });
    });
    socket.on('error', function(e){
        console.error('Cmd Socket: Error',e);
    });
    socket.on('close', function(){
        console.log('Cmd Socket: Close');
    });
    socket.on('end', function(){
        console.log('Cmd Socket: End');
    });
    self.connection.cmdSocket = socket;
    return socket;
};

Client.prototype.reset = function(){
    var self = this;
    clearInterval(self.cmdHeartbeat.timer);
    if(self.connection.dataSocket){
        self.connection.dataSocket.destroy();
    }
    if(self.connection.cmdSocket){
        self.connection.cmdSocket.destroy();
    }

    self.connection.dataSocket = null;
    self.connection.cmdSocket = null;

    self.connection.cmdConnected = false;
    self.connection.cmdConnecting = false;
    self.connection.dataConnected = false;
    self.connection.dataConnecting = false;
    self.connection.dataReady = false;

    self.cmdHeartbeat.last = process.hrtime();
};

Client.prototype.shutdown = function(){
    this.reset();
};

Client.prototype.doAuth = function(callback){
    var self = this;
    var auth = new FqCommand.PlainAuth(
        self.connOpts.user,
        self.connOpts.pass,
        self.connOpts.queue,
        self.connOpts.queueType
    );

    self.sendCmd(auth, function(err, key){
        if(err){
            return callback(err);
        }
        self.clientKey = key;
        return callback(null, self.clientKey !== null);
    });
};

Client.prototype.dataRead = function(size, callback){
    var self = this;
    var buff = self.connection.dataSocket.read(size);
    if(buff){
        return callback(null, buff);
    }
    else {
        self.connection.dataSocket.once('readable', function(){
            return self.dataRead(size, callback);
        });
    }
};

Client.prototype.cmdRead = function(size, callback){
    var self = this;
    var buff = self.connection.cmdSocket.read(size);
    if(buff){
        return callback(null, buff);
    }
    else {
        self.connection.cmdSocket.once('readable', function(){
            return self.cmdRead(size, callback);
        });
    }
};

Client.prototype.cmdReadShortBuffer = function(callback){
    var self = this;
    self.cmdRead(2, function(err, lengthBuff){
        if(lengthBuff === null){
            return callback(null,null);
        }
        var stringLen = lengthBuff.readInt16BE(0);
        if(stringLen === 0){
            return callback(null, new Buffer(0));
        }
        self.cmdRead(stringLen, function(err, res){
            return callback(err, res);
        });
    });
};

Client.prototype.cmdReadShortString = function(callback){
    this.cmdReadShortBuffer(function(err, buff){
        if(buff === null){
            return callback(null,null);
        }
        return callback(null,buff.toString());
    });
};

Client.prototype.cmdWrite = function(buff, callback){
    if(this.connection.cmdSocket === null && callback){
        return callback(new Error("Can't write to closed cmd socket"));
    }
    return this.connection.cmdSocket.write(buff, undefined, callback);
};

Client.prototype.dataWrite = function(buff, callback){
    if(this.connection.dataSocket === null && callback){
        return callback(new Error("Can't write to closed data socket"));
    }

    return this.connection.dataSocket.write(buff, undefined, callback);
};

Client.prototype.sendCmd = function(fqCmd, callback){
    var self = this;
    if(fqCmd.hasInBandResponse){
        self.cmdQueue.push(fqCmd, callback);
    } else {
        fqCmd.send(self, callback);
    }
};

Client.prototype.bind = function(exchange, program, peerMode, callback){
    var bind = this.binding = new FqCommand.BindRequest(
        exchange,
        program,
        peerMode
    );
    this.sendCmd(bind, callback);
};

Client.prototype.unbind = function(callback){
    var unbind = new FqCommand.UnbindRequest(this.binding);
    this.binding = null;
    this.sendCmd(unbind, callback);
};

Client.prototype.status = function(callback){
    var statusReq = new FqCommand.StatusRequest();
    this.sendCmd(statusReq, callback);
};

Client.prototype.sendMsg = function(fqMsg, callback){
    fqMsg.send(this, callback);
};

Client.prototype.publish = function(options, callback){
    var fqMsg = new FqMessage();
    fqMsg.setMsgId(options.msgId || uuid());
    fqMsg.setRoute(options.route);
    fqMsg.setExchange(options.exchange);
    fqMsg.setPayload(options.payload);
    fqMsg.send(this, callback);
};

Client.prototype.consume = function(){
    var self = this;
    var msg = new FqMessage();
    msg.read(self, function(err){
        if(err){
            self.emit('error',err);
        } else {
            self.emit('message', msg);
        }
        setImmediate(self.consume.bind(self));
    });
};

module.exports = Client;
<commit_msg>Combine connOpts and options<commit_after>/*
 * Copyright (c) 2013 OmniTI Computer Consulting, Inc.
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var uuid = require('node-uuid');
var net = require('net');
var async = require('async');

var FqCommand = require('./command');
var FqMessage = require('./message');
var EnhancedBuffer = require('./enhancedBuffer');

var FQ_PROTO = {
    CMD_MODE: 0xcc50cafe,
    DATA_MODE: 0xcc50face,
    PEER_MODE: 0xcc50fade
};

function Client (connOpts){
    var self = this;
    connOpts = connOpts ? connOpts : {};
    self.connOpts = {
        host: connOpts.host || 'localhost',
        port: connOpts.port || 8765,
        user: connOpts.user || 'node-fq',
        pass: connOpts.pass || 'pass',
        queue: connOpts.queue || uuid(),
        queueType: connOpts.queueType || "mem"
    };

    self.connection = {
        cmdConnected: false,
        cmdConnecting: false,
        dataConnected: false,
        dataConnecting: false,
        shuttingDown: false,
        dataReady: false,
        dataSocket: null,
        cmdSocket: null,
    };
    self.cmdHeartbeat = {
        ms: connOpts.heartbeat || 0,
        timer: null,
        last: process.hrtime(),
        hb: new FqCommand.Heartbeat()
    };

    self.clientKey = null;

    self.cmdQueue = async.queue(function(fqCmd, asyncCallback){
        fqCmd.send(self, function(err){
            if(err){
                return asyncCallback();
            }
            fqCmd.process(self, function(err, res){
                return asyncCallback(err, res);
            });
        });
    },1);
}
util.inherits(Client, EventEmitter);

Client.prototype.setHeartbeat = function(cmdHeartbeatMs){
    var self = this;
    clearInterval(self.cmdHeartbeat.timer);
    if(cmdHeartbeatMs !== undefined){
        self.cmdHeartbeat.ms = cmdHeartbeatMs;
    }
    var ms = self.cmdHeartbeat.ms;
    if(!ms){
        return;
    }
    var hbReq = new FqCommand.HeartbeatRequest(ms);
    self.sendCmd(hbReq);
    var hb = self.cmdHeartbeat.hb;
    self.cmdHeartbeat.timer =
        setInterval(function(){
            // Send a heartbeat. No in band response, so don't wait.
            self.sendCmd(hb);
            // Check when the last time we received a heartbeat
            var hrDiff = process.hrtime(self.cmdHeartbeat.last);
            var nsDiff = hrDiff[0] * 1e9 + hrDiff[1]; // nanoseconds
            var nsHBInterval = self.cmdHeartbeat.ms * 1e6; //ms to ns
            if(nsDiff > (nsHBInterval * 3)){
                var error =
                    new Error('Have not received heartbeat in too long');
                self.emit('error', error);
            }
        }, self.cmdHeartbeat.ms);
};

Client.prototype.receiveHeartbeat = function(){
    this.cmdHeartbeat.last = process.hrtime();
};

Client.prototype.connect = function(){
    var self = this;
    self.reset();

    var errorEmitted = false;

    var cmdSocket = self.connectCmd();
    cmdSocket.on('auth', function(success){
        if(!success){
            return self.emit('error', new Error('Unsuccessful auth'));
        }
        var dataSocket = self.connectData();
        dataSocket.on('dataReady', function(){
            self.connection.dataReady = true;
            self.emit('ready');
        });

        dataSocket.on('error', function(e){
            self.reset();
            if(!errorEmitted){
                self.emit('error', e);
            }
            errorEmitted = true;
        });
    });
    cmdSocket.on('error', function(e){
        self.reset();
        if(!errorEmitted){
            self.emit('error', e);
        }
        errorEmitted = true;
    });
    cmdSocket.on('readable', function(){
        // Commands waiting for in band responses will accept heartbeats
        // If there are none waiting when data comes in, go and check for hb
        if((self.cmdQueue.length() + self.cmdQueue.running()) === 0){
            self.cmdHeartbeat.hb.process(self, function(){});
        }
    });
};

Client.prototype.connectData = function(){
    var self = this;
    if(self.connection.dataConnecting){
        return;
    }
    self.connection.dataConnecting = true;

    var params = {
        host: self.connOpts.host,
        port: self.connOpts.port,
        highWaterMark: 262144 // 2x max payload size (2 x 128kb)
    };
    var socket = net.connect( params, function(){
        self.connection.dataConnecting = false;
        self.connection.dataConnected = true;
        var buff = new EnhancedBuffer(4 + 2 + self.clientKey.length);
        buff.writeUInt32BE(FQ_PROTO.DATA_MODE);
        buff.writeUInt16BE(self.clientKey.length);
        buff.write(self.clientKey);
        self.dataWrite(buff.buffer);
        socket.emit('dataReady');
    });
    socket.on('error', function(e){
        console.error('Data Socket: Error',e);
    });
    socket.on('close', function(){
        console.log('Data Socket: Close');
    });
    socket.on('end', function(){
        console.log('Data Socket: End');
    });
    self.connection.dataSocket = socket;
    return socket;
};

Client.prototype.connectCmd = function(){
    var self = this;
    if(self.connection.cmdConnecting){
        return;
    }
    self.connection.cmdConnecting = true;
    var host = self.connOpts.host;
    var port = self.connOpts.port;
    var socket = net.connect( {host: host, port: port}, function(){
        self.connection.cmdConnected = true;
        self.connection.cmdConnecting = false;
        var buff = new Buffer(4);
        buff.writeUInt32BE(FQ_PROTO.CMD_MODE,0);
        self.cmdWrite(buff);
        self.doAuth(function(err, success){
            if(err){
                return socket.emit('error', err);
            }
            self.setHeartbeat();
            return socket.emit('auth', success);
        });
    });
    socket.on('error', function(e){
        console.error('Cmd Socket: Error',e);
    });
    socket.on('close', function(){
        console.log('Cmd Socket: Close');
    });
    socket.on('end', function(){
        console.log('Cmd Socket: End');
    });
    self.connection.cmdSocket = socket;
    return socket;
};

Client.prototype.reset = function(){
    var self = this;
    clearInterval(self.cmdHeartbeat.timer);
    if(self.connection.dataSocket){
        self.connection.dataSocket.destroy();
    }
    if(self.connection.cmdSocket){
        self.connection.cmdSocket.destroy();
    }

    self.connection.dataSocket = null;
    self.connection.cmdSocket = null;

    self.connection.cmdConnected = false;
    self.connection.cmdConnecting = false;
    self.connection.dataConnected = false;
    self.connection.dataConnecting = false;
    self.connection.dataReady = false;

    self.cmdHeartbeat.last = process.hrtime();
};

Client.prototype.shutdown = function(){
    this.reset();
};

Client.prototype.doAuth = function(callback){
    var self = this;
    var auth = new FqCommand.PlainAuth(
        self.connOpts.user,
        self.connOpts.pass,
        self.connOpts.queue,
        self.connOpts.queueType
    );

    self.sendCmd(auth, function(err, key){
        if(err){
            return callback(err);
        }
        self.clientKey = key;
        return callback(null, self.clientKey !== null);
    });
};

Client.prototype.dataRead = function(size, callback){
    var self = this;
    var buff = self.connection.dataSocket.read(size);
    if(buff){
        return callback(null, buff);
    }
    else {
        self.connection.dataSocket.once('readable', function(){
            return self.dataRead(size, callback);
        });
    }
};

Client.prototype.cmdRead = function(size, callback){
    var self = this;
    var buff = self.connection.cmdSocket.read(size);
    if(buff){
        return callback(null, buff);
    }
    else {
        self.connection.cmdSocket.once('readable', function(){
            return self.cmdRead(size, callback);
        });
    }
};

Client.prototype.cmdReadShortBuffer = function(callback){
    var self = this;
    self.cmdRead(2, function(err, lengthBuff){
        if(lengthBuff === null){
            return callback(null,null);
        }
        var stringLen = lengthBuff.readInt16BE(0);
        if(stringLen === 0){
            return callback(null, new Buffer(0));
        }
        self.cmdRead(stringLen, function(err, res){
            return callback(err, res);
        });
    });
};

Client.prototype.cmdReadShortString = function(callback){
    this.cmdReadShortBuffer(function(err, buff){
        if(buff === null){
            return callback(null,null);
        }
        return callback(null,buff.toString());
    });
};

Client.prototype.cmdWrite = function(buff, callback){
    if(this.connection.cmdSocket === null && callback){
        return callback(new Error("Can't write to closed cmd socket"));
    }
    return this.connection.cmdSocket.write(buff, undefined, callback);
};

Client.prototype.dataWrite = function(buff, callback){
    if(this.connection.dataSocket === null && callback){
        return callback(new Error("Can't write to closed data socket"));
    }

    return this.connection.dataSocket.write(buff, undefined, callback);
};

Client.prototype.sendCmd = function(fqCmd, callback){
    var self = this;
    if(fqCmd.hasInBandResponse){
        self.cmdQueue.push(fqCmd, callback);
    } else {
        fqCmd.send(self, callback);
    }
};

Client.prototype.bind = function(exchange, program, peerMode, callback){
    var bind = this.binding = new FqCommand.BindRequest(
        exchange,
        program,
        peerMode
    );
    this.sendCmd(bind, callback);
};

Client.prototype.unbind = function(callback){
    var unbind = new FqCommand.UnbindRequest(this.binding);
    this.binding = null;
    this.sendCmd(unbind, callback);
};

Client.prototype.status = function(callback){
    var statusReq = new FqCommand.StatusRequest();
    this.sendCmd(statusReq, callback);
};

Client.prototype.sendMsg = function(fqMsg, callback){
    fqMsg.send(this, callback);
};

Client.prototype.publish = function(options, callback){
    var fqMsg = new FqMessage();
    fqMsg.setMsgId(options.msgId || uuid());
    fqMsg.setRoute(options.route);
    fqMsg.setExchange(options.exchange);
    fqMsg.setPayload(options.payload);
    fqMsg.send(this, callback);
};

Client.prototype.consume = function(){
    var self = this;
    var msg = new FqMessage();
    msg.read(self, function(err){
        if(err){
            self.emit('error',err);
        } else {
            self.emit('message', msg);
        }
        setImmediate(self.consume.bind(self));
    });
};

module.exports = Client;
<|endoftext|>


<commit_before>// node-raven - A RavenDB client for node.js
// Copyright (c) 2011-2012 Chris Sainty <csainty@hotmail.com>
// MIT License
// [GitHub](https://github.com/csainty/node-raven) [Twitter](http://twitter.com/csainty) [Blog](http://blog.csainty.com)

// `var raven = require('node-raven');`
var qs = require('querystring')
  , _ = require('underscore')
  , utils = require('./RavenUtils.js')
  ;

// Creating your connection
// ========================
// `var client = raven(options);`
// ####Options:
//      {
//        useOptimisticConcurrency: (default false)
//        keyGenerator: Keygen instance (default HiLoKeyGenerator)
//      }
// Also see [Connection](RavenUtils.html#section-4) options for details on specifying your server details
function Client(options) {
  options = utils.prepareServerOptions(options);

  for (var i in options) {
    if (options.hasOwnProperty(i)) {
      this[i] = options[i];
    }
  }

  // When `useOptimisticConcurrency` is true, saving a document fails if it has been modified since we fetched it
  this.useOptimisticConcurrency = this.useOptimisticConcurrency || false;

  // Allow caller to override the `httpClient` or `keyGenerator` if they need to
  if (!this.httpClient) { this.httpClient = require('./ravenhttpclient')(options); }
  if (!this.keyGenerator) { this.keyGenerator = require('./hilokeygenerator')({ client: this }); }
}

// Fetch a document by id
// ======================
// `client.getDocument(key, [options], [callback]);`
// ####Parameters:
// `key` - Document key eg `users/1`
// ####Options:
//      {
//        isRoot: Fetch document from server root
//      }
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        document: The request document
//        response: The server response
//      }
Client.prototype.getDocument = function (key, options, callback) {
  var self = this
    , root = 'docs/'
    ;

  // `options` is optional
  if (typeof options === 'function') {
    callback = options;
    options = { isRoot: false };
  }

  // Are we fetching the doucment from the server root or the opened database
  if (options.isRoot) {
    root = '/' + root;
  }

  self.httpClient.get(root + key, null, function (error, result) {
    var doc;
    if (result.statusCode === 200) {
      doc = result.asJson();
      // Fill out the `metadata` property.
      doc['@metadata'] = {
        'etag': result.httpResponse.headers.etag,
        'raven-entity-name': result.httpResponse.headers['raven-entity-name']
      };
    }
    return callback && callback(error, { document: doc, response: result });
  });
};

// Update a document on the server
// ===============================
// `client.putDocument(key, document, [options], [callback]);`
// ####Parameters:
// `key` - Document key eg `users/1`
// `document` - Document object to store
// ####Options:
//      {
//        isRoot: Save document to server root
//      }
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        ok: (bool) Success?
//        response: The server response
//      }
Client.prototype.putDocument = function (key, doc, options, callback) {
  var self = this
    , headers = {}
    , jsonDoc = doc
    , root = 'docs/'
    ;

  // `options` is optional
  if (typeof options === 'function') {
    callback = options;
    options = { isRoot: false };
  }

  // Are we saving the document to the server root or the opened database?
  if (options.isRoot) {
    root = '/' + root;
  }

  // If our document has metadata - extract it into the request headers and remove it from the document before serialization.
  if (doc['@metadata']) {
    jsonDoc = _.clone(doc);
    delete jsonDoc['@metadata'];

    // If `useOptimisticConcurrency` is enabled, add the required http headers
    if (self.useOptimisticConcurrency) {
      if (doc['@metadata'].etag) {
        headers['If-None-Match'] = doc['@metadata'].etag;
      }
    }
  }

  self.httpClient.put(root + key, null, jsonDoc, headers, function (error, result) {
    // HTTP response 201 is considered a success
    return callback && callback(error, { ok: result.statusCode === 201, response: result});
  });
};

// Store a new document on the server
// ==================================
// This function generates a new document id if required.
// `client.store(document, [callback]);`
// ####Parameters:
// `document` - Document object to store
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        ok: (bool) Success?
//        response: The server response
//      }
Client.prototype.store = function (doc, callback) {
  var self = this;

  // Validate the metadata
  if (!(doc['@metadata'] && doc['@metadata']['raven-entity-name'])) {
    throw new Error('The document you are trying to save has no @metadata.raven-entity-name property.');
  }

  // Does the doucment already have an id?
  if (doc.id) {
    // Yes. so just send the document to the server
    return self.putDocument(doc.id, doc, callback);
  } else {
    // No. Generate a new document id before saving the document
    self.generateDocumentKey(doc['@metadata']['raven-entity-name'], doc, function (error, result) {
      if (error) { return callback && callback(error, { ok: false }); }
      return self.putDocument(doc.id, doc, callback);
    });
  }
};

// Perform a query against an index
// ================================
// `client.queryIndex(indexName, options, [callback]);`
// ####Parameters:
// `indexName` - The index to query
// ####Options:
//      {
//        query: Hash of query variables
//               eg { Name : 'Node' }
//        waitForNonStaleResult: (bool) Requery for non-stale results
//               to be returned
//      }
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        result: Result object
//                eg { IndexName: 'MyIndex', IsStale: false, TotalResults: 10, Results: [ ... ] }
//        response: The server response
//      }
Client.prototype.queryIndex = function (indexName, options, callback) {
  var self = this
    , queryOptions = { 'query' : utils.buildRavenQuery(options.query) }
    ;

  self.httpClient.get('indexes/' + indexName, queryOptions, function (error, result) {
    // If nonstale results requested, then check to see if they are
    if (!error && result.statusCode === 200 && options.waitForNonStaleResults) {
      var data = result.asJson();
      if (data.IsStale) {
        // Simply wait some time and requery
        return setTimeout(function () {
          self.queryIndex(indexName, options, callback);
        }, 500);
      } else {
        return callback && callback(error, { result: data, response: result });
      }
    }

    // If we don't care about stale results, then just return
    return callback && callback(error, { result: result.statusCode === 200 ? result.asJson() : null, response: result });
  });
};

// Create or update the metadata on an entity
// ==========================================
// Each document stored in RavenDB uses an entity type to group similar documents together.
// This function helps you create documents that correctly contain this metadata ready to be stored.
// `var doc = client.createDocument('Users');`
// `client.createDocument('Users', docToUpdate);`
// ####Parameters:
// `entityType` - The type of docuemnt
// `doc` - An existing document to update (optional)
Client.prototype.createDocument = function (entityType, doc) {
  if (!doc) {
    doc = {};
  }
  if (!doc['@metadata']) {
    doc['@metadata'] = {};
  }
  doc['@metadata']['raven-entity-name'] = entityType;
  return doc;
};

// Multi tenancy
// =============

// Fetch a list of database names from the server
// ==============================================
// `client.getDatabaseNames([callback]);`
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        databases: Array of database names
//        response: The server response
//      }
Client.prototype.getDatabaseNames = function (callback) {
  var self = this;

  self.httpClient.get('/databases', null, function (error, result) {
    return callback && callback(error, { databases: result.statusCode === 200 ? result.asJson() : null, response: result });
  });
};

// Select a database
// =================
// Note: It is preferable to use the constructor argument `server_db`
// `client.getDatabaseNames(name);`
// ####Parameters:
// `name` - The name of the database to use
Client.prototype.useDatabase = function (name) {
  this.httpClient.useDatabase(name);
  this.server_url = this.httpClient.server_url;
  this.server_db = name;
};

// Checks if a database esxists, create it if it does not
// ========================================================
// `client.ensureDatabaseExists(name, [callback]);`
// ####Parameters:
// `name` - The name of the database to check
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        ok: (bool) Success?
//        response: The server response
//      }
Client.prototype.ensureDatabaseExists = function (name, callback) {
  var self = this
   , docId
   , doc
   ;

  if (name.search(/[\/\\"'<>]/) !== -1) { return callback && callback(new Error('Database name is invalid'), { ok: false }); } // Invalid database name

  docId = 'Raven/Databases/' + name;
  self.getDocument(docId, { isRoot : true }, function (error, result) {
    if (error || !result) { return callback && callback(error, { ok: false, response: result.response }); } // Error connecting to server
    if (result.document) { return callback && callback(error, { ok: true, response: result.response }); } // Document and therefore database already exists

    doc = { 'Settings' : { "Raven/DataDir" : "~/Tenants/" + name } };
    return self.putDocument(docId, doc, { isRoot: true }, callback); // The database does not exist, save the document that describes it
  });
};

// Helpers
// =======

// Generate a new document key for the specified entity
// ====================================================
// `client.generateDocumentKey(entityType, entity, [callback]);`
// ####Parameters:
// `entityType` - The type of the document
// `entity` - The document object
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        key; The generated key
//        document: The passed in document
//      }
Client.prototype.generateDocumentKey = function (entityType, entity, callback) {
  var self = this;

  self.keyGenerator.generateDocumentKey(entityType, function (error, result) {
    if (error) { return callback && callback(error, { document: entity }); }

    entity.id = result.key; // Assign the id to the entity so it is immediateley available
    return callback && callback(undefined, { document: entity, key: result.key });
  });
};

// Expose client via factory method.
function raven(options) {
  return new Client(options);
}

module.exports = raven;
<commit_msg>Encode document ids when loading by id so they are url safe<commit_after>// node-raven - A RavenDB client for node.js
// Copyright (c) 2011-2012 Chris Sainty <csainty@hotmail.com>
// MIT License
// [GitHub](https://github.com/csainty/node-raven) [Twitter](http://twitter.com/csainty) [Blog](http://blog.csainty.com)

// `var raven = require('node-raven');`
var qs = require('querystring')
  , _ = require('underscore')
  , utils = require('./RavenUtils.js')
  ;

// Creating your connection
// ========================
// `var client = raven(options);`
// ####Options:
//      {
//        useOptimisticConcurrency: (default false)
//        keyGenerator: Keygen instance (default HiLoKeyGenerator)
//      }
// Also see [Connection](RavenUtils.html#section-4) options for details on specifying your server details
function Client(options) {
  options = utils.prepareServerOptions(options);

  for (var i in options) {
    if (options.hasOwnProperty(i)) {
      this[i] = options[i];
    }
  }

  // When `useOptimisticConcurrency` is true, saving a document fails if it has been modified since we fetched it
  this.useOptimisticConcurrency = this.useOptimisticConcurrency || false;

  // Allow caller to override the `httpClient` or `keyGenerator` if they need to
  if (!this.httpClient) { this.httpClient = require('./ravenhttpclient')(options); }
  if (!this.keyGenerator) { this.keyGenerator = require('./hilokeygenerator')({ client: this }); }
}

// Fetch a document by id
// ======================
// `client.getDocument(key, [options], [callback]);`
// ####Parameters:
// `key` - Document key eg `users/1`
// ####Options:
//      {
//        isRoot: Fetch document from server root
//      }
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        document: The request document
//        response: The server response
//      }
Client.prototype.getDocument = function (key, options, callback) {
  var self = this
    , root = 'docs/'
    ;

  // `options` is optional
  if (typeof options === 'function') {
    callback = options;
    options = { isRoot: false };
  }

  // Are we fetching the doucment from the server root or the opened database
  if (options.isRoot) {
    root = '/' + root;
  }

  self.httpClient.get(root + encodeURIComponent(key), null, function (error, result) {
    var doc;
    if (result.statusCode === 200) {
      doc = result.asJson();
      // Fill out the `metadata` property.
      doc['@metadata'] = {
        'etag': result.httpResponse.headers.etag,
        'raven-entity-name': result.httpResponse.headers['raven-entity-name']
      };
    }
    return callback && callback(error, { document: doc, response: result });
  });
};

// Update a document on the server
// ===============================
// `client.putDocument(key, document, [options], [callback]);`
// ####Parameters:
// `key` - Document key eg `users/1`
// `document` - Document object to store
// ####Options:
//      {
//        isRoot: Save document to server root
//      }
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        ok: (bool) Success?
//        response: The server response
//      }
Client.prototype.putDocument = function (key, doc, options, callback) {
  var self = this
    , headers = {}
    , jsonDoc = doc
    , root = 'docs/'
    ;

  // `options` is optional
  if (typeof options === 'function') {
    callback = options;
    options = { isRoot: false };
  }

  // Are we saving the document to the server root or the opened database?
  if (options.isRoot) {
    root = '/' + root;
  }

  // If our document has metadata - extract it into the request headers and remove it from the document before serialization.
  if (doc['@metadata']) {
    jsonDoc = _.clone(doc);
    delete jsonDoc['@metadata'];

    // If `useOptimisticConcurrency` is enabled, add the required http headers
    if (self.useOptimisticConcurrency) {
      if (doc['@metadata'].etag) {
        headers['If-None-Match'] = doc['@metadata'].etag;
      }
    }
  }

  self.httpClient.put(root + key, null, jsonDoc, headers, function (error, result) {
    // HTTP response 201 is considered a success
    return callback && callback(error, { ok: result.statusCode === 201, response: result});
  });
};

// Store a new document on the server
// ==================================
// This function generates a new document id if required.
// `client.store(document, [callback]);`
// ####Parameters:
// `document` - Document object to store
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        ok: (bool) Success?
//        response: The server response
//      }
Client.prototype.store = function (doc, callback) {
  var self = this;

  // Validate the metadata
  if (!(doc['@metadata'] && doc['@metadata']['raven-entity-name'])) {
    throw new Error('The document you are trying to save has no @metadata.raven-entity-name property.');
  }

  // Does the doucment already have an id?
  if (doc.id) {
    // Yes. so just send the document to the server
    return self.putDocument(doc.id, doc, callback);
  } else {
    // No. Generate a new document id before saving the document
    self.generateDocumentKey(doc['@metadata']['raven-entity-name'], doc, function (error, result) {
      if (error) { return callback && callback(error, { ok: false }); }
      return self.putDocument(doc.id, doc, callback);
    });
  }
};

// Perform a query against an index
// ================================
// `client.queryIndex(indexName, options, [callback]);`
// ####Parameters:
// `indexName` - The index to query
// ####Options:
//      {
//        query: Hash of query variables
//               eg { Name : 'Node' }
//        waitForNonStaleResult: (bool) Requery for non-stale results
//               to be returned
//      }
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        result: Result object
//                eg { IndexName: 'MyIndex', IsStale: false, TotalResults: 10, Results: [ ... ] }
//        response: The server response
//      }
Client.prototype.queryIndex = function (indexName, options, callback) {
  var self = this
    , queryOptions = { 'query' : utils.buildRavenQuery(options.query) }
    ;

  self.httpClient.get('indexes/' + indexName, queryOptions, function (error, result) {
    // If nonstale results requested, then check to see if they are
    if (!error && result.statusCode === 200 && options.waitForNonStaleResults) {
      var data = result.asJson();
      if (data.IsStale) {
        // Simply wait some time and requery
        return setTimeout(function () {
          self.queryIndex(indexName, options, callback);
        }, 500);
      } else {
        return callback && callback(error, { result: data, response: result });
      }
    }

    // If we don't care about stale results, then just return
    return callback && callback(error, { result: result.statusCode === 200 ? result.asJson() : null, response: result });
  });
};

// Create or update the metadata on an entity
// ==========================================
// Each document stored in RavenDB uses an entity type to group similar documents together.
// This function helps you create documents that correctly contain this metadata ready to be stored.
// `var doc = client.createDocument('Users');`
// `client.createDocument('Users', docToUpdate);`
// ####Parameters:
// `entityType` - The type of docuemnt
// `doc` - An existing document to update (optional)
Client.prototype.createDocument = function (entityType, doc) {
  if (!doc) {
    doc = {};
  }
  if (!doc['@metadata']) {
    doc['@metadata'] = {};
  }
  doc['@metadata']['raven-entity-name'] = entityType;
  return doc;
};

// Multi tenancy
// =============

// Fetch a list of database names from the server
// ==============================================
// `client.getDatabaseNames([callback]);`
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        databases: Array of database names
//        response: The server response
//      }
Client.prototype.getDatabaseNames = function (callback) {
  var self = this;

  self.httpClient.get('/databases', null, function (error, result) {
    return callback && callback(error, { databases: result.statusCode === 200 ? result.asJson() : null, response: result });
  });
};

// Select a database
// =================
// Note: It is preferable to use the constructor argument `server_db`
// `client.getDatabaseNames(name);`
// ####Parameters:
// `name` - The name of the database to use
Client.prototype.useDatabase = function (name) {
  this.httpClient.useDatabase(name);
  this.server_url = this.httpClient.server_url;
  this.server_db = name;
};

// Checks if a database esxists, create it if it does not
// ========================================================
// `client.ensureDatabaseExists(name, [callback]);`
// ####Parameters:
// `name` - The name of the database to check
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        ok: (bool) Success?
//        response: The server response
//      }
Client.prototype.ensureDatabaseExists = function (name, callback) {
  var self = this
   , docId
   , doc
   ;

  if (name.search(/[\/\\"'<>]/) !== -1) { return callback && callback(new Error('Database name is invalid'), { ok: false }); } // Invalid database name

  docId = 'Raven/Databases/' + name;
  self.getDocument(docId, { isRoot : true }, function (error, result) {
    if (error || !result) { return callback && callback(error, { ok: false, response: result.response }); } // Error connecting to server
    if (result.document) { return callback && callback(error, { ok: true, response: result.response }); } // Document and therefore database already exists

    doc = { 'Settings' : { "Raven/DataDir" : "~/Tenants/" + name } };
    return self.putDocument(docId, doc, { isRoot: true }, callback); // The database does not exist, save the document that describes it
  });
};

// Helpers
// =======

// Generate a new document key for the specified entity
// ====================================================
// `client.generateDocumentKey(entityType, entity, [callback]);`
// ####Parameters:
// `entityType` - The type of the document
// `entity` - The document object
// ####Callback:
//      function (error, result)
//
//      result:
//      {
//        key; The generated key
//        document: The passed in document
//      }
Client.prototype.generateDocumentKey = function (entityType, entity, callback) {
  var self = this;

  self.keyGenerator.generateDocumentKey(entityType, function (error, result) {
    if (error) { return callback && callback(error, { document: entity }); }

    entity.id = result.key; // Assign the id to the entity so it is immediateley available
    return callback && callback(undefined, { document: entity, key: result.key });
  });
};

// Expose client via factory method.
function raven(options) {
  return new Client(options);
}

module.exports = raven;
<|endoftext|>


<commit_before>var ACME = require( '../' )
var URL = require( 'url' )
var base64Url = require( 'base64-url' )
var JSONWebAlgorithms = require( 'json-web-algorithms' )
var JSONWebKey = require( 'json-web-key' )
var request = require( 'request' )
var info = require( '../package.json' )
var inherit = require( 'bloodline' )
var Emitter = require( 'events' ).EventEmitter
var debug = require( 'debug' )( 'acme:client' )

/**
 * ACME Protocol Client
 * @constructor
 * @param {Object} options
 * @return {Client}
 */
function Client( options ) {

  if( !(this instanceof Client) )
    return new Client( options )

  options = Object.assign( {}, Client.defaults, options )

  if( !options.baseUrl || typeof options.baseUrl !== 'string' )
    throw new TypeError( 'baseUrl must be a string' )

  Emitter.call( this, options )

  this.baseUrl = options.baseUrl

  /** @type {Object} ACME Directory */
  this.directory = null
  this.directoryUrl = options.directoryPath || null

  // URL to the registration resource,
  // if an account has already been registered
  this.registrationUrl = null
  this.registration = null

  this.publicKeyPEM = options.publicKey
  this.privateKeyPEM = options.privateKey
  this.publicKey = JSONWebKey.fromPEM( options.publicKey )
  this.privateKey = JSONWebKey.fromPEM( options.privateKey )

  this._nonces = []

  this._request = request.defaults({
    gzip: true,
    json: true,
    forever: true,
    baseUrl: options.baseUrl,
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/jose+json',
      'User-Agent': options.userAgent,
      'Connection': 'Keep-Alive',
    },
  })

}

/**
 * Client default options
 * @type {Object}
 */
Client.defaults = {
  baseUrl: null,
  directoryPath: 'directory',
  privateKey: null,
  publicKey: null,
  userAgent: process.release.name + '/' + process.versions.node +
    ' ' + info.name + '/' + info.version,
}

/**
 * Build a detailed error from a problem response
 * @param  {Error} error
 * @param  {Object} response
 * @param  {Object} body
 * @return {Error}
 */
Client.getResponseError = function( error, response, body ) {

  var error = new Error( response.statusMessage )
  error.detail = body

  if( response.headers['content-type'] === 'application/problem+json' ) {
    var errorType = body.type.replace( 'urn:acme:error:', '' ).toUpperCase()
    error.code = errorType
    error.message = body.detail || ACME.ERROR[ errorType ]
    Object.assign( error, body )
  }

  return error

}

/**
 * Client prototype
 * @type {Object}
 */
Client.prototype = {

  constructor: Client,

  // Clear the replay nonce, once accessed
  get _replayNonce() {
    return this._nonces.shift()
  },

  /** @type {Boolean} isConfigured */
  get isConfigured() {
    return this.directory != null
  },

  /**
   * Check whether the client has a certain capability,
   * e.g. supports new registartions via `.hasCapability('new-reg')`
   * @param  {String}  capability
   * @return {Boolean}
   */
  hasCapability: function( capability ) {
    return !!this.directory[ capability ]
  },

  _clientRequest: function( options, callback ) {

    var self = this

    debug( 'http:request', options )

    return this._request( options, function( error, response, body ) {

      if( response ) {
        debug( 'http:request:headers', response.request.headers )
        debug( 'http:response:status', response.statusCode )
        debug( 'http:response:headers', response.headers )
        debug( 'http:response:body', body )
      }

      if( error ) {
        debug( 'http:error', error.message )
        return callback.call( self, error, body )
      }

      if( response.statusCode < 200 || response.statusCode >= 300 ) {
        error = Client.getResponseError( error, response, body )
        debug( 'http:error', error.message )
      }

      if( response.headers[ 'replay-nonce' ] ) {
        self._nonces.push( response.headers[ 'replay-nonce' ] )
      }

      callback.call( self, error, response, body )

    })

  },

  /**
   * Configure the client directory from a given path / URL
   * @param {String} [path] URL
   * @param {Function} callback
   */
  configure: function(/* [path,] callback */) {

    var self = this
    var argv = Array.prototype.slice.call( arguments )
    var callback = argv.pop()
    var path = this.directoryUrl = ( argv.pop() || this.directoryUrl )

    debug( 'configure %s', path )

    this._clientRequest({ url: path }, function( error, response, body ) {
      self.directory = body
      callback.call( self, error, self.directory )
    })

  },

  /**
   * Fetch a new replay-nonce from the ACME server,
   * utlizing a HEAD request
   * @internal to replenish the nonce, if empty
   * @param  {Function} callback
   */
  getNonce: function( callback ) {

    var self = this

    debug( 'nonce:get' )

    this._clientRequest({
      method: 'HEAD',
      url: this.directoryUrl,
    }, function( error, response, body ) {
      callback.call( self, error )
    })

  },

  /**
   * Generate a JWS signed payload body
   * @internal used for request bodies
   * @param  {Object} header
   * @param  {Object} payload
   * @param  {Array} [protectedFields]
   * @return {Object}
   */
  signPayload: function( payload, header, protectedFields ) {

    header = header || {
      alg: 'RS256',
      jwk: this.publicKey,
      nonce: this._replayNonce,
      typ: 'JWS',
    }

    var jsonProtectedHeader = JSON.stringify( header )
    var jsonPayload = JSON.stringify( payload )
    var publicHeader = Object.assign( {}, header )

    protectedFields = protectedFields || [ 'nonce' ]
    protectedFields.forEach( function( key ) {
      publicHeader[ key ] = void 0
      delete publicHeader[ key ]
    })

    var signatureInput = base64Url.encode( jsonProtectedHeader ) + '.' +
      base64Url.encode( jsonPayload )

    var signature = JSONWebAlgorithms.sign( 'RS256',
      new Buffer( signatureInput ),
      new Buffer( this.privateKeyPEM )
    )

    // var compactSerialization = base64Url.encode( jsonProtectedHeader ) + '.' +
    //   base64Url.encode( jsonPayload ) + '.' +
    //   base64Url.encode( signature )

    return {
      payload: base64Url.encode( jsonPayload ),
      protected: base64Url.encode( jsonProtectedHeader ),
      header: publicHeader,
      signature: base64Url.encode( signature ),
    }

  },

  /**
   * Register a new account for a given contact
   * @param  {Array}    contact
   * @param  {Function} callback
   */
  register: function( contact, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.register( contact, callback )
    }

    debug( 'registration:new', contact )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_REGISTRATION ) )
      return void callback.call( this, new Error( 'New registrations not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    // TODO: new ACME.Registration(...)
    var registration = {
      resource: ACME.NEW_REGISTRATION,
      contact: contact,
    }

    this.registration = registration

    this._clientRequest({
      method: 'POST',
      url: '/acme/new-reg',
      body: this.signPayload( registration ),
    }, function( error, response, body ) {

      self.registrationUrl = ( response && response.headers[ 'location' ] ) ?
        URL.parse( response.headers[ 'location' ] ).pathname :
        null

      debug( 'registration:new %s', self.registrationUrl )

      if( self.registrationUrl ) {
        self.getRegistration( callback )
      } else {
        callback.call( self, error )
      }

    })

  },

  /**
   * Retrieve an existing registration
   * @param  {Function} callback
   */
  getRegistration: function( callback ) {
    debug( 'registration:get %s', this.registrationUrl )
    this.updateRegistration({ resource: ACME.REGISTRATION }, callback )
  },

  /**
   * Update an existing registration, i.e. to agree to the TOS
   * @param  {Object}   registration
   * @param  {Function} callback
   */
  updateRegistration: function( registration, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.updateRegistration( registration, callback )
    }

    debug( 'registration:update %s', this.registrationUrl )
    debug( 'registration:update', registration )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_REGISTRATION ) )
      return void callback.call( this, new Error( 'Registration resource not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    this._clientRequest({
      method: 'POST',
      url: this.registrationUrl,
      body: this.signPayload( registration ),
    }, function( error, response, body ) {

      if( !error ) {
        Object.assign( self.registration, body )
        self.registration.key = JSONWebKey.fromJSON( self.registration.key )
      }

      callback.call( self, error, self.registration )

    })

  },

  newAuthorization: function( authorization, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.newAuthorization( authorization, callback )
    }

    debug( 'authorization:new', authorization )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_AUTHORIZATION ) )
      return void callback.call( this, new Error( 'New authorization not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    this._clientRequest({
      method: 'POST',
      url: '/acme/new-authz',
      body: this.signPayload( authorization ),
    }, function( error, response, body ) {
      callback.call( self, error, body )
    })

  },

  newApplication: function( application, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.newApplication( application, callback )
    }

    debug( 'application:new' )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_APPLICATION ) )
      return void callback.call( this, new Error( 'New applications not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    var payload = {
      // TODO
    }

    this._clientRequest({
      method: 'POST',
      url: '/acme/new-app',
      body: this.signPayload( payload ),
    }, function( error, response, body ) {
      callback.call( self, error, body )
    })

  },

}

inherit( Client, Emitter )
// Exports
module.exports = Client
<commit_msg>Update lib/client: Add docblock for `.newAuthorization()`<commit_after>var ACME = require( '../' )
var URL = require( 'url' )
var base64Url = require( 'base64-url' )
var JSONWebAlgorithms = require( 'json-web-algorithms' )
var JSONWebKey = require( 'json-web-key' )
var request = require( 'request' )
var info = require( '../package.json' )
var inherit = require( 'bloodline' )
var Emitter = require( 'events' ).EventEmitter
var debug = require( 'debug' )( 'acme:client' )

/**
 * ACME Protocol Client
 * @constructor
 * @param {Object} options
 * @return {Client}
 */
function Client( options ) {

  if( !(this instanceof Client) )
    return new Client( options )

  options = Object.assign( {}, Client.defaults, options )

  if( !options.baseUrl || typeof options.baseUrl !== 'string' )
    throw new TypeError( 'baseUrl must be a string' )

  Emitter.call( this, options )

  this.baseUrl = options.baseUrl

  /** @type {Object} ACME Directory */
  this.directory = null
  this.directoryUrl = options.directoryPath || null

  // URL to the registration resource,
  // if an account has already been registered
  this.registrationUrl = null
  this.registration = null

  this.publicKeyPEM = options.publicKey
  this.privateKeyPEM = options.privateKey
  this.publicKey = JSONWebKey.fromPEM( options.publicKey )
  this.privateKey = JSONWebKey.fromPEM( options.privateKey )

  this._nonces = []

  this._request = request.defaults({
    gzip: true,
    json: true,
    forever: true,
    baseUrl: options.baseUrl,
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/jose+json',
      'User-Agent': options.userAgent,
      'Connection': 'Keep-Alive',
    },
  })

}

/**
 * Client default options
 * @type {Object}
 */
Client.defaults = {
  baseUrl: null,
  directoryPath: 'directory',
  privateKey: null,
  publicKey: null,
  userAgent: process.release.name + '/' + process.versions.node +
    ' ' + info.name + '/' + info.version,
}

/**
 * Build a detailed error from a problem response
 * @param  {Error} error
 * @param  {Object} response
 * @param  {Object} body
 * @return {Error}
 */
Client.getResponseError = function( error, response, body ) {

  var error = new Error( response.statusMessage )
  error.detail = body

  if( response.headers['content-type'] === 'application/problem+json' ) {
    var errorType = body.type.replace( 'urn:acme:error:', '' ).toUpperCase()
    error.code = errorType
    error.message = body.detail || ACME.ERROR[ errorType ]
    Object.assign( error, body )
  }

  return error

}

/**
 * Client prototype
 * @type {Object}
 */
Client.prototype = {

  constructor: Client,

  // Clear the replay nonce, once accessed
  get _replayNonce() {
    return this._nonces.shift()
  },

  /** @type {Boolean} isConfigured */
  get isConfigured() {
    return this.directory != null
  },

  /**
   * Check whether the client has a certain capability,
   * e.g. supports new registartions via `.hasCapability('new-reg')`
   * @param  {String}  capability
   * @return {Boolean}
   */
  hasCapability: function( capability ) {
    return !!this.directory[ capability ]
  },

  _clientRequest: function( options, callback ) {

    var self = this

    debug( 'http:request', options )

    return this._request( options, function( error, response, body ) {

      if( response ) {
        debug( 'http:request:headers', response.request.headers )
        debug( 'http:response:status', response.statusCode )
        debug( 'http:response:headers', response.headers )
        debug( 'http:response:body', body )
      }

      if( error ) {
        debug( 'http:error', error.message )
        return callback.call( self, error, body )
      }

      if( response.statusCode < 200 || response.statusCode >= 300 ) {
        error = Client.getResponseError( error, response, body )
        debug( 'http:error', error.message )
      }

      if( response.headers[ 'replay-nonce' ] ) {
        self._nonces.push( response.headers[ 'replay-nonce' ] )
      }

      callback.call( self, error, response, body )

    })

  },

  /**
   * Configure the client directory from a given path / URL
   * @param {String} [path] URL
   * @param {Function} callback
   */
  configure: function(/* [path,] callback */) {

    var self = this
    var argv = Array.prototype.slice.call( arguments )
    var callback = argv.pop()
    var path = this.directoryUrl = ( argv.pop() || this.directoryUrl )

    debug( 'configure %s', path )

    this._clientRequest({ url: path }, function( error, response, body ) {
      self.directory = body
      callback.call( self, error, self.directory )
    })

  },

  /**
   * Fetch a new replay-nonce from the ACME server,
   * utlizing a HEAD request
   * @internal to replenish the nonce, if empty
   * @param  {Function} callback
   */
  getNonce: function( callback ) {

    var self = this

    debug( 'nonce:get' )

    this._clientRequest({
      method: 'HEAD',
      url: this.directoryUrl,
    }, function( error, response, body ) {
      callback.call( self, error )
    })

  },

  /**
   * Generate a JWS signed payload body
   * @internal used for request bodies
   * @param  {Object} header
   * @param  {Object} payload
   * @param  {Array} [protectedFields]
   * @return {Object}
   */
  signPayload: function( payload, header, protectedFields ) {

    header = header || {
      alg: 'RS256',
      jwk: this.publicKey,
      nonce: this._replayNonce,
      typ: 'JWS',
    }

    var jsonProtectedHeader = JSON.stringify( header )
    var jsonPayload = JSON.stringify( payload )
    var publicHeader = Object.assign( {}, header )

    protectedFields = protectedFields || [ 'nonce' ]
    protectedFields.forEach( function( key ) {
      publicHeader[ key ] = void 0
      delete publicHeader[ key ]
    })

    var signatureInput = base64Url.encode( jsonProtectedHeader ) + '.' +
      base64Url.encode( jsonPayload )

    var signature = JSONWebAlgorithms.sign( 'RS256',
      new Buffer( signatureInput ),
      new Buffer( this.privateKeyPEM )
    )

    // var compactSerialization = base64Url.encode( jsonProtectedHeader ) + '.' +
    //   base64Url.encode( jsonPayload ) + '.' +
    //   base64Url.encode( signature )

    return {
      payload: base64Url.encode( jsonPayload ),
      protected: base64Url.encode( jsonProtectedHeader ),
      header: publicHeader,
      signature: base64Url.encode( signature ),
    }

  },

  /**
   * Register a new account for a given contact
   * @param  {Array}    contact
   * @param  {Function} callback
   */
  register: function( contact, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.register( contact, callback )
    }

    debug( 'registration:new', contact )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_REGISTRATION ) )
      return void callback.call( this, new Error( 'New registrations not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    // TODO: new ACME.Registration(...)
    var registration = {
      resource: ACME.NEW_REGISTRATION,
      contact: contact,
    }

    this.registration = registration

    this._clientRequest({
      method: 'POST',
      url: '/acme/new-reg',
      body: this.signPayload( registration ),
    }, function( error, response, body ) {

      self.registrationUrl = ( response && response.headers[ 'location' ] ) ?
        URL.parse( response.headers[ 'location' ] ).pathname :
        null

      debug( 'registration:new %s', self.registrationUrl )

      if( self.registrationUrl ) {
        self.getRegistration( callback )
      } else {
        callback.call( self, error )
      }

    })

  },

  /**
   * Retrieve an existing registration
   * @param  {Function} callback
   */
  getRegistration: function( callback ) {
    debug( 'registration:get %s', this.registrationUrl )
    this.updateRegistration({ resource: ACME.REGISTRATION }, callback )
  },

  /**
   * Update an existing registration, i.e. to agree to the TOS
   * @param  {Object}   registration
   * @param  {Function} callback
   */
  updateRegistration: function( registration, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.updateRegistration( registration, callback )
    }

    debug( 'registration:update %s', this.registrationUrl )
    debug( 'registration:update', registration )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_REGISTRATION ) )
      return void callback.call( this, new Error( 'Registration resource not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    this._clientRequest({
      method: 'POST',
      url: this.registrationUrl,
      body: this.signPayload( registration ),
    }, function( error, response, body ) {

      if( !error ) {
        Object.assign( self.registration, body )
        self.registration.key = JSONWebKey.fromJSON( self.registration.key )
      }

      callback.call( self, error, self.registration )

    })

  },

  /**
   * Request a new authorization
   * @param  {Object}   authorization
   * @param  {Function} callback
   */
  newAuthorization: function( authorization, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.newAuthorization( authorization, callback )
    }

    debug( 'authorization:new', authorization )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_AUTHORIZATION ) )
      return void callback.call( this, new Error( 'New authorization not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    this._clientRequest({
      method: 'POST',
      url: '/acme/new-authz',
      body: this.signPayload( authorization ),
    }, function( error, response, body ) {
      callback.call( self, error, body )
    })

  },

  newApplication: function( application, callback ) {

    var self = this
    var retry = function retry( error ) {
      if( error ) return void callback.call( self, error )
      self.newApplication( application, callback )
    }

    debug( 'application:new' )

    if( !this.isConfigured )
      return this.configure( retry )

    if( !this.hasCapability( ACME.NEW_APPLICATION ) )
      return void callback.call( this, new Error( 'New applications not supported' ) )

    if( !self._nonces.length )
      return self.getNonce( retry )

    var payload = {
      // TODO
    }

    this._clientRequest({
      method: 'POST',
      url: '/acme/new-app',
      body: this.signPayload( payload ),
    }, function( error, response, body ) {
      callback.call( self, error, body )
    })

  },

}

inherit( Client, Emitter )
// Exports
module.exports = Client
<|endoftext|>


<commit_before>var request = require('request');

var defaultBaseUrl = 'http://api.hubapi.com/';

function Client() {
  var self = this;

  self.token;
  self.key;
  self.baseUrl = defaultBaseUrl;

  function useToken (token) {
    if (!token || typeof token !== 'string') {
      return cb(new Error("You must provide a token."));
    }

    self.token = token;
  }

  function useKey (key) {
    if (!key || typeof key !== 'string') {
      return cb(new Error("You must provide a key."));
    }

    self.key = key;
  }

  function setBaseUrl(url) {
    self.baseUrl = url;
  }

  var contacts = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/all/contacts/all',
        qs: options
      }, cb);
    }
  };

  var lists = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists',
        qs: options
      }, cb);
    },
    getOne: function(id, cb) {
      if (!id || typeof(id) === 'function') {
        return cb(new Error("id parameter must be provided."));
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/' + id,
      }, cb);
    },
    getContacts: function(id, options, cb) {
      if (!id || typeof(id) === 'function') {
        return cb(new Error("id parameter must be provided."));
      }

      if (typeof(options) === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/' + id + '/contacts/all',
        qs: options
      }, cb);
    },
    getRecentContacts: function(id, options, cb) {
      if (!id || typeof(id) === 'function') {
        return cb(new Error("id parameter must be provided."));
      }

      if (typeof(options) === 'function') {
        cb = options;
        options = {};
      }
      var call = {
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/' + id + '/contacts/recent',
        qs: options
      };
      sendRequest(call, cb);
    }
  };

  var campaignTracking = {
    events: function(id, opts, cb) {
      if (!id || typeof(id) === "function") {
        return cb(new Error("id parameter must be provided."));
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/campaigns/' + id + '/events',
        qs: opts
      }, cb);
    }
  };

  var campaigns = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/campaigns',
        qs: options
      }, cb);
    },
    getOne: function(id, appId, cb) {
      if (!id || typeof id === "function") {
        return cb(new Error("id parameter must be provided."));
      }

      if (typeof appId === "function") {
        cb = appId;
        appId = null;
      }

      var call = {
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/campaigns/' + id
      };

      if (appId) {
        call.qs = {
          appId: appId
        }
      }

      sendRequest(call, cb);
    },
    tracking: campaignTracking
  };

  var events = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/events',
        qs: options
      }, cb);
    }
  };

  var companies = {
    getRecentlyCreated: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/companies/v2/companies/recent/created',
        qs: options
      }, cb);
    }
  };

  var broadcasts = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/broadcast/v1/broadcasts',
        qs: options
      }, cb);
    }
  };

  var subscriptions = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/subscriptions/timeline',
        qs: options
      }, cb);
    }
  };

  var files = {
    get: function(cb) {
      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/content/api/v2/files'
      }, cb);
    }
  };

  function sendRequest (call, cb) {
    if (!self.key && !self.token) {
      return cb(new Error("You need to provide either a token or a key."));
    }

    call.qs = call.qs || {};
    if (self.key) {
      call.qs.hapikey = self.key;
    } else if (self.token) {
      call.qs.access_token = self.token;
    }

    request(call, handleResponse(cb));
  }

  function handleResponse (cb) {
    return function (err, res, data) {
      if (err) return cb(err);

      if (typeof data === 'string') {
        try {
          var parsed = JSON.parse(data);
          data = parsed;
        } catch (e) {
          return cb(new Error(data)); // sometimes errors are returned as strings.
        }
      }

      data.statusCode = res.statusCode;
      return cb(null, data);
    }
  }

  return {
    campaigns: campaigns,
    events: events,
    subscriptions: subscriptions,
    contacts: contacts,
    companies: companies,
    broadcasts: broadcasts,
    lists: lists,
    files: files,
    useToken: useToken,
    useKey: useKey,
    setBaseUrl: setBaseUrl
  }
}

module.exports = Client;
<commit_msg>add update function for a specific contact<commit_after>var request = require('request');

var defaultBaseUrl = 'http://api.hubapi.com/';

function Client() {
  var self = this;

  self.token;
  self.key;
  self.baseUrl = defaultBaseUrl;

  function useToken (token) {
    if (!token || typeof token !== 'string') {
      return cb(new Error("You must provide a token."));
    }

    self.token = token;
  }

  function useKey (key) {
    if (!key || typeof key !== 'string') {
      return cb(new Error("You must provide a key."));
    }

    self.key = key;
  }

  function setBaseUrl(url) {
    self.baseUrl = url;
  }

  var contacts = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/all/contacts/all',
        qs: options
      }, cb);
    },
    update: function(id, data, cb) {
      sendRequest({
        method: 'POST',
        url: self.baseUrl + 'contacts/v1/contact/vid/' + id + '/profile',
        body: data
      }, cb);
    }
  };

  var lists = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists',
        qs: options
      }, cb);
    },
    getOne: function(id, cb) {
      if (!id || typeof(id) === 'function') {
        return cb(new Error("id parameter must be provided."));
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/' + id,
      }, cb);
    },
    getContacts: function(id, options, cb) {
      if (!id || typeof(id) === 'function') {
        return cb(new Error("id parameter must be provided."));
      }

      if (typeof(options) === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/' + id + '/contacts/all',
        qs: options
      }, cb);
    },
    getRecentContacts: function(id, options, cb) {
      if (!id || typeof(id) === 'function') {
        return cb(new Error("id parameter must be provided."));
      }

      if (typeof(options) === 'function') {
        cb = options;
        options = {};
      }
      var call = {
        method: 'GET',
        url: self.baseUrl + 'contacts/v1/lists/' + id + '/contacts/recent',
        qs: options
      };
      sendRequest(call, cb);
    }
  };

  var campaignTracking = {
    events: function(id, opts, cb) {
      if (!id || typeof(id) === "function") {
        return cb(new Error("id parameter must be provided."));
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/campaigns/' + id + '/events',
        qs: opts
      }, cb);
    }
  };

  var campaigns = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/campaigns',
        qs: options
      }, cb);
    },
    getOne: function(id, appId, cb) {
      if (!id || typeof id === "function") {
        return cb(new Error("id parameter must be provided."));
      }

      if (typeof appId === "function") {
        cb = appId;
        appId = null;
      }

      var call = {
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/campaigns/' + id
      };

      if (appId) {
        call.qs = {
          appId: appId
        }
      }

      sendRequest(call, cb);
    },
    tracking: campaignTracking
  };

  var events = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/events',
        qs: options
      }, cb);
    }
  };

  var companies = {
    getRecentlyCreated: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/companies/v2/companies/recent/created',
        qs: options
      }, cb);
    }
  };

  var broadcasts = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/broadcast/v1/broadcasts',
        qs: options
      }, cb);
    }
  };

  var subscriptions = {
    get: function(options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/email/public/v1/subscriptions/timeline',
        qs: options
      }, cb);
    }
  };

  var files = {
    get: function(cb) {
      sendRequest({
        method: 'GET',
        url: self.baseUrl + '/content/api/v2/files'
      }, cb);
    }
  };

  function sendRequest (call, cb) {
    if (!self.key && !self.token) {
      return cb(new Error("You need to provide either a token or a key."));
    }
    call.json = true
    call.qs = call.qs || {};
    if (self.key) {
      call.qs.hapikey = self.key;
    } else if (self.token) {
      call.qs.access_token = self.token;
    }

    request(call, handleResponse(cb));
  }

  function handleResponse (cb) {
    return function (err, res, data) {
      if (err) return cb(err);
      if (typeof data === 'string') {
        try {
          var parsed = JSON.parse(data);
          data = parsed;
        } catch (e) {
          return cb(new Error(data)); // sometimes errors are returned as strings.
        }
      }
      return cb(null, data, res);
    }
  }

  return {
    campaigns: campaigns,
    events: events,
    subscriptions: subscriptions,
    contacts: contacts,
    companies: companies,
    broadcasts: broadcasts,
    lists: lists,
    files: files,
    useToken: useToken,
    useKey: useKey,
    setBaseUrl: setBaseUrl
  }
}

module.exports = Client;
<|endoftext|>


<commit_before>

module.exports = function(server, io, log, config) {

    function Client(socket, socketManager) {
        this.id = socket.id;
        this.socket = socket;
        this.reconnected = 0;
    }

    Client.prototype.setCredentials = function(creds) {
        this.credentials = creds;
    };

    Client.prototype.setDeviceInfo = function(deviceInfo) {
        this.deviceInfo = deviceInfo;
    };

    Client.prototype.emit = function(event, payload) {
        this.socket.emit(event, payload);
        //TODO: Handle completion through promise
    };

    Client.prototype.on = function(event, handler) {
        this.socket.on(event, handler);
    };

    Client.prototype.addHandler = function(handlerSpec) {
        this.socket.on(handlerSpec.key, handlerSpec.handler.bind(handlerSpec.scope));
    };

    return Client;
};
<commit_msg>Make sure to share client context when handler is called<commit_after>

module.exports = function(server, io, log, config) {

    function Client(socket, socketManager) {
        this.id = socket.id;
        this.socket = socket;
        this.reconnected = 0;
    }

    Client.prototype.setCredentials = function(creds) {
        this.credentials = creds;
    };

    Client.prototype.setDeviceInfo = function(deviceInfo) {
        this.deviceInfo = deviceInfo;
    };

    Client.prototype.emit = function(event, payload) {
        this.socket.emit(event, payload);
        //TODO: Handle completion through promise
    };

    Client.prototype.on = function(event, handler) {
        this.socket.on(event, handler);
    };

    Client.prototype.addHandler = function(handlerSpec) {
        var _this = this;
        this.socket.on(handlerSpec.key, function(event) {

            // We wrap the received event to share our client context with handlers
            handlerSpec.handler.bind(handlerSpec.scope)({
                data:event,
                context: {
                    client: _this
                }
            });

        });
    };

    return Client;
};
<|endoftext|>


<commit_before>'use strict';

/**
 * nodecached client.
 * (C) 2013 Alex Fernández.
 */

// requires
require('prototypes');
var token = require('./token.js');
var server = require('./server.js');
var Log = require('log');
var net = require('net');
var Memcached = require('memcached');
var testing = require('testing');

// constants
var TIMEOUT = 5000;

// globals
var log = new Log('notice');
var memcached;

/**
 * A client object.
 */
exports.Client = function(options, callback)
{
	// self-reference
	var self = this;

	// attributes
	var connection;
	var pending = callback;
	var log = new Log('notice');

	// init
	init();

	/**
	 * Init the connection.
	 */
	function init()
	{
		if (options.info)
		{
			log = new Log('info');
		}
		if (options.debug)
		{
			log = new Log('debug');
		}
		connection = net.connect(options);
		connection.setTimeout(TIMEOUT);
		if (!options.delay)
		{
			connection.setNoDelay();
		}
		connection.on('connect', function()
		{
			log.info('Client connected to server');
			if (pending)
			{
				pending(null);
			}
		});
		connection.on('end', function()
		{
			log.info('Client disconnected from server');
			if (pending)
			{
				pending(null);
			}
		});
		connection.on('error', function(error)
		{
			log.error('Connection error: %s', error);
			if (pending)
			{
				pending(error);
			}
			connection.destroy();
		});
		connection.on('data', function(data)
		{
			if (options.noResponse)
			{
				return pending(null);
			}
			log.debug('Received %s', data);
			if (!pending)
			{
				log.error('Unexpected data %s', data);
				return;
			}
			var message = String(data);
			if (message.length === 0)
			{
				return;
			}
			pending(null, message);
		});
		connection.on('timeout', function()
		{
			if (pending)
			{
				pending('timeout');
			}
		});
	}

	/**
	 * Write a message through the connection.
	 */
	function write(message)
	{
		log.debug('Writing %s', message);
		connection.write(message);
	}

	/**
	 * Get a value.
	 */
	self.get = function(key, callback)
	{
		pending = getGetter(callback);
		write('get ' + key + '\r\n');
	};

	/**
	 * Get a function to receive the output of a get.
	 */
	function getGetter(callback)
	{
		return getPending(callback, function(message)
		{
			var parsed = parseGet(message);
			if (!parsed)
			{
				return callback(null, parsed);
			}
			if (parsed.startsWith('{') && parsed.endsWith('}'))
			{
				try
				{
					return callback(null, JSON.parse(parsed));
				}
				catch(exception)
				{
					log.error('Fake json in %s: %s', parsed, exception);
				}
			}
			callback(null, parsed);
		});
	}

	/**
	 * Parse the result of a get.
	 */
	function parseGet(message)
	{
		var lines = message.split('\r\n');
		var line = lines.shift();
		if (line == 'END')
		{
			return null;
		}
		var words = line.split(' ');
		var word = words.shift();
		if (word == 'ERROR')
		{
			return null;
		}
		if (word != 'VALUE')
		{
			log.error('Unknown token %s in %s', word, lines);
			return null;
		}
		word = words.shift();
		word = words.shift();
		word = words.shift();
		if (!word)
		{
			log.error('Invalid length %s', word);
			return null;
		}
		var length = parseInt(word, 10);
		line = lines.shift();
		if (line.length != length)
		{
			log.error('Unexpected line length not %s in %s', length, line);
			return null;
		}
		return line;
	}

	/**
	 * Set a value.
	 */
	self.set = function(key, value, exptime, callback)
	{
		pending = getSetter(callback);
		var message = value;
		if (typeof value != 'string')
		{
			message = JSON.stringify(value);
		}
		write('set ' + key + ' 0 ' + exptime + ' ' + message.length + '\r\n');
		write(message + '\r\n');
	};

	/**
	 * Get a function to receive the output of a set.
	 */
	function getSetter(callback)
	{
		callback = callback || function () {};
		return getPending(callback, function(message)
		{
			var result = message.trim();
			if (result == 'ERROR')
			{
				callback(null, false);
			}
			if (result == 'STORED')
			{
				return callback(null, true);
			}
			log.error('Set result: %s', result);
			callback(null, false);
		});
	}

	/**
	 * Delete a value.
	 */
	self.delete = function(key, callback)
	{
		pending = getDeleter(callback);
		write('delete ' + key + '\r\n');
	};

	/**
	 * Get a function to receive the output of a delete.
	 */
	function getDeleter(callback)
	{
		return getPending(callback, function(message)
		{
			var result = message.trim();
			if (result == 'DELETED')
			{
				return callback(null, true);
			}
			if (result == 'NOT_FOUND')
			{
				return callback(null, false);
			}
			if (result == 'ERROR')
			{
				callback(null, false);
			}
			log.error('Delete result: %s', result);
			callback(null, false);
		});
	}

	/**
	 * Get a pending function with a callback and a handler for the message.
	 */
	function getPending(callback, handler)
	{
		if (options.noResponse)
		{
			return callback;
		}
		return function(error, result)
		{
			if (error)
			{
				return callback(error);
			}
			return handler(result);
		};
	}

	/**
	 * Stop the client.
	 */
	self.stop = function(callback)
	{
		log.debug('Stopping the client');
		pending = callback;
		connection.end();
	};
};

/**
 * Test get, set and delete.
 */
function testGetSetDelete(callback)
{
	var options = {
		port: 11235,
	};
	var key = 'test' + token.create();
	var value = {
		a: 'b',
	};
	server.start(options, function(error, nodecached)
	{
		testing.check(error, 'Could not start server', callback);
		var client = new exports.Client(options, function(error)
		{
			testing.check(error, 'Could not create client', callback);
			client.set(key, value, 10, function(error, result)
			{
				testing.check(error, 'Could not set value', callback);
				testing.assertEquals(result, true, 'Invalid set result', callback);
				client.get(key, function(error, result)
				{
					testing.check(error, 'Could not get value', callback);
					testing.assertEquals(result, value, 'Invalid get result', callback);
					client.delete(key, function(error, result)
					{
						testing.check(error, 'Could not delete value', callback);
						testing.assertEquals(result, true, 'Invalid delete result', callback);
						client.get(key, function(error, result)
						{
							testing.check(error, 'Could not get after delete', callback);
							testing.assertEquals(result, null, 'Invalid get deleted result', callback);
							client.stop(function(error)
							{
								testing.check(error, 'Could not stop client', callback);
								server.stop(nodecached, callback);
							});
						});
					});
				});
			});
		});
	});
}

/**
 * Test what happens with values not found.
 */
function testNotFound(callback)
{
	var options = {
		port: 11236,
	};
	var key = 'test' + token.create();
	server.start(options, function(error, nodecached)
	{
		testing.check(error, 'Could not start server', callback);
		var client = new exports.Client(options, function(error)
		{
			testing.check(error, 'Could not create client', callback);
			client.get(key, function(error, result)
			{
				testing.check(error, 'Could not get value', callback);
				testing.assertEquals(result, null, 'Invalid empty get result', callback);
				client.delete(key, function(error, result)
				{
					testing.check(error, 'Could not delete value', callback);
					testing.assertEquals(result, false, 'Invalid empty delete result', callback);
					client.stop(function(error)
					{
						testing.check(error, 'Could not stop client', callback);
						server.stop(nodecached, callback);
					});
				});
			});
		});
	});
}

/**
 * A client that uses the memcached driver.
 */
exports.MemcachedClient = function(options, callback)
{
	// self-reference
	var self = this;

	// init
	if (!memcached)
	{
		memcached = new Memcached('localhost:' + options.port);
	}
	process.nextTick(callback);

	/**
	 * Get a value.
	 */
	self.get = function(key, callback)
	{
		memcached.get(key, callback);
	};

	/**
	 * Set a value.
	 */
	self.set = function(key, value, exptime, callback)
	{
		memcached.set(key, value, exptime, callback);
	};

	/**
	 * Delete a value.
	 */
	self.delete = function(key, callback)
	{
		memcached.delete(key, callback);
	};

	/**
	 * Stop the client.
	 */
	self.stop = function(callback)
	{
		log.debug('Stopping the client');
		memcached.end();
		if (callback)
		{
			process.nextTick(callback);
		}
	};
};

/**
 * Run all tests.
 */
exports.test = function(callback)
{
	log.debug('Running tests');
	testing.run({
		getSetDelete: testGetSetDelete,
		notFound: testNotFound,
	}, callback);
};

// run tests if invoked directly
if (__filename == process.argv[1])
{
	exports.test(testing.show);
}

<commit_msg>Added a timeout option.<commit_after>'use strict';

/**
 * nodecached client.
 * (C) 2013 Alex Fernández.
 */

// requires
require('prototypes');
var token = require('./token.js');
var server = require('./server.js');
var Log = require('log');
var net = require('net');
var Memcached = require('memcached');
var testing = require('testing');

// constants
var TIMEOUT = 5000;

// globals
var log = new Log('notice');
var memcached;

/**
 * A client object. Options may have:
 *	- host: to connect to.
 *	- port: of the host.
 *	- timeout: time to wait for an operation.
 *	- delay: to enable Nagle's algorithm.
 */
exports.Client = function(options, callback)
{
	// self-reference
	var self = this;

	// attributes
	var connection;
	var pending = callback;
	var log = new Log('notice');

	// init
	init();

	/**
	 * Init the connection.
	 */
	function init()
	{
		if (options.info)
		{
			log = new Log('info');
		}
		if (options.debug)
		{
			log = new Log('debug');
		}
		connection = net.connect(options);
		connection.setTimeout(options.timeout || TIMEOUT);
		if (!options.delay)
		{
			connection.setNoDelay();
		}
		connection.on('connect', function()
		{
			log.info('Client connected to server');
			if (pending)
			{
				pending(null);
			}
		});
		connection.on('end', function()
		{
			log.info('Client disconnected from server');
			if (pending)
			{
				pending(null);
			}
		});
		connection.on('error', function(error)
		{
			log.error('Connection error: %s', error);
			if (pending)
			{
				pending(error);
			}
			connection.destroy();
		});
		connection.on('data', function(data)
		{
			if (options.noResponse)
			{
				return pending(null);
			}
			log.debug('Received %s', data);
			if (!pending)
			{
				log.error('Unexpected data %s', data);
				return;
			}
			var message = String(data);
			if (message.length === 0)
			{
				return;
			}
			pending(null, message);
		});
		connection.on('timeout', function()
		{
			if (pending)
			{
				pending('timeout');
			}
		});
	}

	/**
	 * Write a message through the connection.
	 */
	function write(message)
	{
		log.debug('Writing %s', message);
		connection.write(message);
	}

	/**
	 * Get a value.
	 */
	self.get = function(key, callback)
	{
		pending = getGetter(callback);
		write('get ' + key + '\r\n');
	};

	/**
	 * Get a function to receive the output of a get.
	 */
	function getGetter(callback)
	{
		return getPending(callback, function(message)
		{
			var parsed = parseGet(message);
			if (!parsed)
			{
				return callback(null, parsed);
			}
			if (parsed.startsWith('{') && parsed.endsWith('}'))
			{
				try
				{
					return callback(null, JSON.parse(parsed));
				}
				catch(exception)
				{
					log.error('Fake json in %s: %s', parsed, exception);
				}
			}
			callback(null, parsed);
		});
	}

	/**
	 * Parse the result of a get.
	 */
	function parseGet(message)
	{
		var lines = message.split('\r\n');
		var line = lines.shift();
		if (line == 'END')
		{
			return null;
		}
		var words = line.split(' ');
		var word = words.shift();
		if (word == 'ERROR')
		{
			return null;
		}
		if (word != 'VALUE')
		{
			log.error('Unknown token %s in %s', word, lines);
			return null;
		}
		word = words.shift();
		word = words.shift();
		word = words.shift();
		if (!word)
		{
			log.error('Invalid length %s', word);
			return null;
		}
		var length = parseInt(word, 10);
		line = lines.shift();
		if (line.length != length)
		{
			log.error('Unexpected line length not %s in %s', length, line);
			return null;
		}
		return line;
	}

	/**
	 * Set a value.
	 */
	self.set = function(key, value, exptime, callback)
	{
		pending = getSetter(callback);
		var message = value;
		if (typeof value != 'string')
		{
			message = JSON.stringify(value);
		}
		write('set ' + key + ' 0 ' + exptime + ' ' + message.length + '\r\n');
		write(message + '\r\n');
	};

	/**
	 * Get a function to receive the output of a set.
	 */
	function getSetter(callback)
	{
		callback = callback || function () {};
		return getPending(callback, function(message)
		{
			var result = message.trim();
			if (result == 'ERROR')
			{
				callback(null, false);
			}
			if (result == 'STORED')
			{
				return callback(null, true);
			}
			log.error('Set result: %s', result);
			callback(null, false);
		});
	}

	/**
	 * Delete a value.
	 */
	self.delete = function(key, callback)
	{
		pending = getDeleter(callback);
		write('delete ' + key + '\r\n');
	};

	/**
	 * Get a function to receive the output of a delete.
	 */
	function getDeleter(callback)
	{
		return getPending(callback, function(message)
		{
			var result = message.trim();
			if (result == 'DELETED')
			{
				return callback(null, true);
			}
			if (result == 'NOT_FOUND')
			{
				return callback(null, false);
			}
			if (result == 'ERROR')
			{
				callback(null, false);
			}
			log.error('Delete result: %s', result);
			callback(null, false);
		});
	}

	/**
	 * Get a pending function with a callback and a handler for the message.
	 */
	function getPending(callback, handler)
	{
		if (options.noResponse)
		{
			return callback;
		}
		return function(error, result)
		{
			if (error)
			{
				return callback(error);
			}
			return handler(result);
		};
	}

	/**
	 * Stop the client.
	 */
	self.stop = function(callback)
	{
		log.debug('Stopping the client');
		pending = callback;
		connection.end();
	};
};

/**
 * Test get, set and delete.
 */
function testGetSetDelete(callback)
{
	var options = {
		port: 11235,
	};
	var key = 'test' + token.create();
	var value = {
		a: 'b',
	};
	server.start(options, function(error, nodecached)
	{
		testing.check(error, 'Could not start server', callback);
		var client = new exports.Client(options, function(error)
		{
			testing.check(error, 'Could not create client', callback);
			client.set(key, value, 10, function(error, result)
			{
				testing.check(error, 'Could not set value', callback);
				testing.assertEquals(result, true, 'Invalid set result', callback);
				client.get(key, function(error, result)
				{
					testing.check(error, 'Could not get value', callback);
					testing.assertEquals(result, value, 'Invalid get result', callback);
					client.delete(key, function(error, result)
					{
						testing.check(error, 'Could not delete value', callback);
						testing.assertEquals(result, true, 'Invalid delete result', callback);
						client.get(key, function(error, result)
						{
							testing.check(error, 'Could not get after delete', callback);
							testing.assertEquals(result, null, 'Invalid get deleted result', callback);
							client.stop(function(error)
							{
								testing.check(error, 'Could not stop client', callback);
								server.stop(nodecached, callback);
							});
						});
					});
				});
			});
		});
	});
}

/**
 * Test what happens with values not found.
 */
function testNotFound(callback)
{
	var options = {
		port: 11236,
	};
	var key = 'test' + token.create();
	server.start(options, function(error, nodecached)
	{
		testing.check(error, 'Could not start server', callback);
		var client = new exports.Client(options, function(error)
		{
			testing.check(error, 'Could not create client', callback);
			client.get(key, function(error, result)
			{
				testing.check(error, 'Could not get value', callback);
				testing.assertEquals(result, null, 'Invalid empty get result', callback);
				client.delete(key, function(error, result)
				{
					testing.check(error, 'Could not delete value', callback);
					testing.assertEquals(result, false, 'Invalid empty delete result', callback);
					client.stop(function(error)
					{
						testing.check(error, 'Could not stop client', callback);
						server.stop(nodecached, callback);
					});
				});
			});
		});
	});
}

/**
 * A client that uses the memcached driver.
 */
exports.MemcachedClient = function(options, callback)
{
	// self-reference
	var self = this;

	// init
	if (!memcached)
	{
		memcached = new Memcached('localhost:' + options.port);
	}
	process.nextTick(callback);

	/**
	 * Get a value.
	 */
	self.get = function(key, callback)
	{
		memcached.get(key, callback);
	};

	/**
	 * Set a value.
	 */
	self.set = function(key, value, exptime, callback)
	{
		memcached.set(key, value, exptime, callback);
	};

	/**
	 * Delete a value.
	 */
	self.delete = function(key, callback)
	{
		memcached.delete(key, callback);
	};

	/**
	 * Stop the client.
	 */
	self.stop = function(callback)
	{
		log.debug('Stopping the client');
		memcached.end();
		if (callback)
		{
			process.nextTick(callback);
		}
	};
};

/**
 * Run all tests.
 */
exports.test = function(callback)
{
	log.debug('Running tests');
	testing.run({
		getSetDelete: testGetSetDelete,
		notFound: testNotFound,
	}, callback);
};

// run tests if invoked directly
if (__filename == process.argv[1])
{
	exports.test(testing.show);
}

<|endoftext|>