<commit_before>/* jshint node: true */
'use strict';

var detect = require('rtc-core/detect');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var mbus = require('mbus');
var getable = require('cog/getable');
var uuid = require('cuid');
var pull = require('pull-stream');
var pushable = require('pull-pushable');
var prepare = require('rtc-signal/prepare');

// ready state constants
var RS_DISCONNECTED = 0;
var RS_CONNECTING = 1;
var RS_CONNECTED = 2;

// initialise signaller metadata so we don't have to include the package.json
// TODO: make this checkable with some kind of prepublish script
var metadata = {
  version: '6.0.0'
};

/**
  # rtc-signaller

  The `rtc-signaller` module provides a transportless signalling
  mechanism for WebRTC.

  ## Purpose

  <<< docs/purpose.md

  ## Getting Started

  While the signaller is capable of communicating by a number of different
  messengers (i.e. anything that can send and receive messages over a wire)
  it comes with support for understanding how to connect to an
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box.

  The following code sample demonstrates how:

  <<< examples/getting-started.js

  <<< docs/events.md

  <<< docs/signalflow-diagrams.md

  ## Reference

  The `rtc-signaller` module is designed to be used primarily in a functional
  way and when called it creates a new signaller that will enable
  you to communicate with other peers via your messaging network.

  ```js
  // create a signaller from something that knows how to send messages
  var signaller = require('rtc-signaller')(messenger);
  ```

  As demonstrated in the getting started guide, you can also pass through
  a string value instead of a messenger instance if you simply want to
  connect to an existing `rtc-switchboard` instance.

**/
module.exports = function(messenger, opts) {
  // get the autoreply setting
  var autoreply = (opts || {}).autoreply;
  var autoconnect = (opts || {}).autoconnect;
  var reconnect = (opts || {}).reconnect;

  // create the signaller
  var signaller = require('rtc-signal/signaller')(opts, bufferMessage);

  // create the outbound message queue
  var queue = require('pull-pushable')();

  var announceTimer = 0;
  var readyState = RS_DISCONNECTED;

  function bufferMessage(message) {
    queue.push(message);

    // if we are not connected (and should autoconnect), then attempt connection
    if (readyState === RS_DISCONNECTED && (autoconnect === undefined || autoconnect)) {
      connect();
    }
  }

  function handleDisconnect() {
    if (reconnect === undefined || reconnect) {
      setTimeout(connect, 50);
    }
  }

  /**
    ### `signaller.connect()`

    Manually connect the signaller using the supplied messenger.

    __NOTE:__ This should never have to be called if the default setting
    for `autoconnect` is used.
  **/
  var connect = signaller.connect = function() {
    // if we are already connecting then do nothing
    if (readyState === RS_CONNECTING) {
      return;
    }

    // initiate the messenger
    readyState = RS_CONNECTING;
    messenger(function(err, source, sink) {
      if (err) {
        readyState = RS_DISCONNECTED;
        return signaller('error', err);
      }

      // flag as connected
      readyState = RS_CONNECTED;

      // pass messages to the processor
      pull(
        source,

        // monitor disconnection
        pull.through(null, function() {
          readyState = RS_DISCONNECTED;
          signaller('disconnected');
        }),
        pull.drain(signaller._process)
      );

      // pass the queue to the sink
      pull(queue, sink);

      // handle disconnection
      signaller.removeListener('disconnected', handleDisconnect);
      signaller.on('disconnected', handleDisconnect);

      // trigger the connected event
      signaller('connected');
    });
  };

  /**
    ### announce(data?)

    The `announce` function of the signaller will pass an `/announce` message
    through the messenger network.  When no additional data is supplied to
    this function then only the id of the signaller is sent to all active
    members of the messenging network.

    #### Joining Rooms

    To join a room using an announce call you simply provide the name of the
    room you wish to join as part of the data block that you annouce, for
    example:

    ```js
    signaller.announce({ room: 'testroom' });
    ```

    Signalling servers (such as
    [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then
    place your peer connection into a room with other peers that have also
    announced in this room.

    Once you have joined a room, the server will only deliver messages that
    you `send` to other peers within that room.

    #### Providing Additional Announce Data

    There may be instances where you wish to send additional data as part of
    your announce message in your application.  For instance, maybe you want
    to send an alias or nick as part of your announce message rather than just
    use the signaller's generated id.

    If for instance you were writing a simple chat application you could join
    the `webrtc` room and tell everyone your name with the following announce
    call:

    ```js
    signaller.announce({
      room: 'webrtc',
      nick: 'Damon'
    });
    ```

    #### Announcing Updates

    The signaller is written to distinguish between initial peer announcements
    and peer data updates (see the docs on the announce handler below). As
    such it is ok to provide any data updates using the announce method also.

    For instance, I could send a status update as an announce message to flag
    that I am going offline:

    ```js
    signaller.announce({ status: 'offline' });
    ```

  **/
  signaller.announce = function(data) {
    // if we are already connected, then ensure we announce on reconnect
    if (readyState === RS_CONNECTED) {
      // always announce on reconnect
      signaller.removeListener('connected', signaller._announce);
      signaller.on('connected', signaller._announce);
    }

    signaller._update(data);
    clearTimeout(announceTimer);

    // send the attributes over the network
    return announceTimer = setTimeout(signaller._announce, (opts || {}).announceDelay || 10);
  };

  /**
    ### leave()

    Tell the signalling server we are leaving.  Calling this function is
    usually not required though as the signalling server should issue correct
    `/leave` messages when it detects a disconnect event.

  **/
  signaller.leave = signaller.close = function() {
    // send the leave signal
    signaller.send('/leave', { id: signaller.id });

    // stop announcing on reconnect
    signaller.removeListener('disconnected', handleDisconnect);
    signaller.removeListener('connected', signaller._announce);

    // end our current queue
    queue.end();

    // create a new queue to buffer new messages
    queue = pushable();

    // set connected to false
    readyState = RS_DISCONNECTED;
  };

  // set the autoreply flag
  signaller.autoreply = autoreply === undefined || autoreply;

  // update the signaller agent
  signaller._update({ agent: 'signaller@' + metadata.version });

  // autoconnect
  if (autoconnect === undefined || autoconnect) {
    connect();
  }

  return signaller;
};
<commit_msg>Move autoreply initialization into rtc-signal/signaller<commit_after>/* jshint node: true */
'use strict';

var detect = require('rtc-core/detect');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var mbus = require('mbus');
var getable = require('cog/getable');
var uuid = require('cuid');
var pull = require('pull-stream');
var pushable = require('pull-pushable');
var prepare = require('rtc-signal/prepare');

// ready state constants
var RS_DISCONNECTED = 0;
var RS_CONNECTING = 1;
var RS_CONNECTED = 2;

// initialise signaller metadata so we don't have to include the package.json
// TODO: make this checkable with some kind of prepublish script
var metadata = {
  version: '6.0.0'
};

