<commit_before>/**
 * d3.tip
 * Copyright (c) 2013 Justin Palmer
 *
 * Tooltips for d3.js SVG visualizations
 */
// eslint-disable-next-line no-extra-semi
;(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module with d3 as a dependency.
    define([
      'd3-collection',
      'd3-selection'
    ], factory)
  } else if (typeof module === 'object' && module.exports) {
    /* eslint-disable global-require */
    // CommonJS
    var d3Collection = require('d3-collection'),
        d3Selection = require('d3-selection')
    module.exports = factory(d3Collection, d3Selection)
    /* eslint-enable global-require */
  } else {
    // Browser global.
    var d3 = root.d3
    // eslint-disable-next-line no-param-reassign
    root.d3.tip = factory(d3, d3)
  }
}(this, function(d3Collection, d3Selection) {
  // Public - contructs a new tooltip
  //
  // Returns a tip
  return function() {
    var direction   = d3TipDirection,
        offset      = d3TipOffset,
        html        = d3TipHTML,
        rootElement = document.body,
        node        = initNode(),
        svg         = null,
        point       = null,
        target      = null

    function tip(vis) {
      svg = getSVGNode(vis)
      if (!svg) return
      point = svg.createSVGPoint()
      rootElement.appendChild(node)
    }

    // Public - show the tooltip on the screen
    //
    // Returns a tip
    tip.show = function() {
      var args = Array.prototype.slice.call(arguments)
      if (args[args.length - 1] instanceof SVGElement) target = args.pop()

      var content = html.apply(this, args),
          poffset = offset.apply(this, args),
          dir     = direction.apply(this, args),
          nodel   = getNodeEl(),
          i       = directions.length,
          coords,
          scrollTop  = document.documentElement.scrollTop ||
            rootElement.scrollTop,
          scrollLeft = document.documentElement.scrollLeft ||
            rootElement.scrollLeft

      nodel.html(content)
        .style('opacity', 1).style('pointer-events', 'all')

      while (i--) nodel.classed(directions[i], false)
      coords = directionCallbacks.get(dir).apply(this)
      nodel.classed(dir, true)
        .style('top', (coords.top + poffset[0]) + scrollTop + 'px')
        .style('left', (coords.left + poffset[1]) + scrollLeft + 'px')

      return tip
    }

    // Public - hide the tooltip
    //
    // Returns a tip
    tip.hide = function() {
      var nodel = getNodeEl()
      nodel.style('opacity', 0).style('pointer-events', 'none')
      return tip
    }

    // Public: Proxy attr calls to the d3 tip container.
    // Sets or gets attribute value.
    //
    // n - name of the attribute
    // v - value of the attribute
    //
    // Returns tip or attribute value
    // eslint-disable-next-line no-unused-vars
    tip.attr = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return getNodeEl().attr(n)
      }

      var args =  Array.prototype.slice.call(arguments)
      d3Selection.selection.prototype.attr.apply(getNodeEl(), args)
      return tip
    }

    // Public: Proxy style calls to the d3 tip container.
    // Sets or gets a style value.
    //
    // n - name of the property
    // v - value of the property
    //
    // Returns tip or style property value
    // eslint-disable-next-line no-unused-vars
    tip.style = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return getNodeEl().style(n)
      }

      var args = Array.prototype.slice.call(arguments)
      d3Selection.selection.prototype.style.apply(getNodeEl(), args)
      return tip
    }

    // Public: Set or get the direction of the tooltip
    //
    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
    //     sw(southwest), ne(northeast) or se(southeast)
    //
    // Returns tip or direction
    tip.direction = function(v) {
      if (!arguments.length) return direction
      direction = v == null ? v : functor(v)

      return tip
    }

    // Public: Sets or gets the offset of the tip
    //
    // v - Array of [x, y] offset
    //
    // Returns offset or
    tip.offset = function(v) {
      if (!arguments.length) return offset
      offset = v == null ? v : functor(v)

      return tip
    }

    // Public: sets or gets the html value of the tooltip
    //
    // v - String value of the tip
    //
    // Returns html value or tip
    tip.html = function(v) {
      if (!arguments.length) return html
      html = v == null ? v : functor(v)

      return tip
    }

    // Public: sets or gets the root element anchor of the tooltip
    //
    // v - root element of the tooltip
    //
    // Returns root node of tip
    tip.rootElement = function(v) {
      if (!arguments.length) return rootElement
      rootElement = v == null ? v : functor(v)

      return tip
    }

    // Public: destroys the tooltip and removes it from the DOM
    //
    // Returns a tip
    tip.destroy = function() {
      if (node) {
        getNodeEl().remove()
        node = null
      }
      return tip
    }

    function d3TipDirection() { return 'n' }
    function d3TipOffset() { return [0, 0] }
    function d3TipHTML() { return ' ' }

    var directionCallbacks = d3Collection.map({
          n:  directionNorth,
          s:  directionSouth,
          e:  directionEast,
          w:  directionWest,
          nw: directionNorthWest,
          ne: directionNorthEast,
          sw: directionSouthWest,
          se: directionSouthEast
        }),
        directions = directionCallbacks.keys()

    function directionNorth() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.n.y - node.offsetHeight,
        left: bbox.n.x - node.offsetWidth / 2
      }
    }

    function directionSouth() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.s.y,
        left: bbox.s.x - node.offsetWidth / 2
      }
    }

    function directionEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.e.y - node.offsetHeight / 2,
        left: bbox.e.x
      }
    }

    function directionWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.w.y - node.offsetHeight / 2,
        left: bbox.w.x - node.offsetWidth
      }
    }

    function directionNorthWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.nw.y - node.offsetHeight,
        left: bbox.nw.x - node.offsetWidth
      }
    }

    function directionNorthEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.ne.y - node.offsetHeight,
        left: bbox.ne.x
      }
    }

    function directionSouthWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.sw.y,
        left: bbox.sw.x - node.offsetWidth
      }
    }

    function directionSouthEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.se.y,
        left: bbox.se.x
      }
    }

    function initNode() {
      var div = d3Selection.select(document.createElement('div'))
      div
        .style('position', 'absolute')
        .style('top', 0)
        .style('opacity', 0)
        .style('pointer-events', 'none')
        .style('box-sizing', 'border-box')

      return div.node()
    }

    function getSVGNode(element) {
      var svgNode = element.node()
      if (!svgNode) return null
      if (svgNode.tagName.toLowerCase() === 'svg') return svgNode
      return svgNode.ownerSVGElement
    }

    function getNodeEl() {
      if (node == null) {
        node = initNode()
        // re-add node to DOM
        rootElement.appendChild(node)
      }
      return d3Selection.select(node)
    }

    // Private - gets the screen coordinates of a shape
    //
    // Given a shape on the screen, will return an SVGPoint for the directions
    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast),
    // nw(northwest), sw(southwest).
    //
    //    +-+-+
    //    |   |
    //    +   +
    //    |   |
    //    +-+-+
    //
    // Returns an Object {n, s, e, w, nw, sw, ne, se}
    function getScreenBBox() {
      var targetel   = target || d3Selection.event.target

      while (targetel.getScreenCTM == null && targetel.parentNode == null) {
        targetel = targetel.parentNode
      }

      var bbox       = {},
          matrix     = targetel.getScreenCTM(),
          tbbox      = targetel.getBBox(),
          width      = tbbox.width,
          height     = tbbox.height,
          x          = tbbox.x,
          y          = tbbox.y

      point.x = x
      point.y = y
      bbox.nw = point.matrixTransform(matrix)
      point.x += width
      bbox.ne = point.matrixTransform(matrix)
      point.y += height
      bbox.se = point.matrixTransform(matrix)
      point.x -= width
      bbox.sw = point.matrixTransform(matrix)
      point.y -= height / 2
      bbox.w = point.matrixTransform(matrix)
      point.x += width
      bbox.e = point.matrixTransform(matrix)
      point.x -= width / 2
      point.y -= height / 2
      bbox.n = point.matrixTransform(matrix)
      point.y += height
      bbox.s = point.matrixTransform(matrix)

      return bbox
    }

    // Private - replace D3JS 3.X d3.functor() function
    function functor(v) {
      return typeof v === 'function' ? v : function() {
        return v
      }
    }

    return tip
  }
// eslint-disable-next-line semi
}));
<commit_msg>Making d3-tip compatible with d3.v3 and d3.v4<commit_after>/**
 * d3.tip
 * Copyright (c) 2013 Justin Palmer
 *
 * Tooltips for d3.js SVG visualizations
 */
