<commit_before>const url          = require('url');
const _            = require('lodash');
const EventEmitter = require('events').EventEmitter;
const util         = require('util');
const reconnect    = require('reconnect-net');
const failover     = require('tcp-client-failover');
const Transform    = require('stream').Transform;
const Protocol     = require('limitd-protocol');

const disyuntor    = require('disyuntor');

const lps = require('length-prefixed-stream');
const lpm = require('length-prefixed-message');

const defaults = {
  port: 9231,
  host: 'localhost'
};

function QueuedRequest(callback) {
  this.start = new Date();
  this.callback = callback;
}

function LimitdClient (options, done) {
  if (options && options.shard) {
    const ShardClient  = require('./shard_client');
    return new ShardClient(options, done);
  }

  EventEmitter.call(this);

  if (typeof options === 'string') {
    options = {
      hosts: [ options ]
    };
  } else if(Array.isArray(options)) {
    options = {
      hosts: options
    };
  } else if(typeof options === 'object') {
    if ('host' in options) {
      const host = options.host;
      options = _.extend(_.omit(options, 'host'), { hosts: [host] });
    } else {
      //clone the graph but keep stream.
      options = _.extend(_.cloneDeep(options), { stream: options.stream});
    }
  } else {
    options = { hosts: [] };
  }

  this._options = options;

  if (!options.hosts || options.hosts.length === 0) {
    options.hosts = [ defaults ];
  }

  options.hosts = _.map(options.hosts, (host) => {
    if (typeof host === 'string') {
      if (host.match(/\.socket$/)) {
        host = { port: host };
      } else {
        host = _.pick(url.parse(host), ['port', 'hostname']);
        host.port = typeof host.port !== 'undefined' ? parseInt(host.port, 10) : undefined;
      }
    }
    return host;
  });

  this.pending_requests = Object.create(null);

  this.connect(done);

  if (!options.breaker) {
    options.breaker = {};
  }

  options.breaker.timeout = options.breaker.timeout || options.timeout || '1s';

  // this._request = this._directRequest;
  this._request = disyuntor((request, type, callback) => {
    this._directRequest(request, type, callback);
  }, _.extend({
    name: 'limitd.request',
    monitor: details => {
      this.emit('breaker_error', details.err);
    }
  }, options.breaker || { }));

  this.resetCircuitBreaker = () => this._request.reset();

  this.currentId = 0;

  this.protocol_version = options.protocol_version || 1;
}

util.inherits(LimitdClient, EventEmitter);

LimitdClient.prototype.nextId = function () {
  //start from 1 and conver to string because the protocol uses strings.
  if (this.currentId < Number.MAX_SAFE_INTEGER) {
    this.currentId++;
  } else {
    this.currentId = 1;
  }

  if (this.protocol_version < 2) {
    //old version support only string ids.
    return this.currentId + '';
  }

  return this.currentId;
};

LimitdClient.prototype.connect = function (done) {
  var options = this._options;

  if (options.stream) {
    return this._onNewStream(options.stream);
  }

  if (options.hosts.length > 1) {
    this._connectUsingFailover(done);
  } else {
    this.host = options.hosts[0];
    this._connectUsingReconnect(done);
  }
};

LimitdClient.prototype._connectUsingReconnect = function (done) {
  const hostConfig = this._options.hosts[0];
  const host = hostConfig.address || hostConfig.hostname || hostConfig.host;
  const port = hostConfig.port;

  done = done || _.noop;

  this.socket = reconnect({
                  initialDelay: 200,
                  maxDelay: 1000
                }, stream => {
                  this._onNewStream(stream);
                }).once('connect', (connection) => {
                  connection.setKeepAlive(true, 50);
                  connection.setNoDelay();
                  setImmediate(() => {
                    this.emit('connect');
                    done();
                  });
                }).on('close', (has_error) => {
                  this.emit('close', has_error);
                }).on('error', (err) => {
                  this.emit('error', err);
                }).on('reconnect', (n, delay) => {
                  this.emit('reconnect', n, delay);
                }).connect(port, host);
};

LimitdClient.prototype._connectUsingFailover = function (done) {
  var self = this;

  done = done || _.noop;

  self.failover = failover.connect(self._options.hosts)
  .on('connected', (stream) => {
    self._onNewStream(stream);
    setImmediate(function () {
      self.emit('connect');
      done();
    });
  })
  .on('disconnected', function () {
    self.emit('close');
  })
  .on('error', function (err) {
    self.emit('error', err);
  });
};

LimitdClient.prototype._onNewStream = function (stream) {
  stream
  .pipe(lps.decode())
  .pipe(Transform({
    objectMode: true,
    transform(chunk, enc, callback) {
      callback(null, Protocol.Response.decode(chunk));
    }
  }))
  .on('data', (response) => {
    const queuedRequest = this.pending_requests[response.request_id];
    this._responseHandler(response, queuedRequest);
  })
  .on('error', (err) => {
    this.emit('error', err);
  });

  this.stream = stream;

  this.emit('ready');
};

LimitdClient.prototype.disconnect = function () {
  if (this.socket) {
    this.socket.disconnect();
  }

  if (this.failover) {
    this.failover.disconnect();
  }
};

LimitdClient.prototype._responseHandler = function(response, queuedRequest) {
  delete this.pending_requests[response.request_id];

  if (!queuedRequest) { return; }

  if (response.error &&
      response.error.type === 'UNKNOWN_BUCKET_TYPE') {
    return queuedRequest.callback(new Error('Invalid bucket type'));
  }

  const resp = response[response.body];

  if (resp) {
    resp.took = Date.now() - queuedRequest.start;

    if (typeof resp.protocol_version !== 'undefined') {
      this.protocol_version = resp.protocol_version;
    }
  }

  this.emit('response', resp);

  queuedRequest.callback(null, resp);
};

LimitdClient.prototype._fireAndForgetRequest = function (request) {
  if (!this.stream || !this.stream.writable) {
    const err = new Error(`Unable to send ${request.method} to limitd. The socket is closed.`);
    return this.emit('error', err);
  }

  lpm.write(this.stream, Protocol.Request.encode(request));
};

LimitdClient.prototype._directRequest = function (request, type, callback) {
  if (!this.stream || !this.stream.writable) {
    const err = new Error(`Unable to send ${request.method} to limitd. The socket is closed.`);
    return setImmediate(callback, err);
  }

  lpm.write(this.stream, Protocol.Request.encode(request));

  this.pending_requests[request.id] = new QueuedRequest(callback);
};

LimitdClient.prototype._takeOrWait = function (method, type, key, count, done) {
  if (typeof count === 'function') {
    done = count;
    count = 1;
  } else if (typeof count === 'undefined' && typeof done === 'undefined') {
    done = _.noop;
    count = 1;
  } else if (typeof done !== 'function') {
    done = _.noop;
  }

  const takeAll = count === 'all';

  const request = {
    'id':     this.nextId(),
    'type':   type,
    'key':    key,
    'method': method,
  };

  if (takeAll) {
    request.all = true;
  } else {
    request.count = count;
  }

  return this._request(request, type, done);
};

LimitdClient.prototype.take = function (type, key, count, done) {
  return this._takeOrWait('TAKE', type, key, count, done);
};

LimitdClient.prototype.wait = function (type, key, count, done) {
  return this._takeOrWait('WAIT', type, key, count, done);
};

LimitdClient.prototype.reset =
LimitdClient.prototype.put = function (type, key, count, done) {
  if (typeof count === 'function') {
    done = count;
    count = 'all';
  } else if (typeof count === 'undefined' && typeof done === 'undefined') {
    done = undefined;
    count = 'all';
  }

  const reset_all = count === 'all';

  const fireAndForget = typeof done !== 'function';

  const request = {
    'id':     this.nextId(),
    'type':   type,
    'key':    key,
    'method': 'PUT',
    'skipResponse': fireAndForget
  };

  if (reset_all) {
    request.all = true;
  } else {
    request.count = count;
  }

  if (fireAndForget) {
    return this._fireAndForgetRequest(request);
  }

  return this._request(request, type, done);
};

LimitdClient.prototype.status = function (type, key, done) {
  const request = {
    'id':     this.nextId(),
    'type':   type,
    'key':    key,
    'method': 'STATUS',
  };

  return this._request(request, type, done);
};

LimitdClient.prototype.ping = function (done) {
  const request = {
    'id':     this.nextId(),
    'type':   '',
    'key':    '',
    'method': 'PING',
  };

  return this._request(request, '', done);
};

module.exports = LimitdClient;
<commit_msg>remove unneeded parameter<commit_after>const url          = require('url');
const _            = require('lodash');
const EventEmitter = require('events').EventEmitter;
const util         = require('util');
const reconnect    = require('reconnect-net');
const failover     = require('tcp-client-failover');
const Transform    = require('stream').Transform;
const Protocol     = require('limitd-protocol');

const disyuntor    = require('disyuntor');

const lps = require('length-prefixed-stream');
const lpm = require('length-prefixed-message');

const defaults = {
  port: 9231,
  host: 'localhost'
};

function QueuedRequest(callback) {
  this.start = new Date();
  this.callback = callback;
}

function LimitdClient (options, done) {
  if (options && options.shard) {
    const ShardClient  = require('./shard_client');
    return new ShardClient(options, done);
  }

  EventEmitter.call(this);

  if (typeof options === 'string') {
    options = {
      hosts: [ options ]
    };
  } else if(Array.isArray(options)) {
    options = {
      hosts: options
    };
  } else if(typeof options === 'object') {
    if ('host' in options) {
      const host = options.host;
      options = _.extend(_.omit(options, 'host'), { hosts: [host] });
    } else {
      //clone the graph but keep stream.
      options = _.extend(_.cloneDeep(options), { stream: options.stream});
    }
  } else {
    options = { hosts: [] };
  }

  this._options = options;

  if (!options.hosts || options.hosts.length === 0) {
    options.hosts = [ defaults ];
  }

  options.hosts = _.map(options.hosts, (host) => {
    if (typeof host === 'string') {
      if (host.match(/\.socket$/)) {
        host = { port: host };
      } else {
        host = _.pick(url.parse(host), ['port', 'hostname']);
        host.port = typeof host.port !== 'undefined' ? parseInt(host.port, 10) : undefined;
      }
    }
    return host;
  });

  this.pending_requests = Object.create(null);

  this.connect(done);

  if (!options.breaker) {
    options.breaker = {};
  }

  options.breaker.timeout = options.breaker.timeout || options.timeout || '1s';

  // this._request = this._directRequest;
  this._request = disyuntor((request, callback) => {
    this._directRequest(request, callback);
  }, _.extend({
    name: 'limitd.request',
    monitor: details => {
      this.emit('breaker_error', details.err);
    }
  }, options.breaker || { }));

  this.resetCircuitBreaker = () => this._request.reset();

  this.currentId = 0;

  this.protocol_version = options.protocol_version || 1;
}

util.inherits(LimitdClient, EventEmitter);

LimitdClient.prototype.nextId = function () {
  //start from 1 and conver to string because the protocol uses strings.
  if (this.currentId < Number.MAX_SAFE_INTEGER) {
    this.currentId++;
  } else {
    this.currentId = 1;
  }

  if (this.protocol_version < 2) {
    //old version support only string ids.
    return this.currentId + '';
  }

  return this.currentId;
};