/**
  # rtc-signaller

  The `rtc-signaller` module provides a transportless signalling
  mechanism for WebRTC.

  ## Purpose

  <<< docs/purpose.md

  ## Getting Started

  While the signaller is capable of communicating by a number of different
  messengers (i.e. anything that can send and receive messages over a wire)
  it comes with support for understanding how to connect to an
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box.

  The following code sample demonstrates how:

  <<< examples/getting-started.js

  <<< docs/events.md

  <<< docs/signalflow-diagrams.md

  ## Reference

  The `rtc-signaller` module is designed to be used primarily in a functional
  way and when called it creates a new signaller that will enable
  you to communicate with other peers via your messaging network.

  ```js
  // create a signaller from something that knows how to send messages
  var signaller = require('rtc-signaller')(messenger);
  ```

  As demonstrated in the getting started guide, you can also pass through
  a string value instead of a messenger instance if you simply want to
  connect to an existing `rtc-switchboard` instance.

**/
module.exports = function(messenger, opts) {
  var autoconnect = (opts || {}).autoconnect;
  var reconnect = (opts || {}).reconnect;

  // create the signaller
  var signaller = require('rtc-signal/signaller')(opts, bufferMessage);

  // create the outbound message queue
  var queue = require('pull-pushable')();

  var announceTimer = 0;
  var readyState = RS_DISCONNECTED;

  function bufferMessage(message) {
    queue.push(message);

    // if we are not connected (and should autoconnect), then attempt connection
    if (readyState === RS_DISCONNECTED && (autoconnect === undefined || autoconnect)) {
      connect();
    }
  }

  function handleDisconnect() {
    if (reconnect === undefined || reconnect) {
      setTimeout(connect, 50);
    }
  }

  /**
    ### `signaller.connect()`

    Manually connect the signaller using the supplied messenger.

    __NOTE:__ This should never have to be called if the default setting
    for `autoconnect` is used.
  **/
  var connect = signaller.connect = function() {
    // if we are already connecting then do nothing
    if (readyState === RS_CONNECTING) {
      return;
    }

    // initiate the messenger
    readyState = RS_CONNECTING;
    messenger(function(err, source, sink) {
      if (err) {
        readyState = RS_DISCONNECTED;
        return signaller('error', err);
      }

      // flag as connected
      readyState = RS_CONNECTED;

      // pass messages to the processor
      pull(
        source,

        // monitor disconnection
        pull.through(null, function() {
          readyState = RS_DISCONNECTED;
          signaller('disconnected');
        }),
        pull.drain(signaller._process)
      );

      // pass the queue to the sink
      pull(queue, sink);

      // handle disconnection
      signaller.removeListener('disconnected', handleDisconnect);
      signaller.on('disconnected', handleDisconnect);

      // trigger the connected event
      signaller('connected');
    });
  };

  /**
    ### announce(data?)

    The `announce` function of the signaller will pass an `/announce` message
    through the messenger network.  When no additional data is supplied to
    this function then only the id of the signaller is sent to all active
    members of the messenging network.

    #### Joining Rooms

    To join a room using an announce call you simply provide the name of the
    room you wish to join as part of the data block that you annouce, for
    example:

    ```js
    signaller.announce({ room: 'testroom' });
    ```

    Signalling servers (such as
    [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then
    place your peer connection into a room with other peers that have also
    announced in this room.

    Once you have joined a room, the server will only deliver messages that
    you `send` to other peers within that room.

    #### Providing Additional Announce Data

    There may be instances where you wish to send additional data as part of
    your announce message in your application.  For instance, maybe you want
    to send an alias or nick as part of your announce message rather than just
    use the signaller's generated id.

    If for instance you were writing a simple chat application you could join
    the `webrtc` room and tell everyone your name with the following announce
    call:

    ```js
    signaller.announce({
      room: 'webrtc',
      nick: 'Damon'
    });
    ```

    #### Announcing Updates

    The signaller is written to distinguish between initial peer announcements
    and peer data updates (see the docs on the announce handler below). As
    such it is ok to provide any data updates using the announce method also.

    For instance, I could send a status update as an announce message to flag
    that I am going offline:

    ```js
    signaller.announce({ status: 'offline' });
    ```

  **/
  signaller.announce = function(data) {
    // if we are already connected, then ensure we announce on reconnect
    if (readyState === RS_CONNECTED) {
      // always announce on reconnect
      signaller.removeListener('connected', signaller._announce);
      signaller.on('connected', signaller._announce);
    }

    signaller._update(data);
    clearTimeout(announceTimer);

    // send the attributes over the network
    return announceTimer = setTimeout(signaller._announce, (opts || {}).announceDelay || 10);
  };

  /**
    ### leave()

    Tell the signalling server we are leaving.  Calling this function is
    usually not required though as the signalling server should issue correct
    `/leave` messages when it detects a disconnect event.

  **/
  signaller.leave = signaller.close = function() {
    // send the leave signal
    signaller.send('/leave', { id: signaller.id });

    // stop announcing on reconnect
    signaller.removeListener('disconnected', handleDisconnect);
    signaller.removeListener('connected', signaller._announce);

    // end our current queue
    queue.end();

    // create a new queue to buffer new messages
    queue = pushable();

    // set connected to false
    readyState = RS_DISCONNECTED;
  };

  // update the signaller agent
  signaller._update({ agent: 'signaller@' + metadata.version });

  // autoconnect
  if (autoconnect === undefined || autoconnect) {
    connect();
  }

  return signaller;
};
<|endoftext|>


<commit_before>var http = require('http');
var ws = require('ws');
var path = require('path');
var events = require('events');
var util = require('util');
var async = require('async');

var ChromeConnection = (function() {
  function ChromeConnection(port, host) {
    events.EventEmitter.call(this);

    this._port = port;
    this._host = host;

    this._callbacks = [];
    this._counter = 0;

    this._scripts = [];
    this._console = new events.EventEmitter();
  }

  util.inherits(ChromeConnection, events.EventEmitter);

  Object.defineProperty(ChromeConnection.prototype, 'console', {
    get: function() {
      return this._console;
    },
  });

  ChromeConnection.prototype._send = function(method, params, callback) {
    var id = this._counter++;

    var message = {
      id: id,
      method: method,
      params: params
    };

    this._callbacks[id] = callback;

    try {
      this._socket.send(JSON.stringify(message));
    } catch (error) {
      return callback(error);
    }
  };

  ChromeConnection.prototype._process = function(message) {
    if (message.id !== undefined) {
      var callback = this._callbacks[message.id];

      if (callback) {
        if (message.error) {
          callback(message.error);
        } else if (message.params) {
          callback(null, message.params);
        } else if (message.result) {
          callback(null, message.result);
        }

        delete this._callbacks[message.id];
      }
    } else {
      if (message.method == 'Debugger.scriptParsed') {
        this._scripts.push(message.params);
      } else if (message.method == 'Console.messageAdded') {
        this._console.emit('data', message.params.message);
      }
    }
  };

  ChromeConnection.prototype.targets = function(callback) {
    var options = {
      host: this._host,
      port: this._port,
      path: '/json'
    };

    var self = this;
    var request = http.get(options, function(response) {
      var data = '';

      response.on('data', function(chunk) {
        data += chunk;
      });

      response.on('end', function() {
        var targets = JSON.parse(data);
        callback(targets);
      });
    });

    request.on('error', function(error) {
      self.emit('error', error);
    });
  };

  ChromeConnection.prototype.attach = function attach(target) {
    var socket = ws.connect(target.webSocketDebuggerUrl);

    var self = this;
    var send = self._send.bind(self);

    socket.on('open', function() {
      async.series([
        async.apply(send, 'Debugger.enable', {}),
        async.apply(send, 'Runtime.enable', {}),
        async.apply(send, 'Console.enable', {}),
      ], function(error) {
        if (error) {
          return self.emit('error', error);
        }

        self.emit('attach', target);
      });
    });

    socket.on('close', function() {
      self.emit('detatch', target);
    });

    socket.on('error', function(error) {
      self.emit('error', error);
    });

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        self._process(message);
      } catch (error) {
        self.error(error);
      }
    });

    this._socket = socket;
  };

  ChromeConnection.prototype.evaluate = function evaluate(expression, callback) {
    var params = {
      expression: expression,
    };

    this._send('Runtime.evaluate', params, function evaluate(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params.result);
    });
  };

  ChromeConnection.prototype.source = function source(filename, contents, callback) {
    var script = this._scripts.filter(function(src) {
      return path.basename(src.url) == filename;
    })[0];

    if (script === undefined) {
      return callback('Unknown script ' + filename);
    }

    var params = {
      scriptId: script.scriptId,
      scriptSource: contents
    };

    this._send('Debugger.setScriptSource', params, function(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params);
    });
  };

  return ChromeConnection;
}());

function connect(port, host, callback) {
  var client = new ChromeConnection(port, host);

  if (callback) {
    client.on('attach', callback);
  }

  return client;
}

module.exports.connect = connect;
<commit_msg>Fix should be emit, not error.<commit_after>var http = require('http');
var ws = require('ws');
var path = require('path');
var events = require('events');
var util = require('util');
var async = require('async');

var ChromeConnection = (function() {
  function ChromeConnection(port, host) {
    events.EventEmitter.call(this);

    this._port = port;
    this._host = host;

    this._callbacks = [];
    this._counter = 0;

    this._scripts = [];
    this._console = new events.EventEmitter();
  }

  util.inherits(ChromeConnection, events.EventEmitter);

  Object.defineProperty(ChromeConnection.prototype, 'console', {
    get: function() {
      return this._console;
    },
  });

  ChromeConnection.prototype._send = function(method, params, callback) {
    var id = this._counter++;

    var message = {
      id: id,
      method: method,
      params: params
    };

    this._callbacks[id] = callback;

    try {
      this._socket.send(JSON.stringify(message));
    } catch (error) {
      return callback(error);
    }
  };

  ChromeConnection.prototype._process = function(message) {
    if (message.id !== undefined) {
      var callback = this._callbacks[message.id];

      if (callback) {
        if (message.error) {
          callback(message.error);
        } else if (message.params) {
          callback(null, message.params);
        } else if (message.result) {
          callback(null, message.result);
        }

        delete this._callbacks[message.id];
      }
    } else {
      if (message.method == 'Debugger.scriptParsed') {
        this._scripts.push(message.params);
      } else if (message.method == 'Console.messageAdded') {
        this._console.emit('data', message.params.message);
      }
    }
  };

  ChromeConnection.prototype.targets = function(callback) {
    var options = {
      host: this._host,
      port: this._port,
      path: '/json'
    };

    var self = this;
    var request = http.get(options, function(response) {
      var data = '';

      response.on('data', function(chunk) {
        data += chunk;
      });

      response.on('end', function() {
        var targets = JSON.parse(data);
        callback(targets);
      });
    });

    request.on('error', function(error) {
      self.emit('error', error);
    });
  };

  ChromeConnection.prototype.attach = function attach(target) {
    var socket = ws.connect(target.webSocketDebuggerUrl);

    var self = this;
    var send = self._send.bind(self);

    socket.on('open', function() {
      async.series([
        async.apply(send, 'Debugger.enable', {}),
        async.apply(send, 'Runtime.enable', {}),
        async.apply(send, 'Console.enable', {}),
      ], function(error) {
        if (error) {
          return self.emit('error', error);
        }

        self.emit('attach', target);
      });
    });

    socket.on('close', function() {
      self.emit('detatch', target);
    });

    socket.on('error', function(error) {
      self.emit('error', error);
    });

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        self._process(message);
      } catch (error) {
        self.emit(error);
      }
    });

    this._socket = socket;
  };

  ChromeConnection.prototype.evaluate = function evaluate(expression, callback) {
    var params = {
      expression: expression,
    };

    this._send('Runtime.evaluate', params, function evaluate(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params.result);
    });
  };

  ChromeConnection.prototype.source = function source(filename, contents, callback) {
    var script = this._scripts.filter(function(src) {
      return path.basename(src.url) == filename;
    })[0];

    if (script === undefined) {
      return callback('Unknown script ' + filename);
    }

    var params = {
      scriptId: script.scriptId,
      scriptSource: contents
    };

    this._send('Debugger.setScriptSource', params, function(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params);
    });
  };

  return ChromeConnection;
}());