// eslint-disable-next-line no-extra-semi
;(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module with d3 as a dependency.
    define([
      'd3-collection',
      'd3-selection'
    ], factory)
  } else if (typeof module === 'object' && module.exports) {
    /* eslint-disable global-require */
    // CommonJS
    var d3Collection = require('d3-collection'),
        d3Selection = require('d3-selection')
    module.exports = factory(d3Collection, d3Selection)
    /* eslint-enable global-require */
  } else {
    // Browser global.
    var d3 = root.d3
    // eslint-disable-next-line no-param-reassign
    root.d3.tip = factory(d3, d3)
  }
}(this, function(d3Collection, d3Selection) {
  // Public - contructs a new tooltip
  //
  // Returns a tip
  return function() {
    var direction   = d3TipDirection,
        offset      = d3TipOffset,
        html        = d3TipHTML,
        rootElement = document.body,
        node        = initNode(),
        svg         = null,
        point       = null,
        target      = null

    function tip(vis) {
      svg = getSVGNode(vis)
      if (!svg) return
      point = svg.createSVGPoint()
      rootElement.appendChild(node)
    }

    // Public - show the tooltip on the screen
    //
    // Returns a tip
    tip.show = function() {
      var args = Array.prototype.slice.call(arguments)
      if (args[args.length - 1] instanceof SVGElement) target = args.pop()

      var content = html.apply(this, args),
          poffset = offset.apply(this, args),
          dir     = direction.apply(this, args),
          nodel   = getNodeEl(),
          i       = directions.length,
          coords,
          scrollTop  = document.documentElement.scrollTop ||
            rootElement.scrollTop,
          scrollLeft = document.documentElement.scrollLeft ||
            rootElement.scrollLeft

      nodel.html(content)
        .style('opacity', 1).style('pointer-events', 'all')

      while (i--) nodel.classed(directions[i], false)
      coords = directionCallbacks.get(dir).apply(this)
      nodel.classed(dir, true)
        .style('top', (coords.top + poffset[0]) + scrollTop + 'px')
        .style('left', (coords.left + poffset[1]) + scrollLeft + 'px')

      return tip
    }

    // Public - hide the tooltip
    //
    // Returns a tip
    tip.hide = function() {
      var nodel = getNodeEl()
      nodel.style('opacity', 0).style('pointer-events', 'none')
      return tip
    }

    // Public: Proxy attr calls to the d3 tip container.
    // Sets or gets attribute value.
    //
    // n - name of the attribute
    // v - value of the attribute
    //
    // Returns tip or attribute value
    // eslint-disable-next-line no-unused-vars
    tip.attr = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return getNodeEl().attr(n)
      }

      var args =  Array.prototype.slice.call(arguments)
      d3Selection.selection.prototype.attr.apply(getNodeEl(), args)
      return tip
    }

    // Public: Proxy style calls to the d3 tip container.
    // Sets or gets a style value.
    //
    // n - name of the property
    // v - value of the property
    //
    // Returns tip or style property value
    // eslint-disable-next-line no-unused-vars
    tip.style = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return getNodeEl().style(n)
      }

      var args = Array.prototype.slice.call(arguments)
      d3Selection.selection.prototype.style.apply(getNodeEl(), args)
      return tip
    }

    // Public: Set or get the direction of the tooltip
    //
    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
    //     sw(southwest), ne(northeast) or se(southeast)
    //
    // Returns tip or direction
    tip.direction = function(v) {
      if (!arguments.length) return direction
      direction = v == null ? v : functor(v)

      return tip
    }

    // Public: Sets or gets the offset of the tip
    //
    // v - Array of [x, y] offset
    //
    // Returns offset or
    tip.offset = function(v) {
      if (!arguments.length) return offset
      offset = v == null ? v : functor(v)

      return tip
    }

    // Public: sets or gets the html value of the tooltip
    //
    // v - String value of the tip
    //
    // Returns html value or tip
    tip.html = function(v) {
      if (!arguments.length) return html
      html = v == null ? v : functor(v)

      return tip
    }

    // Public: sets or gets the root element anchor of the tooltip
    //
    // v - root element of the tooltip
    //
    // Returns root node of tip
    tip.rootElement = function(v) {
      if (!arguments.length) return rootElement
      rootElement = v == null ? v : functor(v)

      return tip
    }

    // Public: destroys the tooltip and removes it from the DOM
    //
    // Returns a tip
    tip.destroy = function() {
      if (node) {
        getNodeEl().remove()
        node = null
      }
      return tip
    }

    function d3TipDirection() { return 'n' }
    function d3TipOffset() { return [0, 0] }
    function d3TipHTML() { return ' ' }

    var directionCallbacks = d3Collection.map({
          n:  directionNorth,
          s:  directionSouth,
          e:  directionEast,
          w:  directionWest,
          nw: directionNorthWest,
          ne: directionNorthEast,
          sw: directionSouthWest,
          se: directionSouthEast
        }),
        directions = directionCallbacks.keys()

    function directionNorth() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.n.y - node.offsetHeight,
        left: bbox.n.x - node.offsetWidth / 2
      }
    }

    function directionSouth() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.s.y,
        left: bbox.s.x - node.offsetWidth / 2
      }
    }

    function directionEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.e.y - node.offsetHeight / 2,
        left: bbox.e.x
      }
    }

    function directionWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.w.y - node.offsetHeight / 2,
        left: bbox.w.x - node.offsetWidth
      }
    }

    function directionNorthWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.nw.y - node.offsetHeight,
        left: bbox.nw.x - node.offsetWidth
      }
    }

    function directionNorthEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.ne.y - node.offsetHeight,
        left: bbox.ne.x
      }
    }

    function directionSouthWest() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.sw.y,
        left: bbox.sw.x - node.offsetWidth
      }
    }

    function directionSouthEast() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.se.y,
        left: bbox.se.x
      }
    }

    function initNode() {
      var div = d3Selection.select(document.createElement('div'))
      div
        .style('position', 'absolute')
        .style('top', 0)
        .style('opacity', 0)
        .style('pointer-events', 'none')
        .style('box-sizing', 'border-box')

      return div.node()
    }

    function getSVGNode(element) {
      var svgNode = element.node()
      if (!svgNode) return null
      if (svgNode.tagName.toLowerCase() === 'svg') return svgNode
      return svgNode.ownerSVGElement
    }

    function getNodeEl() {
      if (node == null) {
        node = initNode()
        // re-add node to DOM
        rootElement.appendChild(node)
      }
      return d3Selection.select(node)
    }

    // Private - gets the screen coordinates of a shape
    //
    // Given a shape on the screen, will return an SVGPoint for the directions
    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast),
    // nw(northwest), sw(southwest).
    //
    //    +-+-+
    //    |   |
    //    +   +
    //    |   |
    //    +-+-+
    //
    // Returns an Object {n, s, e, w, nw, sw, ne, se}
    function getScreenBBox() {
      var targetel   = target || d3Selection.event.target || d3.event.target;

      while (targetel.getScreenCTM == null && targetel.parentNode == null) {
        targetel = targetel.parentNode
      }

      var bbox       = {},
          matrix     = targetel.getScreenCTM(),
          tbbox      = targetel.getBBox(),
          width      = tbbox.width,
          height     = tbbox.height,
          x          = tbbox.x,
          y          = tbbox.y

      point.x = x
      point.y = y
      bbox.nw = point.matrixTransform(matrix)
      point.x += width
      bbox.ne = point.matrixTransform(matrix)
      point.y += height
      bbox.se = point.matrixTransform(matrix)
      point.x -= width
      bbox.sw = point.matrixTransform(matrix)
      point.y -= height / 2
      bbox.w = point.matrixTransform(matrix)
      point.x += width
      bbox.e = point.matrixTransform(matrix)
      point.x -= width / 2
      point.y -= height / 2
      bbox.n = point.matrixTransform(matrix)
      point.y += height
      bbox.s = point.matrixTransform(matrix)

      return bbox
    }

    // Private - replace D3JS 3.X d3.functor() function
    function functor(v) {
      return typeof v === 'function' ? v : function() {
        return v
      }
    }

    return tip
  }
// eslint-disable-next-line semi
}));
<|endoftext|>


<commit_before>'use strict';
const app = require('app');
const BrowserWindow = require('browser-window');

// report crashes to the Electron project
require('crash-reporter').start();

// prevent window being GC'd
let mainWindow = null;

app.on('window-all-closed', function () {
	if (process.platform !== 'darwin') {
		app.quit();
	}
});

app.on('ready', function () {
	mainWindow = new BrowserWindow({
		width: 1000,
		height: 800,
		resizable: true
	});

	mainWindow.loadUrl(`file://${__dirname}/index.html`);

	mainWindow.on('closed', function () {
		// deref the window
		// for multiple windows store them in an array
		mainWindow = null;
	})
});<commit_msg>removed resizable<commit_after>'use strict';
const app = require('app');
const BrowserWindow = require('browser-window');

// report crashes to the Electron project
require('crash-reporter').start();

// prevent window being GC'd
let mainWindow = null;

app.on('window-all-closed', function () {
	if (process.platform !== 'darwin') {
		app.quit();
	}
});

app.on('ready', function () {
	mainWindow = new BrowserWindow({
		width: 1000,
		height: 800,
		resizable: false
	});

	mainWindow.loadUrl(`file://${__dirname}/index.html`);

	mainWindow.on('closed', function () {
		// deref the window
		// for multiple windows store them in an array
		mainWindow = null;
	})
});<|endoftext|>


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

var http = require('request');
var async = require('async');

TWITCH_API = 'https://api.twitch.tv/kraken/';

//this is where the bufferized emoticons will be stored
var bufferizedEmoticons = [];

function Client() {
    // do nothing
}

/**
 * Returns a list of 'stream' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v3_resources/geStreams.md
 *
 * @param args args.number (optional): number of geStreams to retrieve
 *             args.offset (optional): offset from which you want to start to get the geStreams list
 * @param callback called when the geStreams have been retrieved, with format (err, geStreams)
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getStreams = function (number, callback) {
    if (typeof number == 'function') callback = number;
    if (!callback || typeof callback != 'function') return false;

    return retrieveStreams(number, callback);
}

function retrieveStreams(number, callback) {
    if (!callback || typeof callback != 'function') return false;

    number = number || 25;

    var parts = Math.ceil(number / 25);
    var urls = [];

    for(var i = 0; i < parts; i++) {
        var offset = (i * 25);
        var url = TWITCH_API + 'streams?number=' + 25 + '&offset=' + offset;
        urls.push(url);
    }

    var asyncProperties = {};

    for(var i = 0; i < urls.length; i++) {
        var index = i;
        asyncProperties[i] = function(callback) {
            return retrieveResource(urls[index], function(err, body) {
                var streams = body.streams;
                if (!streams) err = new Error('Failed to parse the resource in order to get the games list!');
                callback(err, streams);
            });
        }
    }

    async.parallel(asyncProperties, function(err, results) {
        var streams = [];

        for(var i = 0; i < Object.keys(results).length; i++) {
            streams = streams.concat(results[i]);
        }

        streams = streams.slice(0, number);

        callback(null, streams);
    });
}

/**
 * Returns a list of 'game' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v3_resources/games.md
 *
 * @param args args.number (optional): number of geStreams to retrieve
 *             args.offset (optional): offset from which you want to start to get the geStreams list
 * @param callback called when the geStreams have been retrieved, with format (err, geStreams)
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getGames = function(callback) {
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + 'games/top', function (err, body) {
        var games = body.top;
        if (!games) err = new Error('Failed to parse the resource in order to get the games list!');
        callback(err, games);
    });
}

/**
 * Returns a list of 'emoticon' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v3_resources/chat.md#get-chatemoticons
 *
 * Note that this function bufferize emoticons from a channel, as they rarely change.
 *
 * @param channel the twitch.tv channel
 * @param callback called when the emoticons have been retrieved, with format (err, geStreams)
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getEmoticons = function (channel, callback) {
    if (!channel || typeof channel != 'string') return false;
    if (!callback || typeof callback != 'function') return false;

    var emoticons = getBufferizedEmoticons(channel);

    if (emoticons != null) {
        return callback(null, emoticons);
    }

    return retrieveResource(TWITCH_API + 'chat/' + channel + '/emoticons', function (err, body) {
        emoticons = body.emoticons;
        if (!emoticons) err = new Error('Failed to parse the resource in order to get the emoticons list!');

        //TODO update the buffer every x minutes / hours
        bufferizedEmoticons.push({channel: channel, emoticons: emoticons});
        callback(err, emoticons);
    });
}

/**
 * Returns a 'video' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v2_resources/videos.md#get-videosid
 *
 * @param id the video id
 * @param callback called when the video has been retrieved, using an error-first callback
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getVideo = function(id, callback) {
    if (!id) return false;
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + 'videos/' + id, function (err, body) {
        if (!body) err = new Error('Failed to parse the resource in order to get the video!');
        callback(err, body);
    });
}

/**
 * Returns a list of 'teams' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v2_resources/teams.md#get-teams
 *
 * @param callback called when the teams have been retrieved, using an error-first callback
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getTeams = function(callback) {
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + 'teams', function (err, body) {
        var teams = body.teams;
        if (!teams) err = new Error('Failed to parse the resource in order to get the teams list!');
        callback(err, teams);
    });
}

/**
 * Returns a 'team' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v2_resources/teams.md#get-teamsteam
 *
 * @param id the team id
 * @param callback called when the team has been retrieved, using an error-first callback
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getTeam = function(id, callback) {
    if (!id) return false;
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + "teams/" + id, function(err, body) {
        if (!body) err = new Error('Failed to parse the resource in order to get the team!');
        callback(err, body);
    });
}

/**
 * Returns the body of the response of an HTTP GET request on an URL
 *
 * @param callback called when the response has been retrieved, using an error-first callback.
 *                  the second parameter of the callback contains the body of the response
 * @returns {boolean} false if arguments are missing
 */