LimitdClient.prototype.connect = function (done) {
  var options = this._options;

  if (options.stream) {
    return this._onNewStream(options.stream);
  }

  if (options.hosts.length > 1) {
    this._connectUsingFailover(done);
  } else {
    this.host = options.hosts[0];
    this._connectUsingReconnect(done);
  }
};

LimitdClient.prototype._connectUsingReconnect = function (done) {
  const hostConfig = this._options.hosts[0];
  const host = hostConfig.address || hostConfig.hostname || hostConfig.host;
  const port = hostConfig.port;

  done = done || _.noop;

  this.socket = reconnect({
                  initialDelay: 200,
                  maxDelay: 1000
                }, stream => {
                  this._onNewStream(stream);
                }).once('connect', (connection) => {
                  connection.setKeepAlive(true, 50);
                  connection.setNoDelay();
                  setImmediate(() => {
                    this.emit('connect');
                    done();
                  });
                }).on('close', (has_error) => {
                  this.emit('close', has_error);
                }).on('error', (err) => {
                  this.emit('error', err);
                }).on('reconnect', (n, delay) => {
                  this.emit('reconnect', n, delay);
                }).connect(port, host);
};

LimitdClient.prototype._connectUsingFailover = function (done) {
  var self = this;

  done = done || _.noop;

  self.failover = failover.connect(self._options.hosts)
  .on('connected', (stream) => {
    self._onNewStream(stream);
    setImmediate(function () {
      self.emit('connect');
      done();
    });
  })
  .on('disconnected', function () {
    self.emit('close');
  })
  .on('error', function (err) {
    self.emit('error', err);
  });
};

LimitdClient.prototype._onNewStream = function (stream) {
  stream
  .pipe(lps.decode())
  .pipe(Transform({
    objectMode: true,
    transform(chunk, enc, callback) {
      callback(null, Protocol.Response.decode(chunk));
    }
  }))
  .on('data', (response) => {
    const queuedRequest = this.pending_requests[response.request_id];
    this._responseHandler(response, queuedRequest);
  })
  .on('error', (err) => {
    this.emit('error', err);
  });

  this.stream = stream;

  this.emit('ready');
};

LimitdClient.prototype.disconnect = function () {
  if (this.socket) {
    this.socket.disconnect();
  }

  if (this.failover) {
    this.failover.disconnect();
  }
};

LimitdClient.prototype._responseHandler = function(response, queuedRequest) {
  delete this.pending_requests[response.request_id];

  if (!queuedRequest) { return; }

  if (response.error &&
      response.error.type === 'UNKNOWN_BUCKET_TYPE') {
    return queuedRequest.callback(new Error('Invalid bucket type'));
  }

  const resp = response[response.body];

  if (resp) {
    resp.took = Date.now() - queuedRequest.start;

    if (typeof resp.protocol_version !== 'undefined') {
      this.protocol_version = resp.protocol_version;
    }
  }

  this.emit('response', resp);

  queuedRequest.callback(null, resp);
};

LimitdClient.prototype._fireAndForgetRequest = function (request) {
  if (!this.stream || !this.stream.writable) {
    const err = new Error(`Unable to send ${request.method} to limitd. The socket is closed.`);
    return this.emit('error', err);
  }

  lpm.write(this.stream, Protocol.Request.encode(request));
};

LimitdClient.prototype._directRequest = function (request, callback) {
  if (!this.stream || !this.stream.writable) {
    const err = new Error(`Unable to send ${request.method} to limitd. The socket is closed.`);
    return setImmediate(callback, err);
  }

  lpm.write(this.stream, Protocol.Request.encode(request));

  this.pending_requests[request.id] = new QueuedRequest(callback);
};

LimitdClient.prototype._takeOrWait = function (method, type, key, count, done) {
  if (typeof count === 'function') {
    done = count;
    count = 1;
  } else if (typeof count === 'undefined' && typeof done === 'undefined') {
    done = _.noop;
    count = 1;
  } else if (typeof done !== 'function') {
    done = _.noop;
  }

  const takeAll = count === 'all';

  const request = {
    'id':     this.nextId(),
    'type':   type,
    'key':    key,
    'method': method,
  };

  if (takeAll) {
    request.all = true;
  } else {
    request.count = count;
  }

  return this._request(request, done);
};

LimitdClient.prototype.take = function (type, key, count, done) {
  return this._takeOrWait('TAKE', type, key, count, done);
};

LimitdClient.prototype.wait = function (type, key, count, done) {
  return this._takeOrWait('WAIT', type, key, count, done);
};

LimitdClient.prototype.reset =
LimitdClient.prototype.put = function (type, key, count, done) {
  if (typeof count === 'function') {
    done = count;
    count = 'all';
  } else if (typeof count === 'undefined' && typeof done === 'undefined') {
    done = undefined;
    count = 'all';
  }

  const reset_all = count === 'all';

  const fireAndForget = typeof done !== 'function';

  const request = {
    'id':     this.nextId(),
    'type':   type,
    'key':    key,
    'method': 'PUT',
    'skipResponse': fireAndForget
  };

  if (reset_all) {
    request.all = true;
  } else {
    request.count = count;
  }

  if (fireAndForget) {
    return this._fireAndForgetRequest(request);
  }

  return this._request(request, done);
};

LimitdClient.prototype.status = function (type, key, done) {
  const request = {
    'id':     this.nextId(),
    'type':   type,
    'key':    key,
    'method': 'STATUS',
  };

  return this._request(request, done);
};

LimitdClient.prototype.ping = function (done) {
  const request = {
    'id':     this.nextId(),
    'type':   '',
    'key':    '',
    'method': 'PING',
  };

  return this._request(request, done);
};

module.exports = LimitdClient;
<|endoftext|>


<commit_before>var mixpanel = require("mixpanel-browserify")
var ga = require("ga-browserify")
var uuid = require("node-uuid")
var xhr = require("xhr")
var ever = require("ever")
var window = require("global/window")
var ReadySignal = require("ready-signal")

module.exports = Client

var methods = require("./index")

function Client(options) {
    options = options || {}
    var client = {}
    setupMixpanel(client, options)
    setupXHR(client, options)
    setupMessage(client, options)
    setupPostMessage(client, options)

    var gaOptions = options.ga

    if (gaOptions) {
        client.tracker = ga(gaOptions)
    }

    return client
}

function setupMixpanel(client, options) {
    var mixpanelOptions = options.mixpanel

    if (!mixpanelOptions) {
        return
    }

    if (typeof mixpanelOptions === "string") {
        mixpanelOptions = { token: mixpanelOptions }
    }

    var randomName = uuid()
    mixpanel.init(mixpanelOptions.token, mixpanelOptions, randomName)
    client.mixpanel = mixpanel[randomName]
}

function setupXHR(client, options) {
    var xhrOptions = options.xhr

    if (!xhrOptions) {
        return
    }

    if (typeof xhrOptions === "string") {
        xhrOptions = { uri: xhrOptions }
    }

    var uri = xhrOptions.uri

    client.xhr = callXHR

    function callXHR(data, callback) {
        xhr({
            method: "POST"
            , uri: uri
            , data: JSON.stringify(data)
            , headers: {
                "content-type": "application/json"
            }
        }, callback)
    }
}

function setupMessage(client, options) {
    var onMessage = options.onMessage

    if (!onMessage) {
        return
    }

    if (Array.isArray(onMessage)) {
        onMessage = { origin: onMessage }
    }

    var origin = onMessage.origin

    window.addEventListener("message", function (ev) {
        if (origin.indexOf(ev.origin) === -1) {
            return
        }

        var command = JSON.parse(ev.data)
        var args = command.args
        args.unshift(client)

        methods[command.functionName].apply(null, args)
    })
}

function setupPostMessage(client, options) {
    var postMessageOptions = options.postMessage

    if (!postMessageOptions) {
        return
    }

    var ready = ReadySignal()
    var iframe = postMessageOptions.iframe
    var targetOrigin = postMessageOptions.origin

    iframe.addEventListener("load", ready)

    client.postMessage = callPostMessage

    function callPostMessage(functionName, args) {
        ready(function (ev) {
            var target = iframe.contentWindow
            var command = { functionName: functionName, args: args }
            target.postMessage(JSON.stringify(command), targetOrigin)
        })
    }
}
<commit_msg>allow origin to be '*'<commit_after>var mixpanel = require("mixpanel-browserify")
var ga = require("ga-browserify")
var uuid = require("node-uuid")
var xhr = require("xhr")
var ever = require("ever")
var window = require("global/window")
var ReadySignal = require("ready-signal")

module.exports = Client

var methods = require("./index")

function Client(options) {
    options = options || {}
    var client = {}
    setupMixpanel(client, options)
    setupXHR(client, options)
    setupMessage(client, options)
    setupPostMessage(client, options)

    var gaOptions = options.ga

    if (gaOptions) {
        client.tracker = ga(gaOptions)
    }

    return client
}

function setupMixpanel(client, options) {
    var mixpanelOptions = options.mixpanel

    if (!mixpanelOptions) {
        return
    }

    if (typeof mixpanelOptions === "string") {
        mixpanelOptions = { token: mixpanelOptions }
    }

    var randomName = uuid()
    mixpanel.init(mixpanelOptions.token, mixpanelOptions, randomName)
    client.mixpanel = mixpanel[randomName]
}

function setupXHR(client, options) {
    var xhrOptions = options.xhr

    if (!xhrOptions) {
        return
    }

    if (typeof xhrOptions === "string") {
        xhrOptions = { uri: xhrOptions }
    }

    var uri = xhrOptions.uri

    client.xhr = callXHR

    function callXHR(data, callback) {
        xhr({
            method: "POST"
            , uri: uri
            , data: JSON.stringify(data)
            , headers: {
                "content-type": "application/json"
            }
        }, callback)
    }
}

function setupMessage(client, options) {
    var onMessage = options.onMessage

    if (!onMessage) {
        return
    }

    if (Array.isArray(onMessage)) {
        onMessage = { origin: onMessage }
    }

    var origin = onMessage.origin

    window.addEventListener("message", function (ev) {
        if (origin !== "*" && origin.indexOf(ev.origin) === -1) {
            return
        }

        var command = JSON.parse(ev.data)
        var args = command.args
        args.unshift(client)

        methods[command.functionName].apply(null, args)
    })
}

function setupPostMessage(client, options) {
    var postMessageOptions = options.postMessage

    if (!postMessageOptions) {
        return
    }

    var ready = ReadySignal()
    var iframe = postMessageOptions.iframe
    var targetOrigin = postMessageOptions.origin

    iframe.addEventListener("load", ready)

    client.postMessage = callPostMessage

    function callPostMessage(functionName, args) {
        ready(function (ev) {
            var target = iframe.contentWindow
            var command = { functionName: functionName, args: args }
            target.postMessage(JSON.stringify(command), targetOrigin)
        })
    }
}
<|endoftext|>


<commit_before>/**
 * Copyright 2014, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
/*global App, document, window */
'use strict';
var React = require('react/addons'),
    debug = require('debug'),
    bootstrapDebug = debug('Example:bootstrap'),
    Application = require('./app');

window.React = React; // For chrome dev tool support
debug.enable('*');

bootstrapDebug('rehydrating app');
var application = new Application(App.Context);

var app = application.getComponent(),
    mountNode = document.getElementById('app');