function connect(port, host, callback) {
  var client = new ChromeConnection(port, host);

  if (callback) {
    client.on('attach', callback);
  }

  return client;
}

module.exports.connect = connect;
<|endoftext|>


<commit_before>'use strict';

var React = require('react-native');
var TimerMixin = require('react-timer-mixin');
var Dimensions = require('Dimensions');
var {width, height} = Dimensions.get('window');

var {
  AppRegistry,
  StyleSheet,
  Image,
  Text,
  ScrollView,
  View
} = React;

var PAGE_CHANGE_DELAY = 4000;

/**
 * Animates pages in cycle
 * (loop possible if children count > 1)
*/
var Carousel = React.createClass({
    propTypes:{
        children: React.PropTypes.node.isRequired,
        delay: React.PropTypes.number,
        style: View.propTypes.style
    },
    mixins: [TimerMixin],
    getDefaultProps: function() {
      return {delay: PAGE_CHANGE_DELAY};
    },
    getInitialState: function() {
      var childrenCount = this.props.children.length;
      return {
        contentOffset: {x: childrenCount > 1 ? width : 0, y: 0},
        currentPage: childrenCount > 1 ? 1 : 0,
      };
    },
    componentDidMount:function(){
      this._setUpTimer();
    },
    _onScrollBegin: function(event) {
      clearTimeout(this.timer);
    },
    _onScrollEnd: function(event) {
      this._setUpTimer();

      var offset = event.nativeEvent.contentOffset;

      var childrenCount = this.props.children.length;
      if (offset.x == 0) {
        offset = {x: childrenCount*width, y: 0};
      } else if (offset.x == (childrenCount+1)*width) {
        offset = {x: width, y: 0};
      }

      this._calculateCurrentPage(offset.x);
      this.setState({contentOffset: offset});
    },
    _setUpTimer: function() {
      //only for cycling
      if (this.props.children.length > 1) {
        clearTimeout(this.timer);
        this.timer = this.setTimeout(this._animateNextPage, this.props.delay);
      }
    },
    _animateNextPage: function() {
      var k = this.state.currentPage;
      k++;

      this.setState({currentPage: k});
      this.refs.scrollView.scrollTo(0, k*width);
      this._setUpTimer();
    },
    _calculateCurrentPage: function(offset) {
      var page = Math.floor((offset - width/2) / width) + 1;
      this.setState({currentPage: page});
    },
    //TODO: add optional `dots` for displaying current page (like pageControl)
    render: function() {
      var pages = [];

      var children = this.props.children;

      //to make infinite pages structure like this is needed: 3-1-2-3-1
      //add last one at the 1st place
      if (children.length > 1) {
        pages.push(children[children.length-1]);
      }

      //add all pages
      for (var i=0; i<children.length; i++) {
        pages.push(children[i]);
      }

      //add first one at the last place
      if (children.length > 1) {
        pages.push(children[0]);
      }

      pages = pages.map((page, i) => {
        return <View style={{width: width}} key={"page"+i}>{page}</View>;
      });

      return (
        <ScrollView
          ref='scrollView'
          onScrollBeginDrag={this._onScrollBegin}
          onMomentumScrollEnd={this._onScrollEnd}
          alwaysBounceHorizontal={false}
          alwaysBounceVertical={false}
          showsHorizontalScrollIndicator={false}
          horizontal={true}
          pagingEnabled={true}
          bounces={false}
          contentOffset={this.state.contentOffset}
          contentContainerStyle={[
            styles.horizontalScroll,
            this.props.style,
            {width: width*(this.props.children.length+(this.props.children.length>1?2:0))}
          ]}
        >
          {pages}
        </ScrollView>
      );
    },
});

var styles = StyleSheet.create({
  horizontalScroll: {
    height: height,
    position:'absolute'
  }
});

module.exports = Carousel;
<commit_msg>resolves #1<commit_after>'use strict';

var React = require('react-native');
var TimerMixin = require('react-timer-mixin');
var Dimensions = require('Dimensions');
var {width, height} = Dimensions.get('window');

var {
  AppRegistry,
  StyleSheet,
  Image,
  Text,
  ScrollView,
  View
} = React;

var PAGE_CHANGE_DELAY = 4000;

/**
 * Animates pages in cycle
 * (loop possible if children count > 1)
*/
var Carousel = React.createClass({
    propTypes:{
        children: React.PropTypes.node.isRequired,
        delay: React.PropTypes.number,
        style: View.propTypes.style
    },
    mixins: [TimerMixin],
    getDefaultProps: function() {
      return {delay: PAGE_CHANGE_DELAY};
    },
    getInitialState: function() {
      if (!!this.props.children) {
        var childrenCount = this.props.children.length;
        return {
          contentOffset: {x: childrenCount > 1 ? width : 0, y: 0},
          currentPage: childrenCount > 1 ? 1 : 0,
          hasChildren: true,
        };
      } else {
        return {
          hasChildren: false,
        }
      }
    },
    componentDidMount:function(){
      if (this.state.hasChildren) {
        this._setUpTimer();
      }
    },
    _onScrollBegin: function(event) {
      clearTimeout(this.timer);
    },
    _onScrollEnd: function(event) {
      this._setUpTimer();

      var offset = event.nativeEvent.contentOffset;

      var childrenCount = this.props.children.length;
      if (offset.x == 0) {
        offset = {x: childrenCount*width, y: 0};
      } else if (offset.x == (childrenCount+1)*width) {
        offset = {x: width, y: 0};
      }

      this._calculateCurrentPage(offset.x);
      this.setState({contentOffset: offset});
    },
    _setUpTimer: function() {
      //only for cycling
      if (this.props.children.length > 1) {
        clearTimeout(this.timer);
        this.timer = this.setTimeout(this._animateNextPage, this.props.delay);
      }
    },
    _animateNextPage: function() {
      var k = this.state.currentPage;
      k++;

      this.setState({currentPage: k});
      this.refs.scrollView.scrollTo(0, k*width);
      this._setUpTimer();
    },
    _calculateCurrentPage: function(offset) {
      var page = Math.floor((offset - width/2) / width) + 1;
      this.setState({currentPage: page});
    },
    //TODO: add optional `dots` for displaying current page (like pageControl)
    render: function() {
      var pages = [];

      if (!this.state.hasChildren) {
        return (
          <Text style={{backgroundColor: 'white'}}>
            You are supposed to add children inside Carousel
          </Text>
        );
      }

      var children = this.props.children;

      //to make infinite pages structure like this is needed: 3-1-2-3-1
      //add last one at the 1st place
      if (children.length > 1) {
        pages.push(children[children.length-1]);
      }

      //add all pages
      for (var i=0; i<children.length; i++) {
        pages.push(children[i]);
      }

      //add first one at the last place
      if (children.length > 1) {
        pages.push(children[0]);
      }

      pages = pages.map((page, i) => {
        return <View style={{width: width}} key={"page"+i}>{page}</View>;
      });

      return (
        <ScrollView
          ref='scrollView'
          onScrollBeginDrag={this._onScrollBegin}
          onMomentumScrollEnd={this._onScrollEnd}
          alwaysBounceHorizontal={false}
          alwaysBounceVertical={false}
          showsHorizontalScrollIndicator={false}
          horizontal={true}
          pagingEnabled={true}
          bounces={false}
          contentOffset={this.state.contentOffset}
          contentContainerStyle={[
            styles.horizontalScroll,
            this.props.style,
            {width: width*(this.props.children.length+(this.props.children.length>1?2:0))}
          ]}
        >
          {pages}
        </ScrollView>
      );
    },
});

var styles = StyleSheet.create({
  horizontalScroll: {
    height: height,
    position:'absolute'
  }
});

module.exports = Carousel;
<|endoftext|>


<commit_before>var config = require('./config');
var pocket = require('pocket-api');
var Q = require('q');
var request = require('request');
var express = require('express');
var session = require('express-session');

var app = express();

var pocketUrl = {
	redirect: 'https://getpocket.com/auth/authorize',
	get: 'https://getpocket.com/v3/get'
};
var headers = {
	"content-type": "application/x-www-form-urlencoded",
	"X-Accept": "application/json"
};

app.set('json spaces', 4);
app.use(session({
	secret: config.secret,
	cookie: {
		maxAge: 60000
	}
}));

app.get('/', function(req, res) {
	constructRedirectUrl(req.session).then(function(url) {
		res.send('Authorize');
	});
});

app.get('/response', function(req, res) {
	getAccessToken(req.session)
	.then(getFavorites)
	.then(function(favorites) {
		res.json(favorites);
	});
});

app.listen(config.port);

function constructRedirectUrl(session) {
	return Q.fcall(function() {
		return getRequestToken(session);
	})
	.then(function() {
		return pocketUrl.redirect + '?request_token=' + session.request_token + '&redirect_uri=' + config.url + ':' + config.port + '/response';
	});
}

function getRequestToken(session) {
	var d = Q.defer();
	pocket.getRequestToken(config.api_key, function(data) {
		session.request_token = data.code;
		d.resolve();
	});
	return d.promise;
}

function getAccessToken(session) {
	var d = Q.defer();
	pocket.getAccessToken(config.api_key, session.request_token, function(data) {
		session.username = data.username;
		session.access_token = data.access_token;
		d.resolve(session);
	});
	return d.promise;
}