function retrieveResource(url, callback) {
    if (!url || typeof url != 'string') return false;
    if (!callback || typeof callback != 'function') return false;

    http.get({
        url: url
    }, function (err, response, body) {
        //console.dir(body);
        if (err) {
            callback(err);
        } else {
            var ex = null;

            try {
                body = JSON.parse(body);
            } catch(err) {
                ex = err;
            }

            callback(ex, body);
        }
    })
}

/**
 *
 * @param channel the channel (stream) from which we want the emoticons list
 * @returns the bufferized list of emoticons of the specified channel. The buffer is updated every script's reboot (at the moment).
 */
function getBufferizedEmoticons(channel) {
    var emoticons = null;

    bufferizedEmoticons.forEach(function (buffer) {
        if (buffer.channel == channel) {
            emoticons = buffer.emoticons;
        }
    });

    return emoticons;
}

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

var http = require('request');
var async = require('async');

TWITCH_API = 'https://api.twitch.tv/kraken/';

//this is where the bufferized emoticons will be stored
var bufferizedEmoticons = [];

function Client() {
    // do nothing
}

/**
 * Returns a list of 'game' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v3_resources/games.md
 *
 * @param args args.number (optional): number of geStreams to retrieve
 *             args.offset (optional): offset from which you want to start to get the geStreams list
 * @param callback called when the geStreams have been retrieved, with format (err, geStreams)
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getGames = function (callback) {
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + 'games/top', function (err, body) {
        var games = body.top;
        if (!games) err = new Error('Failed to parse the resource in order to get the games list!');
        callback(err, games);
    });
}

Client.prototype.getChannel = function (id, callback) {
    if (!id) return false;
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + '/channels/' + id, function(err, body) {
        if (!body) err = new Error('Failed to parse the resource in order to get the stream!');
        callback(err, body);
    });
}

Client.prototype.getStream = function (id, callback) {
    return this.getChannel(id, callback);
}

/**
 * Returns a list of 'stream' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v3_resources/geStreams.md
 *
 * @param args args.number (optional): number of geStreams to retrieve
 *             args.offset (optional): offset from which you want to start to get the geStreams list
 * @param callback called when the geStreams have been retrieved, with format (err, geStreams)
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getStreams = function (number, callback) {
    if (typeof number == 'function') callback = number;
    if (!callback || typeof callback != 'function') return false;

    return retrieveStreams(number, callback);
}

function retrieveStreams(number, callback) {
    if (!callback || typeof callback != 'function') return false;

    number = number || 25;

    var parts = Math.ceil(number / 25);
    var urls = [];

    for (var i = 0; i < parts; i++) {
        var offset = (i * 25);
        var url = TWITCH_API + 'streams?number=' + 25 + '&offset=' + offset;
        urls.push(url);
    }

    var asyncProperties = {};

    for (var i = 0; i < urls.length; i++) {
        var index = i;
        asyncProperties[i] = function (callback) {
            return retrieveResource(urls[index], function (err, body) {
                var streams = body.streams;
                if (!streams) err = new Error('Failed to parse the resource in order to get the games list!');
                callback(err, streams);
            });
        }
    }

    async.parallel(asyncProperties, function (err, results) {
        var streams = [];

        for (var i = 0; i < Object.keys(results).length; i++) {
            streams = streams.concat(results[i]);
        }

        streams = streams.slice(0, number);

        callback(null, streams);
    });
}

/**
 * Returns a list of 'emoticon' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v3_resources/chat.md#get-chatemoticons
 *
 * Note that this function bufferize emoticons from a channel, as they rarely change.
 *
 * @param channel the twitch.tv channel
 * @param callback called when the emoticons have been retrieved, with format (err, geStreams)
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getEmoticons = function (channel, callback) {
    if (!channel || typeof channel != 'string') return false;
    if (!callback || typeof callback != 'function') return false;

    var emoticons = getBufferizedEmoticons(channel);

    if (emoticons != null) {
        return callback(null, emoticons);
    }

    return retrieveResource(TWITCH_API + 'chat/' + channel + '/emoticons', function (err, body) {
        emoticons = body.emoticons;
        if (!emoticons) err = new Error('Failed to parse the resource in order to get the emoticons list!');

        //TODO update the buffer every x minutes / hours
        bufferizedEmoticons.push({channel: channel, emoticons: emoticons});
        callback(err, emoticons);
    });
}

/**
 * Returns a list of 'teams' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v2_resources/teams.md#get-teams
 *
 * @param callback called when the teams have been retrieved, using an error-first callback
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getTeams = function (callback) {
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + 'teams', function (err, body) {
        var teams = body.teams;
        if (!teams) err = new Error('Failed to parse the resource in order to get the teams list!');
        callback(err, teams);
    });
}

/**
 * Returns a 'team' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v2_resources/teams.md#get-teamsteam
 *
 * @param id the team id
 * @param callback called when the team has been retrieved, using an error-first callback
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getTeam = function (id, callback) {
    if (!id) return false;
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + "teams/" + id, function (err, body) {
        if (!body) err = new Error('Failed to parse the resource in order to get the team!');
        callback(err, body);
    });
}

/**
 * Returns a 'video' resource based on this documentation:
 * https://github.com/justintv/Twitch-API/blob/master/v2_resources/videos.md#get-videosid
 *
 * @param id the video id
 * @param callback called when the video has been retrieved, using an error-first callback
 * @returns {boolean} false if arguments are missing
 */
Client.prototype.getVideo = function (id, callback) {
    if (!id) return false;
    if (!callback || typeof callback != 'function') return false;

    return retrieveResource(TWITCH_API + 'videos/' + id, function (err, body) {
        if (!body) err = new Error('Failed to parse the resource in order to get the video!');
        callback(err, body);
    });
}

/**
 * Returns the body of the response of an HTTP GET request on an URL
 *
 * @param callback called when the response has been retrieved, using an error-first callback.
 *                  the second parameter of the callback contains the body of the response
 * @returns {boolean} false if arguments are missing
 */
function retrieveResource(url, callback) {
    if (!url || typeof url != 'string') return false;
    if (!callback || typeof callback != 'function') return false;

    http.get({
        url: url
    }, function (err, response, body) {
        //console.dir(body);
        if (err) {
            callback(err);
        } else {
            var ex = null;

            try {
                body = JSON.parse(body);
            } catch (err) {
                ex = err;
            }

            callback(ex, body);
        }
    })
}

/**
 *
 * @param channel the channel (stream) from which we want the emoticons list
 * @returns the bufferized list of emoticons of the specified channel. The buffer is updated every script's reboot
 *              (at the moment).
 */
function getBufferizedEmoticons(channel) {
    var emoticons = null;

    bufferizedEmoticons.forEach(function (buffer) {
        if (buffer.channel == channel) {
            emoticons = buffer.emoticons;
        }
    });

    return emoticons;
}

exports.Client = Client;
<|endoftext|>


<commit_before>var React   = require('react/addons'),
    scroll  = require('scroll'),
    Beacon  = require('./Beacon'),
    Tooltip = require('./Tooltip');

var joyride = {
    steps: []
};