bootstrapDebug('React Rendering');
React.renderComponent(app, mountNode, function () {
    bootstrapDebug('React Rendered');
});
<commit_msg>Clarify server sent state<commit_after>/**
 * Copyright 2014, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
/*global App, document, window */
'use strict';
var React = require('react/addons'),
    debug = require('debug'),
    bootstrapDebug = debug('Example:bootstrap'),
    Application = require('./app'),
    dehydratedState = App && App.Context; // Sent from the server

window.React = React; // For chrome dev tool support
debug.enable('*');

bootstrapDebug('rehydrating app');
var application = new Application(dehydratedState);

var app = application.getComponent(),
    mountNode = document.getElementById('app');

bootstrapDebug('React Rendering');
React.renderComponent(app, mountNode, function () {
    bootstrapDebug('React Rendered');
});
<|endoftext|>


<commit_before>//
// client.js
// courage.js
//
// Copyright (c) 2014 The New Tricks, LLC.
// MIT License.
//

var TheNewTricks = TheNewTricks || {};

TheNewTricks.Courage = (function(Courage) {

  // Class private container.
  var PrivateCourage = Courage._private = Courage._private || {};

  var DSN_KEYS               = 'username password host port providerId'.split(' '),
      DSN_PATTERN            = /^([0-9a-z-]+):([0-9a-z-]+)@([0-9a-z-\.]+):([0-9]+)\/([0-9a-z-]+)$/i,
      DEVICE_ID_KEY          = 'com.thenewtricks.courage.deviceId',
      SUBSCRIBE_PROTOCOL_ID  = 1,
      REQUEST_MESSAGE_TYPE   = 0,
      STREAMING_MESSAGE_TYPE = 3;

  // The Courage Client is used to subscribe to streaming events from the Courage service.
  //
  // It is initially configured using a DSN, which contains authorization credentials, the location
  // of the service, and the id of the provider for this app.
  //
  // A DSN is structured as follows:
  //     {username}:{password}@{host}:{port}/{providerId}
  //
  // All fields are required. Here is an example:
  //     sessionid:sessionkey@rt.thenewtricks.com:9090/928308cd-eff8-4ef6-a154-f8268ec663d5
  //
  // The app can then use the `bind` method to subscribe to events from a channel:
  //     client.bind('28955ba1-fc5d-4553-9d1b-c751d5110c82', function(data) { console.log(data); });
  Courage.Client = function Client(dsn) {

    // Parse the DSN, get the persistent device id, and initialize a data structure to map channel ids
    // to callbacks. Store as private members.
    this._private = {
      dsn: parseDSN(dsn),
      deviceId: persistentDeviceId(),
      handlers: {},
      connectionManager: null,
    };
  };

  Courage.Client.prototype = {

    // The app can use the `bind` method of the Client to subscribe to events from a channel defined
    // by a channelId. All events that match that channelId will be fed to the callback function
    // registered with `bind`.
    //
    // Example:
    //     client.bind('28955ba1-fc5d-4553-9d1b-c751d5110c82', function(data) { console.log(data); });
    //
    // `data` is a Uint8Array.
    bind: function bind(channelId, callback) {

      // Access to private members.
      var my = this._private;

      // If the connection manager isn't started, start it now.
      if (!my.connectionManager) {

        var url = 'ws://' + my.dsn.host + ':' + my.dsn.port + '/';
        my.connectionManager = new PrivateCourage.ConnectionManager(url);

        my.connectionManager.onopen = onConnectionOpen.bind(this);
        my.connectionManager.onmessage = onConnectionMessage.bind(this);

      }

      // Register the callback for events on the bound channel.
      my.handlers[channelId.toLowerCase()] = callback;

      // If the connection manager is started and connected, subscribe right away. Otherwise no-op;
      // all channels will be subscribed to automatically when the connection is reopened.
      if (my.connectionManager.connected) {

        var uuid = TheNewTricks.UUID.parse(channelId);
        subscribeToChannel.bind(this)(uuid);
      }
    },
  };

  // parseDSN converts a DSN string to its component parts.
  function parseDSN(dsn) {

    var parsed = {};
    var m = DSN_PATTERN.exec(dsn);
    // TODO: fail if pattern fails to match.
    // TODO: Validate auth strings length and valid parameters.

    for (var i = 0; i < DSN_KEYS.length; i++) {
      parsed[DSN_KEYS[i]] = m[i + 1] || '';
    }

    // Replace the providerId with a parsed version.
    parsed.providerId = TheNewTricks.UUID.parse(parsed.providerId);

    return parsed;
  }

  // persistentDeviceId returns a UUID unique to this browser.
  //
  // Each browser is identified by a persistent, unique UUID, even if they belong to the same
  // user. Retrieve the device id from local storage if it exists, or generate a new one.
  function persistentDeviceId() {

    var deviceId = TheNewTricks.UUID.parse(localStorage[DEVICE_ID_KEY]);
    if (!deviceId) {
      deviceId = TheNewTricks.UUID.generateV4();
      localStorage[DEVICE_ID_KEY] = TheNewTricks.UUID.unparse(deviceId);
    }

    return deviceId;
  }

  // subscribeToChannel formats and send a subscribtion request for the specified channelId
  // to teh service.
  function subscribeToChannel(channelId) {

      // Access to private members.
      var my = this._private;

      // Form the subscribe request.
      var request = new PrivateCourage.MessageBuffer(SUBSCRIBE_PROTOCOL_ID, REQUEST_MESSAGE_TYPE);

      request.writeString(my.dsn.username);
      request.writeString(my.dsn.password);
      request.writeUUID(my.dsn.providerId);
      request.writeUUID(channelId);
      request.writeUUID(my.deviceId);
      request.writeUint8(0);

      // Send the subscribe request.
      my.connectionManager.send(request.buffer().buffer);
    }

  // onConnectionOpened, resubscribe to the channels we are bound to.
  function onConnectionOpen() {

    // Access to private members.
    var my = this._private;

    for (var channelId in my.handlers) {
      if (my.handlers.hasOwnProperty(channelId)) {

        var uuid = TheNewTricks.UUID.parse(channelId);
        subscribeToChannel.bind(this)(uuid);
      }
    }
  }

  // onConnectionMessage, deliver streaming events to each of the callbacks bound to a channel id.
  function onConnectionMessage(event) {

    // Access to private members.
    var my = this._private;

    var parser = new PrivateCourage.MessageParser(event.data);

    // Discard messages with unrecognized headers.
    var header = parser.readHeader();
    if (header.protocol !== SUBSCRIBE_PROTOCOL_ID || header.messageType !== STREAMING_MESSAGE_TYPE) {
      return;
    }

    // Parse the channel id and get the registered callback.
    var channelId = parser.readUUID();
    var callback = my.handlers[TheNewTricks.UUID.unparse(channelId)];

    // For each event, deliver the event data.
    var numEvents = parser.readUint8();
    for (var i = 0; i < numEvents; i++) {

      var data = parser.readBlob();
      callback(data);
    }
  }

  return Courage;

})(TheNewTricks.Courage || {});
<commit_msg>Remove awkward newline<commit_after>//
// client.js
// courage.js
//
// Copyright (c) 2014 The New Tricks, LLC.
// MIT License.
//

var TheNewTricks = TheNewTricks || {};

TheNewTricks.Courage = (function(Courage) {

  // Class private container.
  var PrivateCourage = Courage._private = Courage._private || {};

  var DSN_KEYS               = 'username password host port providerId'.split(' '),
      DSN_PATTERN            = /^([0-9a-z-]+):([0-9a-z-]+)@([0-9a-z-\.]+):([0-9]+)\/([0-9a-z-]+)$/i,
      DEVICE_ID_KEY          = 'com.thenewtricks.courage.deviceId',
      SUBSCRIBE_PROTOCOL_ID  = 1,
      REQUEST_MESSAGE_TYPE   = 0,
      STREAMING_MESSAGE_TYPE = 3;

  // The Courage Client is used to subscribe to streaming events from the Courage service.
  //
  // It is initially configured using a DSN, which contains authorization credentials, the location
  // of the service, and the id of the provider for this app.
  //
  // A DSN is structured as follows:
  //     {username}:{password}@{host}:{port}/{providerId}
  //
  // All fields are required. Here is an example:
  //     sessionid:sessionkey@rt.thenewtricks.com:9090/928308cd-eff8-4ef6-a154-f8268ec663d5
  //
  // The app can then use the `bind` method to subscribe to events from a channel:
  //     client.bind('28955ba1-fc5d-4553-9d1b-c751d5110c82', function(data) { console.log(data); });
  Courage.Client = function Client(dsn) {

    // Parse the DSN, get the persistent device id, and initialize a data structure to map channel ids
    // to callbacks. Store as private members.
    this._private = {
      dsn: parseDSN(dsn),
      deviceId: persistentDeviceId(),
      handlers: {},
      connectionManager: null,
    };
  };

  Courage.Client.prototype = {

    // The app can use the `bind` method of the Client to subscribe to events from a channel defined
    // by a channelId. All events that match that channelId will be fed to the callback function
    // registered with `bind`.
    //
    // Example:
    //     client.bind('28955ba1-fc5d-4553-9d1b-c751d5110c82', function(data) { console.log(data); });
    //
    // `data` is a Uint8Array.
    bind: function bind(channelId, callback) {

      // Access to private members.
      var my = this._private;

      // If the connection manager isn't started, start it now.
      if (!my.connectionManager) {

        var url = 'ws://' + my.dsn.host + ':' + my.dsn.port + '/';
        my.connectionManager = new PrivateCourage.ConnectionManager(url);

        my.connectionManager.onopen = onConnectionOpen.bind(this);
        my.connectionManager.onmessage = onConnectionMessage.bind(this);

      }

      // Register the callback for events on the bound channel.
      my.handlers[channelId.toLowerCase()] = callback;

      // If the connection manager is started and connected, subscribe right away. Otherwise no-op;
      // all channels will be subscribed to automatically when the connection is reopened.
      if (my.connectionManager.connected) {

        var uuid = TheNewTricks.UUID.parse(channelId);
        subscribeToChannel.bind(this)(uuid);
      }
    },
  };

  // parseDSN converts a DSN string to its component parts.
  function parseDSN(dsn) {

    var parsed = {};
    var m = DSN_PATTERN.exec(dsn);
    // TODO: fail if pattern fails to match.
    // TODO: Validate auth strings length and valid parameters.

    for (var i = 0; i < DSN_KEYS.length; i++) {
      parsed[DSN_KEYS[i]] = m[i + 1] || '';
    }

    // Replace the providerId with a parsed version.
    parsed.providerId = TheNewTricks.UUID.parse(parsed.providerId);

    return parsed;
  }

  // persistentDeviceId returns a UUID unique to this browser.
  //
  // Each browser is identified by a persistent, unique UUID, even if they belong to the same
  // user. Retrieve the device id from local storage if it exists, or generate a new one.
  function persistentDeviceId() {

    var deviceId = TheNewTricks.UUID.parse(localStorage[DEVICE_ID_KEY]);
    if (!deviceId) {
      deviceId = TheNewTricks.UUID.generateV4();
      localStorage[DEVICE_ID_KEY] = TheNewTricks.UUID.unparse(deviceId);
    }

    return deviceId;
  }

  // subscribeToChannel formats and send a subscribtion request for the specified channelId
  // to teh service.
  function subscribeToChannel(channelId) {

      // Access to private members.
      var my = this._private;

      // Form the subscribe request.
      var request = new PrivateCourage.MessageBuffer(SUBSCRIBE_PROTOCOL_ID, REQUEST_MESSAGE_TYPE);

      request.writeString(my.dsn.username);
      request.writeString(my.dsn.password);
      request.writeUUID(my.dsn.providerId);
      request.writeUUID(channelId);
      request.writeUUID(my.deviceId);
      request.writeUint8(0);

      // Send the subscribe request.
      my.connectionManager.send(request.buffer().buffer);
    }

  // onConnectionOpened, resubscribe to the channels we are bound to.
  function onConnectionOpen() {

    // Access to private members.
    var my = this._private;

    for (var channelId in my.handlers) {
      if (my.handlers.hasOwnProperty(channelId)) {

        var uuid = TheNewTricks.UUID.parse(channelId);
        subscribeToChannel.bind(this)(uuid);
      }
    }
  }

  // onConnectionMessage, deliver streaming events to each of the callbacks bound to a channel id.
  function onConnectionMessage(event) {

    // Access to private members.
    var my = this._private;

    var parser = new PrivateCourage.MessageParser(event.data);

    // Discard messages with unrecognized headers.
    var header = parser.readHeader();
    if (header.protocol !== SUBSCRIBE_PROTOCOL_ID || header.messageType !== STREAMING_MESSAGE_TYPE) {
      return;
    }

    // Parse the channel id and get the registered callback.
    var channelId = parser.readUUID();
    var callback = my.handlers[TheNewTricks.UUID.unparse(channelId)];

    // For each event, deliver the event data.
    var numEvents = parser.readUint8();
    for (var i = 0; i < numEvents; i++) {
      var data = parser.readBlob();
      callback(data);
    }
  }

  return Courage;

})(TheNewTricks.Courage || {});
<|endoftext|>