function getFavorites(session) {
	var d = Q.defer();
	var options = {
		headers: headers,
		url: pocketUrl.get,
		body: 'consumer_key=' + config.api_key + '&access_token=' + session.access_token + '&favorite=1&detailType=complete'
	};

	request.post(options, function(error, response, body) {
		var list = JSON.parse(body).list;
		var favorites = Object.keys(list).map(function(key) {
			return list[key];
		});
		d.resolve(favorites);
	});
	return d.promise;
}

function getTags(favorites) {
	return [].concat.apply([], favorites.map(function(favorite) {
		var tagKeys = Object.keys(favorite.tags);
		return tagKeys.map(function(tagKey) {
			return favorite.tags[tagKey].tag;
		});
	})).filter(onlyUnique).sort();
}

function onlyUnique(value, index, self) {
	return self.indexOf(value) === index;
}
<commit_msg>Add initial Markdown construction<commit_after>var config = require('./config');
var pocket = require('pocket-api');
var Q = require('q');
var request = require('request');
var express = require('express');
var session = require('express-session');

var app = express();

var pocketUrl = {
	redirect: 'https://getpocket.com/auth/authorize',
	get: 'https://getpocket.com/v3/get'
};
var headers = {
	"content-type": "application/x-www-form-urlencoded",
	"X-Accept": "application/json"
};

app.set('json spaces', 4);
app.use(session({
	secret: config.secret,
	cookie: {
		maxAge: 60000
	}
}));

app.get('/', function(req, res) {
	constructRedirectUrl(req.session).then(function(url) {
		res.send('Authorize');
	});
});

app.get('/response', function(req, res) {
	getAccessToken(req.session)
	.then(getFavorites)
	.then(function(favorites) {
		res.send(formatFavorites(favorites, req.session));
	});
});

app.listen(config.port);

function constructRedirectUrl(session) {
	return Q.fcall(function() {
		return getRequestToken(session);
	})
	.then(function() {
		return pocketUrl.redirect + '?request_token=' + session.request_token + '&redirect_uri=' + config.url + ':' + config.port + '/response';
	});
}

function getRequestToken(session) {
	var d = Q.defer();
	pocket.getRequestToken(config.api_key, function(data) {
		session.request_token = data.code;
		d.resolve();
	});
	return d.promise;
}

function getAccessToken(session) {
	var d = Q.defer();
	pocket.getAccessToken(config.api_key, session.request_token, function(data) {
		session.username = data.username;
		session.access_token = data.access_token;
		d.resolve(session);
	});
	return d.promise;
}

function getFavorites(session) {
	var d = Q.defer();
	var options = {
		headers: headers,
		url: pocketUrl.get,
		body: 'consumer_key=' + config.api_key + '&access_token=' + session.access_token + '&favorite=1&detailType=complete'
	};

	request.post(options, function(error, response, body) {
		var list = JSON.parse(body).list;
		var favorites = Object.keys(list).map(function(key) {
			return list[key];
		});
		d.resolve(favorites);
	});
	return d.promise;
}

function formatFavorites(favorites, session) {
	var tags = getTags(favorites);
	var mkdn = '# ' + session.username + '\'s Reading List\n\n';
	mkdn += tags.map(function(tag) {
		return formatTag(favorites, tag);
	}).join('\n');
	return mkdn;
}

function getTags(favorites) {
	return [].concat.apply([], favorites.map(function(item) {
		return getItemTags(item);
	})).filter(onlyUnique).sort();
}

function getItemTags(item) {
	var tagKeys = Object.keys(item.tags);
	return tagKeys.map(function(tagKey) {
		return item.tags[tagKey].tag;
	});
}

function onlyUnique(value, index, self) {
	return self.indexOf(value) === index;
}

function formatTag(favorites, tag) {
	var filteredFavorites = filterFavorites(favorites, tag);
	var mkdn = '## ' + tag + '\n';
	mkdn += '\n' + filteredFavorites.map(function(item) {
		return '- [' + item.resolved_title + '](' + item.resolved_url + ') - ' + item.excerpt + '\n';
	}).join('');
	return mkdn;
}

function filterFavorites(favorites, tag) {
	return favorites.filter(function(item) {
		return getItemTags(item).indexOf(tag) != -1;
	}).sort(function(item1, item2) {
		return item1.resolved_title.localeCompare(item2.resolved_title);
	});
}
<|endoftext|>


<commit_before>// Process definition lists
//
'use strict';

module.exports = function deflist_plugin(md) {
  var isSpace = md.utils.isSpace;

  // Search `[:~][\n ]`, returns next pos after marker on success
  // or -1 on fail.
  function skipMarker(state, line) {
    var pos, marker,
        start = state.bMarks[line] + state.tShift[line],
        max = state.eMarks[line];

    if (start >= max) { return -1; }

    // Check bullet
    marker = state.src.charCodeAt(start++);
    if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1; }

    pos = state.skipSpaces(start);

    // require space after ":"
    if (start === pos) { return -1; }

    // no empty definitions, e.g. "  : "
    if (pos >= max) { return -1; }

    return start;
  }

  function markTightParagraphs(state, idx) {
    var i, l,
        level = state.level + 2;

    for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
      if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
        state.tokens[i + 2].hidden = true;
        state.tokens[i].hidden = true;
        i += 2;
      }
    }
  }

  function deflist(state, startLine, endLine, silent) {
    var ch,
        contentStart,
        ddLine,
        dtLine,
        itemLines,
        listLines,
        listTokIdx,
        max,
        nextLine,
        offset,
        oldDDIndent,
        oldIndent,
        oldParentType,
        oldSCount,
        oldTShift,
        oldTight,
        pos,
        prevEmptyEnd,
        tight,
        token;

    if (silent) {
      // quirk: validation mode validates a dd block only, not a whole deflist
      if (state.ddIndent < 0) { return false; }
      return skipMarker(state, startLine) >= 0;
    }

    nextLine = startLine + 1;
    if (nextLine >= endLine) { return false; }

    if (state.isEmpty(nextLine)) {
      nextLine++;
      if (nextLine >= endLine) { return false; }
    }

    if (state.sCount[nextLine] < state.blkIndent) { return false; }
    contentStart = skipMarker(state, nextLine);
    if (contentStart < 0) { return false; }

    // Start list
    listTokIdx = state.tokens.length;
    tight = true;

    token     = state.push('dl_open', 'dl', 1);
    token.map = listLines = [ startLine, 0 ];

    //
    // Iterate list items
    //

    dtLine = startLine;
    ddLine = nextLine;

    // One definition list can contain multiple DTs,
    // and one DT can be followed by multiple DDs.
    //
    // Thus, there is two loops here, and label is
    // needed to break out of the second one
    //
    /*eslint no-labels:0,block-scoped-var:0*/
    OUTER:
    for (;;) {
      prevEmptyEnd = false;

      token          = state.push('dt_open', 'dt', 1);
      token.map      = [ dtLine, dtLine ];

      token          = state.push('inline', '', 0);
      token.map      = [ dtLine, dtLine ];
      token.content  = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim();
      token.children = [];

      token          = state.push('dt_close', 'dt', -1);

      for (;;) {
        token     = state.push('dd_open', 'dd', 1);
        token.map = itemLines = [ nextLine, 0 ];

        pos = contentStart;
        max = state.eMarks[ddLine];
        offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine]);

        while (pos < max) {
          ch = state.src.charCodeAt(pos);

          if (isSpace(ch)) {
            if (ch === 0x09) {
              offset += 4 - offset % 4;
            } else {
              offset++;
            }
          } else {
            break;
          }

          pos++;
        }

        contentStart = pos;

        oldTight = state.tight;
        oldDDIndent = state.ddIndent;
        oldIndent = state.blkIndent;
        oldTShift = state.tShift[ddLine];
        oldSCount = state.sCount[ddLine];
        oldParentType = state.parentType;
        state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2;
        state.tShift[ddLine] = contentStart - state.bMarks[ddLine];
        state.sCount[ddLine] = offset;
        state.tight = true;
        state.parentType = 'deflist';

        state.md.block.tokenize(state, ddLine, endLine, true);

        // If any of list item is tight, mark list as tight
        if (!state.tight || prevEmptyEnd) {
          tight = false;
        }
        // Item become loose if finish with empty line,
        // but we should filter last element, because it means list finish
        prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1);

        state.tShift[ddLine] = oldTShift;
        state.sCount[ddLine] = oldSCount;
        state.tight = oldTight;
        state.parentType = oldParentType;
        state.blkIndent = oldIndent;
        state.ddIndent = oldDDIndent;

        token = state.push('dd_close', 'dd', -1);

        itemLines[1] = nextLine = state.line;

        if (nextLine >= endLine) { break OUTER; }

        if (state.sCount[nextLine] < state.blkIndent) { break OUTER; }
        contentStart = skipMarker(state, nextLine);
        if (contentStart < 0) { break; }

        ddLine = nextLine;

        // go to the next loop iteration:
        // insert DD tag and repeat checking
      }

      if (nextLine >= endLine) { break; }
      dtLine = nextLine;

      if (state.isEmpty(dtLine)) { break; }
      if (state.sCount[dtLine] < state.blkIndent) { break; }

      ddLine = dtLine + 1;
      if (ddLine >= endLine) { break; }
      if (state.isEmpty(ddLine)) { ddLine++; }
      if (ddLine >= endLine) { break; }

      if (state.sCount[ddLine] < state.blkIndent) { break; }
      contentStart = skipMarker(state, ddLine);
      if (contentStart < 0) { break; }

      // go to the next loop iteration:
      // insert DT and DD tags and repeat checking
    }

    // Finilize list
    token = state.push('dl_close', 'dl', -1);

    listLines[1] = nextLine;

    state.line = nextLine;

    // mark paragraphs tight if needed
    if (tight) {
      markTightParagraphs(state, listTokIdx);
    }

    return true;
  }

  md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] });
};
<commit_msg>Fix test case for blockquote inside dd<commit_after>// Process definition lists
//
'use strict';