var Mixin = {
    getInitialState: function () {
        return {
            joyrideCurrentIndex: 0,
            joyrideLocale: {
                close: 'Close',
                last: 'Last',
                next: 'Next'
            },
            joyrideTooltipOffset: 30,
            joyrideScrollToSteps: true,
            joyrideShowTooltip: false,
            joyrideShowBackdrop: true,
            joyridePlay: false,
            joyrideType: 'guided',
            joyrideCompleteCallback: undefined,
            joyrideStepCallback: undefined,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        };
    },

    componentDidMount: function () {
        var state = this.state;

        this._target = document.createElement('div');
        document.body.appendChild(this._target);

        if (state.joyridePlay && joyride.steps[state.joyrideCurrentIndex]) {
            this._renderLayer();
        }
        window.addEventListener('resize', this._calculatePlacement);
    },

    componentWillUnmount: function () {
        this._unrenderLayer();
        document.body.removeChild(this._target);
        window.removeEventListener('resize', this._calculatePlacement);
    },

    componentDidUpdate: function (prevProps, prevState) {
        var state = this.state,
            opt   = {
                shouldRun: state.joyridePlay && (state.joyridePlay !== prevState.joyridePlay || state.joyrideCurrentIndex !== prevState.joyrideCurrentIndex),
                hasStep: Boolean(joyride.steps[state.joyrideCurrentIndex]),
                hasSteps: joyride.steps.length > 0,
                newX: state.joyrideXPos !== prevState.joyrideXPos,
                newY: state.joyrideYPos !== prevState.joyrideYPos,
                toggleTooltip: state.joyrideShowTooltip !== prevState.joyrideShowTooltip
            };

        if ((opt.shouldRun || opt.toggleTooltip || opt.newX || opt.newY) && opt.hasStep) {
            this._renderLayer();
        }
        else if (opt.shouldRun && opt.hasSteps && !opt.hasStep) {
            if (typeof state.joyrideCompleteCallback === 'function') {
                state.joyrideCompleteCallback(joyride.steps);
            }
            this._unrenderLayer();
        }
    },

    /**
     * Starts the tour
     * @param {boolean} [autorun]- Starts with the first tooltip opened
     */
    joyrideStart: function (autorun) {
        autorun = autorun || false;

        this.setState({
            joyrideShowTooltip: autorun,
            joyridePlay: true
        });
    },

    /**
     * Add Steps
     * @param {object|array} steps - Steps to add to the tour
     * @param {boolean} [start] - Starts the tour right away
     */
    joyrideAddSteps: function (steps, start) {
        var tmpSteps  = [],
            el;

        start = start || this.state.joyridePlay;

        if (Array.isArray(steps)) {
            steps.forEach(function (s) {
                if (s instanceof Object) {
                    tmpSteps.push(s);
                }
            });
        }
        else {
            tmpSteps = [steps];
        }

        tmpSteps.forEach(function (s) {
            el = document.querySelector(s.selector);
            s.position = s.position || 'top';

            if (el && el.offsetParent) {
                joyride.steps.push(s);
            }
        });

        this.setState(
            React.addons.update(this.state, {
                joyridePlay: { $set: start }
            })
        );
    },

    /**
     * Retrieve the current progress of your tour
     * @returns {{index: (number|*), percentageComplete: number, step: *}}
     */
    joyrideGetProgress: function () {
        return {
            index: this.state.joyrideCurrentIndex,
            percentageComplete: parseFloat(((this.state.joyrideCurrentIndex / joyride.steps.length) * 100).toFixed(2).replace('.00', '')),
            step: joyride.steps[this.state.joyrideCurrentIndex]
        };
    },

    _onClickBeacon: function (e) {
        e.preventDefault();

        this.setState({
            joyrideShowTooltip: true,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        });
    },

    _onClickTooltip: function (e) {
        e.preventDefault();

        var state = this.state,
            type  = e.currentTarget.dataset.type;

        if (typeof state.joyrideStepCallback === 'function') {
            state.joyrideStepCallback(joyride.steps[state.joyrideCurrentIndex]);
        }

        this.setState({
            joyrideShowTooltip: type === 'button' && state.joyrideType === 'guided' && Boolean(joyride.steps[state.joyrideCurrentIndex + 1]),
            joyrideCurrentIndex: state.joyrideCurrentIndex + 1,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        }, function () {
            this._scrollToNextStep();
        }.bind(this));
    },

    _calculatePlacement: function () {
        var state     = this.state,
            step      = joyride.steps[state.joyrideCurrentIndex],
            position,
            body,
            target,
            component = document.querySelector((state.joyrideShowTooltip ? '.joyride-tooltip' : '.joyride-beacon')),
            placement = {
                x: -1000,
                y: -1000
            };

        if (step && (!component.classList.contains('animate') || state.joyrideXPos < 0)) {
            position = step.position;
            body = document.body.getBoundingClientRect();
            target = document.querySelector(step.selector).getBoundingClientRect();
            component = component.getBoundingClientRect();

            if (window.innerWidth < 768) {
                if (/^left/.test(position)) {
                    position = 'top';
                }
                else if (/^right/.test(position)) {
                    position = 'bottom';
                }
            }

            // Calculate x position
            if (/^left/.test(position)) {
                placement.x = target.left - (state.joyrideShowTooltip ? component.width + state.joyrideTooltipOffset : component.width / 2);
            }
            else if (/^right/.test(position)) {
                placement.x = target.left + target.width - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.width / 2);
            }
            else {
                placement.x = target.left + target.width / 2 - component.width / 2;
            }

            // Calculate y position
            if (/^top/.test(position)) {
                placement.y = (target.top - body.top) - (state.joyrideShowTooltip ? component.height + state.joyrideTooltipOffset : component.height / 2);
            }
            else if (/^bottom/.test(position)) {
                placement.y = (target.top - body.top) + target.height - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.height / 2);
            }
            else {
                placement.y = (target.top - body.top) + target.height / 2 - component.height / 2 + (state.joyrideShowTooltip ? state.joyrideTooltipOffset : 0);
            }

            if (/^bottom|^top/.test(position)) {
                if (/left/.test(position)) {
                    placement.x = target.left - (state.joyrideShowTooltip ? state.joyrideTooltipOffset : component.width / 2);
                }
                else if (/right/.test(position)) {
                    placement.x = target.left + target.width - (state.joyrideShowTooltip ? component.width - state.joyrideTooltipOffset : component.width / 2);
                }
            }

            this.setState({
                joyrideXPos: this._preventWindowOverflow(Math.ceil(placement.x), 'x', component.width, component.height),
                joyrideYPos: this._preventWindowOverflow(Math.ceil(placement.y), 'y', component.width, component.height)
            });
        }
    },

    _preventWindowOverflow: function (value, axis, elWidth, elHeight) {
        var winWidth = window.innerWidth;
        var docHeight = document.body.offsetHeight;

        if (axis.toLowerCase() === 'x') {
            if (value + elWidth > winWidth) {
                value = winWidth - elWidth - 10;
            }
            else if (value < 0) {
                value = 10;
            }
        }
        else if (axis.toLowerCase() === 'y') {
            if (value + elHeight > docHeight) {
                value = docHeight - elHeight - 10;
            }
            else if (value < 0) {
                value = 10;
            }
        }

        return value;
    },

    _getPosition: function () {
        var step = joyride.steps[this.state.joyrideCurrentIndex],
            position = step.position,
            elPosition = document.querySelector(step.selector).getBoundingClientRect().top - document.body.getBoundingClientRect().top;

        if (window.innerWidth < 768) {
            if (/^left/.test(position)) {
                position = 'top';
            }
            else if (/^right/.test(position)) {
                position = 'bottom';
            }
        }

        if (/^top/.test(position)) {
            position = this.state.joyrideYPos - 20;
        }
        else if (/^bottom|^left|^right/.test(position)) {
            position = elPosition - 20;
        }

        return position;
    },

    _scrollToNextStep: function (cb) {
        if (joyride.steps[this.state.joyrideCurrentIndex] && this.state.joyrideScrollToSteps) {
            scroll.top(document.body, this._getPosition(), cb);
        }
    },

    _renderLayer: function () {
        React.render(this._renderCurrentStep(), this._target);
        this._calculatePlacement();
    },

    _unrenderLayer: function () {
        React.unmountComponentAtNode(this._target);
    },

    _renderCurrentStep: function () {
        var state       = this.state,
            component,
            currentStep = joyride.steps[state.joyrideCurrentIndex],
            hasNextStep = Boolean(joyride.steps[state.joyrideCurrentIndex + 1]),
            buttonText  = state.joyrideType === 'guided' ? (hasNextStep ? (state.joyrideLocale.next + ' ' + (state.joyrideCurrentIndex + 1) + '/' + joyride.steps.length) : state.joyrideLocale.last) : state.joyrideLocale.close,
            target      = currentStep && currentStep.selector ? document.querySelector(currentStep.selector) : null,
            cssPosition = target ? target.style.position : null;

        if (target) {
            if (state.joyrideShowTooltip) {
                component = React.createElement(Tooltip, {
                    animate: state.joyrideXPos > -1,
                    cssPosition: cssPosition,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    step: currentStep,
                    buttonText: buttonText,
                    showBackdrop: state.joyrideShowBackdrop,
                    onClick: this._onClickTooltip
                });
            }
            else {
                component = React.createElement(Beacon, {
                    cssPosition: cssPosition,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    handleClick: this._onClickBeacon
                });
            }
        }

        return component;
    }
};

module.exports = Mixin;
<commit_msg>cleanup Mixin<commit_after>var React   = require('react/addons'),
    scroll  = require('scroll'),
    Beacon  = require('./Beacon'),
    Tooltip = require('./Tooltip');

var joyride = {
    steps: []
};