<commit_before>module.exports = Client

var debug = require('debug')('bittorrent-tracker')
var EventEmitter = require('events').EventEmitter
var extend = require('xtend')
var inherits = require('inherits')
var once = require('once')
var parallel = require('run-parallel')
var uniq = require('uniq')
var url = require('url')

var common = require('./lib/common')
var HTTPTracker = require('./lib/client/http-tracker') // empty object in browser
var UDPTracker = require('./lib/client/udp-tracker') // empty object in browser
var WebSocketTracker = require('./lib/client/websocket-tracker')

inherits(Client, EventEmitter)

/**
 * BitTorrent tracker client.
 *
 * Find torrent peers, to help a torrent client participate in a torrent swarm.
 *
 * @param {string|Buffer} peerId           peer id
 * @param {Number} port                    torrent client listening port
 * @param {Object} torrent                 parsed torrent
 * @param {Object} opts                    options object
 * @param {Number} opts.rtcConfig          RTCPeerConnection configuration object
 * @param {Number} opts.wrtc               custom webrtc impl (useful in node.js)
 * @param {function} opts.getAnnounceOpts  callback to provide data to tracker
 */
function Client (peerId, port, torrent, opts) {
  var self = this
  if (!(self instanceof Client)) return new Client(peerId, port, torrent, opts)
  EventEmitter.call(self)
  if (!opts) opts = {}

  // required
  self.peerId = typeof peerId === 'string'
    ? peerId
    : peerId.toString('hex')
  self.peerIdBuffer = new Buffer(self.peerId, 'hex')
  self._peerIdBinary = self.peerIdBuffer.toString('binary')

  self.infoHash = typeof torrent.infoHash === 'string'
    ? torrent.infoHash
    : torrent.infoHash.toString('hex')
  self.infoHashBuffer = new Buffer(self.infoHash, 'hex')
  self._infoHashBinary = self.infoHashBuffer.toString('binary')

  self.torrentLength = torrent.length
  self.destroyed = false

  self._port = port

  self._rtcConfig = opts.rtcConfig
  self._wrtc = opts.wrtc
  self._getAnnounceOpts = opts.getAnnounceOpts

  debug('new client %s', self.infoHash)

  var webrtcSupport = !!self._wrtc || typeof window !== 'undefined'

  var announce = (typeof torrent.announce === 'string')
    ? [ torrent.announce ]
    : torrent.announce == null
      ? []
      : torrent.announce

  announce = announce.map(function (announceUrl) {
    announceUrl = announceUrl.toString()
    if (announceUrl[announceUrl.length - 1] === '/') {
      // remove trailing slash from trackers to catch duplicates
      announceUrl = announceUrl.substring(0, announceUrl.length - 1)
    }
    return announceUrl
  })

  announce = uniq(announce)

  self._trackers = announce
    .map(function (announceUrl) {
      var protocol = url.parse(announceUrl).protocol
      if ((protocol === 'http:' || protocol === 'https:') &&
          typeof HTTPTracker === 'function') {
        return new HTTPTracker(self, announceUrl)
      } else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
        return new UDPTracker(self, announceUrl)
      } else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) {
        // Don't try to add http tracker on an https website
        if (protocol === 'ws:' && location && location.protocol && location.protocol === 'https:') {
          process.nextTick(function () {
            var err = new Error('unsupported http tracker on https: ' + announceUrl)
            self.emit('warning', err)
          })
          return null
        }
        return new WebSocketTracker(self, announceUrl)
      } else {
        process.nextTick(function () {
          var err = new Error('unsupported tracker protocol for ' + announceUrl)
          self.emit('warning', err)
        })
      }
      return null
    })
    .filter(Boolean)
}

/**
 * Simple convenience function to scrape a tracker for an info hash without needing to
 * create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple
 * torrents at the same time.
 * @param  {string} announceUrl
 * @param  {string|Array.<string>} infoHash
 * @param  {function} cb
 */
Client.scrape = function (announceUrl, infoHash, cb) {
  cb = once(cb)

  var peerId = new Buffer('01234567890123456789') // dummy value
  var port = 6881 // dummy value
  var torrent = {
    infoHash: Array.isArray(infoHash) ? infoHash[0] : infoHash,
    announce: [ announceUrl ]
  }
  var client = new Client(peerId, port, torrent)
  client.once('error', cb)

  var len = Array.isArray(infoHash) ? infoHash.length : 1
  var results = {}
  client.on('scrape', function (data) {
    len -= 1
    results[data.infoHash] = data
    if (len === 0) {
      client.destroy()
      var keys = Object.keys(results)
      if (keys.length === 1) {
        cb(null, results[keys[0]])
      } else {
        cb(null, results)
      }
    }
  })

  infoHash = Array.isArray(infoHash)
    ? infoHash.map(function (infoHash) { return new Buffer(infoHash, 'hex') })
    : new Buffer(infoHash, 'hex')
  client.scrape({ infoHash: infoHash })
}

/**
 * Send a `start` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.start = function (opts) {
  var self = this
  debug('send `start`')
  opts = self._defaultAnnounceOpts(opts)
  opts.event = 'started'
  self._announce(opts)

  // start announcing on intervals
  self._trackers.forEach(function (tracker) {
    tracker.setInterval()
  })
}

/**
 * Send a `stop` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.numwant
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.stop = function (opts) {
  var self = this
  debug('send `stop`')
  opts = self._defaultAnnounceOpts(opts)
  opts.event = 'stopped'
  self._announce(opts)
}

/**
 * Send a `complete` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.numwant
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.complete = function (opts) {
  var self = this
  debug('send `complete`')
  if (!opts) opts = {}
  if (opts.downloaded == null && self.torrentLength != null) {
    opts.downloaded = self.torrentLength
  }
  opts = self._defaultAnnounceOpts(opts)
  opts.event = 'completed'
  self._announce(opts)
}

/**
 * Send a `update` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.numwant
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.update = function (opts) {
  var self = this
  debug('send `update`')
  opts = self._defaultAnnounceOpts(opts)
  if (opts.event) delete opts.event
  self._announce(opts)
}

Client.prototype._announce = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    // tracker should not modify `opts` object, it's passed to all trackers
    tracker.announce(opts)
  })
}

/**
 * Send a scrape request to the trackers.
 * @param {Object} opts
 */
Client.prototype.scrape = function (opts) {
  var self = this
  debug('send `scrape`')
  if (!opts) opts = {}
  self._trackers.forEach(function (tracker) {
    // tracker should not modify `opts` object, it's passed to all trackers
    tracker.scrape(opts)
  })
}

Client.prototype.setInterval = function (intervalMs) {
  var self = this
  debug('setInterval %d', intervalMs)
  self._trackers.forEach(function (tracker) {
    tracker.setInterval(intervalMs)
  })
}

Client.prototype.destroy = function (cb) {
  var self = this
  if (self.destroyed) return
  self.destroyed = true
  debug('destroy')

  var tasks = self._trackers.map(function (tracker) {
    return function (cb) {
      tracker.destroy(cb)
    }
  })

  parallel(tasks, cb)
  self._trackers = []
}

Client.prototype._defaultAnnounceOpts = function (opts) {
  var self = this
  if (!opts) opts = {}

  if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS

  if (opts.uploaded == null) opts.uploaded = 0
  if (opts.downloaded == null) opts.downloaded = 0

  if (opts.left == null && self.torrentLength != null) {
    opts.left = self.torrentLength - opts.downloaded
  }

  if (self._getAnnounceOpts) opts = extend(opts, self._getAnnounceOpts())
  return opts
}
<commit_msg>Fixes for PR #128<commit_after>module.exports = Client

var debug = require('debug')('bittorrent-tracker')
var EventEmitter = require('events').EventEmitter
var extend = require('xtend')
var inherits = require('inherits')
var once = require('once')
var parallel = require('run-parallel')
var uniq = require('uniq')
var url = require('url')

var common = require('./lib/common')
var HTTPTracker = require('./lib/client/http-tracker') // empty object in browser
var UDPTracker = require('./lib/client/udp-tracker') // empty object in browser
var WebSocketTracker = require('./lib/client/websocket-tracker')

inherits(Client, EventEmitter)

/**
 * BitTorrent tracker client.
 *
 * Find torrent peers, to help a torrent client participate in a torrent swarm.
 *
 * @param {string|Buffer} peerId           peer id
 * @param {Number} port                    torrent client listening port
 * @param {Object} torrent                 parsed torrent
 * @param {Object} opts                    options object
 * @param {Number} opts.rtcConfig          RTCPeerConnection configuration object
 * @param {Number} opts.wrtc               custom webrtc impl (useful in node.js)
 * @param {function} opts.getAnnounceOpts  callback to provide data to tracker
 */