module.exports = function deflist_plugin(md) {
  var isSpace = md.utils.isSpace;

  // Search `[:~][\n ]`, returns next pos after marker on success
  // or -1 on fail.
  function skipMarker(state, line) {
    var pos, marker,
        start = state.bMarks[line] + state.tShift[line],
        max = state.eMarks[line];

    if (start >= max) { return -1; }

    // Check bullet
    marker = state.src.charCodeAt(start++);
    if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1; }

    pos = state.skipSpaces(start);

    // require space after ":"
    if (start === pos) { return -1; }

    // no empty definitions, e.g. "  : "
    if (pos >= max) { return -1; }

    return start;
  }

  function markTightParagraphs(state, idx) {
    var i, l,
        level = state.level + 2;

    for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
      if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
        state.tokens[i + 2].hidden = true;
        state.tokens[i].hidden = true;
        i += 2;
      }
    }
  }

  function deflist(state, startLine, endLine, silent) {
    var ch,
        contentStart,
        ddLine,
        dtLine,
        itemLines,
        listLines,
        listTokIdx,
        max,
        nextLine,
        offset,
        oldDDIndent,
        oldIndent,
        oldParentType,
        oldSCount,
        oldTShift,
        oldTight,
        pos,
        prevEmptyEnd,
        tight,
        token;

    if (silent) {
      // quirk: validation mode validates a dd block only, not a whole deflist
      if (state.ddIndent < 0) { return false; }
      return skipMarker(state, startLine) >= 0;
    }

    nextLine = startLine + 1;
    if (nextLine >= endLine) { return false; }

    if (state.isEmpty(nextLine)) {
      nextLine++;
      if (nextLine >= endLine) { return false; }
    }

    if (state.sCount[nextLine] < state.blkIndent) { return false; }
    contentStart = skipMarker(state, nextLine);
    if (contentStart < 0) { return false; }

    // Start list
    listTokIdx = state.tokens.length;
    tight = true;

    token     = state.push('dl_open', 'dl', 1);
    token.map = listLines = [ startLine, 0 ];

    //
    // Iterate list items
    //

    dtLine = startLine;
    ddLine = nextLine;

    // One definition list can contain multiple DTs,
    // and one DT can be followed by multiple DDs.
    //
    // Thus, there is two loops here, and label is
    // needed to break out of the second one
    //
    /*eslint no-labels:0,block-scoped-var:0*/
    OUTER:
    for (;;) {
      prevEmptyEnd = false;

      token          = state.push('dt_open', 'dt', 1);
      token.map      = [ dtLine, dtLine ];

      token          = state.push('inline', '', 0);
      token.map      = [ dtLine, dtLine ];
      token.content  = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim();
      token.children = [];

      token          = state.push('dt_close', 'dt', -1);

      for (;;) {
        token     = state.push('dd_open', 'dd', 1);
        token.map = itemLines = [ nextLine, 0 ];

        pos = contentStart;
        max = state.eMarks[ddLine];
        offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine]);

        while (pos < max) {
          ch = state.src.charCodeAt(pos);

          if (isSpace(ch)) {
            if (ch === 0x09) {
              offset += 4 - offset % 4;
            } else {
              offset++;
            }
          } else {
            break;
          }

          pos++;
        }

        contentStart = pos;

        oldTight = state.tight;
        oldDDIndent = state.ddIndent;
        oldIndent = state.blkIndent;
        oldTShift = state.tShift[ddLine];
        oldSCount = state.sCount[ddLine];
        oldParentType = state.parentType;
        state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2;
        state.tShift[ddLine] = contentStart - state.bMarks[ddLine];
        state.sCount[ddLine] = offset;
        state.tight = true;
        state.parentType = 'deflist';

        state.md.block.tokenize(state, ddLine, endLine, true);

        // If any of list item is tight, mark list as tight
        if (!state.tight || prevEmptyEnd) {
          tight = false;
        }
        // Item become loose if finish with empty line,
        // but we should filter last element, because it means list finish
        prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1);

        state.tShift[ddLine] = oldTShift;
        state.sCount[ddLine] = oldSCount;
        state.tight = oldTight;
        state.parentType = oldParentType;
        state.blkIndent = oldIndent;
        state.ddIndent = oldDDIndent;

        token = state.push('dd_close', 'dd', -1);

        itemLines[1] = nextLine = state.line;

        if (nextLine >= endLine) { break OUTER; }

        if (state.sCount[nextLine] < state.blkIndent) { break OUTER; }
        contentStart = skipMarker(state, nextLine);
        if (contentStart < 0) { break; }

        ddLine = nextLine;

        // go to the next loop iteration:
        // insert DD tag and repeat checking
      }

      if (nextLine >= endLine) { break; }
      dtLine = nextLine;

      if (state.isEmpty(dtLine)) { break; }
      if (state.sCount[dtLine] < state.blkIndent) { break; }

      ddLine = dtLine + 1;
      if (ddLine >= endLine) { break; }
      if (state.isEmpty(ddLine)) { ddLine++; }
      if (ddLine >= endLine) { break; }

      if (state.sCount[ddLine] < state.blkIndent) { break; }
      contentStart = skipMarker(state, ddLine);
      if (contentStart < 0) { break; }

      // go to the next loop iteration:
      // insert DT and DD tags and repeat checking
    }

    // Finilize list
    token = state.push('dl_close', 'dl', -1);

    listLines[1] = nextLine;

    state.line = nextLine;

    // mark paragraphs tight if needed
    if (tight) {
      markTightParagraphs(state, listTokIdx);
    }

    return true;
  }

  md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference', 'blockquote' ] });
};
<|endoftext|>


<commit_before>var _ = require('lodash')
  , _s = require('underscore.string')
  , async = require('async')
  , dotty = require('dotty')
  , passport = require('passport')
  , passwordHash = require('password-hash');