var Mixin = {
    getInitialState: function () {
        return {
            joyrideCurrentIndex: 0,
            joyrideLocale: {
                close: 'Close',
                last: 'Last',
                next: 'Next'
            },
            joyrideTooltipOffset: 30,
            joyrideScrollToSteps: true,
            joyrideShowTooltip: false,
            joyrideShowBackdrop: true,
            joyridePlay: false,
            joyrideType: 'guided',
            joyrideCompleteCallback: undefined,
            joyrideStepCallback: undefined,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        };
    },

    componentDidMount: function () {
        var state = this.state;

        this._target = document.createElement('div');
        this._target.className = 'joyride';
        document.body.appendChild(this._target);

        if (state.joyridePlay && joyride.steps[state.joyrideCurrentIndex]) {
            this._renderLayer();
        }
        window.addEventListener('resize', this._calculatePlacement);
    },

    componentWillUnmount: function () {
        this._unrenderLayer();
        document.body.removeChild(this._target);
        window.removeEventListener('resize', this._calculatePlacement);
    },

    componentDidUpdate: function (prevProps, prevState) {
        var state = this.state,
            opt   = {
                shouldRun: state.joyridePlay && (state.joyridePlay !== prevState.joyridePlay || state.joyrideCurrentIndex !== prevState.joyrideCurrentIndex),
                hasStep: Boolean(joyride.steps[state.joyrideCurrentIndex]),
                hasSteps: joyride.steps.length > 0,
                newX: state.joyrideXPos !== prevState.joyrideXPos,
                newY: state.joyrideYPos !== prevState.joyrideYPos,
                toggleTooltip: state.joyrideShowTooltip !== prevState.joyrideShowTooltip
            };

        if ((opt.shouldRun || opt.toggleTooltip || opt.newX || opt.newY) && opt.hasStep) {
            this._renderLayer();
        }
        else if (opt.shouldRun && opt.hasSteps && !opt.hasStep) {
            if (typeof state.joyrideCompleteCallback === 'function') {
                state.joyrideCompleteCallback(joyride.steps);
            }
            this._unrenderLayer();
        }
    },

    /**
     * Starts the tour
     * @param {boolean} [autorun]- Starts with the first tooltip opened
     */
    joyrideStart: function (autorun) {
        autorun = autorun || false;

        this.setState({
            joyrideShowTooltip: autorun,
            joyridePlay: true
        });
    },

    /**
     * Add Steps
     * @param {object|array} steps - Steps to add to the tour
     * @param {boolean} [start] - Starts the tour right away
     */
    joyrideAddSteps: function (steps, start) {
        var tmpSteps  = [],
            el;

        start = start || this.state.joyridePlay;

        if (Array.isArray(steps)) {
            steps.forEach(function (s) {
                if (s instanceof Object) {
                    tmpSteps.push(s);
                }
            });
        }
        else {
            tmpSteps = [steps];
        }

        tmpSteps.forEach(function (s) {
            el = document.querySelector(s.selector);
            s.position = s.position || 'top';

            if (el && el.offsetParent) {
                joyride.steps.push(s);
            }
        });

        this.setState(
            React.addons.update(this.state, {
                joyridePlay: { $set: start }
            })
        );
    },

    /**
     * Retrieve the current progress of your tour
     * @returns {{index: (number|*), percentageComplete: number, step: (object|null)}}
     */
    joyrideGetProgress: function () {
        return {
            index: this.state.joyrideCurrentIndex,
            percentageComplete: parseFloat(((this.state.joyrideCurrentIndex / joyride.steps.length) * 100).toFixed(2).replace('.00', '')),
            step: joyride.steps[this.state.joyrideCurrentIndex]
        };
    },

    _onClickBeacon: function (e) {
        e.preventDefault();

        this.setState({
            joyrideShowTooltip: true,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        });
    },

    _onClickTooltip: function (e) {
        e.preventDefault();
        e.stopPropagation();

        var state = this.state,
            type  = e.currentTarget.dataset.type;

        if (typeof state.joyrideStepCallback === 'function') {
            state.joyrideStepCallback(joyride.steps[state.joyrideCurrentIndex]);
        }

        this.setState({
            joyrideShowTooltip: type === 'button' && state.joyrideType === 'guided' && Boolean(joyride.steps[state.joyrideCurrentIndex + 1]),
            joyrideCurrentIndex: state.joyrideCurrentIndex + 1,
            joyrideXPos: -1000,
            joyrideYPos: -1000
        }, function () {
            this._scrollToNextStep();
        }.bind(this));
    },

    _calculatePlacement: function () {
        var state     = this.state,
            step      = joyride.steps[state.joyrideCurrentIndex],
            position,
            body,
            target,
            component = document.querySelector((state.joyrideShowTooltip ? '.joyride-tooltip' : '.joyride-beacon')),
            placement = {
                x: -1000,
                y: -1000
            };

        if (step && (!component.classList.contains('animate') || state.joyrideXPos < 0)) {
            position = step.position;
            body = document.body.getBoundingClientRect();
            target = document.querySelector(step.selector).getBoundingClientRect();
            component = component.getBoundingClientRect();

            if (window.innerWidth < 768) {
                if (/^left/.test(position)) {
                    position = 'top';
                }
                else if (/^right/.test(position)) {
                    position = 'bottom';
                }
            }

            // Calculate x position
            if (/^left/.test(position)) {
                placement.x = target.left - (state.joyrideShowTooltip ? component.width + state.joyrideTooltipOffset : component.width / 2);
            }
            else if (/^right/.test(position)) {
                placement.x = target.left + target.width - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.width / 2);
            }
            else {
                placement.x = target.left + target.width / 2 - component.width / 2;
            }

            // Calculate y position
            if (/^top/.test(position)) {
                placement.y = (target.top - body.top) - (state.joyrideShowTooltip ? component.height + state.joyrideTooltipOffset : component.height / 2);
            }
            else if (/^bottom/.test(position)) {
                placement.y = (target.top - body.top) + target.height - (state.joyrideShowTooltip ? -state.joyrideTooltipOffset : component.height / 2);
            }
            else {
                placement.y = (target.top - body.top) + target.height / 2 - component.height / 2 + (state.joyrideShowTooltip ? state.joyrideTooltipOffset : 0);
            }

            if (/^bottom|^top/.test(position)) {
                if (/left/.test(position)) {
                    placement.x = target.left - (state.joyrideShowTooltip ? state.joyrideTooltipOffset : component.width / 2);
                }
                else if (/right/.test(position)) {
                    placement.x = target.left + target.width - (state.joyrideShowTooltip ? component.width - state.joyrideTooltipOffset : component.width / 2);
                }
            }

            this.setState({
                joyrideXPos: this._preventWindowOverflow(Math.ceil(placement.x), 'x', component.width, component.height),
                joyrideYPos: this._preventWindowOverflow(Math.ceil(placement.y), 'y', component.width, component.height)
            });
        }
    },

    _preventWindowOverflow: function (value, axis, elWidth, elHeight) {
        var winWidth = window.innerWidth;
        var docHeight = document.body.offsetHeight;

        if (axis.toLowerCase() === 'x') {
            if (value + elWidth > winWidth) {
                value = winWidth - elWidth - 10;
            }
            else if (value < 0) {
                value = 10;
            }
        }
        else if (axis.toLowerCase() === 'y') {
            if (value + elHeight > docHeight) {
                value = docHeight - elHeight - 10;
            }
            else if (value < 0) {
                value = 10;
            }
        }

        return value;
    },

    _getPosition: function () {
        var step = joyride.steps[this.state.joyrideCurrentIndex],
            position = step.position,
            elPosition = document.querySelector(step.selector).getBoundingClientRect().top - document.body.getBoundingClientRect().top;

        if (window.innerWidth < 768) {
            if (/^left/.test(position)) {
                position = 'top';
            }
            else if (/^right/.test(position)) {
                position = 'bottom';
            }
        }

        if (/^top/.test(position)) {
            position = this.state.joyrideYPos - 20;
        }
        else if (/^bottom|^left|^right/.test(position)) {
            position = elPosition - 20;
        }

        return position;
    },

    _scrollToNextStep: function (cb) {
        if (joyride.steps[this.state.joyrideCurrentIndex] && this.state.joyrideScrollToSteps) {
            scroll.top(document.body, this._getPosition(), cb);
        }
    },

    _renderLayer: function () {
        React.render(this._renderCurrentStep(), this._target);
        this._calculatePlacement();
    },

    _unrenderLayer: function () {
        React.unmountComponentAtNode(this._target);
    },

    _renderCurrentStep: function () {
        var state       = this.state,
            component,
            currentStep = joyride.steps[state.joyrideCurrentIndex],
            hasNextStep = Boolean(joyride.steps[state.joyrideCurrentIndex + 1]),
            buttonText  = state.joyrideType === 'guided' ? (hasNextStep ? (state.joyrideLocale.next + ' ' + (state.joyrideCurrentIndex + 1) + '/' + joyride.steps.length) : state.joyrideLocale.last) : state.joyrideLocale.close,
            target      = currentStep && currentStep.selector ? document.querySelector(currentStep.selector) : null,
            cssPosition = target ? target.style.position : null;

        if (target) {
            if (state.joyrideShowTooltip) {
                component = React.createElement(Tooltip, {
                    animate: state.joyrideXPos > -1,
                    cssPosition: cssPosition,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    step: currentStep,
                    buttonText: buttonText,
                    showBackdrop: state.joyrideShowBackdrop,
                    onClick: this._onClickTooltip
                });
            }
            else {
                component = React.createElement(Beacon, {
                    cssPosition: cssPosition,
                    xPos: state.joyrideXPos,
                    yPos: state.joyrideYPos,
                    handleClick: this._onClickBeacon
                });
            }
        }

        return component;
    }
};

module.exports = Mixin;
<|endoftext|>


<commit_before>var discovery = require('discovery-channel')
var pump = require('pump')
var events = require('events')
var util = require('util')
var net = require('net')
var equals = require('buffer-equals')
var toBuffer = require('to-buffer')
var crypto = require('crypto')
var lpmessage = require('length-prefixed-message')
var connections = require('connections')

try {
  var utp = require('utp-native')
} catch (err) {
  // do nothing
}

var PEER_SEEN = 1
var PEER_BANNED = 2

var HANDSHAKE_TIMEOUT = 5000
var CONNECTION_TIMEOUT = 3000
var RECONNECT_WAIT = [1000, 1000, 5000, 15000]
// var DEFAULT_SIZE = 100 // TODO enable max connections

module.exports = Swarm

function Swarm (opts) {
  if (!(this instanceof Swarm)) return new Swarm(opts)
  if (!opts) opts = {}
  events.EventEmitter.call(this)

  var self = this

  this.maxConnections = opts.maxConnections || 0
  this.totalConnections = 0

  this.connections = []
  this.id = opts.id || crypto.randomBytes(32)
  this.destroyed = false

  this._stream = opts.stream
  this._options = opts || {}
  this._discovery = null
  this._tcp = opts.tcp === false ? null : net.createServer().on('connection', onconnection)
  this._utp = opts.utp === false || !utp ? null : utp().on('connection', onconnection)
  this._tcpConnections = this._tcp && connections(this._tcp)
  this._adding = null
  this._listening = false

  this._peersIds = {}
  this._peersSeen = {}
  this._peersQueued = []

  if (this._options.discovery !== false) this.on('listening', this._ondiscover)

  function onconnection (connection) {
    var type = this === this._tcp ? 'tcp' : 'utp'
    connection.on('error', onerror)
    self._onconnection(connection, type, null)
  }
}

util.inherits(Swarm, events.EventEmitter)

Swarm.prototype.close =
Swarm.prototype.destroy = function (onclose) {
  if (onclose) this.once('close', onclose)
  if (this._listening && this._adding) return this.once('listening', this.destroy)

  this.destroyed = true
  if (this._discovery) this._discovery.destroy()

  var self = this
  var missing = 0

  if (this._utp) {
    missing++
    for (var i = 0; i < this._utp.connections.length; i++) {
      this._utp.connections[i].destroy()
    }
  }

  if (this._tcp) {
    missing++
    this._tcpConnections.destroy()
  }

  if (this._listening) {
    if (this._tcp) this._tcp.close(onserverclose)
    if (this._utp) this._utp.close(onserverclose)
  }

  function onserverclose () {
    if (--missing) self.emit('close')
  }
}

Swarm.prototype.__defineGetter__('queued', function () {
  return this._peersQueued.length
})

Swarm.prototype.__defineGetter__('connecting', function () {
  return this.totalConnections - this.connections.length
})

Swarm.prototype.__defineGetter__('connected', function () {
  return this.connections.length
})

Swarm.prototype.join = function (name) {
  name = toBuffer(name)

  if (!this._listening && !this._adding) this._listenNext()

  if (this._adding) {
    this._adding.push(name)
  } else {
    this._discovery.join(name, this._tcp.address().port, {impliedPort: !!this._utp})
  }
}

Swarm.prototype.leave = function (name) {
  name = toBuffer(name)

  if (this._adding) {
    for (var i = 0; i < this._adding.length; i++) {
      if (equals(this._adding[i], name)) {
        this._adding.splice(i, 1)
        return
      }
    }
  } else {
    this._discovery.leave(name, this._tcp.address().port)
  }
}

Swarm.prototype.addPeer = function (peer) {
  peer = peerify(peer)
  if (this._peersSeen[peer.id]) return
  this._peersSeen[peer.id] = PEER_SEEN
  this._peersQueued.push(peer)
  this.emit('peer', peer)
  this._kick()
}

Swarm.prototype.removePeer = function (peer) {
  peer = peerify(peer)
  this._peersSeen[peer.id] = PEER_BANNED
  // delete this._peersSeen[peer.id]
}

Swarm.prototype.address = function () {
  return this._tcp ? this._tcp.address() : this._utp.address()
}

Swarm.prototype._ondiscover = function () {
  var self = this
  var names = this._adding

  if (this._options.dns !== false) {
    if (!this._options.dns || this._options.dns === true) this._options.dns = {}
    this._options.dns.socket = this._utp
  }

  if (this._options.dht !== false) {
    if (!this._options.dht || this._options.dht === true) this._options.dht = {}
    this._options.dht.socket = this._utp
  }

  this._discovery = discovery(this._options)
  this._discovery.on('peer', onpeer)
  this._discovery.on('whoami', onwhoami)
  this._adding = null

  if (!names) return
  for (var i = 0; i < names.length; i++) this.join(names[i])

  function onwhoami (me) {
    self._peersSeen[me.host + ':' + me.port] = PEER_BANNED
  }

  function onpeer (channel, peer) {
    var id = peer.host + ':' + peer.port
    if (self._peersSeen[id]) return
    self._peersSeen[id] = PEER_SEEN
    self._peersQueued.push(peerify(peer))
    self.emit('peer', peer)
    self._kick()
  }
}