function Client (peerId, port, torrent, opts) {
  var self = this
  if (!(self instanceof Client)) return new Client(peerId, port, torrent, opts)
  EventEmitter.call(self)
  if (!opts) opts = {}

  // required
  self.peerId = typeof peerId === 'string'
    ? peerId
    : peerId.toString('hex')
  self.peerIdBuffer = new Buffer(self.peerId, 'hex')
  self._peerIdBinary = self.peerIdBuffer.toString('binary')

  self.infoHash = typeof torrent.infoHash === 'string'
    ? torrent.infoHash
    : torrent.infoHash.toString('hex')
  self.infoHashBuffer = new Buffer(self.infoHash, 'hex')
  self._infoHashBinary = self.infoHashBuffer.toString('binary')

  self.torrentLength = torrent.length
  self.destroyed = false

  self._port = port

  self._rtcConfig = opts.rtcConfig
  self._wrtc = opts.wrtc
  self._getAnnounceOpts = opts.getAnnounceOpts

  debug('new client %s', self.infoHash)

  var webrtcSupport = !!self._wrtc || typeof window !== 'undefined'

  var announce = (typeof torrent.announce === 'string')
    ? [ torrent.announce ]
    : torrent.announce == null
      ? []
      : torrent.announce

  announce = announce.map(function (announceUrl) {
    announceUrl = announceUrl.toString()
    if (announceUrl[announceUrl.length - 1] === '/') {
      // remove trailing slash from trackers to catch duplicates
      announceUrl = announceUrl.substring(0, announceUrl.length - 1)
    }
    return announceUrl
  })

  announce = uniq(announce)

  self._trackers = announce
    .map(function (announceUrl) {
      var protocol = url.parse(announceUrl).protocol
      if ((protocol === 'http:' || protocol === 'https:') &&
          typeof HTTPTracker === 'function') {
        return new HTTPTracker(self, announceUrl)
      } else if (protocol === 'udp:' && typeof UDPTracker === 'function') {
        return new UDPTracker(self, announceUrl)
      } else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) {
        // Skip ws:// trackers on https:// sites because they throw SecurityError
        if (protocol === 'ws:' && typeof window !== 'undefined' &&
            window.location.protocol === 'https:') {
          nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl))
          return null
        }
        return new WebSocketTracker(self, announceUrl)
      } else {
        nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl))
        return null
      }
    })
    .filter(Boolean)

  function nextTickWarn (err) {
    process.nextTick(function () {
      self.emit('warning', err)
    })
  }
}

/**
 * Simple convenience function to scrape a tracker for an info hash without needing to
 * create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple
 * torrents at the same time.
 * @param  {string} announceUrl
 * @param  {string|Array.<string>} infoHash
 * @param  {function} cb
 */
Client.scrape = function (announceUrl, infoHash, cb) {
  cb = once(cb)

  var peerId = new Buffer('01234567890123456789') // dummy value
  var port = 6881 // dummy value
  var torrent = {
    infoHash: Array.isArray(infoHash) ? infoHash[0] : infoHash,
    announce: [ announceUrl ]
  }
  var client = new Client(peerId, port, torrent)
  client.once('error', cb)

  var len = Array.isArray(infoHash) ? infoHash.length : 1
  var results = {}
  client.on('scrape', function (data) {
    len -= 1
    results[data.infoHash] = data
    if (len === 0) {
      client.destroy()
      var keys = Object.keys(results)
      if (keys.length === 1) {
        cb(null, results[keys[0]])
      } else {
        cb(null, results)
      }
    }
  })

  infoHash = Array.isArray(infoHash)
    ? infoHash.map(function (infoHash) { return new Buffer(infoHash, 'hex') })
    : new Buffer(infoHash, 'hex')
  client.scrape({ infoHash: infoHash })
}

/**
 * Send a `start` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.start = function (opts) {
  var self = this
  debug('send `start`')
  opts = self._defaultAnnounceOpts(opts)
  opts.event = 'started'
  self._announce(opts)

  // start announcing on intervals
  self._trackers.forEach(function (tracker) {
    tracker.setInterval()
  })
}

/**
 * Send a `stop` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.numwant
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.stop = function (opts) {
  var self = this
  debug('send `stop`')
  opts = self._defaultAnnounceOpts(opts)
  opts.event = 'stopped'
  self._announce(opts)
}

/**
 * Send a `complete` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.numwant
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.complete = function (opts) {
  var self = this
  debug('send `complete`')
  if (!opts) opts = {}
  if (opts.downloaded == null && self.torrentLength != null) {
    opts.downloaded = self.torrentLength
  }
  opts = self._defaultAnnounceOpts(opts)
  opts.event = 'completed'
  self._announce(opts)
}

/**
 * Send a `update` announce to the trackers.
 * @param {Object} opts
 * @param {number=} opts.uploaded
 * @param {number=} opts.downloaded
 * @param {number=} opts.numwant
 * @param {number=} opts.left (if not set, calculated automatically)
 */
Client.prototype.update = function (opts) {
  var self = this
  debug('send `update`')
  opts = self._defaultAnnounceOpts(opts)
  if (opts.event) delete opts.event
  self._announce(opts)
}

Client.prototype._announce = function (opts) {
  var self = this
  self._trackers.forEach(function (tracker) {
    // tracker should not modify `opts` object, it's passed to all trackers
    tracker.announce(opts)
  })
}

/**
 * Send a scrape request to the trackers.
 * @param {Object} opts
 */
Client.prototype.scrape = function (opts) {
  var self = this
  debug('send `scrape`')
  if (!opts) opts = {}
  self._trackers.forEach(function (tracker) {
    // tracker should not modify `opts` object, it's passed to all trackers
    tracker.scrape(opts)
  })
}

Client.prototype.setInterval = function (intervalMs) {
  var self = this
  debug('setInterval %d', intervalMs)
  self._trackers.forEach(function (tracker) {
    tracker.setInterval(intervalMs)
  })
}

Client.prototype.destroy = function (cb) {
  var self = this
  if (self.destroyed) return
  self.destroyed = true
  debug('destroy')

  var tasks = self._trackers.map(function (tracker) {
    return function (cb) {
      tracker.destroy(cb)
    }
  })

  parallel(tasks, cb)
  self._trackers = []
}

Client.prototype._defaultAnnounceOpts = function (opts) {
  var self = this
  if (!opts) opts = {}

  if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS

  if (opts.uploaded == null) opts.uploaded = 0
  if (opts.downloaded == null) opts.downloaded = 0

  if (opts.left == null && self.torrentLength != null) {
    opts.left = self.torrentLength - opts.downloaded
  }

  if (self._getAnnounceOpts) opts = extend(opts, self._getAnnounceOpts())
  return opts
}
<|endoftext|>


<commit_before>var Client = function () {
    'use strict';

    var commandMap = {
        240: 'SE',
        241: 'NOP',
        242: 'Data Mark',
        243: 'BRK',
        244: 'IP',
        245: 'AO',
        246: 'AYT',
        247: 'EC',
        248: 'EL',
        249: 'GA',
        250: 'SB',
        251: 'WILL',
        252: "WON'T",
        253: 'DO',
        254: "DON'T",
        255: 'IAC',
    };

    var commandToString = function (command) {
        if (!commandMap.hasOwnProperty(command)) {
            return sprintf('Unknown <%d>', command);
            throw {
                name: 'ValueError',
                message: 'Invalid command detected: ' + command.toString(),
            };
        }
        return commandMap[command];
    };

    return {
        socket: null,
        outputElem: null,
        currentLine: null,
        continueLine: false,  // Probably not used so often with MUDs...
        passwordPrompt: false,
        shouldAutoScroll: true,
        currentCommand: null,
        outputBuffer: null,
        initialize: function (outputElem) {
            this.currentCommand = [];
            this.outputBuffer = "";
            this.outputElem = outputElem;
            var client = this;
            this.outputElem.addEventListener('scroll', function () {
                client.shouldAutoScroll = (
                    client.outputElem.scrollTop ===
                    client.outputElem.scrollHeight - client.outputElem.clientHeight
                );
            });
        },
        connect: function (addr) {
            this.socket = new WebSocket(addr, ["telnet"]);
            this.socket.addEventListener('message', this.onMessage.bind(this));
            return this.socket;
        },
        onUserInput: function (userInput) {
            this.socket.send(userInput + '\n');

            if (!this.passwordPrompt) {
                // Dunno if we were continuing a previous line, but we
                // aren't now.
                this.continueLine = false;
                this.createNewLine();
                this.appendLine(userInput);
            }
        },
        onMessage: function (event) {
            var fr = new FileReader();
            var client = this;
            fr.addEventListener('loadend', function () {
                return client.handleMessage(fr.result);
            });
            fr.readAsArrayBuffer(event.data);
        },
        handleMessage: function (buffer) {
            var rawOutputBuffer = []
            var uint8 = new Uint8Array(buffer, 0, buffer.length);
            for (var i=0; i<uint8.length; i++) {
                var byte = uint8[i];
                if (this.currentCommand.length === 0) {
                    if (byte === 255) {  // Interpret as Command (IAC)
                        this.currentCommand.push(byte);
                    } else {
                        rawOutputBuffer.push(byte);
                    }
                } else {
                    this.currentCommand.push(byte);
                    if (this.currentCommand.length === 2) {
                        if (byte < 240) {
                            // Invalid telnet command
                            console.log(this.currentCommand.map(commandToString));
                            this.currentCommand = [];
                        } else if (240 <= byte && byte <= 250) {
                            // Valid 2-byte telnet command
                            console.log(this.currentCommand.map(commandToString));
                            // TO DO: do something with this ;)
                            this.currentCommand = [];
                        } else if (251 <= byte && byte <= 254) {
                            // Second byte of a 3-byte command; do nothing yet
                        } else if (byte === 255) {
                            // Literal IAC
                            // TO DO: do something with this ;)
                            // ... likely unneeded for Aard...?
                            this.currentCommand = [];
                        }
                    } else if (this.currentCommand.length === 3) {
                        console.log(this.currentCommand.map(commandToString));
                        // TO DO: do something with this ;)
                        this.currentCommand = [];
                    }
                    // Try to interpret command
                }
            }
            var output = String.fromCharCode.apply(null, rawOutputBuffer);
            this.pushOutput(output);
        },
        pushOutput: function (output) {
            var client = this;
            // Precede output with any leftover bits from previous packets
            output = client.outputBuffer + output;
            var lines = output.split('\n\r');
            lines.forEach(function (line) {
                var lineElem = client.currentLine;
                if (!lineElem || !client.continueLine) {
                    lineElem = client.createNewLine();
                    if (line.trim().length === 0) {
                        // Workaround for empty lines
                        lineElem.innerHTML = ' ';
                        return;
                    }
                }
                if (line.charAt(line.length-1) === '\n') {
                    client.outputBuffer = '\n';
                    line = line.slice(0, line.length-2);
                }
                client.appendLine(line);

                // Auto-scroll

                // TO DO: determine when to continue a previous line...
            });
        },
        createNewLine: function () {
            var lineElem = document.createElement('div');
            lineElem.classList.add('output-line');
            var shouldScroll = this.shouldAutoScroll;
            this.outputElem.appendChild(lineElem);
            this.currentLine = lineElem;
            if (shouldScroll) {
                // Seems we need to release to the browser to allow redrawing/resizing.
                setTimeout(this.autoScroll.bind(this), 0);
            }
            return lineElem;
        },
        appendLine: function (input) {
            var shouldScroll = this.shouldAutoScroll;
            this.currentLine.innerHTML += _.template('<%- input %>')({input: input});
            this.detectPasswordPrompt(input);
            if (shouldScroll) {
                setTimeout(this.autoScroll.bind(this), 0);
            }
        },
        detectPasswordPrompt: function (input) {
            this.passwordPrompt = (input.toLowerCase().indexOf('password') === 0);
        },
        autoScroll: function () {
            // TO DO: Don't autoscroll if the user scrolls up from the
            // bottom.
            var newScrollTop = this.outputElem.scrollHeight - this.outputElem.clientHeight;
            this.outputElem.scrollTop = this.outputElem.scrollHeight - this.outputElem.clientHeight;
        },
    };
}();
<commit_msg>First attempt at using continueLine.  Does not quite work as hoped.<commit_after>var Client = function () {
    'use strict';

    var commandMap = {
        240: 'SE',
        241: 'NOP',
        242: 'Data Mark',
        243: 'BRK',
        244: 'IP',
        245: 'AO',
        246: 'AYT',
        247: 'EC',
        248: 'EL',
        249: 'GA',
        250: 'SB',
        251: 'WILL',
        252: "WON'T",
        253: 'DO',
        254: "DON'T",
        255: 'IAC',
    };

    var commandToString = function (command) {
        if (!commandMap.hasOwnProperty(command)) {
            return sprintf('Unknown <%d>', command);
            throw {
                name: 'ValueError',
                message: 'Invalid command detected: ' + command.toString(),
            };
        }
        return commandMap[command];
    };

    return {
        socket: null,
        outputElem: null,
        currentLine: null,
        continueLine: false,  // Probably not used so often with MUDs...
        passwordPrompt: false,
        shouldAutoScroll: true,
        currentCommand: null,
        outputBuffer: null,
        initialize: function (outputElem) {
            this.currentCommand = [];
            this.outputBuffer = "";
            this.outputElem = outputElem;
            var client = this;
            this.outputElem.addEventListener('scroll', function () {
                client.shouldAutoScroll = (
                    client.outputElem.scrollTop ===
                    client.outputElem.scrollHeight - client.outputElem.clientHeight
                );
            });
        },
        connect: function (addr) {
            this.socket = new WebSocket(addr, ["telnet"]);
            this.socket.addEventListener('message', this.onMessage.bind(this));
            return this.socket;
        },
        onUserInput: function (userInput) {
            this.socket.send(userInput + '\n');

            if (!this.passwordPrompt) {
                // Dunno if we were continuing a previous line, but we
                // aren't now.
                this.continueLine = false;
                this.createNewLine();
                this.appendLine(userInput);
            }
        },
        onMessage: function (event) {
            var fr = new FileReader();
            var client = this;
            fr.addEventListener('loadend', function () {
                return client.handleMessage(fr.result);
            });
            fr.readAsArrayBuffer(event.data);
        },
        handleMessage: function (buffer) {
            var rawOutputBuffer = []
            var uint8 = new Uint8Array(buffer, 0, buffer.length);
            for (var i=0; i<uint8.length; i++) {
                var byte = uint8[i];
                if (this.currentCommand.length === 0) {
                    if (byte === 255) {  // Interpret as Command (IAC)
                        this.currentCommand.push(byte);
                    } else {
                        rawOutputBuffer.push(byte);
                    }
                } else {
                    this.currentCommand.push(byte);
                    if (this.currentCommand.length === 2) {
                        if (byte < 240) {
                            // Invalid telnet command
                            console.log(this.currentCommand.map(commandToString));
                            this.currentCommand = [];
                        } else if (240 <= byte && byte <= 250) {
                            // Valid 2-byte telnet command
                            console.log(this.currentCommand.map(commandToString));
                            // TO DO: do something with this ;)
                            this.currentCommand = [];
                        } else if (251 <= byte && byte <= 254) {
                            // Second byte of a 3-byte command; do nothing yet
                        } else if (byte === 255) {
                            // Literal IAC
                            // TO DO: do something with this ;)
                            // ... likely unneeded for Aard...?
                            this.currentCommand = [];
                        }
                    } else if (this.currentCommand.length === 3) {
                        console.log(this.currentCommand.map(commandToString));
                        // TO DO: do something with this ;)
                        this.currentCommand = [];
                    }
                    // Try to interpret command
                }
            }
            var output = String.fromCharCode.apply(null, rawOutputBuffer);
            this.pushOutput(output);
        },
        pushOutput: function (output) {
            var client = this;
            // Precede output with any leftover bits from previous packets
            output = client.outputBuffer + output;
            var lines = output.split('\n\r');
            lines.forEach(function (line) {
                var lineElem = client.currentLine;
                if (!lineElem || !client.continueLine) {
                    lineElem = client.createNewLine();
                    if (line.trim().length === 0) {
                        // Workaround for empty lines
                        lineElem.innerHTML = ' ';
                        return;
                    }
                }
                if (line.charAt(line.length-1) === '\n') {
                    client.outputBuffer = '\n';
                    line = line.slice(0, line.length-2);
                }
                client.appendLine(line);
            });
            // If we ended on a new line, our final line in the
            // line list should be empty.  If this is *not* true,
            // then we should continue from the current line on
            // the next call.
            client.continueLine = lines[lines.length-1] !== '';
            console.log('Continue line:', client.continueLine);
            // Something seems bugged... test via "clan -h".  Lots of
            // output == weirdness.  Haven't yet traced.
        },
        createNewLine: function () {
            var lineElem = document.createElement('div');
            lineElem.classList.add('output-line');
            var shouldScroll = this.shouldAutoScroll;
            this.outputElem.appendChild(lineElem);
            this.currentLine = lineElem;
            if (shouldScroll) {
                // Seems we need to release to the browser to allow redrawing/resizing.
                setTimeout(this.autoScroll.bind(this), 0);
            }
            return lineElem;
        },
        appendLine: function (input) {
            var shouldScroll = this.shouldAutoScroll;
            this.currentLine.innerHTML += _.template('<%- input %>')({input: input});
            this.detectPasswordPrompt(input);
            if (shouldScroll) {
                setTimeout(this.autoScroll.bind(this), 0);
            }
        },
        detectPasswordPrompt: function (input) {
            this.passwordPrompt = (input.toLowerCase().indexOf('password') === 0);
        },
        autoScroll: function () {
            // TO DO: Don't autoscroll if the user scrolls up from the
            // bottom.
            var newScrollTop = this.outputElem.scrollHeight - this.outputElem.clientHeight;
            this.outputElem.scrollTop = this.outputElem.scrollHeight - this.outputElem.clientHeight;
        },
    };
}();
<|endoftext|>