module.exports = function (app, options) {
  var assembleUser = function (obj) {
    var user = {};

    _.each(options.accessLevels, function (lvl) {
      user[lvl] = {};
    });

    _.each(obj, function (val, key) {
      dotty.put(user, key, val);
    });

    return user;
  };

  var findUserId = function (model, user, callback) {
    if (_.isFunction(options.local.findUserId)) {
      return options.local.findUserId(model, user, callback);
    }

    var found = null;

    async.some(options.local.findUserId, function (path, callback) {
      path = path.split('.');

      var val = dotty.get(user, path)
        , coll = getCollection(path.shift())
        , doc = path.join('.')
        , target = {$query: {}};

      target.$query[doc] = val;
      var query = model.query(coll, target);

      model.fetch(query, function (err) {
        if (err) return callback(err);
        var userId = dotty.get(query.get(), '0.id');
        if (userId) found = userId;
        callback(userId);
      });
    }, function (userId) {
      callback(null, found);
    });
  };

  var getCollection = function (lvl) {
    return options.collectionName + (lvl ? _s.capitalize(lvl) : '');
  };

  options = _.merge(options || {}, {
    accessLevels: [
      'public',
      'private'
    ],
    clientConfigPath: '$auth',
    collectionName: 'users',
    local: {
      findUserId: [
        'private.local.email',
        'public.local.username'
      ],
      password: {
        hash: {},
        maximumLength: 100,
        minimumLength: 6,
        path: 'private.local.password'
      },
    }
    providers: {
      schema: {
        'public': ['displayName', 'username'],
        'private': '*'
      },
      strategies: {},
      path: 'providers'
    },
    session: {
      idPath: 'user.id',
      isRegisteredPath: 'user.isRegistered'
    },
    signInRoute: '/signin',
    signOutRoute: '/signout',
    signUpRoute: '/signup'
  });

  if (_.isString(options.local.findUserId)) {
    options.local.findUserId = [options.local.findUserId];
  }

  if (!options.accessLevels) {
    options.accessLevels = [''];
  }

  _.each(options.accessLevels, function (lvl) {
    var schema = options.providers.schema;
    if (!schema[lvl]) schema[lvl] = [];
    if (_.isString(schema[lvl])) schema[lvl] = [schema[lvl]];
  });

  _.each(options.providers.strategies, function (strategy, name) {
    _.merge(strategy, {
      callback: {
        popup: true,
        url: '/auth/' + name + '/callback'
      },
      config: {},
      module: 'passport-' + name,
      options: {
        url: '/auth/' + name
      },
      verify: function (callback) {
        return function () {
          var req = arguments[0]
            , profile = _.last(arguments, 2)[0]
            , profileId = arguments.length === 4 ? arguments[1] : profile.id
            , done = _.last(arguments);

          callback(req, profileId, profile, done);
        };
      }
    }, strategy);

    strategy.config.passReqToCallback = true;
  });

  return {
    init: function () {
      app.use(passport.initialize());
      app.use(passport.session());

      passport.serializeUser(function (user, done) {
        done(null, user.id);
      });

      passport.deserializeUser(function (userId, done) {
        return done(null, {id: userId});
      });

      _.each(options.providers.strategies, function (strategy, name) {
        var Strategy = require(strategy.module).Strategy;

        passport.use(new Strategy(strategy.config, strategy.verify(
          function (req, profileId, profile, done) {
            var model = req.getModel()
              , target = {$query: {}}
              , coll = getCollection(options.accessLevels[0])
              , doc = options.providers.path + '.' + name + '.id'
              , query = model.query(coll, target)
              , userId = model.get('_session.' + options.session.idPath);

            target.$query[doc] = profileId;

            model.fetch(query, function (err) {
              if (err) return done(err);
              var foundUser = dotty.get(query.get(), '0');

              if (foundUser) {
                userId = foundUser.id;
              } else {
                _.each(options.accessLevels, function (lvl) {
                  var coll = getCollection(lvl)
                    , part = model.at(coll + '.' + userId)
                    , prov = part.at(options.providers.path + '.' + name);

                  part.fetch(function (err) {
                    if (err) return done(err);
                    part.set('id', userId);
                    part.set('registered', true);
                    prov.set('id', profileId)

                    _.each(options.providers.schema[lvl], function (path) {
                      if (path === '*') {
                        prov.set(profile);
                        profile = {};
                      } else {
                        var val = dotty.get(profile, path);
                        if (!val) return;
                        prov.set(path, val);
                        dotty.remove(profile, path);
                      }
                    });
                  });
                });
              }

              model.set('_session.' + options.session.idPath, userId);
              model.set('_session.' + options.session.isRegisteredPath, true);
              dotty.put(req.session, options.session.idPath, userId);
              dotty.put(req.session, options.session.isRegisteredPath, true);
              done(null, {id: userId});
            });
          }
        )));
      });

      return function (req, res, next) {
        var model = req.getModel()
          , userId = dotty.get(req.session, options.session.idPath);

        if (userId) {
          var coll = getCollection(options.accessLevels[0])
            , part = model.at(coll + '.' + userId);

          part.fetch(function (err) {
            if (err) return next(err);
            model.set('_session.' + options.session.isRegisteredPath, true);
          });
        } else {
          userId = model.id();
          dotty.put(req.session, options.session.idPath, userId);

          _.each(options.accessLevels, function (lvl) {
            model.add(getCollection(lvl), {id: userId, registered: false});
          });

          model.set('_session.' + options.session.isRegisteredPath, false);
        }

        model.set('_session.' + options.session.idPath, userId);
        model.set(options.clientConfigPath + '.accessLevels', options.accessLevels);
        model.set(options.clientConfigPath + '.session', options.session);
        next();
      };
    },
    routes: function () {
      _.each(options.providers.strategies, function (strategy, name) {
        app.get(strategy.options.url, passport.authenticate(name, strategy.options));

        app.get(strategy.callback.url, passport.authenticate(name, strategy.callback), function (req, res) {
          if (!strategy.callback.popup) return res.redirect('/');

          var model = req.getModel()
            , userId = model.get('_session.' + options.session.idPath);

          // blank page with a script that immediately closes the window
          var script = _s.sprintf("<script>"
            + "if (opener && opener.location) {"
            + "opener.DERBY.app.model.set('_session.%s', '%s');"
            + "opener.DERBY.app.model.set('_session.%s', true);"
            + "}"
            + "window.close();"
            + "</script>",
            options.session.idPath,
            userId,
            options.session.isRegisteredPath
          );

          res.send(script);
        });
      });

      app.post(options.signInRoute, function (req, res) {
        var model = req.getModel()
          , user = assembleUser(req.body)
          , pass = dotty.get(user, options.local.password.path);

        findUserId(model, user, function (err, userId) {
          var failure = function (err) {
            if (err) console.error(err);
            return res.send();
          }

          var success = function () {
            dotty.put(req.session, options.session.idPath, userId);
            model.set('_session.' + options.session.idPath, userId);
            model.set('_session.' + options.session.isRegisteredPath, true);
            return res.send({id: userId, isRegistered: true});
          }

          if (err) return failure(err);
          if (!userId) return failure();
          if (!options.local.password) return success();

          var coll = getCollection(options.local.password.path.split('.')[0])
            , part = model.at(coll + '.' + userId)
            , path = options.local.password.path.split('.').slice(1).join('.')

          part.fetch(function (err) {
            if (err) return failure(err);
            var dbPass = part.get(path);

            if (options.local.password.hash) {
              return passwordHash.verify(pass, dbPass) ? success() : failure();
            }

            return dbPass === pass ? success() : failure();
          });
        });
      });

      app.post(options.signUpRoute, function (req, res) {
        var error = function (err) {
          return res.send();
        };

        var success = function () {
          return res.send();
        };

        var model = req.getModel()
          , user = assembleUser(req.body)
          , userId = dotty.get(req.session, options.session.idPath);

        if (options.local.password && options.local.password.hash) {
          var pass = dotty.get(user, options.local.password.path)
            , hash = passwordHash.generate(pass, options.local.password.hash);

          dotty.put(user, options.local.password.path, hash);
        }

        _.each(options.accessLevels, function (lvl) {
          var part = model.at(getCollection(lvl) + '.' + userId);

          part.fetch(function (err) {
            if (err) return error(err);
            var obj = _.merge(part.get(), user[lvl], {registered: true});
            part.set(obj);
          });
        });

        return success();
      });

      return function (req, res, next) {
        next();
      };
    }
  }
};<commit_msg>syntax error<commit_after>var _ = require('lodash')
  , _s = require('underscore.string')
  , async = require('async')
  , dotty = require('dotty')
  , passport = require('passport')
  , passwordHash = require('password-hash');