Swarm.prototype._kick = function () {
  if (this.maxConnections && this.totalConnections >= this.maxConnections) return
  if (this.destroyed) return

  var self = this
  var connected = false
  var next = this._peersQueued.shift()
  while (next && this._peersSeen[next.id] === PEER_BANNED) {
    next = this._peersQueued.shift()
  }

  if (!next) return

  this.totalConnections++

  var tcpSocket = null
  var utpSocket = null
  var tcpClosed = true
  var utpClosed = true

  if (this._tcp) {
    tcpClosed = false
    tcpSocket = net.connect(next.port, next.host)
    tcpSocket.on('connect', onconnect)
    tcpSocket.on('error', onerror)
    tcpSocket.on('close', onclose)
    this._tcpConnections.add(tcpSocket)
  }

  if (this._utp) {
    utpClosed = false
    utpSocket = this._utp.connect(next.port, next.host)
    utpSocket.on('connect', onconnect)
    utpSocket.on('error', onerror)
    utpSocket.on('close', onclose)
  }

  var timeout = setTimeoutUnref(ontimeout, CONNECTION_TIMEOUT)

  function ontimeout () {
    if (utpSocket) utpSocket.destroy()
    if (tcpSocket) tcpSocket.destroy()
  }

  function onclose () {
    if (this === utpSocket) utpClosed = true
    if (this === tcpSocket) tcpClosed = true
    if (tcpClosed && utpClosed) {
      clearTimeout(timeout)
      if (utpSocket) utpSocket.removeListener('close', onclose)
      if (tcpSocket) tcpSocket.removeListener('close', onclose)
      self.totalConnections--
      if (!connected) self._requeue(next)
    }
  }

  function onconnect () {
    connected = true
    utpClosed = tcpClosed = true
    onclose() // decs totalConnections which _onconnection also incs

    var type = this === utpSocket ? 'utp' : 'tcp'

    if (type === 'utp' && tcpSocket) tcpSocket.destroy()
    if (type === 'tcp' && utpSocket) utpSocket.destroy()

    self._onconnection(this, type, next)
  }
}

Swarm.prototype._requeue = function (peer) {
  if (this.destroyed) return

  var self = this
  var wait = peer.retries >= RECONNECT_WAIT.length ? 0 : RECONNECT_WAIT[peer.retries++]
  if (wait) setTimeoutUnref(requeue, wait)
  else this.emit('drop', peer)

  function requeue () {
    self._peersQueued.push(peer)
    self._kick()
  }
}

Swarm.prototype._onconnection = function (connection, type, peer) {
  var self = this
  var idHex = this.id.toString('hex')
  var remoteIdHex

  this.totalConnections++
  connection.on('close', onclose)

  if (this._stream) {
    var wire = connection
    connection = this._stream(this.id)
    connection.on('handshake', onhandshake)
    pump(wire, connection, wire)
  } else {
    handshake(connection, this.id, onhandshake)
  }

  var timeout = setTimeoutUnref(ontimeout, HANDSHAKE_TIMEOUT)
  if (this.destroyed) connection.destroy()

  function ontimeout () {
    connection.destroy()
  }

  function onclose () {
    clearTimeout(timeout)
    self.totalConnections--

    var i = self.connections.indexOf(connection)
    if (i > -1) {
      var last = self.connections.pop()
      if (last !== connection) self.connections[i] = last
    }

    if (remoteIdHex && self._peersIds[remoteIdHex] === connection) {
      delete self._peersIds[remoteIdHex]
      if (peer) self._requeue(peer)
    }
  }

  function onhandshake (remoteId) {
    if (!remoteId) remoteId = connection.remoteId
    clearTimeout(timeout)
    remoteIdHex = remoteId.toString('hex')
    if (peer) peer.retries = 0

    if (idHex === remoteIdHex) {
      if (peer) self._peersSeen[peer.id] = PEER_BANNED
      connection.destroy()
      return
    }

    var old = self._peersIds[remoteIdHex]

    if (old) {
      if ((peer && remoteIdHex < idHex) || (!peer && remoteIdHex > idHex)) {
        connection.destroy()
        return
      }
      delete self._peersIds[remoteIdHex] // delete to not trigger re-queue
      old.destroy()
      old = null // help gc
    }

    self._peersIds[remoteIdHex] = connection
    self.connections.push(connection)
    self.emit('connection', connection, remoteId)
  }
}

Swarm.prototype._listenNext = function () {
  var self = this
  if (!this._adding) this._adding = []
  process.nextTick(function () {
    if (!self._listening) self.listen()
  })
}

Swarm.prototype.listen = function (port, onlistening) {
  if (this._tcp && this._utp) return this._listenBoth(port, onlistening)
  if (!port) port = 0
  if (onlistening) this.once('listening', onlistening)

  var self = this
  var server = this._tcp || this._utp

  if (!this._listening) {
    this._listening = true
    server.on('error', onerror)
    server.on('listening', onlisten)
  }

  if (!this._adding) this._adding = []
  server.listen(port)

  function onerror (err) {
    self.emit('error', err)
  }

  function onlisten () {
    self.emit('listening')
  }
}

Swarm.prototype._listenBoth = function (port, onlistening) {
  if (typeof port === 'function') return this.listen(0, port)
  if (!port) port = 0
  if (onlistening) this.once('listening', onlistening)

  var self = this

  if (!this._adding) this._adding = []
  this._listening = true

  this._utp.on('error', onerror)
  this._utp.on('listening', onutplisten)
  this._tcp.on('listening', ontcplisten)
  this._tcp.on('error', onerror)
  this._tcp.listen(port)

  function cleanup () {
    self._utp.removeListener('error', onerror)
    self._tcp.removeListener('error', onerror)
    self._utp.removeListener('listening', onutplisten)
    self._tcp.removeListener('listening', ontcplisten)
  }

  function onerror (err) {
    cleanup()
    self._tcp.close(function () {
      if (!port) return self.listen() // retry
      self.emit('error', err)
    })
  }

  function onutplisten () {
    cleanup()
    self._utp.on('error', forward)
    self._tcp.on('error', forward)
    self.emit('listening')
  }

  function ontcplisten () {
    self._utp.listen(this.address().port)
  }

  function forward (err) {
    self.emit('error', err)
  }
}

function handshake (socket, id, cb) {
  lpmessage.write(socket, id)
  lpmessage.read(socket, cb)
}

function onerror () {
  this.destroy()
}

function peerify (peer) {
  if (typeof peer === 'number') peer = {port: peer}
  if (!peer.host) peer.host = '127.0.0.1'
  peer.id = peer.host + ':' + peer.port
  peer.retries = 0
  return peer
}

function setTimeoutUnref (fn, time) {
  var timeout = setTimeout(fn, time)
  if (timeout.unref) timeout.unref()
  return timeout
}
<commit_msg>emit connecting with peer<commit_after>var discovery = require('discovery-channel')
var pump = require('pump')
var events = require('events')
var util = require('util')
var net = require('net')
var equals = require('buffer-equals')
var toBuffer = require('to-buffer')
var crypto = require('crypto')
var lpmessage = require('length-prefixed-message')
var connections = require('connections')

try {
  var utp = require('utp-native')
} catch (err) {
  // do nothing
}

var PEER_SEEN = 1
var PEER_BANNED = 2

var HANDSHAKE_TIMEOUT = 5000
var CONNECTION_TIMEOUT = 3000
var RECONNECT_WAIT = [1000, 1000, 5000, 15000]
// var DEFAULT_SIZE = 100 // TODO enable max connections

module.exports = Swarm

function Swarm (opts) {
  if (!(this instanceof Swarm)) return new Swarm(opts)
  if (!opts) opts = {}
  events.EventEmitter.call(this)

  var self = this

  this.maxConnections = opts.maxConnections || 0
  this.totalConnections = 0

  this.connections = []
  this.id = opts.id || crypto.randomBytes(32)
  this.destroyed = false

  this._stream = opts.stream
  this._options = opts || {}
  this._discovery = null
  this._tcp = opts.tcp === false ? null : net.createServer().on('connection', onconnection)
  this._utp = opts.utp === false || !utp ? null : utp().on('connection', onconnection)
  this._tcpConnections = this._tcp && connections(this._tcp)
  this._adding = null
  this._listening = false

  this._peersIds = {}
  this._peersSeen = {}
  this._peersQueued = []

  if (this._options.discovery !== false) this.on('listening', this._ondiscover)

  function onconnection (connection) {
    var type = this === this._tcp ? 'tcp' : 'utp'
    connection.on('error', onerror)
    self._onconnection(connection, type, null)
  }
}

util.inherits(Swarm, events.EventEmitter)

Swarm.prototype.close =
Swarm.prototype.destroy = function (onclose) {
  if (onclose) this.once('close', onclose)
  if (this._listening && this._adding) return this.once('listening', this.destroy)

  this.destroyed = true
  if (this._discovery) this._discovery.destroy()

  var self = this
  var missing = 0

  if (this._utp) {
    missing++
    for (var i = 0; i < this._utp.connections.length; i++) {
      this._utp.connections[i].destroy()
    }
  }

  if (this._tcp) {
    missing++
    this._tcpConnections.destroy()
  }

  if (this._listening) {
    if (this._tcp) this._tcp.close(onserverclose)
    if (this._utp) this._utp.close(onserverclose)
  }

  function onserverclose () {
    if (--missing) self.emit('close')
  }
}

Swarm.prototype.__defineGetter__('queued', function () {
  return this._peersQueued.length
})

Swarm.prototype.__defineGetter__('connecting', function () {
  return this.totalConnections - this.connections.length
})

Swarm.prototype.__defineGetter__('connected', function () {
  return this.connections.length
})

Swarm.prototype.join = function (name) {
  name = toBuffer(name)

  if (!this._listening && !this._adding) this._listenNext()

  if (this._adding) {
    this._adding.push(name)
  } else {
    this._discovery.join(name, this._tcp.address().port, {impliedPort: !!this._utp})
  }
}

Swarm.prototype.leave = function (name) {
  name = toBuffer(name)

  if (this._adding) {
    for (var i = 0; i < this._adding.length; i++) {
      if (equals(this._adding[i], name)) {
        this._adding.splice(i, 1)
        return
      }
    }
  } else {
    this._discovery.leave(name, this._tcp.address().port)
  }
}

Swarm.prototype.addPeer = function (peer) {
  peer = peerify(peer)
  if (this._peersSeen[peer.id]) return
  this._peersSeen[peer.id] = PEER_SEEN
  this._peersQueued.push(peer)
  this.emit('peer', peer)
  this._kick()
}

Swarm.prototype.removePeer = function (peer) {
  peer = peerify(peer)
  this._peersSeen[peer.id] = PEER_BANNED
  // delete this._peersSeen[peer.id]
}

Swarm.prototype.address = function () {
  return this._tcp ? this._tcp.address() : this._utp.address()
}