<commit_before>/**
 * TinyQueries Admin Tool
 *
 * @author 	Wouter Diesveld <wouter@tinyqueries.com>
 * @package TinyQueries
 */

var admin = angular.module('TinyQueriesAdmin', ['ngCookies', 'ngRoute']);

admin.config(['$routeProvider',
	function($routeProvider)
	{
		$routeProvider
		.when('/',
		{
			templateUrl: 'templates/home.html'
		})
		.when('/queries/:queryID',
		{
			templateUrl: 'templates/query.html'
		})
		.otherwise(
		{
			redirectTo: ''
		});
	}
]);

/**
 * Service for the api
 */
admin.factory('$api', ['$http', function($http)
{
	/**
	 * Returns the interface for the api object
	 */
	return {

		compile: function()
		{
			return $http.get('api/?_method=compile');
		},

		getProject:	function()
		{
			return $http.get('api/?_method=getProject');
		},

		getQuery: function(queryID)
		{
			return $http.get('api/?_method=getInterface&query=' + queryID);
		},

		runQuery: function(call, params)
		{
			var apiParams =
			{
				_profiling: 1
			};

			for (p in call)
				apiParams[ p ] = removeWhitespace( call[p] );

			for (var id in params)
			{
				var p = params[ id ];

				var isArray = $.isArray( p.type )
							? ($.inArray('array', p.type) != -1)
							: (p.type == 'array');

				// Add [] for array parameters
				var name = (isArray)
					? id + '[]'
					: id;

				// Convert CSV to arrays
				apiParams[ name ] = (isArray)
					? ( (p.value) ? p.value.toString().split(',') : '')
					: ( (p.value || p.value===0) ? p.value : '');
			}

			return $http.get('api/', { params: apiParams });
		},

		getTermParams: function(term)
		{
			return $http.get('api/?_method=getTermParams&query=' + removeWhitespace(term));
		}

	};
}]);

/**
 * Main controller which is attached to the body element
 */
admin.controller('main', ['$scope', '$api', '$cookies', function($scope, $api, $cookies)
{
	// Set scope vars
	$scope.view					= 'queries';
	$scope.nav 					= 'queries';
	$scope.project				= {};
	$scope.globals				= {};
	$scope.error				= null;
	$scope.compileStatusCode 	= -1;
	$scope.compileStatus		= '';

	$scope.$watch('compileStatusCode', function(value)
	{
		if (value != 0)
			return;

		$scope.refresh();
	});

	// Initialize queries var
	$scope.refresh = function()
	{
		$api.getProject().success( function(data)
		{
			$scope.project = data;

			// update query path
			setRestPaths( $scope.project );

			$scope.globals = setValues( reformatParams(data.globals), $cookies );

		}).error( function(data)
		{
			$scope.error = data.error;
		});
	};

	$scope.compile = function()
	{
		$scope.compileStatusCode = 1;
		$scope.compileStatus = 'Compiler is being called...';
		$api.compile().success(function(data)
		{
			$scope.compileStatusCode = 0;
			$scope.compileStatus = data.message;
			$scope.refresh();
		}).error(function(data)
		{
			$scope.compileStatusCode = 2;
			$scope.compileStatus = data.error;
		});
	}

	$scope.refresh();
}]);

// not in use yet
admin.controller('message', ['$scope', function($scope)
{
	$scope.content = '';
}]);

/**
 * Controller for query info
 */
admin.controller('query', ['$scope', '$api', '$cookies', '$routeParams', function($scope, $api, $cookies, $routeParams)
{
	$scope.query 		= {};
	$scope.error		= null;
	$scope.params		= {};
	$scope.tab			= 'run';
	$scope.queryTerm	= null;
	$scope.output		= '';
	$scope.profiling	= {};

	$scope.$watch('compileStatusCode', function(value)
	{
		if (value != 0)
			return;

		$scope.refresh();
	});

	$scope.numberOfParams = function()
	{
		var n=0;
		for (var p in $scope.params)
			n++;

		return n;
	};

	$scope.saveParams = function()
	{
		// Copy params to cookies
		for (var p in $scope.params)
			$cookies[p] = $scope.params[p].value;

	};

	$scope.updateParams = function()
	{
		if (!$scope.queryTerm)
		{
			$scope.params = {};
			return;
		}

		$api.getTermParams( $scope.queryTerm ).success( function(data)
		{
			$scope.error = null;
			$scope.params = setValues( data.params, $cookies );
		}).error(function(data)
		{
			$scope.error = data.error;
		});
	}

	$scope.run = function()
	{
		$scope.status = "Query is running...";

		$scope.saveParams();

		var params = {};
		// Merge params & globals
		for (var name in $scope.params)
			// Leaf out defaultParam for REST, since this param is already sent through the URL
			if ($scope.view != 'rest' && name != $scope.defaultParam)
				params[ name ] = $scope.params[ name ];
		for (var name in $scope.globals)
			params[ name ] = $scope.globals[ name ];

		var call = ($scope.view == 'rest')
			? { _path: $scope.query.path }
			: { query: $scope.queryTerm };

		$api.runQuery( call, params ).success( function(data)
		{
			$scope.error 	= null;
			$scope.output 	= data.result;
			$scope.nRows 	= (data.result && data.result.length) ? data.result.length + ' rows' : '';

			if (data.profiling)
				for (var pv in data.profiling)
					$scope.profiling[ pv ] = pv + ': ' + new String( data.profiling[pv] ).substr(0,5) + ' sec';

		}).error( function(data)
		{
			$scope.error 	= data.error;
			$scope.output 	= data;
			$scope.nRows 	= null;
			$scope.profiling = {};
		}).finally( function()
		{
			$scope.status 	= null;
		});
	};

	$scope.refresh = function()
	{
		var queryID = $routeParams.queryID;

		$scope.query.id = queryID;

		$api.getQuery( queryID ).success( function(data)
		{
			$scope.error 	= null;
			$scope.query 	= reformatQueryDef( data );
			$scope.query.id = queryID;
			$scope.query.path = getPath( $scope.query, queryID );
			$scope.query.method = getMethod( $scope.query );

			if ($scope.query && !$scope.queryTerm)
				$scope.queryTerm = $scope.query.id;

			// Set the parameters
			$scope.params = setValues( $scope.query.params, $cookies );

		}).error( function(data)
		{
			$scope.error = data.error;
		});
	};

	$scope.refresh();
}]);