module.exports = function (app, options) {
  var assembleUser = function (obj) {
    var user = {};

    _.each(options.accessLevels, function (lvl) {
      user[lvl] = {};
    });

    _.each(obj, function (val, key) {
      dotty.put(user, key, val);
    });

    return user;
  };

  var findUserId = function (model, user, callback) {
    if (_.isFunction(options.local.findUserId)) {
      return options.local.findUserId(model, user, callback);
    }

    var found = null;

    async.some(options.local.findUserId, function (path, callback) {
      path = path.split('.');

      var val = dotty.get(user, path)
        , coll = getCollection(path.shift())
        , doc = path.join('.')
        , target = {$query: {}};

      target.$query[doc] = val;
      var query = model.query(coll, target);

      model.fetch(query, function (err) {
        if (err) return callback(err);
        var userId = dotty.get(query.get(), '0.id');
        if (userId) found = userId;
        callback(userId);
      });
    }, function (userId) {
      callback(null, found);
    });
  };

  var getCollection = function (lvl) {
    return options.collectionName + (lvl ? _s.capitalize(lvl) : '');
  };

  options = _.merge(options || {}, {
    accessLevels: [
      'public',
      'private'
    ],
    clientConfigPath: '$auth',
    collectionName: 'users',
    local: {
      findUserId: [
        'private.local.email',
        'public.local.username'
      ],
      password: {
        hash: {},
        maximumLength: 100,
        minimumLength: 6,
        path: 'private.local.password'
      },
    },
    providers: {
      schema: {
        'public': ['displayName', 'username'],
        'private': '*'
      },
      strategies: {},
      path: 'providers'
    },
    session: {
      idPath: 'user.id',
      isRegisteredPath: 'user.isRegistered'
    },
    signInRoute: '/signin',
    signOutRoute: '/signout',
    signUpRoute: '/signup'
  });

  if (_.isString(options.local.findUserId)) {
    options.local.findUserId = [options.local.findUserId];
  }

  if (!options.accessLevels) {
    options.accessLevels = [''];
  }

  _.each(options.accessLevels, function (lvl) {
    var schema = options.providers.schema;
    if (!schema[lvl]) schema[lvl] = [];
    if (_.isString(schema[lvl])) schema[lvl] = [schema[lvl]];
  });

  _.each(options.providers.strategies, function (strategy, name) {
    _.merge(strategy, {
      callback: {
        popup: true,
        url: '/auth/' + name + '/callback'
      },
      config: {},
      module: 'passport-' + name,
      options: {
        url: '/auth/' + name
      },
      verify: function (callback) {
        return function () {
          var req = arguments[0]
            , profile = _.last(arguments, 2)[0]
            , profileId = arguments.length === 4 ? arguments[1] : profile.id
            , done = _.last(arguments);

          callback(req, profileId, profile, done);
        };
      }
    }, strategy);

    strategy.config.passReqToCallback = true;
  });

  return {
    init: function () {
      app.use(passport.initialize());
      app.use(passport.session());

      passport.serializeUser(function (user, done) {
        done(null, user.id);
      });

      passport.deserializeUser(function (userId, done) {
        return done(null, {id: userId});
      });

      _.each(options.providers.strategies, function (strategy, name) {
        var Strategy = require(strategy.module).Strategy;

        passport.use(new Strategy(strategy.config, strategy.verify(
          function (req, profileId, profile, done) {
            var model = req.getModel()
              , target = {$query: {}}
              , coll = getCollection(options.accessLevels[0])
              , doc = options.providers.path + '.' + name + '.id'
              , query = model.query(coll, target)
              , userId = model.get('_session.' + options.session.idPath);

            target.$query[doc] = profileId;

            model.fetch(query, function (err) {
              if (err) return done(err);
              var foundUser = dotty.get(query.get(), '0');

              if (foundUser) {
                userId = foundUser.id;
              } else {
                _.each(options.accessLevels, function (lvl) {
                  var coll = getCollection(lvl)
                    , part = model.at(coll + '.' + userId)
                    , prov = part.at(options.providers.path + '.' + name);

                  part.fetch(function (err) {
                    if (err) return done(err);
                    part.set('id', userId);
                    part.set('registered', true);
                    prov.set('id', profileId)

                    _.each(options.providers.schema[lvl], function (path) {
                      if (path === '*') {
                        prov.set(profile);
                        profile = {};
                      } else {
                        var val = dotty.get(profile, path);
                        if (!val) return;
                        prov.set(path, val);
                        dotty.remove(profile, path);
                      }
                    });
                  });
                });
              }

              model.set('_session.' + options.session.idPath, userId);
              model.set('_session.' + options.session.isRegisteredPath, true);
              dotty.put(req.session, options.session.idPath, userId);
              dotty.put(req.session, options.session.isRegisteredPath, true);
              done(null, {id: userId});
            });
          }
        )));
      });

      return function (req, res, next) {
        var model = req.getModel()
          , userId = dotty.get(req.session, options.session.idPath);

        if (userId) {
          var coll = getCollection(options.accessLevels[0])
            , part = model.at(coll + '.' + userId);

          part.fetch(function (err) {
            if (err) return next(err);
            model.set('_session.' + options.session.isRegisteredPath, true);
          });
        } else {
          userId = model.id();
          dotty.put(req.session, options.session.idPath, userId);

          _.each(options.accessLevels, function (lvl) {
            model.add(getCollection(lvl), {id: userId, registered: false});
          });

          model.set('_session.' + options.session.isRegisteredPath, false);
        }

        model.set('_session.' + options.session.idPath, userId);
        model.set(options.clientConfigPath + '.accessLevels', options.accessLevels);
        model.set(options.clientConfigPath + '.session', options.session);
        next();
      };
    },
    routes: function () {
      _.each(options.providers.strategies, function (strategy, name) {
        app.get(strategy.options.url, passport.authenticate(name, strategy.options));

        app.get(strategy.callback.url, passport.authenticate(name, strategy.callback), function (req, res) {
          if (!strategy.callback.popup) return res.redirect('/');

          var model = req.getModel()
            , userId = model.get('_session.' + options.session.idPath);

          // blank page with a script that immediately closes the window
          var script = _s.sprintf("<script>"
            + "if (opener && opener.location) {"
            + "opener.DERBY.app.model.set('_session.%s', '%s');"
            + "opener.DERBY.app.model.set('_session.%s', true);"
            + "}"
            + "window.close();"
            + "</script>",
            options.session.idPath,
            userId,
            options.session.isRegisteredPath
          );

          res.send(script);
        });
      });

      app.post(options.signInRoute, function (req, res) {
        var model = req.getModel()
          , user = assembleUser(req.body)
          , pass = dotty.get(user, options.local.password.path);

        findUserId(model, user, function (err, userId) {
          var failure = function (err) {
            if (err) console.error(err);
            return res.send();
          }

          var success = function () {
            dotty.put(req.session, options.session.idPath, userId);
            model.set('_session.' + options.session.idPath, userId);
            model.set('_session.' + options.session.isRegisteredPath, true);
            return res.send({id: userId, isRegistered: true});
          }

          if (err) return failure(err);
          if (!userId) return failure();
          if (!options.local.password) return success();

          var coll = getCollection(options.local.password.path.split('.')[0])
            , part = model.at(coll + '.' + userId)
            , path = options.local.password.path.split('.').slice(1).join('.')

          part.fetch(function (err) {
            if (err) return failure(err);
            var dbPass = part.get(path);

            if (options.local.password.hash) {
              return passwordHash.verify(pass, dbPass) ? success() : failure();
            }

            return dbPass === pass ? success() : failure();
          });
        });
      });

      app.post(options.signUpRoute, function (req, res) {
        var error = function (err) {
          return res.send();
        };

        var success = function () {
          return res.send();
        };

        var model = req.getModel()
          , user = assembleUser(req.body)
          , userId = dotty.get(req.session, options.session.idPath);

        if (options.local.password && options.local.password.hash) {
          var pass = dotty.get(user, options.local.password.path)
            , hash = passwordHash.generate(pass, options.local.password.hash);

          dotty.put(user, options.local.password.path, hash);
        }

        _.each(options.accessLevels, function (lvl) {
          var part = model.at(getCollection(lvl) + '.' + userId);

          part.fetch(function (err) {
            if (err) return error(err);
            var obj = _.merge(part.get(), user[lvl], {registered: true});
            part.set(obj);
          });
        });

        return success();
      });

      return function (req, res, next) {
        next();
      };
    }
  }
};<|endoftext|>


<commit_before>var express = require("express");
var body_parser = require("body-parser");
var app = express();
var http = require("http").Server(app);
var io = require("socket.io")(http);
var sass_middleware = require("node-sass-middleware");
var sheet_db = require("./sheet_db");
var user_model = require("./user_model");
var chat_db = require("./chat_db");
var stats = require("./stats");

app.use(body_parser.json());

/* Stylesheets */
app.use(sass_middleware({
    src: "./sass",
    dest: "./public/css",
    debug: false,
    outputStyle: "compressed",
    prefix: "/css"
}));

// Serve static files
app.use(express.static('public'));

app.locals.pretty = true;

// Views
app.set('views', './views')
app.set('view engine', 'pug');

var namespaces = [];

app.get('/', function (req, res) {
    res.render('base',{landing:true});
});

app.get("/sheet/:id",function (req, res) {
    var sheet_id = req.params.id;

    // See if namespace is already in memory
    if(namespaces.indexOf(sheet_id) <= -1){
        new_namespace(sheet_id);
    }

    res.render('base');
});

app.get("/copy/:id",function (req, res) {
    var sheet_id = req.params.id;
    var new_id = require("./tokens").generate_token();

    sheet_db.get_sheet(sheet_id, function(data){
        data.params.locked = false;
        sheet_db.store_sheet(new_id, data);
        res.redirect("/sheet/"+new_id);
    });
});

app.get("/new",function (req, res) {
    res.redirect("/sheet/"+(require("./tokens").generate_token()));
});

function new_namespace(namespace){
    var nsp = io.of("/"+namespace);

    livecalc(namespace, nsp);

}

/* Sidebar chat */
function livechat(namespace, nsp, socket, user){
    var user;

    var exports = {};

    exports.set_user = function(new_user){
        user = new_user;
    };

    socket.on("load more messages",function(last_sent){
        chat_db.get_conv(namespace,function(data){
            socket.emit("past messages", data);
        });
    });

    socket.on("new message", function(data){
        var data = {
            message: data.message,
            sender: user.get_nickname(),
            user_id: user.get_id()
        };

        chat_db.add_message(namespace, data);

        socket.broadcast.emit("new message", data);
        socket.emit("own message", data);
    });

    return exports;
}

function livecalc(namespace, nsp){
    var model = require("./sheet_model").create();
    var user_count = 0;

    namespaces.push(namespace);

    // Check if sheet exists
    // Then load it and serve normally
    sheet_db.exists(namespace, function(exists){
        if(exists){
            sheet_db.get_sheet(namespace, function(data){
                model.set_sheet(data);
                listen();
            })
        } else {
            // Nope, new sheet
            stats.new_sheet();
            listen();
        }
    });

    function listen(){
        var users = {};

        nsp.on("connection", function(socket){
            // rate limiting
            if(user_count >= 3){
                socket.emit("too many users");
                return;
            }

            stats.new_sheet_visit(namespace);

            stats.get_sheet_visits(namespace, function(num){
                nsp.emit("sheet visit count", num);
            });

            user_count++;
            console.log("connection - " + user_count + " users");
            nsp.emit("user count", user_count);

            // Temporary user
            // Will be saved at disconnection
            // To keep nickname and other info
            // Will be changed immediatly with socket.io
            // if the browser already contains a user_id
            // in localStorage
            var user = user_model.create();
            var user_id = user.get_id();

            users[user_id] = {focus:-1};

            var chat = livechat(namespace, nsp, socket, user);

            /*
              Build array containing array of nicknames
              of user focussing on each cell

              [
              ["Paul","Anonymous"],
              [],
              ["George"]
              ]

              Goal: show the users who's editing what.

            */
            function send_focus_index(){
                var fi = [];

                for(var i = 0; i < model.get_length(); i++){
                    fi.push([]);
                }

                if(!model.is_locked()){
                    for(var i in users){
                        var user = users[i];
                        if(user == undefined){
                            continue;
                        }
                        if( user.focus != -1 ){
                            fi[user.focus].push(user.nickname);
                        }
                    }
                }
                nsp.emit("focus index", fi);
            }

            // Send sheet to user
            socket.emit("sheet", JSON.stringify(model.get_sheet()));

            socket.on("set nickname",function(data){
                // Prevent XSS
                var nickname = data.nickname.replace(/[^A-Za-z0-9\-]/g,"");
                users[user_id].nickname = nickname;
                user.set_nickname(nickname);
                // At this point, we can store in db
                user.save();
                send_focus_index();
            });

            // When browser asks user id
            socket.on("give user id",function(){
                // We send it
                send_user_id()
            });

            // When browser already has a user id
            socket.on("user id",function(data){
                var new_id = data.user_id;
                var old_id = user.get_id();

                user_model.exists(new_id, function(exists){
                    if(exists){
                        // This ID is effectively in database
                        // Todo: prevent session hijacking
                        // with tokens or something
                        // (everybody can se user ids now)
                        // Not a priority now because it does not
                        // give access to anything
                        user = user_model.User(new_id);

                        user.fetch(function(){
                            user_id = user.get_id();
                            users[user_id] = user.get_public_data();
                            chat.set_user(user);
                            send_user_data();

                            // Delete old temp user in memory
                            if(new_id != old_id){
                                delete users[old_id];
                            }
                        });
                    } else {
                        // Nope, we don't have this id
                        // Here is your new one
                        send_user_id();
                    }
                });

            });

            function send_user_id(){
                socket.emit("user id",{
                    user_id: user_id
                });
            }

            function send_user_data(){
                socket.emit("user data", {
                    nickname: user.get_nickname()
                });
            }

            socket.on("set focus",function(data){
                if(model.is_locked()){
                    return;
                }

                var index = data.index;
                users[user_id].focus = index;

                send_focus_index();
            });

            socket.on("lock sheet",function(data){
                // Don't lock demo
                if(namespace == "demo"){
                    return;
                }

                // Already locked?
                if(model.is_locked()){
                    return;
                }

                model.lock();
                save(true);

                send_focus_index();

                socket.emit("sheet locked", {
                    initiator: users[user_id].nickname
                });
            });

            socket.on("edit cell", function(data){
                if(!model.is_locked()){
                    model.edit(data);
                    socket.broadcast.emit("edit cell", data);
                    save();
                }
            });

            socket.on("delete cell", function(data){
                if(!model.is_locked()){
                    model.remove(data);

                    save();
                    socket.broadcast.emit("delete cell", data);
                }
            });

            socket.on("disconnect",function(socket){
                console.log("disconnection");
                user_count--;
                nsp.emit("user count", user_count);
                // Save user in memory
                user.save();

                // Delete user from memory
                delete users[user_id];
                send_focus_index();
            });
        });

        function save(even_if_locked){
            var even_if_locked = even_if_locked || false;
            if(!model.is_locked() || even_if_locked){
                sheet_db.store_sheet(namespace, model.get_sheet());
            }
        }
    }
}