Swarm.prototype._ondiscover = function () {
  var self = this
  var names = this._adding

  if (this._options.dns !== false) {
    if (!this._options.dns || this._options.dns === true) this._options.dns = {}
    this._options.dns.socket = this._utp
  }

  if (this._options.dht !== false) {
    if (!this._options.dht || this._options.dht === true) this._options.dht = {}
    this._options.dht.socket = this._utp
  }

  this._discovery = discovery(this._options)
  this._discovery.on('peer', onpeer)
  this._discovery.on('whoami', onwhoami)
  this._adding = null

  if (!names) return
  for (var i = 0; i < names.length; i++) this.join(names[i])

  function onwhoami (me) {
    self._peersSeen[me.host + ':' + me.port] = PEER_BANNED
  }

  function onpeer (channel, peer) {
    var id = peer.host + ':' + peer.port
    if (self._peersSeen[id]) return
    self._peersSeen[id] = PEER_SEEN
    self._peersQueued.push(peerify(peer))
    self.emit('peer', peer)
    self._kick()
  }
}

Swarm.prototype._kick = function () {
  if (this.maxConnections && this.totalConnections >= this.maxConnections) return
  if (this.destroyed) return

  var self = this
  var connected = false
  var next = this._peersQueued.shift()
  while (next && this._peersSeen[next.id] === PEER_BANNED) {
    next = this._peersQueued.shift()
  }

  if (!next) return

  this.totalConnections++
  this.emit('connecting', next)

  var tcpSocket = null
  var utpSocket = null
  var tcpClosed = true
  var utpClosed = true

  if (this._tcp) {
    tcpClosed = false
    tcpSocket = net.connect(next.port, next.host)
    tcpSocket.on('connect', onconnect)
    tcpSocket.on('error', onerror)
    tcpSocket.on('close', onclose)
    this._tcpConnections.add(tcpSocket)
  }

  if (this._utp) {
    utpClosed = false
    utpSocket = this._utp.connect(next.port, next.host)
    utpSocket.on('connect', onconnect)
    utpSocket.on('error', onerror)
    utpSocket.on('close', onclose)
  }

  var timeout = setTimeoutUnref(ontimeout, CONNECTION_TIMEOUT)

  function ontimeout () {
    if (utpSocket) utpSocket.destroy()
    if (tcpSocket) tcpSocket.destroy()
  }

  function onclose () {
    if (this === utpSocket) utpClosed = true
    if (this === tcpSocket) tcpClosed = true
    if (tcpClosed && utpClosed) {
      clearTimeout(timeout)
      if (utpSocket) utpSocket.removeListener('close', onclose)
      if (tcpSocket) tcpSocket.removeListener('close', onclose)
      self.totalConnections--
      if (!connected) self._requeue(next)
    }
  }

  function onconnect () {
    connected = true
    utpClosed = tcpClosed = true
    onclose() // decs totalConnections which _onconnection also incs

    var type = this === utpSocket ? 'utp' : 'tcp'

    if (type === 'utp' && tcpSocket) tcpSocket.destroy()
    if (type === 'tcp' && utpSocket) utpSocket.destroy()

    self._onconnection(this, type, next)
  }
}

Swarm.prototype._requeue = function (peer) {
  if (this.destroyed) return

  var self = this
  var wait = peer.retries >= RECONNECT_WAIT.length ? 0 : RECONNECT_WAIT[peer.retries++]
  if (wait) setTimeoutUnref(requeue, wait)
  else this.emit('drop', peer)

  function requeue () {
    self._peersQueued.push(peer)
    self._kick()
  }
}

Swarm.prototype._onconnection = function (connection, type, peer) {
  var self = this
  var idHex = this.id.toString('hex')
  var remoteIdHex

  this.totalConnections++
  connection.on('close', onclose)

  if (this._stream) {
    var wire = connection
    connection = this._stream(this.id)
    connection.on('handshake', onhandshake)
    pump(wire, connection, wire)
  } else {
    handshake(connection, this.id, onhandshake)
  }

  var timeout = setTimeoutUnref(ontimeout, HANDSHAKE_TIMEOUT)
  if (this.destroyed) connection.destroy()

  function ontimeout () {
    connection.destroy()
  }

  function onclose () {
    clearTimeout(timeout)
    self.totalConnections--

    var i = self.connections.indexOf(connection)
    if (i > -1) {
      var last = self.connections.pop()
      if (last !== connection) self.connections[i] = last
    }

    if (remoteIdHex && self._peersIds[remoteIdHex] === connection) {
      delete self._peersIds[remoteIdHex]
      if (peer) self._requeue(peer)
    }
  }

  function onhandshake (remoteId) {
    if (!remoteId) remoteId = connection.remoteId
    clearTimeout(timeout)
    remoteIdHex = remoteId.toString('hex')
    if (peer) peer.retries = 0

    if (idHex === remoteIdHex) {
      if (peer) self._peersSeen[peer.id] = PEER_BANNED
      connection.destroy()
      return
    }

    var old = self._peersIds[remoteIdHex]

    if (old) {
      if ((peer && remoteIdHex < idHex) || (!peer && remoteIdHex > idHex)) {
        connection.destroy()
        return
      }
      delete self._peersIds[remoteIdHex] // delete to not trigger re-queue
      old.destroy()
      old = null // help gc
    }

    self._peersIds[remoteIdHex] = connection
    self.connections.push(connection)
    self.emit('connection', connection, remoteId)
  }
}

Swarm.prototype._listenNext = function () {
  var self = this
  if (!this._adding) this._adding = []
  process.nextTick(function () {
    if (!self._listening) self.listen()
  })
}

Swarm.prototype.listen = function (port, onlistening) {
  if (this._tcp && this._utp) return this._listenBoth(port, onlistening)
  if (!port) port = 0
  if (onlistening) this.once('listening', onlistening)

  var self = this
  var server = this._tcp || this._utp

  if (!this._listening) {
    this._listening = true
    server.on('error', onerror)
    server.on('listening', onlisten)
  }

  if (!this._adding) this._adding = []
  server.listen(port)

  function onerror (err) {
    self.emit('error', err)
  }

  function onlisten () {
    self.emit('listening')
  }
}

Swarm.prototype._listenBoth = function (port, onlistening) {
  if (typeof port === 'function') return this.listen(0, port)
  if (!port) port = 0
  if (onlistening) this.once('listening', onlistening)

  var self = this

  if (!this._adding) this._adding = []
  this._listening = true

  this._utp.on('error', onerror)
  this._utp.on('listening', onutplisten)
  this._tcp.on('listening', ontcplisten)
  this._tcp.on('error', onerror)
  this._tcp.listen(port)

  function cleanup () {
    self._utp.removeListener('error', onerror)
    self._tcp.removeListener('error', onerror)
    self._utp.removeListener('listening', onutplisten)
    self._tcp.removeListener('listening', ontcplisten)
  }

  function onerror (err) {
    cleanup()
    self._tcp.close(function () {
      if (!port) return self.listen() // retry
      self.emit('error', err)
    })
  }

  function onutplisten () {
    cleanup()
    self._utp.on('error', forward)
    self._tcp.on('error', forward)
    self.emit('listening')
  }

  function ontcplisten () {
    self._utp.listen(this.address().port)
  }

  function forward (err) {
    self.emit('error', err)
  }
}

function handshake (socket, id, cb) {
  lpmessage.write(socket, id)
  lpmessage.read(socket, cb)
}

function onerror () {
  this.destroy()
}

function peerify (peer) {
  if (typeof peer === 'number') peer = {port: peer}
  if (!peer.host) peer.host = '127.0.0.1'
  peer.id = peer.host + ':' + peer.port
  peer.retries = 0
  return peer
}

function setTimeoutUnref (fn, time) {
  var timeout = setTimeout(fn, time)
  if (timeout.unref) timeout.unref()
  return timeout
}
<|endoftext|>


<commit_before>/**
 * react-menu-aim is a React Mixin heavily inspired by jQuery-menu-aim. All rights
 * reserved by the original author.
 *
 * https://github.com/jasonslyvia/react-menu-aim
 * https://github.com/kamens/jQuery-menu-aim
*/

var React = require('react/addons');
var MOUSE_LOCS_TRACKED = 3;  // number of past mouse locations to trackv
var DELAY = 300;       // ms delay when user appears to be entering submenu

/**
 *
 * DOM helpers
 *
 */
function on(el, eventName, callback) {
  if (el.addEventListener) {
    el.addEventListener(eventName, callback, false);
  }
  else if (el.attachEvent) {
    el.attachEvent('on'+eventName, function(e) {
      callback.call(el, e || window.event);
    });
  }
}

function off(el, eventName, callback) {
  if (el.removeEventListener) {
    el.removeEventListener(eventName, callback);
  }
  else if (el.detachEvent) {
    el.detachEvent('on'+eventName, callback);
  }
}

function offset(el) {
  if (!el) {
    return {
      left: 0,
      top: 0
    };
  }

  var rect = el.getBoundingClientRect();
  return {
    top: rect.top + document.body.scrollTop,
    left: rect.left + document.body.scrollLeft
  };
}

function outerWidth(el) {
  var _width = el.offsetWidth;
  var style = el.currentStyle || getComputedStyle(el);

  _width += (parseInt(style.marginLeft, 10) || 0);
  return _width;
}

function outerHeight(el) {
  var _height = el.offsetHeight;
  var style = el.currentStyle || getComputedStyle(el);

  _height += (parseInt(style.marginLeft, 10) || 0);
  return _height;
}

/**
 *
 * Util helpers
 *
 */
function getActivateDealy() {
  var menu = React.findDOMNode(this);
  var menuOffset = offset(menu);

  var upperLeft = {
    x: menuOffset.left,
    y: menuOffset.top
  };
  var upperRight = {
    x: menuOffset.left + outerWidth(menu),
    y: upperLeft.y
  };
  var lowerLeft = {
    x: offset.left,
    y: offset.top - outerHeight(menu)
  };
  var lowerRight = {
    x: offset.left + outerWidth(menu),
    y: lowerLeft.y
  };

  var loc = this._mouseLocs[this._mouseLocs.length - 1];
  var prevLoc = this._mouseLocs[0];

  if (!loc) {
    return 0;
  }

  if (!prevLoc) {
    prevLoc = loc;
  }

  // If the previous mouse location was outside of the entire
  // menu's bounds, immediately activate.
  if (prevLoc.x < menuOffset.left || prevLoc.x > lowerRight.x ||
      prevLoc.y < menuOffset.top || prevLoc.y > lowerRight.y
    ) {
    return 0;
  }

  // If the mouse hasn't moved since the last time we checked
  // for activation status, immediately activate.
  if (this._lastDelayDoc &&
        loc.x === this._lastDelayDoc.x && loc.y === this._lastDelayDoc.y) {
    return 0;
  }

  // Detect if the user is moving towards the currently activated
  // submenu.
  //
  // If the mouse is heading relatively clearly towards
  // the submenu's content, we should wait and give the user more
  // time before activating a new row. If the mouse is heading
  // elsewhere, we can immediately activate a new row.
  //
  // We detect this by calculating the slope formed between the
  // current mouse location and the upper/lower right points of
  // the menu. We do the same for the previous mouse location.
  // If the current mouse location's slopes are
  // increasing/decreasing appropriately compared to the
  // previous's, we know the user is moving toward the submenu.
  //
  // Note that since the y-axis increases as the cursor moves
  // down the screen, we are looking for the slope between the
  // cursor and the upper right corner to decrease over time, not
  // increase (somewhat counterintuitively).
  function slope(a, b) {
    return (b.y - a.y) / (b.x - a.x);
  }

  var decreasingCorner = upperRight;
  var increasingCorner = lowerRight;

  // Our expectations for decreasing or increasing slope values
  // depends on which direction the submenu opens relative to the
  // main menu. By default, if the menu opens on the right, we
  // expect the slope between the cursor and the upper right
  // corner to decrease over time, as explained above. If the
  // submenu opens in a different direction, we change our slope
  // expectations.
  if (this._submenuDirection === 'left') {
    decreasingCorner = lowerLeft;
    increasingCorner = upperLeft;
  }
  else if (this._submenuDirection === 'below') {
    decreasingCorner = lowerRight;
    increasingCorner = lowerLeft;
  }
  else if (this._submenuDirection === 'above') {
    decreasingCorner = upperLeft;
  }

  var decreasingSlope = slope(loc, decreasingCorner);
  var increasingSlope = slope(loc, increasingCorner);
  var prevDecreasingSlope = slope(prevLoc, decreasingCorner);
  var prevIncreasingSlope = slope(prevLoc, increasingCorner);

  // Mouse is moving from previous location towards the
  // currently activated submenu. Delay before activating a
  // new menu row, because user may be moving into submenu.
  if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
    this._lastDelayLoc = loc;
    return DELAY;
  }

  this._lastDelayLoc = null;
  return 0;
}