/**
 * Does some changes to the params structure for the view template
 *
 */
function reformatParams( params )
{
	for (var p in params)
	{
		params[ p ].default_str =
			(params[ p ]['default'] === null)
			 ? 'null'
			 : params[ p ]['default'];

		if (!$.isArray( params[ p ].type ))
			params[ p ].type = [ params[ p ].type ];

		if (params[ p ].expose != 'public')
			delete params[ p ];
	}

	return params;
}

/**
 * Does some changes to the query json structure for the view template
 *
 */
function reformatQueryDef( query )
{
	if (query.doc)
		query.doc = query.doc.split("\n");

	if (!query.params)
		query.params = {};

	query.params = reformatParams( query.params );

	if (query.output)
		for (var f in query.output.fields)
		{
			if (!$.isPlainObject( query.output.fields[f] ))
			{
				// substitute 'json' for 'array'
				if (query.output.fields[f] == 'json')
					query.output.fields[f] = 'array';

				query.output.fields[f] =
				{
					type: query.output.fields[f]
				};
			}

			if (!$.isArray( query.output.fields[ f ].type ))
				query.output.fields[ f ].type = [ query.output.fields[ f ].type ];
		}

	return query;
}

/**
 * Set the values of the params (either by using the cookie or by default which is set for the param)
 *
 */
function setValues( params, cookies )
{
	var params1 = {};

	for (var p in params)
		if (params[p].expose == 'public')
		{
			params1[ p ] = params[p];
			params1[ p ].value = (cookies[p])
				? cookies[p]
				: (
					(params[p]['default'] === null)
						? 'null'
						: params[p]['default']
					);
		}

	return params1;
}

/**
 * Removes all whitespace from the string
 */
function removeWhitespace(string)
{
	var s = new String(string);
	return s.replace(/\s+/g, '');
}

/**
 * Derives the REST path & method for each query
 */
function setRestPaths(project)
{
	project.paths = {};

	for (var id in project.queries)
	if (project.queries[id].expose == 'public')
	{
		var path = getPath( project.queries[id], id );

		project.queries[id].method = getMethod( project.queries[id] );
		project.paths[ path ] = {
			queryID: id
		};
	}
}

/**
 * Gets the HTTP method corresponding to the query oparation
 */
function getMethod(query)
{
	switch (query.operation)
	{
		case 'create': 	return 'POST';
		case 'read': 	return 'GET';
		case 'update': 	return 'PUT';
		case 'delete': 	return 'DELETE';
	}

	return null;
}

/**
 * Transforms a queryID into a REST path
 */
function getPath(query, id)
{
	var parts = id.split(".");
	var defaultParamSet = false;

	switch (query.operation)
	{
		case 'read':
			switch (query.type)
			{
				case 'nest':
					path = "/" + parts[1] + "/:" + query.defaultParam + "/" + parts[0];
					defaultParamSet = true;
					break;
				case 'filter':
					path = "/" + parts[0] + "/" + parts[1];
					break;
				case 'attach':
					path = "/" + parts[0] + "+" + parts[1];
					break;
				default:
					path = "/" + id;
					break;
			}
			break;

		default:
			if (parts.length == 1)
				path = "/" + id;
			else
				path = "/" + parts[0] + "/:" + query.defaultParam + "/" + parts[1];
				defaultParamSet = true;
			break;
	}

	if (query.defaultParam && !defaultParamSet)
		path += "/:" + query.defaultParam;

	return path;
}

<commit_msg>Bugfix: globals were not saved in cookies<commit_after>/**
 * TinyQueries Admin Tool
 *
 * @author 	Wouter Diesveld <wouter@tinyqueries.com>
 * @package TinyQueries
 */

var admin = angular.module('TinyQueriesAdmin', ['ngCookies', 'ngRoute']);

admin.config(['$routeProvider',
	function($routeProvider)
	{
		$routeProvider
		.when('/',
		{
			templateUrl: 'templates/home.html'
		})
		.when('/queries/:queryID',
		{
			templateUrl: 'templates/query.html'
		})
		.otherwise(
		{
			redirectTo: ''
		});
	}
]);

/**
 * Service for the api
 */
admin.factory('$api', ['$http', function($http)
{
	/**
	 * Returns the interface for the api object
	 */
	return {

		compile: function()
		{
			return $http.get('api/?_method=compile');
		},

		getProject:	function()
		{
			return $http.get('api/?_method=getProject');
		},

		getQuery: function(queryID)
		{
			return $http.get('api/?_method=getInterface&query=' + queryID);
		},

		runQuery: function(call, params)
		{
			var apiParams =
			{
				_profiling: 1
			};

			for (p in call)
				apiParams[ p ] = removeWhitespace( call[p] );

			for (var id in params)
			{
				var p = params[ id ];

				var isArray = $.isArray( p.type )
							? ($.inArray('array', p.type) != -1)
							: (p.type == 'array');

				// Add [] for array parameters
				var name = (isArray)
					? id + '[]'
					: id;

				// Convert CSV to arrays
				apiParams[ name ] = (isArray)
					? ( (p.value) ? p.value.toString().split(',') : '')
					: ( (p.value || p.value===0) ? p.value : '');
			}

			return $http.get('api/', { params: apiParams });
		},

		getTermParams: function(term)
		{
			return $http.get('api/?_method=getTermParams&query=' + removeWhitespace(term));
		}

	};
}]);

/**
 * Main controller which is attached to the body element
 */
admin.controller('main', ['$scope', '$api', '$cookies', function($scope, $api, $cookies)
{
	// Set scope vars
	$scope.view					= 'queries';
	$scope.nav 					= 'queries';
	$scope.project				= {};
	$scope.globals				= {};
	$scope.error				= null;
	$scope.compileStatusCode 	= -1;
	$scope.compileStatus		= '';

	$scope.$watch('compileStatusCode', function(value)
	{
		if (value != 0)
			return;

		$scope.refresh();
	});

	// Initialize queries var
	$scope.refresh = function()
	{
		$api.getProject().success( function(data)
		{
			$scope.project = data;

			// update query path
			setRestPaths( $scope.project );

			$scope.globals = setValues( reformatParams(data.globals), $cookies );

		}).error( function(data)
		{
			$scope.error = data.error;
		});
	};

	$scope.compile = function()
	{
		$scope.compileStatusCode = 1;
		$scope.compileStatus = 'Compiler is being called...';
		$api.compile().success(function(data)
		{
			$scope.compileStatusCode = 0;
			$scope.compileStatus = data.message;
			$scope.refresh();
		}).error(function(data)
		{
			$scope.compileStatusCode = 2;
			$scope.compileStatus = data.error;
		});
	}

	$scope.refresh();
}]);

// not in use yet
admin.controller('message', ['$scope', function($scope)
{
	$scope.content = '';
}]);

/**
 * Controller for query info
 */
admin.controller('query', ['$scope', '$api', '$cookies', '$routeParams', function($scope, $api, $cookies, $routeParams)
{
	$scope.query 		= {};
	$scope.error		= null;
	$scope.params		= {};
	$scope.tab			= 'run';
	$scope.queryTerm	= null;
	$scope.output		= '';
	$scope.profiling	= {};

	$scope.$watch('compileStatusCode', function(value)
	{
		if (value != 0)
			return;

		$scope.refresh();
	});

	$scope.numberOfParams = function()
	{
		var n=0;
		for (var p in $scope.params)
			n++;

		return n;
	};

	$scope.saveParams = function()
	{
		// Copy params to cookies
		for (var p in $scope.params)
			$cookies[p] = $scope.params[p].value;
		for (var p in $scope.globals)
			$cookies[p] = $scope.globals[p].value;

	};

	$scope.updateParams = function()
	{
		if (!$scope.queryTerm)
		{
			$scope.params = {};
			return;
		}

		$api.getTermParams( $scope.queryTerm ).success( function(data)
		{
			$scope.error = null;
			$scope.params = setValues( data.params, $cookies );
		}).error(function(data)
		{
			$scope.error = data.error;
		});
	}

	$scope.run = function()
	{
		$scope.status = "Query is running...";

		$scope.saveParams();

		var params = {};
		// Merge params & globals
		for (var name in $scope.params)
			// Leaf out defaultParam for REST, since this param is already sent through the URL
			if ($scope.view != 'rest' && name != $scope.defaultParam)
				params[ name ] = $scope.params[ name ];
		for (var name in $scope.globals)
			params[ name ] = $scope.globals[ name ];

		var call = ($scope.view == 'rest')
			? { _path: $scope.query.path }
			: { query: $scope.queryTerm };

		$api.runQuery( call, params ).success( function(data)
		{
			$scope.error 	= null;
			$scope.output 	= data.result;
			$scope.nRows 	= (data.result && data.result.length) ? data.result.length + ' rows' : '';

			if (data.profiling)
				for (var pv in data.profiling)
					$scope.profiling[ pv ] = pv + ': ' + new String( data.profiling[pv] ).substr(0,5) + ' sec';

		}).error( function(data)
		{
			$scope.error 	= data.error;
			$scope.output 	= data;
			$scope.nRows 	= null;
			$scope.profiling = {};
		}).finally( function()
		{
			$scope.status 	= null;
		});
	};

	$scope.refresh = function()
	{
		var queryID = $routeParams.queryID;

		$scope.query.id = queryID;

		$api.getQuery( queryID ).success( function(data)
		{
			$scope.error 	= null;
			$scope.query 	= reformatQueryDef( data );
			$scope.query.id = queryID;
			$scope.query.path = getPath( $scope.query, queryID );
			$scope.query.method = getMethod( $scope.query );

			if ($scope.query && !$scope.queryTerm)
				$scope.queryTerm = $scope.query.id;

			// Set the parameters
			$scope.params = setValues( $scope.query.params, $cookies );

		}).error( function(data)
		{
			$scope.error = data.error;
		});
	};

	$scope.refresh();
}]);

/**
 * Does some changes to the params structure for the view template
 *
 */
function reformatParams( params )
{
	for (var p in params)
	{
		params[ p ].default_str =
			(params[ p ]['default'] === null)
			 ? 'null'
			 : params[ p ]['default'];

		if (!$.isArray( params[ p ].type ))
			params[ p ].type = [ params[ p ].type ];

		if (params[ p ].expose != 'public')
			delete params[ p ];
	}

	return params;
}

/**
 * Does some changes to the query json structure for the view template
 *
 */
function reformatQueryDef( query )
{
	if (query.doc)
		query.doc = query.doc.split("\n");

	if (!query.params)
		query.params = {};

	query.params = reformatParams( query.params );

	if (query.output)
		for (var f in query.output.fields)
		{
			if (!$.isPlainObject( query.output.fields[f] ))
			{
				// substitute 'json' for 'array'
				if (query.output.fields[f] == 'json')
					query.output.fields[f] = 'array';

				query.output.fields[f] =
				{
					type: query.output.fields[f]
				};
			}

			if (!$.isArray( query.output.fields[ f ].type ))
				query.output.fields[ f ].type = [ query.output.fields[ f ].type ];
		}

	return query;
}

