<commit_before>import Ember from 'ember';
import Space from 'hyperchannel/models/space';
import Channel from 'hyperchannel/models/channel';
import UserChannel from 'hyperchannel/models/user_channel';
import Message from 'hyperchannel/models/message';
import config from 'hyperchannel/config/environment';
import moment from 'moment';
import { storageFor as localStorageFor } from 'ember-local-storage';

const {
  Service,
  inject: {
    service
  },
  RSVP,
  isEmpty,
  isPresent,
  Logger,
  get
} = Ember;

export default Service.extend({
  userSettings: localStorageFor('user-settings'),
  ajax: service(),
  logger: service(),
  storage: service('remotestorage'),

  spaces: null,

  instantiateSpacesAndChannels() {
    this.set('spaces', []);
    let rs = this.get('storage.rs');

    return new RSVP.Promise((resolve, reject) => {
      rs.kosmos.spaces.getAll().then(spaceData => {
        if (isEmpty(Object.keys(spaceData))) {
          Logger.debug('No space data found in RS. Adding default space...');
          this.get('storage').addDefaultSpace().then((space) => {
            this.connectToIRCServer(space);
            this.get('spaces').pushObject(space);
            this.instantiateChannels();
            resolve();
          });
        } else {
          Object.keys(spaceData).forEach((id) => {
            let space = Space.create({
              name: spaceData[id].name,
              protocol: spaceData[id].protocol,
              server: spaceData[id].server,
              channelList: spaceData[id].channels
            });
            this.connectToIRCServer(space);
            this.get('spaces').pushObject(space);
          });
          this.instantiateChannels();
          resolve();
        }
      }, e => {
        this.log('error', 'couldn\'d load spaces from RS', e);
        reject();
      });
    });
  },

  connectToIRCServer(space) {
    this.sockethub.ActivityStreams.Object.create({
      '@id': space.get('sockethubPersonId'),
      '@type': "person",
      displayName: space.get('server.nickname')
    });

    var credentials = {
      actor: space.get('sockethubPersonId'),
      context: 'irc',
      object: {
        '@type': 'credentials',
        nick: space.get('server.nickname'),
        server: space.get('server.hostname'),
        port: space.get('server.port'),
        secure: space.get('server.secure')
      }
    };

    this.log('connection', 'connecting to irc', credentials);
    this.sockethub.socket.emit('credentials', credentials);
  },

  transferMessage(space, target, content) {
    let job = {
      context: 'irc',
      '@type': 'send',
      actor: space.get('sockethubPersonId'),
      target: target,
      object: {
        '@type': 'message',
        content: content
      }
    };

    this.log('send', 'sending message job', job);
    this.sockethub.socket.emit('message', job);
  },

  transferMeMessage(space, target, content) {
    let job = {
      context: 'irc',
      '@type': 'send',
      actor: space.get('sockethubPersonId'),
      target: target,
      object: {
        '@type': 'me',
        content: content
      }
    };

    this.log('send', 'sending message job', job);
    this.sockethub.socket.emit('message', job);
  },

  setupListeners() {
    this.sockethub.socket.on('completed', (message) => {
      this.log('sh_completed', message);

      switch(message['@type']) {
        case 'join':
          if (message['@type'] === 'join') {
            var space = this.get('spaces').findBy('sockethubPersonId', message.actor);
            if (!isEmpty(space)) {
              var channel = space.get('channels').findBy('sockethubChannelId', message.target);
              if (!isEmpty(channel)) {
                channel.set('connected', true);
                this.observeChannel(space.get('sockethubPersonId'), channel.get('sockethubChannelId'));
              }
            }
          }
          break;
        case 'observe':
          if (message.object['@type'] === 'attendance') {
            this.updateChannelUserList(message);
          }
          break;
      }
    });

    this.sockethub.socket.on('message', (message) => {
      this.log('message', 'SH message', message);

      switch(message['@type']) {
        case 'observe':
          if (message.object['@type'] === 'attendance') {
            this.updateChannelUserList(message);
          }
          break;
        case 'join':
          this.addUserToChannelUserList(message);
          break;
        case 'leave':
          this.removeUserFromChannelUserList(message);
          break;
        case 'send':
          switch(message.object['@type']) {
            case 'message':
            case 'me':
              this.addMessageToChannel(message);
              break;
          }
          break;
        case 'update':
          if (message.object['@type'] === 'topic') {
            this.updateChannelTopic(message);
          }
          break;
      }

      // user list for a channel
    });

    this.sockethub.socket.on('failure', function(message) {
      Logger.error('SH failure', message);
    });
  },

  updateChannelUserList(message) {
    const channel = this.getChannelByMessage(message);
    if (channel) {
      channel.set('userList', message.object.members.sort());
    }
  },

  addUserToChannelUserList(message) {
    const channel = this.getChannelByMessage(message);
    if (channel) {
      channel.addUser(message.actor.displaName);
    }
  },

  removeUserFromChannelUserList(message) {
    const channel = this.getChannelByMessage(message);
    if (channel) {
      channel.removeUser(message.actor.displayName);
    }
  },

  getChannelByMessage(message) {
    var addressWithHostname, hostname;
    if (typeof message.actor === 'object') {
      addressWithHostname = message.actor['@id'];
    } else if (typeof message.actor === 'string') {
      addressWithHostname = message.actor;
    }

    hostname = addressWithHostname.match(/irc:\/\/(?:.+@)?(.+?)(?:\/|$)/)[1];

    var space = this.get('spaces').findBy('server.hostname', hostname);

    if (!isEmpty(space)) {
      var channel = space.get('channels').findBy('sockethubChannelId', message.target['@id']);
      if (!isEmpty(channel)) {
        return channel;
      }
    }
  },

  updateChannelTopic(message) {
    let hostname;
    if (typeof message.target === 'object') {
      hostname = message.target['@id'].match(/irc:\/\/(.+)\//)[1];
    } else if (typeof message.actor === 'string') {
      hostname = message.actor.match(/irc:\/\/.+\@(.+)/)[1];
    }

    let space = this.get('spaces').findBy('server.hostname', hostname);

    if (!isEmpty(space)) {
      let channel = space.get('channels').findBy('sockethubChannelId', message.target['@id']);

      if (isEmpty(channel)) {
        channel = this.createChannel(space, message.target['@id']);
      }

      let currentTopic = channel.get('topic');
      let newTopic = message.object.topic;

      channel.set('topic', newTopic);

      if (isPresent(currentTopic) && (newTopic !== currentTopic) && !channel.get('visible')) {
        Notification.requestPermission(function(){
          new Notification(channel.name, {
            body: `New Topic: ${newTopic}`
          });
        });
      }

      // let notification = Message.create({
      //   type: 'notification-topic-change',
      //   date: new Date(message.published),
      //   nickname: message.actor.displayName,
      //   content: message.object.topic
      // });

      // channel.get('messages').pushObject(notification);

    }
  },

  addMessageToChannel: function(message) {
    var space = this.get('spaces').findBy('server.hostname',
                message.actor['@id'].match(/irc:\/\/.+\@(.+)/)[1]);
    var nickname = space.get('userNickname');

    var targetChannelName;
    if (nickname === message.target.displayName) {
      targetChannelName = message.actor.displayName;
    } else {
      targetChannelName = message.target.displayName;
    }

    var channel = space.get('channels').findBy('name', targetChannelName);
    if (!channel) {
      channel = this.createChannel(space, targetChannelName);
    }

    let messageType;
    if (message.object['@type'] === 'me') {
      messageType = 'message-chat-me';
    } else {
      messageType = 'message-chat';
    }

    var channelMessage = Message.create({
      type: messageType,
      date: new Date(message.published),
      nickname: message.actor.displayName,
      content: message.object.content
    });

    // TODO should check for message and update sent status if exists
    if (message.actor.displayName !== nickname) {
      channel.addMessage(channelMessage);
    }
  },

  observeChannel: function(person, channelId) {
    var observeMsg = {
      context: 'irc',
      '@type': 'observe',
      actor: person,
      target: channelId,
      object: {
        '@type': 'attendance'
      }
    };

    Logger.debug('asking for attendance list', observeMsg);
    this.sockethub.socket.emit('message', observeMsg);
  },

  instantiateChannels: function() {
    this.get('spaces').forEach((space) => {
      space.set('channels', []);

      space.get('channelList').forEach((channelName) => {
        this.createChannel(space, channelName);
      });
    });
  },

  createChannel: function(space, channelName) {
    var channel = Channel.create({
      space: space,
      name: channelName,
      sockethubChannelId: `irc://${space.get('server.hostname')}/${channelName}`
    });

    this.joinChannel(space, channel, "room");
    space.get('channels').pushObject(channel);
    this.loadArchiveMessages(space, channel).catch(() => {});

    return channel;
  },

  loadLastMessages(space, channel, date) {
    let day = date ? moment(date) : moment();

    let maximumSearchDepth;
    if (channel.get('previousLogsDate')) {
      maximumSearchDepth = moment(channel.get('previousLogsDate')).subtract(14, 'days');
    } else {
      maximumSearchDepth = moment().subtract(14, 'days');
    }

    if (day.isBefore(maximumSearchDepth)) {
      channel.set('previousLogsDate', day.format('YYYY-MM-DD'));
      return;
    }

    return this.loadArchiveMessages(space, channel, day.format('YYYY-MM-DD')).catch(() => {
      day.subtract(1, 'day');
      return this.loadLastMessages(space, channel, day);
    });
  },

  loadArchiveMessages(space, channel, date) {
    let day = moment(date).utc();
    let logsUrl = `${config.publicLogsUrl}/${space.get('name').toLowerCase()}/channels/${channel.get('slug')}/`;
        logsUrl += day.format('YYYY/MM/DD');

    return this.get('ajax').request(logsUrl, {
      type: 'GET',
      dataType: 'json'
    }).then(archive => {
      get(archive, 'today.messages').forEach((message) => {
        this.log('message', message);

        let channelMessage = Message.create({
          type: 'message-chat',
          date: new Date(message.timestamp),
          nickname: message.from,
          content: message.text
        });

        channel.addMessage(channelMessage);
      });
      let previous = get(archive, 'today.previous');
      channel.set('previousLogsDate', previous.replace(/\//g, '-'));
    }).catch(error => {
      this.log('error', error);
      throw(error);
    });
  },

  createUserChannel: function(space, userName) {
    var channel = UserChannel.create({
      name: userName,
      sockethubChannelId: `irc://${space.get('server.hostname')}/${userName}`
    });

    this.joinChannel(space, channel, "person");
    space.get('channels').pushObject(channel);

    return channel;
  },

  removeChannel: function(space, channelName) {
    var channel = space.get('channels').findBy('name', channelName);
    this.leaveChannel(space, channel);

    space.get('channels').removeObject(channel);

    // Update persisted channel list
    this.get('storage.rs').kosmos.spaces.store(space.serialize())
      .then(() => this.log('leave', 'stored space', space.get('name')));

    return channel;
  },

  joinChannel: function(space, channel, type) {
    this.sockethub.ActivityStreams.Object.create({
      '@type': type,
      '@id': channel.get('sockethubChannelId'),
      displayName: channel.get('name')
    });

    var joinMsg = {
      context: 'irc',
      '@type': 'join',
      actor: space.get('sockethubPersonId'),
      target: channel.get('sockethubChannelId'),
      object: {}
    };

    this.log('join', 'joining channel', joinMsg);
    this.sockethub.socket.emit('message', joinMsg);
  },

  leaveChannel: function(space, channel) {
    this.sockethub.ActivityStreams.Object.create({
      '@type': "room",
      '@id': channel.get('sockethubChannelId'),
      displayName: channel.get('name')
    });

    var joinMsg = {
      context: 'irc',
      '@type': 'leave',
      actor: space.get('sockethubPersonId'),
      target: channel.get('sockethubChannelId'),
      object: {}
    };

    this.log('leave', 'leaving channel', joinMsg);
    this.sockethub.socket.emit('message', joinMsg);
  },

  changeTopic: function(space, channel, topic) {
    var topicMsg = {
      context: 'irc',
      '@type': 'update',
      actor: space.get('sockethubPersonId'),
      target: channel.get('sockethubChannelId'),
      object: {
        '@type': 'topic',
        topic: topic
      }
    };

    this.sockethub.socket.emit('message', topicMsg);
  },

  // Utility function
  log() {
    this.get('logger').log(...arguments);
  }
});
<commit_msg>Look for messages from last 2 days when joining a channel<commit_after>import Ember from 'ember';
import Space from 'hyperchannel/models/space';
import Channel from 'hyperchannel/models/channel';
import UserChannel from 'hyperchannel/models/user_channel';
import Message from 'hyperchannel/models/message';
import config from 'hyperchannel/config/environment';
import moment from 'moment';
import { storageFor as localStorageFor } from 'ember-local-storage';

const {
  Service,
  inject: {
    service
  },
  RSVP,
  isEmpty,
  isPresent,
  Logger,
  get
} = Ember;

export default Service.extend({
  userSettings: localStorageFor('user-settings'),
  ajax: service(),
  logger: service(),
  storage: service('remotestorage'),

  spaces: null,

  instantiateSpacesAndChannels() {
    this.set('spaces', []);
    let rs = this.get('storage.rs');

    return new RSVP.Promise((resolve, reject) => {
      rs.kosmos.spaces.getAll().then(spaceData => {
        if (isEmpty(Object.keys(spaceData))) {
          Logger.debug('No space data found in RS. Adding default space...');
          this.get('storage').addDefaultSpace().then((space) => {
            this.connectToIRCServer(space);
            this.get('spaces').pushObject(space);
            this.instantiateChannels();
            resolve();
          });
        } else {
          Object.keys(spaceData).forEach((id) => {
            let space = Space.create({
              name: spaceData[id].name,
              protocol: spaceData[id].protocol,
              server: spaceData[id].server,
              channelList: spaceData[id].channels
            });
            this.connectToIRCServer(space);
            this.get('spaces').pushObject(space);
          });
          this.instantiateChannels();
          resolve();
        }
      }, e => {
        this.log('error', 'couldn\'d load spaces from RS', e);
        reject();
      });
    });
  },

  connectToIRCServer(space) {
    this.sockethub.ActivityStreams.Object.create({
      '@id': space.get('sockethubPersonId'),
      '@type': "person",
      displayName: space.get('server.nickname')
    });

    var credentials = {
      actor: space.get('sockethubPersonId'),
      context: 'irc',
      object: {
        '@type': 'credentials',
        nick: space.get('server.nickname'),
        server: space.get('server.hostname'),
        port: space.get('server.port'),
        secure: space.get('server.secure')
      }
    };

    this.log('connection', 'connecting to irc', credentials);
    this.sockethub.socket.emit('credentials', credentials);
  },

  transferMessage(space, target, content) {
    let job = {
      context: 'irc',
      '@type': 'send',
      actor: space.get('sockethubPersonId'),
      target: target,
      object: {
        '@type': 'message',
        content: content
      }
    };

    this.log('send', 'sending message job', job);
    this.sockethub.socket.emit('message', job);
  },

  transferMeMessage(space, target, content) {
    let job = {
      context: 'irc',
      '@type': 'send',
      actor: space.get('sockethubPersonId'),
      target: target,
      object: {
        '@type': 'me',
        content: content
      }
    };

    this.log('send', 'sending message job', job);
    this.sockethub.socket.emit('message', job);
  },

  setupListeners() {
    this.sockethub.socket.on('completed', (message) => {
      this.log('sh_completed', message);

      switch(message['@type']) {
        case 'join':
          if (message['@type'] === 'join') {
            var space = this.get('spaces').findBy('sockethubPersonId', message.actor);
            if (!isEmpty(space)) {
              var channel = space.get('channels').findBy('sockethubChannelId', message.target);
              if (!isEmpty(channel)) {
                channel.set('connected', true);
                this.observeChannel(space.get('sockethubPersonId'), channel.get('sockethubChannelId'));
              }
            }
          }
          break;
        case 'observe':
          if (message.object['@type'] === 'attendance') {
            this.updateChannelUserList(message);
          }
          break;
      }
    });

    this.sockethub.socket.on('message', (message) => {
      this.log('message', 'SH message', message);

      switch(message['@type']) {
        case 'observe':
          if (message.object['@type'] === 'attendance') {
            this.updateChannelUserList(message);
          }
          break;
        case 'join':
          this.addUserToChannelUserList(message);
          break;
        case 'leave':
          this.removeUserFromChannelUserList(message);
          break;
        case 'send':
          switch(message.object['@type']) {
            case 'message':
            case 'me':
              this.addMessageToChannel(message);
              break;
          }
          break;
        case 'update':
          if (message.object['@type'] === 'topic') {
            this.updateChannelTopic(message);
          }
          break;
      }

      // user list for a channel
    });

    this.sockethub.socket.on('failure', function(message) {
      Logger.error('SH failure', message);
    });
  },

  updateChannelUserList(message) {
    const channel = this.getChannelByMessage(message);
    if (channel) {
      channel.set('userList', message.object.members.sort());
    }
  },

  addUserToChannelUserList(message) {
    const channel = this.getChannelByMessage(message);
    if (channel) {
      channel.addUser(message.actor.displaName);
    }
  },

  removeUserFromChannelUserList(message) {
    const channel = this.getChannelByMessage(message);
    if (channel) {
      channel.removeUser(message.actor.displayName);
    }
  },

  getChannelByMessage(message) {
    var addressWithHostname, hostname;
    if (typeof message.actor === 'object') {
      addressWithHostname = message.actor['@id'];
    } else if (typeof message.actor === 'string') {
      addressWithHostname = message.actor;
    }

    hostname = addressWithHostname.match(/irc:\/\/(?:.+@)?(.+?)(?:\/|$)/)[1];

    var space = this.get('spaces').findBy('server.hostname', hostname);

    if (!isEmpty(space)) {
      var channel = space.get('channels').findBy('sockethubChannelId', message.target['@id']);
      if (!isEmpty(channel)) {
        return channel;
      }
    }
  },

  updateChannelTopic(message) {
    let hostname;
    if (typeof message.target === 'object') {
      hostname = message.target['@id'].match(/irc:\/\/(.+)\//)[1];
    } else if (typeof message.actor === 'string') {
      hostname = message.actor.match(/irc:\/\/.+\@(.+)/)[1];
    }

    let space = this.get('spaces').findBy('server.hostname', hostname);

    if (!isEmpty(space)) {
      let channel = space.get('channels').findBy('sockethubChannelId', message.target['@id']);

      if (isEmpty(channel)) {
        channel = this.createChannel(space, message.target['@id']);
      }

      let currentTopic = channel.get('topic');
      let newTopic = message.object.topic;

      channel.set('topic', newTopic);

      if (isPresent(currentTopic) && (newTopic !== currentTopic) && !channel.get('visible')) {
        Notification.requestPermission(function(){
          new Notification(channel.name, {
            body: `New Topic: ${newTopic}`
          });
        });
      }

      // let notification = Message.create({
      //   type: 'notification-topic-change',
      //   date: new Date(message.published),
      //   nickname: message.actor.displayName,
      //   content: message.object.topic
      // });

      // channel.get('messages').pushObject(notification);

    }
  },

  addMessageToChannel: function(message) {
    var space = this.get('spaces').findBy('server.hostname',
                message.actor['@id'].match(/irc:\/\/.+\@(.+)/)[1]);
    var nickname = space.get('userNickname');

    var targetChannelName;
    if (nickname === message.target.displayName) {
      targetChannelName = message.actor.displayName;
    } else {
      targetChannelName = message.target.displayName;
    }

    var channel = space.get('channels').findBy('name', targetChannelName);
    if (!channel) {
      channel = this.createChannel(space, targetChannelName);
    }

    let messageType;
    if (message.object['@type'] === 'me') {
      messageType = 'message-chat-me';
    } else {
      messageType = 'message-chat';
    }

    var channelMessage = Message.create({
      type: messageType,
      date: new Date(message.published),
      nickname: message.actor.displayName,
      content: message.object.content
    });

    // TODO should check for message and update sent status if exists
    if (message.actor.displayName !== nickname) {
      channel.addMessage(channelMessage);
    }
  },

  observeChannel: function(person, channelId) {
    var observeMsg = {
      context: 'irc',
      '@type': 'observe',
      actor: person,
      target: channelId,
      object: {
        '@type': 'attendance'
      }
    };

    Logger.debug('asking for attendance list', observeMsg);
    this.sockethub.socket.emit('message', observeMsg);
  },

  instantiateChannels: function() {
    this.get('spaces').forEach((space) => {
      space.set('channels', []);

      space.get('channelList').forEach((channelName) => {
        this.createChannel(space, channelName);
      });
    });
  },

  createChannel: function(space, channelName) {
    var channel = Channel.create({
      space: space,
      name: channelName,
      sockethubChannelId: `irc://${space.get('server.hostname')}/${channelName}`
    });

    this.joinChannel(space, channel, "room");
    space.get('channels').pushObject(channel);

    this.loadLastMessages(space, channel, moment(), 2).catch(() => {});

    return channel;
  },

  loadLastMessages(space, channel, date, maximumDays = 14) {
    let day = date ? moment(date) : moment();

    let maximumSearchDepth;
    if (channel.get('previousLogsDate')) {
      maximumSearchDepth = moment(channel.get('previousLogsDate')).subtract(maximumDays, 'days');
    } else {
      maximumSearchDepth = moment().subtract(maximumDays, 'days');
    }

    if (day.isBefore(maximumSearchDepth)) {
      channel.set('previousLogsDate', day.format('YYYY-MM-DD'));
      return;
    }

    return this.loadArchiveMessages(space, channel, day.format('YYYY-MM-DD')).catch(() => {
      day.subtract(1, 'day');
      return this.loadLastMessages(space, channel, day);
    });
  },

  loadArchiveMessages(space, channel, date) {
    let day = moment(date).utc();
    let logsUrl = `${config.publicLogsUrl}/${space.get('name').toLowerCase()}/channels/${channel.get('slug')}/`;
        logsUrl += day.format('YYYY/MM/DD');

    return this.get('ajax').request(logsUrl, {
      type: 'GET',
      dataType: 'json'
    }).then(archive => {
      get(archive, 'today.messages').forEach((message) => {
        this.log('message', message);

        let channelMessage = Message.create({
          type: 'message-chat',
          date: new Date(message.timestamp),
          nickname: message.from,
          content: message.text
        });

        channel.addMessage(channelMessage);
      });
      let previous = get(archive, 'today.previous');
      channel.set('previousLogsDate', previous.replace(/\//g, '-'));
    }).catch(error => {
      this.log('error', error);
      throw(error);
    });
  },

  createUserChannel: function(space, userName) {
    var channel = UserChannel.create({
      name: userName,
      sockethubChannelId: `irc://${space.get('server.hostname')}/${userName}`
    });

    this.joinChannel(space, channel, "person");
    space.get('channels').pushObject(channel);

    return channel;
  },

  removeChannel: function(space, channelName) {
    var channel = space.get('channels').findBy('name', channelName);
    this.leaveChannel(space, channel);

    space.get('channels').removeObject(channel);

    // Update persisted channel list
    this.get('storage.rs').kosmos.spaces.store(space.serialize())
      .then(() => this.log('leave', 'stored space', space.get('name')));

    return channel;
  },

  joinChannel: function(space, channel, type) {
    this.sockethub.ActivityStreams.Object.create({
      '@type': type,
      '@id': channel.get('sockethubChannelId'),
      displayName: channel.get('name')
    });

    var joinMsg = {
      context: 'irc',
      '@type': 'join',
      actor: space.get('sockethubPersonId'),
      target: channel.get('sockethubChannelId'),
      object: {}
    };

    this.log('join', 'joining channel', joinMsg);
    this.sockethub.socket.emit('message', joinMsg);
  },

  leaveChannel: function(space, channel) {
    this.sockethub.ActivityStreams.Object.create({
      '@type': "room",
      '@id': channel.get('sockethubChannelId'),
      displayName: channel.get('name')
    });

    var joinMsg = {
      context: 'irc',
      '@type': 'leave',
      actor: space.get('sockethubPersonId'),
      target: channel.get('sockethubChannelId'),
      object: {}
    };

    this.log('leave', 'leaving channel', joinMsg);
    this.sockethub.socket.emit('message', joinMsg);
  },

  changeTopic: function(space, channel, topic) {
    var topicMsg = {
      context: 'irc',
      '@type': 'update',
      actor: space.get('sockethubPersonId'),
      target: channel.get('sockethubChannelId'),
      object: {
        '@type': 'topic',
        topic: topic
      }
    };

    this.sockethub.socket.emit('message', topicMsg);
  },

  // Utility function
  log() {
    this.get('logger').log(...arguments);
  }
});
<|endoftext|>


<commit_before>/*jslint node: true */
"use strict";

var debug = require('debug')('query-v1');
var Q = require('q');
var request = require('superagent');

function ManualAuthApp(secrets, options) {
  var rootThis = this;
  var app = options.app;

  this.clientId = secrets.web.client_id;
  this.clientSecret = secrets.web.client_secret;
  this.authUri = secrets.web.auth_uri;
  this.tokenUri = secrets.web.token_uri;
  this.appBaseUrl = options.appBaseUrl;
  this.appReturnUrl = this.appBaseUrl + "/auth/versionone/callback";

  app.get('/start', function (req, res) {
    var authUri = rootThis.authUri +
      '?response_type=code' +
      '&client_id=' + rootThis.clientId +
      '&redirect_uri=' + rootThis.appReturnUrl +
      '&scope=ap1v1 query-api-1.0' +
      '&state=foobaz';
    debug('/start redirecting to %s', authUri);
    res.redirect(authUri);
  });

  app.get('/auth/versionone/callback', function (req, res) {
    var pageRes = res;
    var tokenPromise;
    if (req.param('code')) {
      tokenPromise = rootThis.hitTokenUri({code: req.param('code')});
      tokenPromise.then(function fulfilled(tokensJson) {
        rootThis.tokenDfd.resolve(tokensJson);
        pageRes.send('got token');
        pageRes.end();
      }, function rejected(errMessage) {
        rootThis.tokenDfd.reject('error turning code into token', errMessage);
        pageRes.send('failed to get token');
        pageRes.end();
      });
    } else {
      rootThis.tokenDfd.reject("error obtaining authorization code");
      res.send("Didn't get authorization code!");
      res.end();
    }
  });

  this.tokenDfd = Q.defer();
}

/** Resolve an authorization code OR a refresh token into an access token.
 * Must be called with either
 *   {code: "**authorization code from oauth flow calback**"}
 *   -- OR --
 *   {refreshToken: "**refresh token from previous successful flow**"}
 */
ManualAuthApp.prototype.hitTokenUri = function (params) {
  var dfd = Q.defer();
  var tokenRequest = request.post(this.tokenUri)
        .send('client_id=' + this.clientId)
        .send('client_secret=' + this.clientSecret);

  if (params.code) {
    tokenRequest = tokenRequest
      .send('code=' + params.code)
      .send('grant_type=authorization_code')
      .send('redirect_uri=' + this.appReturnUrl)
      .send('scope=apiv1 query-api-1.0');
  } else if (params.refreshToken) {
    tokenRequest = tokenRequest
      .send('refresh_token=' + params.refreshToken)
      .send('grant_type=refresh_token');
  } else {
    dfd.reject('must call with code or refreshToken');
  }
  tokenRequest
    .end(function (res) {
      if (res.ok) {
        dfd.resolve(res.body);
      } else {
        dfd.reject("error getting access token", res.text);
      }
    });
  return dfd.promise;
};

ManualAuthApp.prototype.url = function () {
  return this.appBaseUrl + "/start";
};

ManualAuthApp.prototype.tokenPromise = function () {
  return this.tokenDfd.promise;
};

module.exports = ManualAuthApp;<commit_msg>Start giving auth app token responsibility Clients of the auth app don't want to cache the auth tokens themselves. Push token management into the auth app, or at least into another object the auth app collaborates with instead of forcing clients to keep track of tokens, and the age of access tokens.<commit_after>/*jslint node: true */
"use strict";

var debug = require('debug')('query-v1');
var Q = require('q');
var request = require('superagent');

function ManualAuthApp(secrets, options) {
  var rootThis = this;
  var app = options.app;

  this.clientId = secrets.web.client_id;
  this.clientSecret = secrets.web.client_secret;
  this.authUri = secrets.web.auth_uri;
  this.tokenUri = secrets.web.token_uri;
  this.appBaseUrl = options.appBaseUrl;
  this.cacheDirectory = options.cacheDirectory || process.cwd();
  this.appReturnUrl = this.appBaseUrl + "/auth/versionone/callback";

  app.get('/start', function (req, res) {
    var authUri = rootThis.authUri +
      '?response_type=code' +
      '&client_id=' + rootThis.clientId +
      '&redirect_uri=' + rootThis.appReturnUrl +
      '&scope=ap1v1 query-api-1.0' +
      '&state=foobaz';
    debug('/start redirecting to %s', authUri);
    res.redirect(authUri);
  });

  app.get('/auth/versionone/callback', function (req, res) {
    var pageRes = res;
    var tokenPromise;
    if (req.param('code')) {
      tokenPromise = rootThis.hitTokenUri({code: req.param('code')});
      tokenPromise.then(function fulfilled(tokensJson) {
        rootThis.tokenDfd.resolve(tokensJson);
        pageRes.send('got token');
        pageRes.end();
      }, function rejected(errMessage) {
        rootThis.tokenDfd.reject('error turning code into token', errMessage);
        pageRes.send('failed to get token');
        pageRes.end();
      });
    } else {
      rootThis.tokenDfd.reject("error obtaining authorization code");
      res.send("Didn't get authorization code!");
      res.end();
    }
  });

  this.tokenDfd = Q.defer();
}

/** Resolve an authorization code OR a refresh token into an access token.
 * Must be called with either
 *   {code: "**authorization code from oauth flow calback**"}
 *   -- OR --
 *   {refreshToken: "**refresh token from previous successful flow**"}
 */
ManualAuthApp.prototype.hitTokenUri = function (params) {
  var dfd = Q.defer();
  var tokenRequest = request.post(this.tokenUri)
        .send('client_id=' + this.clientId)
        .send('client_secret=' + this.clientSecret);

  if (params.code) {
    tokenRequest = tokenRequest
      .send('code=' + params.code)
      .send('grant_type=authorization_code')
      .send('redirect_uri=' + this.appReturnUrl)
      .send('scope=apiv1 query-api-1.0');
  } else if (params.refreshToken) {
    tokenRequest = tokenRequest
      .send('refresh_token=' + params.refreshToken)
      .send('grant_type=refresh_token');
  } else {
    dfd.reject('must call with code or refreshToken');
  }
  tokenRequest
    .end(function (res) {
      if (res.ok) {
        dfd.resolve(res.body);
      } else {
        dfd.reject("error getting access token", res.text);
      }
    });
  return dfd.promise;
};

ManualAuthApp.prototype.url = function () {
  return this.appBaseUrl + "/start";
};

ManualAuthApp.prototype.tokenPromise = function () {
  return this.tokenDfd.promise;
};

// TODO make the auth app responsible for caching and maintaining the tokens

// TODO lazily obtain a refresh token
ManualAuthApp.prototype.refreshTokenPromise = function() {
  // IDEA: Consider a forceRefresh option to ignore cached refresh tokens
  if (!this.refreshTokenDfd) {
    this.refreshToken = Q.defer();

    // TODO attempt reading from file cache

    // TODO generate a new token via oauth if necessary

  }

  return this.refreshTokenDfd.promise;
};

// TODO lazily obtain an accessToken
ManualAuthApp.prototype.accessTokenPromise = function(forceRefresh) {
  if (!this.accessTokenDfd || this.accessTokenIsStale() || forceRefresh) {
    // access token is too stale, doesn't exist, or we are being forced to refresh

    // TODO reject unfulfilled promises? Deal with forceRefresh and normal refresh conflicts

    this.refreshTokenPromise().then(function () {

    }, function (err) {

    });
  }

  return this.accessTokenDfd.promise;
};

ManualAuthApp.prototype.accessTokenIsStale = function() {
  // TODO return true if the access token I have is "too close" to its expiration time, or past it.
};

module.exports = ManualAuthApp;<|endoftext|>


<commit_before>(function cometSpawner() {
  var game, loop,
      resizeRunning = false;

  /******************
    Utility Object
  *******************/
  var utility = {
    htmlSize: function htmlSize() {
      return {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      };
    },

    weightElements: function weightElements() {
      var items = document.body.getElementsByTagName('*'),
          i, itemsLen;

      for (i = 0, itemsLen = items.length; i < itemsLen; i += 1) {

      }
    }
  };

  /******************
    Game Prototype
  *******************/
  function Game() {
    this.ignoredElements = ['html', 'head', 'body', 'script', 'style', 'link', 'meta', 'br', 'hr'];
    this.fps = 50;
    this.comets = [];
    this.canvas = document.createElement('canvas');

    var keysPressed = {
      'Alt': false,
      'Win': false, 'OS': false,
      'c': false,
      'success': false
    };
    var eventsFuncs = {
      keydownEvent: function keydown(e) {
        if (e.key in Object.keys(keysPressed)) {
          keysPressed[e.key] = true;
          if (keysPressed['Alt'] &&
              (keysPressed['Win'] || keysPressed['OS']) &&
              keysPressed['c']) {
            keysPressed['success'] = true;
          }
        }
      },
      keyupEvent: function keyup(e) {
        if (e.key in Object.keys(keysPressed)) {
          keysPressed[e.key] = false;
          if (keysPressed['success']) {
            keysPressed['success'] = false;
          }
        }
      },
      clickEvent: function click(e) {
        if (e.button === 0) {
          if (keysPressed['success']) {
            var comet = new Comet(e.clientX, e.clientY);
            comet.spawn();
          }
        } else if (e.button === 2) {
          var i, cometsLen;
          for (i = 0, cometsLen = game.comets.length; i < cometsLen; i += 1) {
            if (game.comets[i].isCollide({ left: e.clientX, right: e.clientX, top: e.clientY, bottom: e.clientY })) {
              game.comets.splice(i, 1);
              break;
            }
          }
        }
      },
      resizeEvent: function resize(e) {
        function resizeCanvas() {
          this.canvas.style.display = 'none';
          var htmlSize = utility.htmlSize();
          this.canvas.setAttribute('width', htmlSize.width);
          this.canvas.setAttribute('height', htmlSize.height);
          resizeRunning = false;
        }

        if (!resizeRunning) {
          resizeRunning = true;
          if (window.requestAnimationFrame) {
            window.requestAnimationFrame(resizeCanvas);
          }
        }
      }
    };

    this.events = {
      'keydown': eventsFuncs.keydownEvent,
      'keyup': eventsFuncs.keyupEvent,
      'click': eventsFuncs.clickEvent,
      'resize': eventsFuncs.resizeEvent
    };
  }

  Game.prototype = {
    start: function start() {
      // add canvas
      var htmlSize = utility.htmlSize();

      this.canvas.setAttribute('width', htmlSize.width);
      this.canvas.setAttribute('height', htmlSize.height);
      this.canvas.id = 'comet-canvas';
      document.body.appendChild(this.canvas);

      // add listeners
      var eventsKeys = Object.keys(this.events),
          i, eventsLen, eventsKey;

      for (i = 0, eventsLen = eventsKeys.length; i < eventsLen; i += 1) {
        eventsKey = eventsKeys[i];
        this.canvas.addEventListener(eventsKey, this.events[eventsKey]);
      }

      //loop = setInterval(this.update, 1000 / this.fps);
    },

    update: function update() {
      this.comets.forEach(function (comet) {
        comet.move();
      });
    },

    end: function end() {
      //clearInterval(loop);

      // remove listeners
      var eventsKeys = Object.keys(this.events),
          i, eventsLen, eventsKey;

      for (i = 0, eventsLen = eventsKeys.length; i < eventsLen; i += 1) {
        eventsKey = eventsKeys[i];
        this.canvas.removeEventListener(eventsKey, this.events[eventsKey]);
      }

      // remove canvas
      document.body.removeChild(this.canvas);
    }
  };

  /******************
    Comet Prototype
  *******************/
  function Comet(x, y) {
    this.x = x;
    this.y = y;
    this.size = 5;
    this.shape = new Path2D();
  }

  Comet.prototype = {
    length: function length(point) {
      var xDiff, yDiff;
      xDiff = point.x - this.x;
      yDiff = point.y - this.y;
      return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
    },

    angle: function angle(point) {
      var xDiff, yDiff;
      xDiff = point.x - this.x;
      yDiff = point.y - this.y;
      return Math.atan2(yDiff, xDiff);
    },

    move: function move() {
      // move towards denser elements

    },

    trail: function trail(rect) {
      //var rect = element.getBoundingClientRect();
      var isCollide =
        this.x > rect.left && this.y > rect.top &&
        this.x < rect.right + rect.width && this.y < rect.bottom + rect.height;

      if (isCollide) {
        // render comet trails by consuming colliding element (increase size and add colors)
      }
    },

    spawn: function spawn() {
      this.shape.arc(100, 35, 25, 0, 2 * Math.PI);
      this.shape.moveTo(this.x, this.y);
      game.comets.push(this);
    }
  };

  /******************
    Game Runner
  *******************/
  game = new Game();
  game.start();
  console.log(game);
}());
<commit_msg>update keydown logic<commit_after>(function cometSpawner() {
  var game, loop,
      resizeRunning = false;

  /******************
    Utility Object
  *******************/
  var utility = {
    htmlSize: function htmlSize() {
      return {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      };
    },

    weightElements: function weightElements() {
      var items = document.body.getElementsByTagName('*'),
          i, itemsLen;

      for (i = 0, itemsLen = items.length; i < itemsLen; i += 1) {

      }
    }
  };

  /******************
    Game Prototype
  *******************/
  function Game() {
    this.ignoredElements = ['html', 'head', 'body', 'script', 'style', 'link', 'meta', 'br', 'hr'];
    this.fps = 50;
    this.comets = [];
    this.canvas = document.createElement('canvas');

    var keysPressed = {
      '18': false, // Alt
      '17': false, // Control
      '67': false, // C
      'success': false
    };
    var eventsFuncs = {
      keydownEvent: function keydown(e) {
        var key = String(e.keyCode || e.which);
        if (Object.keys(keysPressed).indexOf(key) > -1) {
          keysPressed[key] = true;
          if (keysPressed['18'] &&
              keysPressed['17'] &&
              keysPressed['67']) {
            keysPressed['success'] = true;
          }
        }
      },
      keyupEvent: function keyup(e) {
        var key = String(e.keyCode || e.which);
        if (Object.keys(keysPressed).indexOf(key) > -1) {
          keysPressed[key] = false;
          if (keysPressed['success']) {
            keysPressed['success'] = false;
          }
        }
      },
      clickEvent: function click(e) {
        if (e.button === 0) {
          if (keysPressed['success']) {
            var comet = new Comet(e.clientX, e.clientY);
            comet.spawn();
          }
        } else if (e.button === 2) {
          var i, cometsLen;
          for (i = 0, cometsLen = game.comets.length; i < cometsLen; i += 1) {
            if (game.comets[i].isCollide({ left: e.clientX, right: e.clientX, top: e.clientY, bottom: e.clientY })) {
              game.comets.splice(i, 1);
              break;
            }
          }
        }
      },
      resizeEvent: function resize(e) {
        function resizeCanvas() {
          this.canvas.style.display = 'none';
          var htmlSize = utility.htmlSize();
          this.canvas.setAttribute('width', htmlSize.width);
          this.canvas.setAttribute('height', htmlSize.height);
          resizeRunning = false;
        }

        if (!resizeRunning) {
          resizeRunning = true;
          if (window.requestAnimationFrame) {
            window.requestAnimationFrame(resizeCanvas);
          }
        }
      }
    };

    this.events = {
      'keydown': eventsFuncs.keydownEvent,
      'keyup': eventsFuncs.keyupEvent,
      'click': eventsFuncs.clickEvent,
      'resize': eventsFuncs.resizeEvent
    };
  }

  Game.prototype = {
    start: function start() {
      // add canvas
      var htmlSize = utility.htmlSize();

      this.canvas.setAttribute('width', htmlSize.width);
      this.canvas.setAttribute('height', htmlSize.height);
      this.canvas.id = 'comet-canvas';
      document.body.appendChild(this.canvas);

      // add listeners
      var eventsKeys = Object.keys(this.events),
          i, eventsLen, eventsKey;

      for (i = 0, eventsLen = eventsKeys.length; i < eventsLen; i += 1) {
        eventsKey = eventsKeys[i];
        this.canvas.addEventListener(eventsKey, this.events[eventsKey]);
      }

      //loop = setInterval(this.update, 1000 / this.fps);
    },

    update: function update() {
      this.comets.forEach(function (comet) {
        comet.move();
      });
    },

    end: function end() {
      //clearInterval(loop);

      // remove listeners
      var eventsKeys = Object.keys(this.events),
          i, eventsLen, eventsKey;

      for (i = 0, eventsLen = eventsKeys.length; i < eventsLen; i += 1) {
        eventsKey = eventsKeys[i];
        this.canvas.removeEventListener(eventsKey, this.events[eventsKey]);
      }

      // remove canvas
      document.body.removeChild(this.canvas);
    }
  };

  /******************
    Comet Prototype
  *******************/
  function Comet(x, y) {
    this.x = x;
    this.y = y;
    this.size = 5;
    this.shape = new Path2D();
  }

  Comet.prototype = {
    length: function length(point) {
      var xDiff, yDiff;
      xDiff = point.x - this.x;
      yDiff = point.y - this.y;
      return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
    },

    angle: function angle(point) {
      var xDiff, yDiff;
      xDiff = point.x - this.x;
      yDiff = point.y - this.y;
      return Math.atan2(yDiff, xDiff);
    },

    move: function move() {
      // move towards denser elements

    },

    trail: function trail(rect) {
      //var rect = element.getBoundingClientRect();
      var isCollide =
        this.x > rect.left && this.y > rect.top &&
        this.x < rect.right + rect.width && this.y < rect.bottom + rect.height;

      if (isCollide) {
        // render comet trails by consuming colliding element (increase size and add colors)
      }
    },

    spawn: function spawn() {
      this.shape.arc(100, 35, 25, 0, 2 * Math.PI);
      this.shape.moveTo(this.x, this.y);
      game.comets.push(this);
    }
  };

  /******************
    Game Runner
  *******************/
  game = new Game();
  game.start();
  console.log(game);
}());
<|endoftext|>


<commit_before>var comments = {
"comments" : [
	{
		"el": "header[role=banner]",
		"title" : "Masthead",
		"comment": "The main header of the site doesn't take up too much screen real estate in order to keep the focus on the core content. It's using a linear CSS gradient instead of a background image to give greater design flexibility and reduce HTTP requests."
	},
	{
		"el": ".logo",
		"title" : "Logo",
		"comment": "First Test The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don't support SVG images.

Further reading: Optimizing Web Experiences for High Resolution Screens
"
	},
	{
		"el": "#nav",
		"title" : "Navigation",
		"comment": "
Navigation for adaptive web experiences can be tricky. Top navigations are typical on desktop sites, but mobile screen sizes don't give us the luxury of space. We're dealing with this situation by creating a simple menu anchor that toggles the main navigation on small screens. This is just one method. Bagcheck and Contents Magazine add an anchor in the header that jumps users to the navigation which is placed in the footer. This solution works well because it doesn't require any Javascript in order to work. Other methods exist too. For example, ESPN's mobile navigation overlays the main content of the page.

The nav is only hidden when a certain level of javascript is supported in order to ensure that users with little/poor javascript support can still access the navigation. Once the screen size is large enough to accommodate the nav, we show the main navigation links and hide the menu anchor.

See also: Responsive Navigation Patterns
"
	},
	{
		"el": ".search-form",
		"title" : "Search",
		"comment": "
Search is an incredibly important priority, especially for mobile. It is a great idea to give users the ability to jump directly to what they are looking for without forcing them to wade through your site's navigation. Check out the Burton and Yelp mobile sites for great examples of experiences that prioritize search.

We're also using the HTML5 search input type, which is great for mobile devices that can bring up the appropriate virtual keyboard for many smartphones. And like the main header navigation, we're hiding the search form on small screens to save space. Clicking the search anchor toggles the form.
"
	},
	{
		"el": ".article-header h1",
		"title" : "Article Header",
		"comment": "
The article header should be no more than 140 characters.
"
	},
	{
		"el": ".block-hero",
		"title" : "Hero",
		"comment": "
The hero area highlights one major story using a large image and a captivating headline.
"
	}
]
};<commit_msg>add annotation for colors<commit_after>var comments = {
"comments" : [
	{
		"el": "#atoms-colors",
		"title" : "Colors",
		"comment": "Colors"
	},
	{
		"el": "header[role=banner]",
		"title" : "Masthead",
		"comment": "The main header of the site doesn't take up too much screen real estate in order to keep the focus on the core content. It's using a linear CSS gradient instead of a background image to give greater design flexibility and reduce HTTP requests."
	},
	{
		"el": ".logo",
		"title" : "Logo",
		"comment": "First Test The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don't support SVG images.

Further reading: Optimizing Web Experiences for High Resolution Screens
"
	},
	{
		"el": "#nav",
		"title" : "Navigation",
		"comment": "
Navigation for adaptive web experiences can be tricky. Top navigations are typical on desktop sites, but mobile screen sizes don't give us the luxury of space. We're dealing with this situation by creating a simple menu anchor that toggles the main navigation on small screens. This is just one method. Bagcheck and Contents Magazine add an anchor in the header that jumps users to the navigation which is placed in the footer. This solution works well because it doesn't require any Javascript in order to work. Other methods exist too. For example, ESPN's mobile navigation overlays the main content of the page.

The nav is only hidden when a certain level of javascript is supported in order to ensure that users with little/poor javascript support can still access the navigation. Once the screen size is large enough to accommodate the nav, we show the main navigation links and hide the menu anchor.

See also: Responsive Navigation Patterns
"
	},
	{
		"el": ".search-form",
		"title" : "Search",
		"comment": "
Search is an incredibly important priority, especially for mobile. It is a great idea to give users the ability to jump directly to what they are looking for without forcing them to wade through your site's navigation. Check out the Burton and Yelp mobile sites for great examples of experiences that prioritize search.

We're also using the HTML5 search input type, which is great for mobile devices that can bring up the appropriate virtual keyboard for many smartphones. And like the main header navigation, we're hiding the search form on small screens to save space. Clicking the search anchor toggles the form.
"
	},
	{
		"el": ".article-header h1",
		"title" : "Article Header",
		"comment": "
The article header should be no more than 140 characters.
"
	},
	{
		"el": ".block-hero",
		"title" : "Hero",
		"comment": "
The hero area highlights one major story using a large image and a captivating headline.
"
	}
]
};<|endoftext|>


<commit_before>
var SC=µ.shortcut({
	File:"File",
	Promise:"Promise",
	es:"errorSerializer"
});
var LOG=require("../logger");
var logger;

var rest={};

worker.configManager=function(){return require("./configManager").apply(null,arguments)};
worker.init=function(param)
{
	worker.name=param.name;
	logger=LOG.setCoreLogger(LOG("apps/"+param.name));

	return new SC.File("rest").exists()
	.then(function()
	{
		return this.listFiles().then(files=>
			files.filter(s=>s.slice(-3)===".js").map(s=>s.slice(0,-3))//cut ".js"
			.forEach(script=>
				rest[script]=require(new SC.File("./rest").changePath(script).getAbsolutePath())
			)
		);
	},
	function()
	{
		return "'rest' folder does not exist";
	})
	.then(function()
	{
		return new SC.File("package.json").read()
		.then(JSON.parse)
		.then(function(json)
		{
			return {
				id:json.name,
				dependencies:json.niwaDependencies,
				uses:json.niwaUses
			};
		})
		.catch(()=>({}));
	},
	e=>
	{
		setTimeout(function(){throw e;},1) //for native error message
		return Promise.reject(SC.es(e));
	});
}
worker.restCall=function(param)
{
	if(param.path.length==0)
	{//print all rest services
		return JSON.stringify(rest,function(key,value)
		{
			if(value instanceof Function) return value.toString().match(/^[^\(]+[^,]*,?([^\)]*)/)[1].split(",").map(s=>s.trim()).filter(s=>s!="");
			return value;
		});
	}
	else
	{
		var method=rest;
		var i=0;
		while(method&&typeof method!="function")
		{
			method=method[param.path[i++]];
		}
		if(!method)
		{
			return Promise.reject({data:"no such method: "+param.path.slice(0,i)});
		}
		else
		{
			param.path=param.path.slice(i);
			param.headers=null;
			param.status=null;
			return new SC.Promise(method,{args:[param].concat(param.path),simple:true})
			.then(data=>({data:data,headers:param.headers,status:param.status}),
			function(error)
			{
				if(error instanceof Error)
				{
					error=SC.es(error);
					if(!param.status) param.status=500;
				}
				return Promise.reject({error:error,headers:param.headers,status:param.status})
			});
		}
	}
};
worker.eventData=new Map();
worker.event=function(context,data,event,eventData)
{
	worker.eventData.set(context,data);
	worker.send({context:context,event:event,data:eventData});
};
worker.getEventData=worker.eventData.get.bind(worker.eventData);

worker.ask=function(name,method,data)
{
	worker.feedback(name,{method:method,data:data});
}
worker.appsStarted=µ.constantFunctions.ndef;
<commit_msg>fixed error handling<commit_after>
var SC=µ.shortcut({
	File:"File",
	Promise:"Promise",
	es:"errorSerializer"
});
var LOG=require("../logger");
var logger;

var rest={};

worker.configManager=function(){return require("./configManager").apply(null,arguments)};
worker.init=function(param)
{
	worker.name=param.name;
	logger=LOG.setCoreLogger(LOG("apps/"+param.name));

	return new SC.File("rest").exists()
	.then(function()
	{
		return this.listFiles().then(files=>
			files.filter(s=>s.slice(-3)===".js").map(s=>s.slice(0,-3))//cut ".js"
			.forEach(script=>
				rest[script]=require(new SC.File("./rest").changePath(script).getAbsolutePath())
			)
		);
	},
	function()
	{
		return "'rest' folder does not exist";
	})
	.then(function()
	{
		return new SC.File("package.json").read()
		.then(JSON.parse)
		.then(function(json)
		{
			return {
				id:json.name,
				dependencies:json.niwaDependencies,
				uses:json.niwaUses
			};
		})
		.catch(()=>({}));
	},
	e=>
	{
		setTimeout(function(){throw e;},1) //for native error message
		return Promise.reject(SC.es(e));
	});
}
worker.restCall=function(param)
{
	if(param.path.length==0)
	{//print all rest services
		return JSON.stringify(rest,function(key,value)
		{
			if(value instanceof Function) return value.toString().match(/^[^\(]+[^,]*,?([^\)]*)/)[1].split(",").map(s=>s.trim()).filter(s=>s!="");
			return value;
		});
	}
	else
	{
		var method=rest;
		var i=0;
		while(method&&typeof method!="function")
		{
			method=method[param.path[i++]];
		}
		if(!method)
		{
			return Promise.reject({error:"no such method: "+param.path.slice(0,i)});
		}
		else
		{
			param.path=param.path.slice(i);
			param.headers=null;
			param.status=null;
			return new SC.Promise(method,{args:[param].concat(param.path),simple:true})
			.then(data=>({data:data,headers:param.headers,status:param.status}),
			function(error)
			{
				if(error instanceof Error)
				{
					error=SC.es(error);
					if(!param.status) param.status=500;
				}
				return Promise.reject({error:error,headers:param.headers,status:param.status})
			});
		}
	}
};
worker.eventData=new Map();
worker.event=function(context,data,event,eventData)
{
	worker.eventData.set(context,data);
	worker.send({context:context,event:event,data:eventData});
};
worker.getEventData=worker.eventData.get.bind(worker.eventData);

worker.ask=function(name,method,data)
{
	worker.feedback(name,{method:method,data:data});
}
worker.appsStarted=µ.constantFunctions.ndef;
<|endoftext|>


<commit_before>var PreviewBoard = function() {
  this.board = generateEmptyBoard();
  this.tetrinimo;
}
PreviewBoard.prototype.blit = function() {
  this.board = generateEmptyBoard();

  var blockX, blockY;
  for(var block in this.tetrinimo.blocks) {
    blockX = this.tetrinimo.blocks[block].x - 4;
    blockY = this.tetrinimo.blocks[block].y;
    this.board[blockY][blockX] = this.tetrinimo.element;
  }
};

var generateEmptyBoard = function() {
  emptyBoard = new Array;
  for(var y = 0; y < 4; y++) {
    var row = new Array;
    for(var x = 0; x < 4; x++) {
      row.push(0);
    }
    emptyBoard.push(row);
  }
  return emptyBoard;
}
<commit_msg>Update preview board to initialize with a null tetrinimo<commit_after>var PreviewBoard = function() {
  this.board = generateEmptyBoard();
  this.tetrinimo = null;
}
PreviewBoard.prototype.blit = function() {
  this.board = generateEmptyBoard();

  var blockX, blockY;
  for(var block in this.tetrinimo.blocks) {
    blockX = this.tetrinimo.blocks[block].x - 4;
    blockY = this.tetrinimo.blocks[block].y;
    this.board[blockY][blockX] = this.tetrinimo.element;
  }
};

var generateEmptyBoard = function() {
  emptyBoard = new Array;
  for(var y = 0; y < 4; y++) {
    var row = new Array;
    for(var x = 0; x < 4; x++) {
      row.push(0);
    }
    emptyBoard.push(row);
  }
  return emptyBoard;
}
<|endoftext|>


<commit_before>const { Router } = require('express')
const crypto = require('crypto')
const Robot = require('../models/robot')
const router = Router()

router.get('/robots', (req, res, next) => {
  Robot.find({}, (err, docs) => {
    if (err) next(err)
    return res.status(200).json(docs)
  })
})

router.post('/robots', (req, res, next) => {
  if (!req.body) {
    return res.status(400).json({ error: 'The request body is empty.' })
  }
  if (!req.body.name) {
    return res.status(400).json({ error: 'The name must not be empty.' })
  }
  Robot.create(req.body, (err, doc) => {
    if (err) next(err)
    return res.status(200).json(doc)
  })
})

router.get('/robots/:id', (req, res, next) => {
  Robot.findById(req.params.id, (err, doc) => {
    if (err) next(err)
    return res.status(200).json(doc)
  })
})

router.patch('/robots/:id', (req, res, next) => {
  const tokenRegex = /[a-f0-9]{32}/
  if (!req.body) {
    return res.status(400).json({ error: 'The request body is empty.' })
  }
  if (req.body.token && !tokenRegex.test(req.body.token)) {
    crypto.randomBytes(16, (err, buffer) => {
      req.body.token = buffer.toString('hex')
      Robot.findByIdAndUpdate(req.params.id, { $set: req.body }, (err, doc) => {
        if (err) return next(err)
        return res.status(200).json(doc)
      })
    })
  } else {
    Robot.findByIdAndUpdate(req.params.id, { $set: req.body }, (err, doc) => {
      if (err) return next(err)
      return res.status(200).json(doc)
    })
  }
})

router.delete('/robots/:id', (req, res, next) => {
  Robot.findByIdAndRemove(req.params.id, err => {
    if (err) return next(err)
    return res
      .status(200)
      .json({ message: 'The entity was successfully removed.' })
  })
})

module.exports = exports = router
<commit_msg>Update router import<commit_after>const router = require('express').Router()
const crypto = require('crypto')
const Robot = require('../models/robot')

router.get('/robots', (req, res, next) => {
  Robot.find({}, (err, docs) => {
    if (err) next(err)
    return res.status(200).json(docs)
  })
})

router.post('/robots', (req, res, next) => {
  if (!req.body) {
    return res.status(400).json({ error: 'The request body is empty.' })
  }
  if (!req.body.name) {
    return res.status(400).json({ error: 'The name must not be empty.' })
  }
  Robot.create(req.body, (err, doc) => {
    if (err) next(err)
    return res.status(200).json(doc)
  })
})

router.get('/robots/:id', (req, res, next) => {
  Robot.findById(req.params.id, (err, doc) => {
    if (err) next(err)
    return res.status(200).json(doc)
  })
})

router.patch('/robots/:id', (req, res, next) => {
  const tokenRegex = /[a-f0-9]{32}/
  if (!req.body) {
    return res.status(400).json({ error: 'The request body is empty.' })
  }
  if (req.body.token && !tokenRegex.test(req.body.token)) {
    crypto.randomBytes(16, (err, buffer) => {
      req.body.token = buffer.toString('hex')
      Robot.findByIdAndUpdate(req.params.id, { $set: req.body }, (err, doc) => {
        if (err) return next(err)
        return res.status(200).json(doc)
      })
    })
  } else {
    Robot.findByIdAndUpdate(req.params.id, { $set: req.body }, (err, doc) => {
      if (err) return next(err)
      return res.status(200).json(doc)
    })
  }
})

router.delete('/robots/:id', (req, res, next) => {
  Robot.findByIdAndRemove(req.params.id, err => {
    if (err) return next(err)
    return res
      .status(200)
      .json({ message: 'The entity was successfully removed.' })
  })
})

module.exports = exports = router
<|endoftext|>


<commit_before>/*
 *  longpoll.js
 *
 *  David Janes
 *  IOTDB.org
 *  2016-08-13
 *
 *  Copyright [2013-2016] [David P. Janes]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

"use strict";

const iotdb = require('iotdb');
const _ = iotdb._;
const iotdb_transport = require('iotdb-transport');
const errors = require('iotdb-errors');

const Rx = require('rx');
const assert = require('assert');

const logger = iotdb.logger({
    name: 'iotdb-transport-fs',
    module: 'longpoll',
});

const make = (initd, underlying, app) => {
    const self = iotdb_transport.make();

    assert.ok(underlying);
    assert.ok(app);

    const _subject_map = new Map();

    const _initd = _.d.compose.shallow(
        initd, {
            channel: iotdb_transport.channel,
            unchannel: iotdb_transport.unchannel,
            encode: s => s.replace(/[\/$%#\]\[]/g, (c) => '%' + c.charCodeAt(0).toString(16)),
            decode: s => decodeURIComponent(s),
        },
        iotdb.keystore().get("/transports/iotdb-transport-express/initd"), {
            prefix: "/",
            name: ".longpoll",
            cookie_key: "transport-longpoll",
        }
    );

    // -- internals
    const _make_replay_subject = () => {
        return new Rx.ReplaySubject(null, 5 * 60 * 1000);
    };

    const _updated = d => {
        const rd = _.d.clone.shallow(d);

        _subject_map
            .forEach(subject => {
                subject.onNext(rd);
                subject.__any = true;

                if (subject.__waiting) {
                    subject.onCompleted();
                }
            })
    };

    const _monitor = () => {
        underlying.updated()
            .subscribe(
                _updated,
                error => {
                    logger.error({
                        method: "_monitor/updated/error",
                        error: _.error.message(error),
                    }, "this should really never happy - likely no more updated will happen");
                }
            );
    };

    const _app_get = () => {
        const url = _initd.channel(_initd, {
            id: _initd.name,
        });

        app.use(url, (request, response) => {
            if (!request.cookies) {
                throw new errors.Internal("cookies middleware is required");
            }

            let cookie_value = request.cookies[_initd.cookie_key]
            if (!cookie_value || !_subject_map.get(cookie_value)) {
                cookie_value = _.random.id(32);
            }

            response.cookie(_initd.cookie_key, cookie_value, { maxAge: 900000, httpOnly: true });

            let subject = _subject_map.get(cookie_value);
            if (!subject) {
                _subject_map.set(cookie_value, subject = _make_replay_subject());
            }

            subject.__waiting = true;
            subject
                .reduce((ad, d) => { ad[_initd.channel(_initd, d)] = d.value; return ad }, {})
                .subscribe(
                    ad => {
                        response
                            .set('Content-Type', 'application/json')
                            .send(JSON.stringify(ad, null, 2));
                    },
                    error => {
                        logger.error({
                            method: "_app_get/app.use/subject.error",
                            error: _.error.message(error),
                        }, "may not be serious");
                    },
                    done => {
                        _subject_map.set(cookie_value, subject = _make_replay_subject());
                        // console.log("+", "<end>")
                    }
                )

            if (subject.__any) {
                subject.onCompleted();
            }
        });
    };

    _app_get();
    _monitor();

    return self;
};

/**
 *  API
 */
exports.make = make;
<commit_msg>missing name in longpoll<commit_after>/*
 *  longpoll.js
 *
 *  David Janes
 *  IOTDB.org
 *  2016-08-13
 *
 *  Copyright [2013-2016] [David P. Janes]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

"use strict";

const iotdb = require('iotdb');
const _ = iotdb._;
const iotdb_transport = require('iotdb-transport');
const errors = require('iotdb-errors');

const Rx = require('rx');
const assert = require('assert');

const logger = iotdb.logger({
    name: 'iotdb-transport-fs',
    module: 'longpoll',
});

const make = (initd, underlying, app) => {
    const self = iotdb_transport.make();
    self.name = "iotdb-transport-express/longpoll";

    assert.ok(underlying);
    assert.ok(app);

    const _subject_map = new Map();

    const _initd = _.d.compose.shallow(
        initd, {
            channel: iotdb_transport.channel,
            unchannel: iotdb_transport.unchannel,
            encode: s => s.replace(/[\/$%#\]\[]/g, (c) => '%' + c.charCodeAt(0).toString(16)),
            decode: s => decodeURIComponent(s),
        },
        iotdb.keystore().get("/transports/iotdb-transport-express/initd"), {
            prefix: "/",
            name: ".longpoll",
            cookie_key: "transport-longpoll",
        }
    );

    // -- internals
    const _make_replay_subject = () => {
        return new Rx.ReplaySubject(null, 5 * 60 * 1000);
    };

    const _updated = d => {
        const rd = _.d.clone.shallow(d);

        _subject_map
            .forEach(subject => {
                subject.onNext(rd);
                subject.__any = true;

                if (subject.__waiting) {
                    subject.onCompleted();
                }
            })
    };

    const _monitor = () => {
        underlying.updated()
            .subscribe(
                _updated,
                error => {
                    logger.error({
                        method: "_monitor/updated/error",
                        error: _.error.message(error),
                    }, "this should really never happy - likely no more updated will happen");
                }
            );
    };

    const _app_get = () => {
        const url = _initd.channel(_initd, {
            id: _initd.name,
        });

        app.use(url, (request, response) => {
            if (!request.cookies) {
                throw new errors.Internal("cookies middleware is required");
            }

            let cookie_value = request.cookies[_initd.cookie_key]
            if (!cookie_value || !_subject_map.get(cookie_value)) {
                cookie_value = _.random.id(32);
            }

            response.cookie(_initd.cookie_key, cookie_value, { maxAge: 900000, httpOnly: true });

            let subject = _subject_map.get(cookie_value);
            if (!subject) {
                _subject_map.set(cookie_value, subject = _make_replay_subject());
            }

            subject.__waiting = true;
            subject
                .reduce((ad, d) => { ad[_initd.channel(_initd, d)] = d.value; return ad }, {})
                .subscribe(
                    ad => {
                        response
                            .set('Content-Type', 'application/json')
                            .send(JSON.stringify(ad, null, 2));
                    },
                    error => {
                        logger.error({
                            method: "_app_get/app.use/subject.error",
                            error: _.error.message(error),
                        }, "may not be serious");
                    },
                    done => {
                        _subject_map.set(cookie_value, subject = _make_replay_subject());
                        // console.log("+", "<end>")
                    }
                )

            if (subject.__any) {
                subject.onCompleted();
            }
        });
    };

    _app_get();
    _monitor();

    return self;
};

/**
 *  API
 */
exports.make = make;
<|endoftext|>


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

const join = require('path').join;
const getPort = require('./utils').getPort;

class Configurator {
    constructor(config){
        this.init(config);
    }

    init(config){
        this.config = config;
        this.logger = config.logger || console;

        this.validate(config);
    }

    validate(config){

        if(!config.subapps) config.subapps = {};
        if(!config.routesPath) config.routesPath = [];
        if(!config.publicPaths) config.publicPaths = [];

        //TODO: we should ensure we have a valid config!!
        //List of required properties:
        //port
        if(!config.port) {
            config.port = getPort();
            this.logger.info('validate: No "config.port" specified. Using %s.', config.port);
        }

        //hostname
        if(!config.hostname){
            config.hostname = 'localhost';
        }

        //baseUrl
        if(!config.baseUrl){
            /*
             * This actually should be a bit more nuanced.
             * If we have a hostname, and it has a domain,
             * or if we have ssl, and what not.
             */
            let protocol = '';
            if(config.hostname.indexOf('http') === -1){
                /*
                 * We are not supporting https, right
                 * now I prefer to terminate SSL before
                 * hitting node. Eventually we could
                 * check config for either
                 * config.ssl === true
                 * config.ssl && (config.ssl.key && config.ssl.crt)
                 */
                protocol = (config.port === 443 ? 'https' : 'http') + '://';
            }

            let port = (config.port === 80 || config.port === 443) ? '' : ':' + config.port;

            config.baseUrl = `${protocol}${config.hostname}${port}`;
            this.logger.info('validate: No "config.baseUrl" specified. Using %s.', config.baseUrl);
        }
    }

    /**
     * Ensures that the configuration object has
     * the required fields.
     *
     * This way, our sub applications can inherit
     * required properties from `server`
     * configuration, or inferred using the module
     * name.
     *
     * @method addDefaults
     *
     * @param {Object} config   Sub application config.
     * @return {void}
     */
    addDefaults(config){
        if(!config.basedir){

            if(!config.hasOwnProperty('moduleid')){
                throw new Error('You need to specify "config.basedir". Else set "config.moduleid" so we can guess.');
            }

            config.basedir = './modules/' + config.moduleid;
            //Now we dont have access to context.modulespath...
            // let sep = require('path').sep;
            // config.basedir = [context.modulespath, config.moduleid].join(sep);

            this.logger.warn('You did not specify a "basedir"?');
            this.logger.warn('We are making a guess: %s', config.basedir);
        } else {
            this.logger.warn('Loading views and assets from: %s', config.basedir);
        }

        if(!config.baseUrl){
            config.baseUrl = this.config.baseUrl;
        }

        if(!config.routes){
            config.routes = {
                path: [join(config.basedir, 'routes')]
            };
        }

        if(!config.mount){
            config.mount = '/' + config.moduleid;
        }
    }

    mount(id, app){
        if(this.subapps[id]){
            this.logger.warn('We are overriding sub app %s. Already registered.', id);
        }

        app.once('mount', (parent)=>{
            this.logger.warn('Sub app %s mounted. Attaching parent.', id, app.get('appId'));
            app.parent = parent;
        });

        this.subapps[id] = app;
    }

    addRoute(path){
        this.routes.push(path);
        return this;
    }

    addPublicPath(path){
        this.publics.push(path);
        return this;
    }

    get subapps(){
        return this.config.subapps;
    }

    get routes(){
        let routesPath = this.config.routesPath;
        if(!Array.isArray(routesPath)) routesPath = [routesPath];
        return routesPath;
    }

    get publics(){
        let routesPath = this.config.publicPaths;
        if(!Array.isArray(routesPath)) routesPath = [routesPath];
        return routesPath;
    }

    set context(v){ this._context = v;}
    get context(){ return this._context;}

}

module.exports = Configurator;
<commit_msg>Add default checking to configuragor, also added DEFAULTS<commit_after>/*jshint esversion:6, node:true*/
'use strict';

const join = require('path').join;
const getPort = require('./utils').getPort;
const extend = require('gextend');

class Configurator {

    constructor(config) {
        this.init(config);
    }

    init(config){
        this.config = config;
        this.logger = config.logger || console;

        this.validate(config);
    }

    validate(config) {

        extend(config, Configurator.DEFAULTS, config);

        //TODO: we should ensure we have a valid config!!
        //List of required properties:
        //port
        if(!config.port) {
            config.port = getPort();
            this.logger.info('validate: No "config.port" specified. Using %s.', config.port);
            this.logger.info('You can set PORT OR NODE_APP_PORT env vars');
        }

        //baseUrl
        if(!config.baseUrl) {
            /*
             * This actually should be a bit more nuanced.
             * If we have a hostname, and it has a domain,
             * or if we have ssl, and what not.
             */
            let protocol = '';
            if(config.hostname.indexOf('http') === -1) {
                /*
                 * We are not supporting https, right
                 * now I prefer to terminate SSL before
                 * hitting node. Eventually we could
                 * check config for either
                 * config.ssl === true
                 * config.ssl && (config.ssl.key && config.ssl.crt)
                 */
                protocol = (config.port === 443 ? 'https' : 'http') + '://';
            }

            let port = (config.port === 80 || config.port === 443) ? '' : ':' + config.port;

            config.baseUrl = `${protocol}${config.hostname}${port}`;
            this.logger.info('validate: No "config.baseUrl" specified. Using %s.', config.baseUrl);
        }

        return config;
    }

    /**
     * Ensures that the configuration object has
     * the required fields.
     *
     * This way, our sub applications can inherit
     * required properties from `server`
     * configuration, or inferred using the module
     * name.
     *
     * @method addDefaults
     *
     * @param {Object} config   Sub application config.
     * @return {void}
     */
    addDefaults(config) {
        if(!config.basedir) {

            if(!config.hasOwnProperty('moduleid')){
                throw new Error('You need to specify "config.basedir". Else set "config.moduleid" so we can guess.');
            }

            config.basedir = './modules/' + config.moduleid;
            //Now we dont have access to context.modulespath...
            // let sep = require('path').sep;
            // config.basedir = [context.modulespath, config.moduleid].join(sep);

            this.logger.warn('You did not specify a "basedir"?');
            this.logger.warn('We are making a guess: %s', config.basedir);
        } else {
            this.logger.warn('Loading views and assets from: %s', config.basedir);
        }

        if(!config.baseUrl){
            config.baseUrl = this.config.baseUrl;
        }

        if(!config.routes){
            config.routes = {
                path: [join(config.basedir, 'routes')]
            };
        }

        if(!config.mount){
            config.mount = '/' + config.moduleid;
        }
    }

    mount(id, app){
        if(this.subapps[id]){
            this.logger.warn('We are overriding sub app %s. Already registered.', id);
        }

        app.once('mount', (parent)=>{
            this.logger.warn('Sub app %s mounted. Attaching parent.', id, app.get('appId'));
            app.parent = parent;
        });

        this.subapps[id] = app;
    }

    addRoute(path){
        this.routes.push(path);
        return this;
    }

    addPublicPath(path){
        this.publics.push(path);
        return this;
    }

    get subapps(){
        return this.config.subapps;
    }

    get routes(){
        let routesPath = this.config.routesPath;
        if(!Array.isArray(routesPath)) routesPath = [routesPath];
        return routesPath;
    }

    get publics(){
        let routesPath = this.config.publicPaths;
        if(!Array.isArray(routesPath)) routesPath = [routesPath];
        return routesPath;
    }

    set context(v){ this._context = v;}
    get context(){ return this._context;}

}

Configurator.DEFAULTS = {
    subapps: {},
    routesPath: [],
    publicPaths: [],
    hostname: 'localhost',
    //TODO: Figure out the best way to
    //do this. Should we have a class
    //handling this? Right now this is
    //not being used :)
    formatErrorResponse: {
        json: function(res, err) {
            res.send({
                success: false,
            });
        }
    }
};

module.exports = Configurator;
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Imre Fazekas. All rights reserved.
 *
 * A restful web service middleware for Connect.
 */
var VERSION = '0.0.22';

var connect = require('connect');
var url = require('url');
var async = require('async');
var semver = require('semver');
var bunyan = require('bunyan');
var http = require('http');
var _ = require('underscore');
_.str = require('underscore.string');
_.mixin(_.str.exports());
_.str.include('Underscore.string', 'string');

var Route = require('./route');
var httphelper = require('./http-helper');

var LOAD_SIZE_LIMIT = 1e6;
var CONTEXT = '';
var ERROR_MESSAGE = '';

var SERVICE_METHOD_PATTERN = /^[a-zA-Z]([a-zA-Z]|\d|_)*$/g;

var mapping = {
	"HEAD": [],
	"GET": [],
	"POST": [],
	"PUT": [],
	"DELETE": []
};

var API_KEYS;
var logger;

function DummyLogger(){ }
DummyLogger.prototype.info = function() { console.log( parameters ); };
DummyLogger.prototype.debug = function() { console.debug( parameters ); };
DummyLogger.prototype.error = function() { console.error( parameters ); };

function addPath(key, path, action, prototypeObject, options){
	mapping[ key ].push( new Route( path, prototypeObject, options,
		function(request, content, callback){
			if(action.length == 3) {
				action(request, content, function(err, result, resOptions){
					callback(err, { contentType: options.contentType || 'application/json', result : result, resOptions: resOptions || {} } );
				});
			} else {
				callback(null, { contentType: options.contentType || 'application/json', result : action(request, content), resOptions:{} } );
			}
		}, _
	) );
}

function protoPather( request, content ){
	var utokens = _.words( request.parameters.path, '/');
	var method = _.first( utokens );
	var version = utokens[1];
	var pathname =	'/' + _.toSentence( _.rest(utokens, 2 ), '/', '/' );

	var routes = _.filter(
		mapping[ method ], function(route){ return route.matches(
			null, pathname, CONTEXT, version, _, semver, false
		); }
	);

	return routes.length > 0 ? _.first( _.map( routes, function(route){ return route.prototypeObject; } ) ) : 'No matching service';
}

function discover( request, content ){
	var version = request.parameters.version;

	var matchingMaps = {};
	_.each(mapping, function(value, key, list){
		matchingMaps[ key ] = [];

		_.each(value, function(element, index, list){
			_.each( element.matchings( version, _, semver ), function(match, matchIndex, matchList){
				matchingMaps[ key ].push( match );
			});
		});
	});

	return matchingMaps;
}

/*
var getClientAddress = function (req) {
    return (req.headers['x-forwarded-for'] || '').split(',')[0]
        || req.connection.remoteAddress;
};
*/

function process(req, res, matching, bodyObj){
	logger.debug('Payload object: ', bodyObj );

	var callChain =  _.map( matching, function(func){
		var headers = req.headers; headers.httpVersion=req.httpVersion; headers.method=req.method; headers.originalUrl=req.originalUrl;
		return async.apply( func, {headers: headers, parameters: req.query}, bodyObj );
	} );

	logger.debug('Calling service functions.' );

	req.headers.clientAddress = (req.headers['x-forwarded-for'] || '').split(',')[0] || req.connection.remoteAddress;

	var asyncCall = req.query.callbackURL;

	async.series( callChain,
		function(err, results){
			logger.info('Service(s) calling finished.', err, results );

			if( asyncCall ){
				var voptions = global._.clone( httphelper.opt );
				voptions.path = async;
				httphelper.generalCall( http, voptions, function(err, response){
					if(err)
						logger.error( err );
					else
						logger.info('Response:', response);
				} );
			} else{
				if( err ){
					logger.error( err );
					res.statusCode = err.statusCode || 500;
					res.end( 'Error occurred: ' + err );
				}
				else{
					logger.debug( 'Results:', results );
					var result = _.find(results, function(returnValue){ return returnValue && returnValue.result; }) || {};
					res.writeHead( result.resOptions.statusCode || 200, {'Content-Type': result.contentType || 'application/json'}, result.resOptions.headers );

					res.end( result && result.result ? ((result.contentType == 'application/json') ? JSON.stringify( result.result ) : result.result) : '' );
				}
			}
		}
	);

	if( asyncCall ){
		res.statusCode = 200;
		res.end( { answer: 'OK.'} );
	}
}

exports.head = function headRest(path, functionRef, options){
	addPath("HEAD", path, functionRef, null, options || {} );
};
exports.get = function getRest(path, functionRef, options){
	addPath("GET", path, functionRef, null, options || {} );
};
exports.post = function postRest(path, functionRef, prototypeObject, options){
	addPath("POST", path, functionRef, prototypeObject, options || {} );
};
exports.put = function putRest(path, functionRef, prototypeObject, options){
	addPath("PUT", path, functionRef, prototypeObject, options || {} );
};
exports.delete = function deleteRest(path, functionRef, prototypeObject, options){
	addPath("DELETE", path, functionRef, prototypeObject, options || {} );
};
exports.assign = function headRest(methods, path, functionRef, prototypeObject, options){
	if( _.isString(methods) && methods == '*' ){
		exports.rest( ['head','get','post','put','delete'], path, functionRef, prototypeObject, options );
	} else if( _.isArray(methods) ){
		_.each( methods, function(element, index, list){
			if( element == 'head' )
				addPath("HEAD", path, functionRef, null, options || {} );
			else if( element == 'get' )
				addPath("GET", path, functionRef, null, options || {} );
			else if( element == 'post' )
				addPath("POST", path, functionRef, prototypeObject, options || {} );
			else if( element == 'put' )
				addPath("PUT", path, functionRef, prototypeObject, options || {} );
			else if( element == 'delete' )
				addPath("DELETE", path, functionRef, prototypeObject, options || {} );
			else logger.error('Not known rest type:', element);
		} );
	}
	else
		logger.error('Not correct given methods', methods);
};

exports.publish = function (services){
	_.each( _.values( services ), function(element, index, list){
		if( _.isFunction( element ) && SERVICE_METHOD_PATTERN.test( element.name ) ){
			if( element.length == 1 )
				getRest( '/' + element.name, element );
			if( element.length == 2 )
				postRest( '/' + element.name, element );
		}
	} );
};

exports.context = function (context){
	CONTEXT = context;
};

exports.rester = function( options ) {
	var domain;
	if( options ){
		if( options.discoverPath )
			addPath('GET', options.discoverPath + '/:version', discover );
		if( options.protoPath )
			addPath('GET', options.protoPath + '/*path', protoPather );
		API_KEYS = options.apiKeys;

		logger = new DummyLogger();
		if( options.logger )
			logger = _.isString( options.logger ) ? bunyan.createLogger({name: options.logger, src: true, level: options.logLevel || 'info'}) : options.logger;

		domain = options.domain;

		logger.info('connect-rest has been configured. ', options);
	}

	return function(req, res, next) {
		if( domain ){
			domain.add(req);
			domain.add(res);
			domain.on('error', function(err) {
				try {
					res.statusCode = 500;
					res.end(err.message + '\n');
					res.on('close', function() {
						domain.dispose();
					});
				} catch (er) {
					logger.error('Error sending 500', er, req.url);
					domain.dispose();
				}
			});
		}

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

		logger.info('Incoming request.', req.headers, req.query, req.httpVersion, req.method, req.originalUrl );

		if( API_KEYS && API_KEYS.indexOf(req.query.api_key)==-1 ){
			logger.info('Request without api key.', req.query );

			res.statusCode = 401;
			res.end( 'API_KEY is required.' );
			return;
		}

		var pathname = url.parse( req.url ).pathname;

		logger.debug('Request received from: ', pathname );

		var routes = _.filter(
			mapping[ req.method ], function(route){ return route.matches(
				req, pathname, CONTEXT, req.headers['accept-version'] || req.headers['x-api-version'] || '*', _, semver, true
			) && (!route.options.validator || route.options.validator(req, res) ); }
		);
		var matching = _.map( routes, function(route){ return route.action; } );

		if( matching.length === 0 ){
			logger.info('Request won\'t be handled by connect-rest.', req.url );

			return next();
		}

		logger.debug('Routes matching: ', routes );

		if( req.body ){
			process(req, res, matching, req.body);
		} else{
			var body = '';
			req.on('data', function(chunk) {
				body += chunk;
				if (body.length > LOAD_SIZE_LIMIT) {
					req.connection.destroy();
				}
			});

			req.on('end', function() {
				logger.debug('Body payload: ', body );

				var bodyObj = body.length ? (('application/json' == connect.utils.mime(req)) ? JSON.parse( body ) : body) : '';

				process(req, res, matching, bodyObj);
			} );
		}
	};
};

exports.VERSION = VERSION;
exports.LOAD_SIZE_LIMIT = LOAD_SIZE_LIMIT;
exports.CONTEXT = CONTEXT;
exports.ERROR_MESSAGE = ERROR_MESSAGE;
exports.SERVICE_METHOD_PATTERN = SERVICE_METHOD_PATTERN;
<commit_msg>0.0.21 release<commit_after>/*
 * Copyright (c) 2013 Imre Fazekas. All rights reserved.
 *
 * A restful web service middleware for Connect.
 */
var VERSION = '0.0.22';

var connect = require('connect');
var url = require('url');
var async = require('async');
var semver = require('semver');
var bunyan = require('bunyan');
var http = require('http');
var _ = require('underscore');
_.str = require('underscore.string');
_.mixin(_.str.exports());
_.str.include('Underscore.string', 'string');

var Route = require('./route');
var httphelper = require('./http-helper');

var LOAD_SIZE_LIMIT = 1e6;
var CONTEXT = '';
var ERROR_MESSAGE = '';

var SERVICE_METHOD_PATTERN = /^[a-zA-Z]([a-zA-Z]|\d|_)*$/g;

var mapping = {
	"HEAD": [],
	"GET": [],
	"POST": [],
	"PUT": [],
	"DELETE": []
};

var API_KEYS;
var logger;

function DummyLogger(){ }
DummyLogger.prototype.info = function() { console.log( parameters ); };
DummyLogger.prototype.debug = function() { console.debug( parameters ); };
DummyLogger.prototype.error = function() { console.error( parameters ); };

function addPath(key, path, action, prototypeObject, options){
	mapping[ key ].push( new Route( path, prototypeObject, options,
		function(request, content, callback){
			if(action.length == 3) {
				action(request, content, function(err, result, resOptions){
					callback(err, { contentType: options.contentType || 'application/json', result : result, resOptions: resOptions || {} } );
				});
			} else {
				callback(null, { contentType: options.contentType || 'application/json', result : action(request, content), resOptions:{} } );
			}
		}, _
	) );
}

function protoPather( request, content ){
	var utokens = _.words( request.parameters.path, '/');
	var method = _.first( utokens );
	var version = utokens[1];
	var pathname =	'/' + _.toSentence( _.rest(utokens, 2 ), '/', '/' );

	var routes = _.filter(
		mapping[ method ], function(route){ return route.matches(
			null, pathname, CONTEXT, version, _, semver, false
		); }
	);

	return routes.length > 0 ? _.first( _.map( routes, function(route){ return route.prototypeObject; } ) ) : 'No matching service';
}

function discover( request, content ){
	var version = request.parameters.version;

	var matchingMaps = {};
	_.each(mapping, function(value, key, list){
		matchingMaps[ key ] = [];

		_.each(value, function(element, index, list){
			_.each( element.matchings( version, _, semver ), function(match, matchIndex, matchList){
				matchingMaps[ key ].push( match );
			});
		});
	});

	return matchingMaps;
}

/*
var getClientAddress = function (req) {
    return (req.headers['x-forwarded-for'] || '').split(',')[0]
        || req.connection.remoteAddress;
};
*/

function process(req, res, matching, bodyObj){
	logger.debug('Payload object: ', bodyObj );

	var callChain =  _.map( matching, function(func){
		var headers = req.headers; headers.httpVersion=req.httpVersion; headers.method=req.method; headers.originalUrl=req.originalUrl;
		return async.apply( func, {headers: headers, parameters: req.query}, bodyObj );
	} );

	logger.debug('Calling service functions.' );

	req.headers.clientAddress = (req.headers['x-forwarded-for'] || '').split(',')[0] || req.connection.remoteAddress;

	var asyncCall = req.query.callbackURL;

	async.series( callChain,
		function(err, results){
			logger.info('Service(s) calling finished.', err, results );

			if( asyncCall ){
				var voptions = global._.clone( httphelper.opt );
				voptions.path = async;
				httphelper.generalCall( http, voptions, function(err, response){
					if(err)
						logger.error( err );
					else
						logger.info('Response:', response);
				} );
			} else{
				if( err ){
					logger.error( err );
					res.statusCode = err.statusCode || 500;
					res.end( 'Error occurred: ' + err );
				}
				else{
					logger.debug( 'Results:', results );
					var result = _.find(results, function(returnValue){ return returnValue && returnValue.result; }) || {};
					var headers = result.resOptions.headers || {};
					headers['Content-Type'] = result.contentType || 'application/json';
					res.writeHead( result.resOptions.statusCode || 200, headers );

					res.end( result && result.result ? ((result.contentType == 'application/json') ? JSON.stringify( result.result ) : result.result) : '' );
				}
			}
		}
	);

	if( asyncCall ){
		res.statusCode = 200;
		res.end( { answer: 'OK.'} );
	}
}

exports.head = function headRest(path, functionRef, options){
	addPath("HEAD", path, functionRef, null, options || {} );
};
exports.get = function getRest(path, functionRef, options){
	addPath("GET", path, functionRef, null, options || {} );
};
exports.post = function postRest(path, functionRef, prototypeObject, options){
	addPath("POST", path, functionRef, prototypeObject, options || {} );
};
exports.put = function putRest(path, functionRef, prototypeObject, options){
	addPath("PUT", path, functionRef, prototypeObject, options || {} );
};
exports.delete = function deleteRest(path, functionRef, prototypeObject, options){
	addPath("DELETE", path, functionRef, prototypeObject, options || {} );
};
exports.assign = function headRest(methods, path, functionRef, prototypeObject, options){
	if( _.isString(methods) && methods == '*' ){
		exports.rest( ['head','get','post','put','delete'], path, functionRef, prototypeObject, options );
	} else if( _.isArray(methods) ){
		_.each( methods, function(element, index, list){
			if( element == 'head' )
				addPath("HEAD", path, functionRef, null, options || {} );
			else if( element == 'get' )
				addPath("GET", path, functionRef, null, options || {} );
			else if( element == 'post' )
				addPath("POST", path, functionRef, prototypeObject, options || {} );
			else if( element == 'put' )
				addPath("PUT", path, functionRef, prototypeObject, options || {} );
			else if( element == 'delete' )
				addPath("DELETE", path, functionRef, prototypeObject, options || {} );
			else logger.error('Not known rest type:', element);
		} );
	}
	else
		logger.error('Not correct given methods', methods);
};

exports.publish = function (services){
	_.each( _.values( services ), function(element, index, list){
		if( _.isFunction( element ) && SERVICE_METHOD_PATTERN.test( element.name ) ){
			if( element.length == 1 )
				getRest( '/' + element.name, element );
			if( element.length == 2 )
				postRest( '/' + element.name, element );
		}
	} );
};

exports.context = function (context){
	CONTEXT = context;
};

exports.rester = function( options ) {
	var domain;
	if( options ){
		if( options.discoverPath )
			addPath('GET', options.discoverPath + '/:version', discover );
		if( options.protoPath )
			addPath('GET', options.protoPath + '/*path', protoPather );
		API_KEYS = options.apiKeys;

		logger = new DummyLogger();
		if( options.logger )
			logger = _.isString( options.logger ) ? bunyan.createLogger({name: options.logger, src: true, level: options.logLevel || 'info'}) : options.logger;

		domain = options.domain;

		logger.info('connect-rest has been configured. ', options);
	}

	return function(req, res, next) {
		if( domain ){
			domain.add(req);
			domain.add(res);
			domain.on('error', function(err) {
				try {
					res.statusCode = 500;
					res.end(err.message + '\n');
					res.on('close', function() {
						domain.dispose();
					});
				} catch (er) {
					logger.error('Error sending 500', er, req.url);
					domain.dispose();
				}
			});
		}

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

		logger.info('Incoming request.', req.headers, req.query, req.httpVersion, req.method, req.originalUrl );

		if( API_KEYS && API_KEYS.indexOf(req.query.api_key)==-1 ){
			logger.info('Request without api key.', req.query );

			res.statusCode = 401;
			res.end( 'API_KEY is required.' );
			return;
		}

		var pathname = url.parse( req.url ).pathname;

		logger.debug('Request received from: ', pathname );

		var routes = _.filter(
			mapping[ req.method ], function(route){ return route.matches(
				req, pathname, CONTEXT, req.headers['accept-version'] || req.headers['x-api-version'] || '*', _, semver, true
			) && (!route.options.validator || route.options.validator(req, res) ); }
		);
		var matching = _.map( routes, function(route){ return route.action; } );

		if( matching.length === 0 ){
			logger.info('Request won\'t be handled by connect-rest.', req.url );

			return next();
		}

		logger.debug('Routes matching: ', routes );

		if( req.body ){
			process(req, res, matching, req.body);
		} else{
			var body = '';
			req.on('data', function(chunk) {
				body += chunk;
				if (body.length > LOAD_SIZE_LIMIT) {
					req.connection.destroy();
				}
			});

			req.on('end', function() {
				logger.debug('Body payload: ', body );

				var bodyObj = body.length ? (('application/json' == connect.utils.mime(req)) ? JSON.parse( body ) : body) : '';

				process(req, res, matching, bodyObj);
			} );
		}
	};
};

exports.VERSION = VERSION;
exports.LOAD_SIZE_LIMIT = LOAD_SIZE_LIMIT;
exports.CONTEXT = CONTEXT;
exports.ERROR_MESSAGE = ERROR_MESSAGE;
exports.SERVICE_METHOD_PATTERN = SERVICE_METHOD_PATTERN;
<|endoftext|>