// For the concern of further changes might happen when activeRowIndex changes,
// this mixin doesn't setState directly, instead it calls the callback provided
// by user.
function activate(rowIdentifier, handler) {
  handler.call(this, rowIdentifier);
}

function possiblyActivate(rowIdentifier, handler) {
  var delay = getActivateDealy.call(this);

  if (delay) {
    var self = this;
    this.__reactMenuAimTimer = setTimeout(function(){
      possiblyActivate.call(self, rowIdentifier, handler);
    }, delay);
  }
  else {
    activate.call(this, rowIdentifier, handler);
  }
}

/**
 * @export
 */
module.exports = exports = {
  componentDidMount: function() {
    on(document, 'mousemove.react-menu-aim', this.handleMouseMoveDocument);
  },

  componentWillUnmount: function() {
    off(document, 'mousemove.react-menu-aim', this.handleMouseMoveDocument);
  },

  handleMouseMoveDocument: function(e) {
    var _mouseLocs = this._mouseLocs || [];
    _mouseLocs.push({
      x: e.pageX,
      y: e.pageY
    });

    if (_mouseLocs.length > MOUSE_LOCS_TRACKED) {
      _mouseLocs.shift();
    }

    this._mouseLocs = _mouseLocs;
  },

  /**
   * @param  {object}   e     React's synthetic event object
   * @param  {function} handler The true event handler for your app
   */
  handleMouseLeaveMenu: function(e, handler) {
    if (this.__reactMenuAimTimer) {
      clearTimeout(this.__reactMenuAimTimer);
    }

    if (typeof handler === 'function') {
      handler.call(this, e);
    }
  },

  /**
   * @param  {number}   rowIdentifier  The identifier of current row, ie. index or name
   * @param  {function} handler        The true event handler for your app
   * @param  {object}   e              React's synthetic event object
   */
  handleMouseEnterRow: function(rowIdentifier, handler) {
    if (this.__reactMenuAimTimer) {
      clearTimeout(this.__reactMenuAimTimer);
    }

    possiblyActivate.call(this, rowIdentifier, handler);
  }
};

<commit_msg>bugfix<commit_after>/**
 * react-menu-aim is a React Mixin heavily inspired by jQuery-menu-aim. All rights
 * reserved by the original author.
 *
 * https://github.com/jasonslyvia/react-menu-aim
 * https://github.com/kamens/jQuery-menu-aim
*/

var React = require('react/addons');
var MOUSE_LOCS_TRACKED = 3;  // number of past mouse locations to trackv
var DELAY = 300;       // ms delay when user appears to be entering submenu

/**
 *
 * DOM helpers
 *
 */
function on(el, eventName, callback) {
  if (el.addEventListener) {
    el.addEventListener(eventName, callback, false);
  }
  else if (el.attachEvent) {
    el.attachEvent('on'+eventName, function(e) {
      callback.call(el, e || window.event);
    });
  }
}

function off(el, eventName, callback) {
  if (el.removeEventListener) {
    el.removeEventListener(eventName, callback);
  }
  else if (el.detachEvent) {
    el.detachEvent('on'+eventName, callback);
  }
}

function offset(el) {
  if (!el) {
    return {
      left: 0,
      top: 0
    };
  }

  var rect = el.getBoundingClientRect();
  return {
    top: rect.top + document.body.scrollTop,
    left: rect.left + document.body.scrollLeft
  };
}

function outerWidth(el) {
  var _width = el.offsetWidth;
  var style = el.currentStyle || getComputedStyle(el);

  _width += (parseInt(style.marginLeft, 10) || 0);
  return _width;
}

function outerHeight(el) {
  var _height = el.offsetHeight;
  var style = el.currentStyle || getComputedStyle(el);

  _height += (parseInt(style.marginLeft, 10) || 0);
  return _height;
}

/**
 *
 * Util helpers
 *
 */
function getActivateDealy() {
  var menu = React.findDOMNode(this);
  var menuOffset = offset(menu);

  var upperLeft = {
    x: menuOffset.left,
    y: menuOffset.top
  };
  var upperRight = {
    x: menuOffset.left + outerWidth(menu),
    y: upperLeft.y
  };
  var lowerLeft = {
    x: menuOffset.left,
    y: menuOffset.top - outerHeight(menu)
  };
  var lowerRight = {
    x: menuOffset.left + outerWidth(menu),
    y: lowerLeft.y
  };

  var loc = this._mouseLocs[this._mouseLocs.length - 1];
  var prevLoc = this._mouseLocs[0];

  if (!loc) {
    return 0;
  }

  if (!prevLoc) {
    prevLoc = loc;
  }

  // If the previous mouse location was outside of the entire
  // menu's bounds, immediately activate.
  if (prevLoc.x < menuOffset.left || prevLoc.x > lowerRight.x ||
      prevLoc.y < menuOffset.top || prevLoc.y > lowerRight.y
    ) {
    return 0;
  }

  // If the mouse hasn't moved since the last time we checked
  // for activation status, immediately activate.
  if (this._lastDelayDoc &&
        loc.x === this._lastDelayDoc.x && loc.y === this._lastDelayDoc.y) {
    return 0;
  }

  // Detect if the user is moving towards the currently activated
  // submenu.
  //
  // If the mouse is heading relatively clearly towards
  // the submenu's content, we should wait and give the user more
  // time before activating a new row. If the mouse is heading
  // elsewhere, we can immediately activate a new row.
  //
  // We detect this by calculating the slope formed between the
  // current mouse location and the upper/lower right points of
  // the menu. We do the same for the previous mouse location.
  // If the current mouse location's slopes are
  // increasing/decreasing appropriately compared to the
  // previous's, we know the user is moving toward the submenu.
  //
  // Note that since the y-axis increases as the cursor moves
  // down the screen, we are looking for the slope between the
  // cursor and the upper right corner to decrease over time, not
  // increase (somewhat counterintuitively).
  function slope(a, b) {
    return (b.y - a.y) / (b.x - a.x);
  }

  var decreasingCorner = upperRight;
  var increasingCorner = lowerRight;

  // Our expectations for decreasing or increasing slope values
  // depends on which direction the submenu opens relative to the
  // main menu. By default, if the menu opens on the right, we
  // expect the slope between the cursor and the upper right
  // corner to decrease over time, as explained above. If the
  // submenu opens in a different direction, we change our slope
  // expectations.
  if (this._submenuDirection === 'left') {
    decreasingCorner = lowerLeft;
    increasingCorner = upperLeft;
  }
  else if (this._submenuDirection === 'below') {
    decreasingCorner = lowerRight;
    increasingCorner = lowerLeft;
  }
  else if (this._submenuDirection === 'above') {
    decreasingCorner = upperLeft;
  }

  var decreasingSlope = slope(loc, decreasingCorner);
  var increasingSlope = slope(loc, increasingCorner);
  var prevDecreasingSlope = slope(prevLoc, decreasingCorner);
  var prevIncreasingSlope = slope(prevLoc, increasingCorner);

  // Mouse is moving from previous location towards the
  // currently activated submenu. Delay before activating a
  // new menu row, because user may be moving into submenu.
  if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
    this._lastDelayLoc = loc;
    return DELAY;
  }

  this._lastDelayLoc = null;
  return 0;
}

// For the concern of further changes might happen when activeRowIndex changes,
// this mixin doesn't setState directly, instead it calls the callback provided
// by user.
function activate(rowIdentifier, handler) {
  handler.call(this, rowIdentifier);
}

function possiblyActivate(rowIdentifier, handler) {
  var delay = getActivateDealy.call(this);

  if (delay) {
    var self = this;
    this.__reactMenuAimTimer = setTimeout(function(){
      possiblyActivate.call(self, rowIdentifier, handler);
    }, delay);
  }
  else {
    activate.call(this, rowIdentifier, handler);
  }
}

/**
 * @export
 */
module.exports = exports = {
  componentWillMount: function() {
    this._mouseLocs = [];
  },

  componentDidMount: function() {
    on(document, 'mousemove', this.handleMouseMoveDocument);
  },

  componentWillUnmount: function() {
    off(document, 'mousemove', this.handleMouseMoveDocument);
  },

  handleMouseMoveDocument: function(e) {
    var _mouseLocs = this._mouseLocs || [];
    _mouseLocs.push({
      x: e.pageX,
      y: e.pageY
    });

    if (_mouseLocs.length > MOUSE_LOCS_TRACKED) {
      _mouseLocs.shift();
    }

    this._mouseLocs = _mouseLocs;
  },

  /**
   * @param  {object}   e     React's synthetic event object
   * @param  {function} handler The true event handler for your app
   */
  handleMouseLeaveMenu: function(e, handler) {
    if (this.__reactMenuAimTimer) {
      clearTimeout(this.__reactMenuAimTimer);
    }

    if (typeof handler === 'function') {
      handler.call(this, e);
    }
  },

  /**
   * @param  {number}   rowIdentifier  The identifier of current row, ie. index or name
   * @param  {function} handler        The true event handler for your app
   * @param  {object}   e              React's synthetic event object
   */
  handleMouseEnterRow: function(rowIdentifier, handler) {
    if (this.__reactMenuAimTimer) {
      clearTimeout(this.__reactMenuAimTimer);
    }

    possiblyActivate.call(this, rowIdentifier, handler);
  }
};

<|endoftext|>