/**
 * Set the values of the params (either by using the cookie or by default which is set for the param)
 *
 */
function setValues( params, cookies )
{
	var params1 = {};

	for (var p in params)
		if (params[p].expose == 'public')
		{
			params1[ p ] = params[p];
			params1[ p ].value = (cookies[p])
				? cookies[p]
				: (
					(params[p]['default'] === null)
						? 'null'
						: params[p]['default']
					);
		}

	return params1;
}

/**
 * Removes all whitespace from the string
 */
function removeWhitespace(string)
{
	var s = new String(string);
	return s.replace(/\s+/g, '');
}

/**
 * Derives the REST path & method for each query
 */
function setRestPaths(project)
{
	project.paths = {};

	for (var id in project.queries)
	if (project.queries[id].expose == 'public')
	{
		var path = getPath( project.queries[id], id );

		project.queries[id].method = getMethod( project.queries[id] );
		project.paths[ path ] = {
			queryID: id
		};
	}
}

/**
 * Gets the HTTP method corresponding to the query oparation
 */
function getMethod(query)
{
	switch (query.operation)
	{
		case 'create': 	return 'POST';
		case 'read': 	return 'GET';
		case 'update': 	return 'PUT';
		case 'delete': 	return 'DELETE';
	}

	return null;
}

/**
 * Transforms a queryID into a REST path
 */
function getPath(query, id)
{
	var parts = id.split(".");
	var defaultParamSet = false;

	switch (query.operation)
	{
		case 'read':
			switch (query.type)
			{
				case 'nest':
					path = "/" + parts[1] + "/:" + query.defaultParam + "/" + parts[0];
					defaultParamSet = true;
					break;
				case 'filter':
					path = "/" + parts[0] + "/" + parts[1];
					break;
				case 'attach':
					path = "/" + parts[0] + "+" + parts[1];
					break;
				default:
					path = "/" + id;
					break;
			}
			break;

		default:
			if (parts.length == 1)
				path = "/" + id;
			else
				path = "/" + parts[0] + "/:" + query.defaultParam + "/" + parts[1];
				defaultParamSet = true;
			break;
	}

	if (query.defaultParam && !defaultParamSet)
		path += "/:" + query.defaultParam;

	return path;
}

<|endoftext|>


<commit_before>var shoe = require('shoe');
var through = require('through');
var dnode = require('dnode');

var result = document.getElementById('result');

var stream = shoe('/mouse');

var s = through(function (msg) {

  // msgs are real mouse position
  console.log('mouse',msg);

  result.appendChild(document.createTextNode(msg));
  this.queue(String(Number(msg)^1));
})

stream.pipe(s).pipe(stream);

var d = dnode();
var dnodeResult = document.getElementById('dresult');

d.on('remote', function (remote) {
  remote.transform('beep', function (s) {
    dnodeResult.textContent = 'beep => ' + s;
  });

  remote.log('touch support',"ontouchend" in document);
  var touchel = document.getElementById('touch')
  var ts = touchStream(touchel,remote);

  document.body.on('mousemove',function(){
    remote.log('mousemove!');
  });

});
d.pipe(shoe('/dnode')).pipe(d);

function touchStream(el,remote){
  var s = through(function(data){
    var last = data;
    if(!this.paused) return this.queue(data);
  });

  var start = function(ev){
    remote.log('touchstart',arguments);
  }, end = function(ev){
    remote.log('touchend',arguments);
  }, move = function(ev){
    remote.log('touchmove',arguments);
  };

  remote.log('binding touch events');
  el.addEventListener('touchstart',start,false);
  el.addEventListener('touchend',end,false);
  el.addEventListener('touchmove',move,false);

  s.on('end',function(){
    el.removeEventListener('touchstart',start);
    el.removeEventListener('touchend',end);
    el.removeEventListener('touchmove',move);
  })

  return s;

}

<commit_msg>the internet likes me more<commit_after>var shoe = require('shoe');
var through = require('through');
var dnode = require('dnode');

var result = document.getElementById('result');

var stream = shoe('/mouse');

alert(3);

var s = through(function (msg) {

  // msgs are real mouse position
  console.log('mouse',msg);

  result.appendChild(document.createTextNode(msg));
  this.queue(String(Number(msg)^1));
})

stream.pipe(s).pipe(stream);

var d = dnode();
var dnodeResult = document.getElementById('dresult');

d.on('remote', function (remote) {
  remote.transform('beep', function (s) {
    dnodeResult.textContent = 'beep => ' + s;
  });

  remote.log('touch support',"ontouchend" in document);
  var touchel = document.getElementById('touch')
  var ts = touchStream(touchel,remote);

});
d.pipe(shoe('/dnode')).pipe(d);

function touchStream(el,remote){
  var s = through(function(data){
    var last = data;
    if(!this.paused) return this.queue(data);
  });

  var start = function(ev){
    console.log('start');
    remote.log('touchstart');

    ev.preventDefault();
    return false;
  }, end = function(ev){
    console.log('end');
    remote.log('touchend');
    ev.preventDefault();
    return false;
  }, move = function(ev){
    console.log('move');
    remote.log('touchmove');
    ev.preventDefault();
    return false;
  };

  remote.log('binding touch events');
  el.addEventListener('touchstart',start,false);
  el.addEventListener('touchend',end,false);
  el.addEventListener('touchmove',move,false);

  s.on('end',function(){
    el.removeEventListener('touchstart',start);
    el.removeEventListener('touchend',end);
    el.removeEventListener('touchmove',move);
  })

  return s;
}

window.onerror = function(e){
  alert('error!: '+e+'');
}
<|endoftext|>


<commit_before>'use strict'; // eslint-disable-line
// note: use strict above is applied to the whole browserified doc
var nodeUrl = require('url'),
  references = require('./services/references'),
  behaviors = require('./services/behaviors'),
  decorators = require('./services/decorators'),
  dom = require('./services/dom'),
  EditorToolbar = require('./controllers/kiln-toolbar'),
  render = require('./services/render');

// manually add built-in behaviors
// since browserify's require() uses static analysis
behaviors.add('component-ref', require('./behaviors/component-ref'));
behaviors.add('text', require('./behaviors/text'));
behaviors.add('soft-maxlength', require('./behaviors/soft-maxlength'));
behaviors.add('radio', require('./behaviors/radio'));
behaviors.add('description', require('./behaviors/description'));
behaviors.add('checkbox', require('./behaviors/checkbox'));
behaviors.add('textarea', require('./behaviors/textarea'));
behaviors.add('url', require('./behaviors/url'));
behaviors.add('select', require('./behaviors/select'));
behaviors.add('simple-list', require('./behaviors/simple-list'));
behaviors.add('simple-list-primary', require('./behaviors/simple-list-primary'));
behaviors.add('wysiwyg', require('./behaviors/wysiwyg'));
behaviors.add('autocomplete', require('./behaviors/autocomplete'));
behaviors.add('drop-image', require('./behaviors/drop-image'));
behaviors.add('label', require('./behaviors/label'));
behaviors.add('segmented-button', require('./behaviors/segmented-button'));
behaviors.add('page-ref', require('./behaviors/page-ref'));

// add default decorators
decorators.add(require('./decorators/placeholder'));
decorators.add(require('./decorators/focus'));
decorators.add(require('./decorators/component-list'));

// kick off controller loading when DOM is ready
document.addEventListener('DOMContentLoaded', function () {
  var parsed = nodeUrl.parse(location.href, true, true);

  if (parsed.query.edit) {
    render.addComponentsHandlers(document);
    // because eslint complains if we don't use the new thing we've created.  We will add to this later.
    pageToolbar = new EditorToolbar(dom.find('[' + references.referenceAttribute + '*="/components/clay-kiln"]'));
  }
});

// expose behavior adding
module.exports.addBehavior = behaviors.add;

// and expose decorator adding, while we're at it
module.exports.addDecorator = decorators.add;
<commit_msg>removed extraneous var<commit_after>'use strict'; // eslint-disable-line
// note: use strict above is applied to the whole browserified doc
var nodeUrl = require('url'),
  references = require('./services/references'),
  behaviors = require('./services/behaviors'),
  decorators = require('./services/decorators'),
  dom = require('./services/dom'),
  EditorToolbar = require('./controllers/kiln-toolbar'),
  render = require('./services/render');

// manually add built-in behaviors
// since browserify's require() uses static analysis
behaviors.add('component-ref', require('./behaviors/component-ref'));
behaviors.add('text', require('./behaviors/text'));
behaviors.add('soft-maxlength', require('./behaviors/soft-maxlength'));
behaviors.add('radio', require('./behaviors/radio'));
behaviors.add('description', require('./behaviors/description'));
behaviors.add('checkbox', require('./behaviors/checkbox'));
behaviors.add('textarea', require('./behaviors/textarea'));
behaviors.add('url', require('./behaviors/url'));
behaviors.add('select', require('./behaviors/select'));
behaviors.add('simple-list', require('./behaviors/simple-list'));
behaviors.add('simple-list-primary', require('./behaviors/simple-list-primary'));
behaviors.add('wysiwyg', require('./behaviors/wysiwyg'));
behaviors.add('autocomplete', require('./behaviors/autocomplete'));
behaviors.add('drop-image', require('./behaviors/drop-image'));
behaviors.add('label', require('./behaviors/label'));
behaviors.add('segmented-button', require('./behaviors/segmented-button'));
behaviors.add('page-ref', require('./behaviors/page-ref'));

// add default decorators
decorators.add(require('./decorators/placeholder'));
decorators.add(require('./decorators/focus'));
decorators.add(require('./decorators/component-list'));

// kick off controller loading when DOM is ready
document.addEventListener('DOMContentLoaded', function () {
  var parsed = nodeUrl.parse(location.href, true, true);

  if (parsed.query.edit) {
    render.addComponentsHandlers(document);
    return new EditorToolbar(dom.find('[' + references.referenceAttribute + '*="/components/clay-kiln"]'));
  }
});

// expose behavior adding
module.exports.addBehavior = behaviors.add;

// and expose decorator adding, while we're at it
module.exports.addDecorator = decorators.add;
<|endoftext|>


<commit_before>angular.module('anol.map')

.directive('anolMap', ['DefaultMapName', 'MapService', function(DefaultMapName, MapService) {
    return {
        scope: {},
        link: function (scope, element, attrs) {
            element
                .attr('id', scope.mapName)
                .attr('class', scope.mapName);

            scope.map.setTarget(scope.mapName);
        },
        controller: function($scope, $element, $attrs) {
            $scope.mapName = DefaultMapName;

            $scope.map = MapService.getMap();

            this.getMap = function() {
                return $scope.map;
            };
        }
    };
}]);
<commit_msg>Don't overwrite element classes<commit_after>angular.module('anol.map')

.directive('anolMap', ['DefaultMapName', 'MapService', function(DefaultMapName, MapService) {
    return {
        scope: {},
        link: function (scope, element, attrs) {
            element
                .attr('id', scope.mapName)
                .addClass(scope.mapName);

            scope.map.setTarget(scope.mapName);
        },
        controller: function($scope, $element, $attrs) {
            $scope.mapName = DefaultMapName;

            $scope.map = MapService.getMap();

            this.getMap = function() {
                return $scope.map;
            };
        }
    };
}]);
<|endoftext|>