http.listen(3000);
<commit_msg>more informative log<commit_after>var express = require("express");
var body_parser = require("body-parser");
var app = express();
var http = require("http").Server(app);
var io = require("socket.io")(http);
var sass_middleware = require("node-sass-middleware");
var sheet_db = require("./sheet_db");
var user_model = require("./user_model");
var chat_db = require("./chat_db");
var stats = require("./stats");

var site_user_count = 0;

app.use(body_parser.json());

/* Stylesheets */
app.use(sass_middleware({
    src: "./sass",
    dest: "./public/css",
    debug: false,
    outputStyle: "compressed",
    prefix: "/css"
}));

// Serve static files
app.use(express.static('public'));

app.locals.pretty = true;

// Views
app.set('views', './views')
app.set('view engine', 'pug');

var namespaces = [];

app.get('/', function (req, res) {
    res.render('base',{landing:true});
});

app.get("/sheet/:id",function (req, res) {
    var sheet_id = req.params.id;

    // See if namespace is already in memory
    if(namespaces.indexOf(sheet_id) <= -1){
        new_namespace(sheet_id);
    }

    res.render('base');
});

app.get("/copy/:id",function (req, res) {
    var sheet_id = req.params.id;
    var new_id = require("./tokens").generate_token();

    sheet_db.get_sheet(sheet_id, function(data){
        data.params.locked = false;
        sheet_db.store_sheet(new_id, data);
        res.redirect("/sheet/"+new_id);
    });
});

app.get("/new",function (req, res) {
    res.redirect("/sheet/"+(require("./tokens").generate_token()));
});

function new_namespace(namespace){
    var nsp = io.of("/"+namespace);

    livecalc(namespace, nsp);

}

/* Sidebar chat */
function livechat(namespace, nsp, socket, user){
    var user;

    var exports = {};

    exports.set_user = function(new_user){
        user = new_user;
    };

    socket.on("load more messages",function(last_sent){
        chat_db.get_conv(namespace,function(data){
            socket.emit("past messages", data);
        });
    });

    socket.on("new message", function(data){
        var data = {
            message: data.message,
            sender: user.get_nickname(),
            user_id: user.get_id()
        };

        chat_db.add_message(namespace, data);

        socket.broadcast.emit("new message", data);
        socket.emit("own message", data);
    });

    return exports;
}

function livecalc(namespace, nsp){
    var model = require("./sheet_model").create();
    var sheet_user_count = 0;

    namespaces.push(namespace);

    // Check if sheet exists
    // Then load it and serve normally
    sheet_db.exists(namespace, function(exists){
        if(exists){
            sheet_db.get_sheet(namespace, function(data){
                model.set_sheet(data);
                listen();
            })
        } else {
            // Nope, new sheet
            stats.new_sheet();
            listen();
        }
    });

    function listen(){
        var users = {};

        nsp.on("connection", function(socket){
            // rate limiting
            if(sheet_user_count >= 3){
                socket.emit("too many users");
                return;
            }

            stats.new_sheet_visit(namespace);

            stats.get_sheet_visits(namespace, function(num){
                nsp.emit("sheet visit count", num);
            });

            sheet_user_count++;
            site_user_count++;

            console.log(
                "disconnection - " +
                    site_user_count +
                    " users in site, " +
                    sheet_user_count +
                    " users in sheet " +
                    namespace
            );

            nsp.emit("user count", sheet_user_count);

            // Temporary user
            // Will be saved at disconnection
            // To keep nickname and other info
            // Will be changed immediatly with socket.io
            // if the browser already contains a user_id
            // in localStorage
            var user = user_model.create();
            var user_id = user.get_id();

            users[user_id] = {focus:-1};

            var chat = livechat(namespace, nsp, socket, user);

            /*
              Build array containing array of nicknames
              of user focussing on each cell

              [
              ["Paul","Anonymous"],
              [],
              ["George"]
              ]

              Goal: show the users who's editing what.

            */
            function send_focus_index(){
                var fi = [];

                for(var i = 0; i < model.get_length(); i++){
                    fi.push([]);
                }

                if(!model.is_locked()){
                    for(var i in users){
                        var user = users[i];
                        if(user == undefined){
                            continue;
                        }
                        if( user.focus != -1 ){
                            fi[user.focus].push(user.nickname);
                        }
                    }
                }
                nsp.emit("focus index", fi);
            }

            // Send sheet to user
            socket.emit("sheet", JSON.stringify(model.get_sheet()));

            socket.on("set nickname",function(data){
                // Prevent XSS
                var nickname = data.nickname.replace(/[^A-Za-z0-9\-]/g,"");
                users[user_id].nickname = nickname;
                user.set_nickname(nickname);
                // At this point, we can store in db
                user.save();
                send_focus_index();
            });

            // When browser asks user id
            socket.on("give user id",function(){
                // We send it
                send_user_id()
            });

            // When browser already has a user id
            socket.on("user id",function(data){
                var new_id = data.user_id;
                var old_id = user.get_id();

                user_model.exists(new_id, function(exists){
                    if(exists){
                        // This ID is effectively in database
                        // Todo: prevent session hijacking
                        // with tokens or something
                        // (everybody can se user ids now)
                        // Not a priority now because it does not
                        // give access to anything
                        user = user_model.User(new_id);

                        user.fetch(function(){
                            user_id = user.get_id();
                            users[user_id] = user.get_public_data();
                            chat.set_user(user);
                            send_user_data();

                            // Delete old temp user in memory
                            if(new_id != old_id){
                                delete users[old_id];
                            }
                        });
                    } else {
                        // Nope, we don't have this id
                        // Here is your new one
                        send_user_id();
                    }
                });

            });

            function send_user_id(){
                socket.emit("user id",{
                    user_id: user_id
                });
            }

            function send_user_data(){
                socket.emit("user data", {
                    nickname: user.get_nickname()
                });
            }

            socket.on("set focus",function(data){
                if(model.is_locked()){
                    return;
                }

                var index = data.index;
                users[user_id].focus = index;

                send_focus_index();
            });

            socket.on("lock sheet",function(data){
                // Don't lock demo
                if(namespace == "demo"){
                    return;
                }

                // Already locked?
                if(model.is_locked()){
                    return;
                }

                model.lock();
                save(true);

                send_focus_index();

                socket.emit("sheet locked", {
                    initiator: users[user_id].nickname
                });
            });

            socket.on("edit cell", function(data){
                if(!model.is_locked()){
                    model.edit(data);
                    socket.broadcast.emit("edit cell", data);
                    save();
                }
            });

            socket.on("delete cell", function(data){
                if(!model.is_locked()){
                    model.remove(data);

                    save();
                    socket.broadcast.emit("delete cell", data);
                }
            });

            socket.on("disconnect",function(socket){
                console.log(
                    "disconnection - " +
                        site_user_count +
                        " users in site, " +
                        sheet_user_count +
                        " users in sheet " +
                        namespace
                );

                sheet_user_count--;
                site_user_count--;

                nsp.emit("user count", sheet_user_count);
                // Save user in memory
                user.save();

                // Delete user from memory
                delete users[user_id];
                send_focus_index();
            });
        });

        function save(even_if_locked){
            var even_if_locked = even_if_locked || false;
            if(!model.is_locked() || even_if_locked){
                sheet_db.store_sheet(namespace, model.get_sheet());
            }
        }
    }
}

http.listen(3000);
<|endoftext|>