<commit_before>/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# SETUP THE APP on HEROKU:
  Create a Slack app. Make sure to configure the bot user!
    -> https://api.slack.com/applications/new
    -> Add the Redirect URI: https://${APP_NAME}.herokuapp.com/oauth
  Deply and run your app on Heroku.
    -> Add Config Vars on the Heroku app setting page: clientId, clientSecret
    -> Deploy and run on Heroku.
  Set RequestURL on Slack.
    -> Add the RequesteURL of Interactive Message: https://${APP_NAME}.herokuapp.com/slack/receive
# USE THE APP
  Add the app to your Slack by visiting the login page:
    -> https://${APP_NAME}.herokuapp.com/login
  After you've added the app, try talking to your bot!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

var Botkit = require('botkit');
var MongoUrl = process.env.MONGODB_URI || 'mongodb://localhost:27017/zangyo-bot';

if (!process.env.clientId || !process.env.clientSecret || !process.env.port) {
  console.log('Error: Specify clientId clientSecret and port in environment');
  process.exit(1);
}

var controller = Botkit.slackbot({
  // interactive_replies: true, // tells botkit to send button clicks into conversations
  storage: require('botkit-storage-mongo')({mongoUri: MongoUrl})
}).configureSlackApp(
  {
    clientId: process.env.clientId,
    clientSecret: process.env.clientSecret,
    scopes: ['bot','incoming-webhook'],
  }
);

var ZangyoBot = require('./lib/zangyo_bot')(controller);

controller.setupWebserver(process.env.port,function(err,webserver) {
  controller.createWebhookEndpoints(controller.webserver);

  controller.createOauthEndpoints(controller.webserver,function(err,req,res) {
    if (err) {
      res.status(500).send('ERROR: ' + err);
    } else {
      res.send('Success!');
    }
  });
});

// just a simple way to make sure we don't
// connect to the RTM twice for the same team
var _bots = {};
function trackBot(bot) {
  _bots[bot.config.token] = bot;
}

controller.on('create_bot',function(bot,config) {
  if (_bots[bot.config.token]) {
    // already online! do nothing.
  } else {
    bot.startRTM(function(err) {
      if (!err) {
        trackBot(bot);
      }

      bot.startPrivateConversation({user: config.createdBy},function(err,convo) {
        if (err) {
          console.log(err);
        } else {
          convo.say('I am a bot that has just joined your team');
          convo.say('You must now /invite me to a channel so that I can be of use!');
        }
      });
    });
  }
});

// Handle events related to the websocket connection to Slack
controller.on('rtm_open',function(bot) {
  console.log('** The RTM api just connected!');
});

controller.on('rtm_close',function(bot) {
  console.log('** The RTM api just closed');
  // you may want to attempt to re-open
});

controller.hears('^.*(申請中|未承認).*一覧.*',['direct_message','direct_mention'],function(bot,message) {
  ZangyoBot.replyPendingList(bot, message);
});

controller.hears('^.*(残業|申請).*一覧.*',['direct_message','direct_mention'],function(bot,message) {
  var range, applicant, filter, is_detailed;

  if (message.text.match(/(今日|本日|今夜|今晩)/)) {
    range = ZangyoBot.ranges.today;
  } else if (message.text.match(/一昨日/)) {
    range = ZangyoBot.ranges.day_before_yesterday;
  } else if (message.text.match(/(昨日|昨夜|昨晩)/)) {
    range = ZangyoBot.ranges.yesterday;
  } else if (message.text.match(/今週/)) {
    range = ZangyoBot.ranges.this_week;
  } else if (message.text.match(/先週/)) {
    range = ZangyoBot.ranges.last_week;
  } else if (message.text.match(/(過去一週間|ここ一週間)/)) {
    range = ZangyoBot.ranges.past_one_week;
  } else if (message.text.match(/今月/)) {
    range = ZangyoBot.ranges.this_month;
  } else if (message.text.match(/先月/)) {
    range = ZangyoBot.ranges.last_month;
  } else if (message.text.match(/(先々月|先先月)/)) {
    range = ZangyoBot.ranges.month_before_last;
  } else if (message.text.match(/(1[0-2]|0?[1-9])\/(3[01]|[12][0-9]|0?[1-9])/g)) {
    range = message.text.match(/(1[0-2]|0?[1-9])\/(3[01]|[12][0-9]|0?[1-9])/g)[0];
  } else if (message.text.match(/(1[0-2]|0?[1-9])月(3[01]|[12][0-9]|0?[1-9])日/g)) {
    range = message.text.match(/(1[0-2]|0?[1-9])月(3[01]|[12][0-9]|0?[1-9])日/g)[0];
  } else if (message.text.match(/(明日|明後日|明々後日|来週|再来週|来月|再来月|来年|再来年)/)) {
    bot.reply(message, "未来のことなどわかるかい！");
    return;
  } else {
    range = ZangyoBot.ranges.today;
  }

  if (message.text.match(/\<\@[a-zA-Z0-9]+\>/g)) {
    applicant = message.text.match(/\<\@[a-zA-Z0-9]+\>/g)[0].slice(2, -1);
  }

  if (message.text.match(/(全て|全部|申請一覧)/)) {
    filter = ZangyoBot.filters.all;
  } else if (message.text.match(/(最後|最終)/)) {
    filter = ZangyoBot.filters.last;
  } else {
    filter = ZangyoBot.filters.approved;
  }

  is_detailed = message.text.match(/(詳細|詳しく)/) != null;

  ZangyoBot.replyList(bot, message, range, applicant, filter, is_detailed);
});

controller.hears('^.*残業.*申請.*',['direct_message','direct_mention'],function(bot,message) {
  bot.startConversation(message, ZangyoBot.createApplication);
});

controller.hears('test', ['direct_message'],function(bot,message) {
  var reply = {
    "text": "ボタンのテストです。",
    "attachments": [{
      "text": "どれか押してください。",
      "author_name": "<@" + message.user + ">",
      "fallback": "失敗しました。",
      "callback_id": "test_button",
      "color": "#808080",
      "mrkdwn_in": ["fields"],
      "fields": [
        {
          "title": "<@" + message.user + "> まで、以下の理由により",
          "value": "*<@" + message.user+ "> ヴァリュー* ですよー",
          "short": false
        }
      ],
      "actions": [
        {
          "type": "button",
          "name": "test_button1",
          "text": "テストボタン1"
        },
        {
          "type": "button",
          "name": "test_button2",
          "text": "テストボタン2"
        }
      ]
    }]
  };
  bot.reply(message, reply);
});

controller.on('interactive_message_callback', function(bot, message) {
  var ids = message.callback_id.split(/\-/);
  var action = ids[0];
  var item_id = ids[1];
  var ans = message.actions[0].name;

  if (action == 'apply') {
    if (ans == 'apply') {
      ZangyoBot.apply(item_id, bot, message);
    } else if (ans == 'redo') {
      ZangyoBot.redoApply(item_id, bot, message);
    } else if (ans == 'cancel') {
      ZangyoBot.cancelApply(item_id, bot, message);
    }
  } else if (action == 'approve') {
    if (ans == 'approve') {
      ZangyoBot.approve(item_id, bot, message);
    } else if (ans == 'reject') {
      ZangyoBot.rejectApprove(item_id, bot, message);
    } else if (ans == 'reject-comment') {
      ZangyoBot.rejectCommentApprove(item_id, bot, message);
    }
  }
});

controller.storage.teams.all(function(err,teams) {
  if (err) {
    throw new Error(err);
  }

  // connect all teams with bots up to slack!
  for (var t  in teams) {
    if (teams[t].bot) {
      controller.spawn(teams[t]).startRTM(function(err, bot) {
        if (err) {
          console.log('Error connecting bot to Slack:',err);
        } else {
          trackBot(bot);
        }
      });
    }
  }
});
<commit_msg>slash commands hee haa<commit_after>/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# SETUP THE APP on HEROKU:
  Create a Slack app. Make sure to configure the bot user!
    -> https://api.slack.com/applications/new
    -> Add the Redirect URI: https://${APP_NAME}.herokuapp.com/oauth
  Deply and run your app on Heroku.
    -> Add Config Vars on the Heroku app setting page: clientId, clientSecret
    -> Deploy and run on Heroku.
  Set RequestURL on Slack.
    -> Add the RequesteURL of Interactive Message: https://${APP_NAME}.herokuapp.com/slack/receive
# USE THE APP
  Add the app to your Slack by visiting the login page:
    -> https://${APP_NAME}.herokuapp.com/login
  After you've added the app, try talking to your bot!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

var Botkit = require('botkit');
var MongoUrl = process.env.MONGODB_URI || 'mongodb://localhost:27017/zangyo-bot';

if (!process.env.clientId || !process.env.clientSecret || !process.env.port) {
  console.log('Error: Specify clientId clientSecret and port in environment');
  process.exit(1);
}

var controller = Botkit.slackbot({
  // interactive_replies: true, // tells botkit to send button clicks into conversations
  storage: require('botkit-storage-mongo')({mongoUri: MongoUrl})
}).configureSlackApp(
  {
    clientId: process.env.clientId,
    clientSecret: process.env.clientSecret,
    scopes: ['bot','incoming-webhook','commands'],
  }
);

var ZangyoBot = require('./lib/zangyo_bot')(controller);

controller.setupWebserver(process.env.port,function(err,webserver) {
  controller.createWebhookEndpoints(controller.webserver);

  controller.createOauthEndpoints(controller.webserver,function(err,req,res) {
    if (err) {
      res.status(500).send('ERROR: ' + err);
    } else {
      res.send('Success!');
    }
  });
});

// just a simple way to make sure we don't
// connect to the RTM twice for the same team
var _bots = {};
function trackBot(bot) {
  _bots[bot.config.token] = bot;
}

controller.on('create_bot',function(bot,config) {
  if (_bots[bot.config.token]) {
    // already online! do nothing.
  } else {
    bot.startRTM(function(err) {
      if (!err) {
        trackBot(bot);
      }

      bot.startPrivateConversation({user: config.createdBy},function(err,convo) {
        if (err) {
          console.log(err);
        } else {
          convo.say('I am a bot that has just joined your team');
          convo.say('You must now /invite me to a channel so that I can be of use!');
        }
      });
    });
  }
});

// Handle events related to the websocket connection to Slack
controller.on('rtm_open',function(bot) {
  console.log('** The RTM api just connected!');
});

controller.on('rtm_close',function(bot) {
  console.log('** The RTM api just closed');
  // you may want to attempt to re-open
});

controller.hears('^.*(申請中|未承認).*一覧.*',['direct_message','direct_mention'],function(bot,message) {
  ZangyoBot.replyPendingList(bot, message);
});

controller.hears('^.*(残業|申請).*一覧.*',['direct_message','direct_mention'],function(bot,message) {
  var range, applicant, filter, is_detailed;

  if (message.text.match(/(今日|本日|今夜|今晩)/)) {
    range = ZangyoBot.ranges.today;
  } else if (message.text.match(/一昨日/)) {
    range = ZangyoBot.ranges.day_before_yesterday;
  } else if (message.text.match(/(昨日|昨夜|昨晩)/)) {
    range = ZangyoBot.ranges.yesterday;
  } else if (message.text.match(/今週/)) {
    range = ZangyoBot.ranges.this_week;
  } else if (message.text.match(/先週/)) {
    range = ZangyoBot.ranges.last_week;
  } else if (message.text.match(/(過去一週間|ここ一週間)/)) {
    range = ZangyoBot.ranges.past_one_week;
  } else if (message.text.match(/今月/)) {
    range = ZangyoBot.ranges.this_month;
  } else if (message.text.match(/先月/)) {
    range = ZangyoBot.ranges.last_month;
  } else if (message.text.match(/(先々月|先先月)/)) {
    range = ZangyoBot.ranges.month_before_last;
  } else if (message.text.match(/(1[0-2]|0?[1-9])\/(3[01]|[12][0-9]|0?[1-9])/g)) {
    range = message.text.match(/(1[0-2]|0?[1-9])\/(3[01]|[12][0-9]|0?[1-9])/g)[0];
  } else if (message.text.match(/(1[0-2]|0?[1-9])月(3[01]|[12][0-9]|0?[1-9])日/g)) {
    range = message.text.match(/(1[0-2]|0?[1-9])月(3[01]|[12][0-9]|0?[1-9])日/g)[0];
  } else if (message.text.match(/(明日|明後日|明々後日|来週|再来週|来月|再来月|来年|再来年)/)) {
    bot.reply(message, "未来のことなどわかるかい！");
    return;
  } else {
    range = ZangyoBot.ranges.today;
  }

  if (message.text.match(/\<\@[a-zA-Z0-9]+\>/g)) {
    applicant = message.text.match(/\<\@[a-zA-Z0-9]+\>/g)[0].slice(2, -1);
  }

  if (message.text.match(/(全て|全部|申請一覧)/)) {
    filter = ZangyoBot.filters.all;
  } else if (message.text.match(/(最後|最終)/)) {
    filter = ZangyoBot.filters.last;
  } else {
    filter = ZangyoBot.filters.approved;
  }

  is_detailed = message.text.match(/(詳細|詳しく)/) != null;

  ZangyoBot.replyList(bot, message, range, applicant, filter, is_detailed);
});

controller.hears('^.*残業.*申請.*',['direct_message','direct_mention'],function(bot,message) {
  bot.startConversation(message, ZangyoBot.createApplication);
});

controller.hears('test', ['direct_message'],function(bot,message) {
  var reply = {
    "text": "ボタンのテストです。",
    "attachments": [{
      "text": "どれか押してください。",
      "author_name": "<@" + message.user + ">",
      "fallback": "失敗しました。",
      "callback_id": "test_button",
      "color": "#808080",
      "mrkdwn_in": ["fields"],
      "fields": [
        {
          "title": "<@" + message.user + "> まで、以下の理由により",
          "value": "*<@" + message.user+ "> ヴァリュー* ですよー",
          "short": false
        }
      ],
      "actions": [
        {
          "type": "button",
          "name": "test_button1",
          "text": "テストボタン1"
        },
        {
          "type": "button",
          "name": "test_button2",
          "text": "テストボタン2"
        }
      ]
    }]
  };
  bot.reply(message, reply);
});

controller.on('interactive_message_callback', function(bot, message) {
  var ids = message.callback_id.split(/\-/);
  var action = ids[0];
  var item_id = ids[1];
  var ans = message.actions[0].name;

  if (action == 'apply') {
    if (ans == 'apply') {
      ZangyoBot.apply(item_id, bot, message);
    } else if (ans == 'redo') {
      ZangyoBot.redoApply(item_id, bot, message);
    } else if (ans == 'cancel') {
      ZangyoBot.cancelApply(item_id, bot, message);
    }
  } else if (action == 'approve') {
    if (ans == 'approve') {
      ZangyoBot.approve(item_id, bot, message);
    } else if (ans == 'reject') {
      ZangyoBot.rejectApprove(item_id, bot, message);
    } else if (ans == 'reject-comment') {
      ZangyoBot.rejectCommentApprove(item_id, bot, message);
    }
  }
});

controller.storage.teams.all(function(err,teams) {
  if (err) {
    throw new Error(err);
  }

  // connect all teams with bots up to slack!
  for (var t  in teams) {
    if (teams[t].bot) {
      controller.spawn(teams[t]).startRTM(function(err, bot) {
        if (err) {
          console.log('Error connecting bot to Slack:',err);
        } else {
          trackBot(bot);
        }
      });
    }
  }
});
<|endoftext|>


<commit_before>hexo.extend.tag.register('deezer', function(args){

    var url = 'http://www.deezer.com/plugins/player';

    var trackId = args[0],
        format = args[1] || 'classic',
        size = args[2] || 'medium',
        autoplay = args[3] || 'false',
        playlist = args[4] || 'false';

    var width = '700';
    var height = '290';

    if (format === 'square') {
        width = '300';
        height = '300';
    }

    var config = hexo.config.deezer || {};
    config.theme = config.theme || 'dark';
    config.appId = config.appid || 1;
    config.colour = config.colour || '1990DB';

    var iframeTag = '<iframe src="' + src +
        '?format=' + format +
        '&autoplay=' + autoplay +
        '&playlist=' + playlist +
        '&width=' + width +
        '&height=' + height +
        '&color=' + config.colour +
        '&layout=' + config.theme +
        '&size=' + size +
        '&type=tracks' +
        '&id=' + trackId +
        '&title=&app_id=' + config.appId +
        ' scrolling="no"' +
        ' frameborder="0"' +
        ' allowTransparency="true"' +
        ' width="' + width + '" height="' + height + '">' +
        '';

    return iframeTag;

}, options);<commit_msg>Fixing undefined options<commit_after>hexo.extend.tag.register('deezer', function(args){

    var url = 'http://www.deezer.com/plugins/player';

    var trackId = args[0],
        format = args[1] || 'classic',
        size = args[2] || 'medium',
        autoplay = args[3] || 'false',
        playlist = args[4] || 'false';

    var width = '700';
    var height = '290';

    if (format === 'square') {
        width = '300';
        height = '300';
    }

    var config = hexo.config.deezer || {};
    config.theme = config.theme || 'dark';
    config.appId = config.appid || 1;
    config.colour = config.colour || '1990DB';

    var iframeTag = '<iframe src="' + src +
        '?format=' + format +
        '&autoplay=' + autoplay +
        '&playlist=' + playlist +
        '&width=' + width +
        '&height=' + height +
        '&color=' + config.colour +
        '&layout=' + config.theme +
        '&size=' + size +
        '&type=tracks' +
        '&id=' + trackId +
        '&title=&app_id=' + config.appId +
        ' scrolling="no"' +
        ' frameborder="0"' +
        ' allowTransparency="true"' +
        ' width="' + width + '" height="' + height + '">' +
        '';

    return iframeTag;

});<|endoftext|>


<commit_before>var tracking = [];

function isVisible(elm) {
  var rect = elm.getBoundingClientRect();

  return rect.bottom > 0 &&
    rect.right > 0 &&
    rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
    rect.top < (window.innerHeight || document.documentElement.clientHeight);
}

function _onScroll() {
  window.requestAnimationFrame(_checkForVisibleElements);
}

function _handleVisible(elm, options) {
  untrack(elm);

  if (options.handler !== void 0) {
    options.handler(elm);
  }
}

function _trackNewElement(elm, options) {
  if (isVisible(elm)) {
    return _handleVisible(elm, options);
  }

  tracking.push({elm: elm, options: options});
}

function _checkForVisibleElements() {
  tracking.forEach((v) => {
    if (isVisible(v.elm)) {
      _handleVisible(v.elm, v.options);
    }
  });

  if (tracking.length === 0) {
    window.removeEventListener('scroll', _onScroll);
  }
}

function track(elm, options) {
  window.requestAnimationFrame(_trackNewElement.bind(this, elm, options));

  if (tracking.length === 0) {
    window.addEventListener('scroll', _onScroll);
  }
}

function untrackAll() {
  tracking = [];
}

function untrack(elm) {
  var elmIndex = -1;

  tracking.some((v, i) => {
    if (v.elm == elm) {
      elmIndex = i;
      return true;
    }
  });

  if (elmIndex !== -1) {
    tracking.splice(elmIndex, 1);
  }
}

export default {track, untrackAll, untrack};
<commit_msg>:violin: get rid of bind trickery<commit_after>var tracking = [];

function isVisible(elm) {
  var rect = elm.getBoundingClientRect();

  return rect.bottom > 0 &&
    rect.right > 0 &&
    rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
    rect.top < (window.innerHeight || document.documentElement.clientHeight);
}

function _onScroll() {
  window.requestAnimationFrame(_checkForVisibleElements);
}

function _handleVisible(elm, options) {
  untrack(elm);

  if (options.handler !== void 0) {
    options.handler(elm);
  }
}

function _trackNewElement(elm, options) {
  if (isVisible(elm)) {
    return _handleVisible(elm, options);
  }

  tracking.push({elm: elm, options: options});
}

function _checkForVisibleElements() {
  tracking.forEach((v) => {
    if (isVisible(v.elm)) {
      _handleVisible(v.elm, v.options);
    }
  });

  if (tracking.length === 0) {
    window.removeEventListener('scroll', _onScroll);
  }
}

function track(elm, options) {
  window.requestAnimationFrame(() => _trackNewElement(elm, options));

  if (tracking.length === 0) {
    window.addEventListener('scroll', _onScroll);
  }
}

function untrackAll() {
  tracking = [];
}

function untrack(elm) {
  var elmIndex = -1;

  tracking.some((v, i) => {
    if (v.elm == elm) {
      elmIndex = i;
      return true;
    }
  });

  if (elmIndex !== -1) {
    tracking.splice(elmIndex, 1);
  }
}

export default {track, untrackAll, untrack};
<|endoftext|>


<commit_before>"use strict";

const url = require("url");
const http = require("http");
const https = require("https");
const crypto = require("crypto");
const util = require("util");
const path = require("path");
const qs = require("querystring");
const stream = require("stream");
const methods = ["PUT", "POST", "PATCH", "DELETE", "GET", "HEAD", "OPTIONS"];
const writeMethods = ["PUT", "POST", "PATCH"];
const mime = require("./mime.json");

var InvalidProtocolError = new Error("Invalid protocol");
InvalidProtocolError.code = "invalid_protocol";

var InvalidMethodError = new Error("Invalid method");
InvalidMethodError.code = "invalid_method";

var WriteOnReadOnlyMethodError = new Error("Write on read-only method");
WriteOnReadOnlyMethodError.code = "write_on_read_method";

class ES6Request extends stream.Duplex {
    constructor(method, urlStr, options) {
        // validate arguments
        if (typeof method !== "string") {
            throw new TypeError("Invalid type for argument \"method\"");
        }
        if (!methods.includes(method)) {
            throw InvalidMethodError;
        }
        if (typeof urlStr !== "string") {
            throw new TypeError("Invalid type for argument \"urlStr\"");
        }
        if (typeof options !== "undefined" && options !== undefined &&
            (typeof options !== "object" || Array.isArray(options))) {
            throw new TypeError("Invalid type for argument \"options\"");
        }

        // initialize duplex stream
        super();

        // parse url string
        this.url = url.parse(urlStr);

        // create base options object
        this._options = {
            hostname: this.url.hostname,
            path: this.url.pathname,
            method: method,
            headers: {},
            custom: {
                bodyAsBuffer: false,
                returnBody: true
            }
        };
        if (typeof options !== "undefined" && options !== undefined) {
            this.options(options);
        }

        this.qs = qs.parse(this.url.query) || {};

        return this;
    }

    headers(obj) {
        // validate arguments
        if (typeof obj !== "object" || Array.isArray(obj)) {
            throw new TypeError("Invalid type for argument \"obj\"");
        }

        Object.assign(this._options.headers, obj);
        return this;
    }

    header(key, value) {
        // validate arguments
        if (typeof key !== "string") {
            throw new TypeError("Invalid type for argument \"key\"");
        }
        if (typeof value !== "number" && typeof value !== "string" && (typeof value !== "object" && Array.isArray(value)) && typeof value !== "undefined") {
            throw new TypeError("Invalid type for argument \"value\"");
        }

        this._options.headers[key] = value;
        return this;
    }

    authBasic(username, password) {
        // validate arguments
        if (typeof username !== "string") {
            throw new TypeError("Invalid type for argument \"username\"");
        }
        if (typeof password !== "string") {
            throw new TypeError("Invalid type for argument \"password\"");
        }

        return this.header("Authorization", "Basic " + Buffer.from(username + ":" + password, "utf8").toString("base64"));
    }

    authBearer(bearer) {
        // validate arguments
        if (typeof bearer !== "string") {
            throw new TypeError("Invalid type for argument \"bearer\"");
        }

        return this.header("Authorization", "Bearer " + bearer);
    }

    options(obj) {
        // validate arguments
        if (typeof obj !== "object" || Array.isArray(obj)) {
            throw new TypeError("Invalid type for argument \"obj\"");
        }

        for (let key of ['hostname', 'path', 'method']) {
            if (obj.hasOwnProperty(key)) {
                this._options[key] = obj[key];
            }
        }
        for (let key of ['headers', 'custom']) {
            if (obj.hasOwnProperty(key)) {
                this._options[key] = Object.assign(this._options[key], obj[key]);
            }
        }
        for (let key of obj) {
            if (!['hostname', 'path', 'method', 'headers', 'custom'].includes(key)) {
                this._options[key] = obj[key];
            }
        }
        return this;
    }

    option(key, value) {
        // validate arguments
        if (typeof key !== "string") {
            throw new TypeError("Invalid type for argument \"key\"");
        }

        this._options[key] = value;
        return this;
    }

    query(key, value) {
        // validate arguments
        if (typeof key === "object" && !Array.isArray(key)) {
            if (typeof value !== "undefined" || value !== undefined) {
                throw new TypeError("Invalid type for argument \"value\"");
            }
            Object.assign(this.qs, key);
        } else {
            if (typeof key !== "string") {
                throw new TypeError("Invalid type for argument \"key\"");
            }
            if (typeof value !== "string") {
                throw new TypeError("Invalid type for argument \"value\"");
            }

            this.qs[key] = value;
        }

        return this;
    }

    start() {
        if (this._started) return this;

        if (Object.keys(this.qs).length > 0) this._options.path = this.url.pathname + "?" + qs.stringify(this.qs);

        // protocol switch
        switch (this.url.protocol) {
            case "https:":
            this._options.port = this.url.port || 443;
            this.req = https.request(this._options);
            this._started = true;
            break;

            case "http:":
            this._options.port = this.url.port || 80;
            this.req = http.request(this._options);
            this._started = true;
            break;

            default:
            throw InvalidProtocolError;
            break;
        }

        return this;
    }

    then(onSuccess, onFailure) {
        // validate arguments
        if (typeof onSuccess !== "undefined" &&
            onSuccess !== undefined &&
            typeof onSuccess !== "function") {
            throw new TypeError("Invalid type for argument \"onSuccess\"");
        }
        if (typeof onFailure !== "undefined" &&
            onFailure !== undefined &&
            typeof onFailure !== "function") {
            throw new TypeError("Invalid type for argument \"onFailure\"");
        }

        return this.perform().then(onSuccess, onFailure);
    }

    catch (onFailure) {
        // validate arguments
        if (typeof onFailure !== "undefined" &&
            onFailure !== undefined &&
            typeof onFailure !== "function") {
            throw new TypeError("Invalid type for argument \"onFailure\"");
        }

        return this.perform().catch(onFailure);
    }

    _destroy() {
        this._active = false;
        this._started = false;
        this.req = null;
        this.body = [];
    }

    perform() {
        return new Promise((resolve, reject) => {
            if (!this._started) {
                this.start();
            }

            this._active = true;

            const errorListener = e => {
                this.removeListener("error", errorListener);
                this.destroy();
                reject(e);
            };

            this.on("error", errorListener);
            this.req.on("error", e => this.emit("error", e));
            this.on("close", () => this.removeListener("error", errorListener));

            this.req.on("response", res => {
                this.body = [];
                const responseLength = parseInt(res.headers['content-length']);
                let curLength = 0;

                res.on("data", chunk => {
                    this.emit("data", chunk);
                    if (this._options.custom.returnBody) {
                        this.body.push(chunk);
                    }
                    if (!isNaN(responseLength)) {
                        curLength += chunk.byteLength;
                        this.emit("progress", curLength / responseLength, curLength, responseLength);
                    }
                });

                res.on("end", () => {
                    this.emit("end");

                    if (this._options.custom.returnBody) {
                        if (this._options.custom.bodyAsBuffer) {
                            resolve([Buffer.concat(this.body), res]);
                        } else {
                            resolve([Buffer.concat(this.body).toString(), res]);
                        }
                    } else {
                        resolve([null, res]);
                    }

                    this.destroy();
                    this.emit("close");
                });

                res.on("error", e => this.emit("error", e));
            });

            this.req.end();
        });
    }

    write(chunk, encoding, callback) {
        // validate arguments
        if (typeof chunk !== "string" && !(typeof chunk === "object" && (chunk instanceof Buffer))) {
            throw new TypeError("Invalid type for argument \"chunk\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined && typeof encoding !== "string") {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }
        if (typeof callback !== "undefined" && callback !== undefined && typeof callback !== "function") {
            throw new TypeError("Invalid type for argument \"callback\"");
        }

        if (!writeMethods.includes(this._options.method)) {
            throw WriteOnReadOnlyMethodError;
        }

        if (!this._started) {
            this.start();
        }

        return this.req.write(chunk, encoding, callback);
    }

    pipe(dest, opt) {
        // validate arguments
        if (typeof dest !== "object" || !(dest instanceof stream.Writable)) {
            throw new TypeError("Invalid type for argument \"dest\"");
        }
        if (typeof opt !== "undefined" && opt !== undefined && typeof opt !== "object" || Array.isArray(opt)) {
            throw new TypeError("Invalid type for argument \"opt\"");
        }

        stream.Duplex.prototype.pipe.call(this, dest, opt);
        this._readStreamEnabled = true;
        return this;
    }

    _read(size) {}
    _write(chunk, encoding, callback) {
        // validate arguments
        if (typeof chunk !== "string" && !(typeof chunk === "object" && (chunk instanceof Buffer))) {
            throw new TypeError("Invalid type for argument \"chunk\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined && typeof encoding !== "string") {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }
        if (typeof callback !== "undefined" && callback !== undefined && typeof callback !== "function") {
            throw new TypeError("Invalid type for argument \"callback\"");
        }

        return this.write(chunk, encoding, callback);
    }

    send(body, encoding, callback) {
        // validate arguments
        if (typeof body !== "string" && !(typeof body === "object" && (body instanceof Buffer))) {
            throw new TypeError("Invalid type for argument \"body\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined && typeof encoding !== "string") {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }
        if (typeof callback !== "undefined" && callback !== undefined && typeof callback !== "function") {
            throw new TypeError("Invalid type for argument \"callback\"");
        }

        this.write(body, encoding, callback);
        return this.perform();
    }

    sendForm(form) {
        if (typeof form !== "object" || Array.isArray(form)) {
            throw new TypeError("Invalid type for argument \"form\"");
        }

        const body = Buffer.from(qs.stringify(form), "utf8");

        return this.headers({
            "Content-Type": "application/x-www-form-urlencoded",
            "Content-Length": body.byteLength
        }).send(body);
    }

    sendMultipart(form, files, filesFieldNameFormat, encoding) {
        if (typeof form !== "undefined" && form !== undefined && typeof form !== "object") {
            throw new TypeError("Invalid type for argument \"form\"");
        }
        if (typeof files !== "undefined" && files !== undefined && typeof files !== "object") {
            throw new TypeError("Invalid type for argument \"files\"");
        }
        if (typeof filesFieldNameFormat !== "undefined" && filesFieldNameFormat !== undefined &&
            typeof filesFieldNameFormat !== "string") {
            throw new TypeError("Invalid type for argument \"filesFieldNameFormat\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined &&
            (typeof encoding !== "string" || !["base64", "BASE64", "utf8", "UTF8", "utf-8", "UTF-8"].includes(encoding))) {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }

        // define default file field name
        filesFieldNameFormat = filesFieldNameFormat || "files[%i]";

        // define default encoding
        encoding = encoding || "utf8";
        const transferEncoding = (["utf8", "utf-8", "UTF8", "UTF-8"].includes(encoding) ? "default" : encoding);

        // generate random multipart form boundary
        const boundary = "--------------------------" + crypto.randomBytes(6).toString("hex");
        let body = Buffer.alloc(0);

        // build multipart form body
        if (form != null) {
            Object.keys(form).forEach(fieldName => {
                if (typeof fieldName !== "string") {
                    throw new Error("Field name is not a string");
                }

                let headers = "--" + boundary + "\r\n";
                if (transferEncoding != "default") {
                    headers += "MIME-Version: 1.0\r\n";
                    headers += "Content-Transfer-Encoding: " + transferEncoding + "\r\n";
                }
                headers += "Content-Disposition: form-data; name=\"" +
                encodeURIComponent(fieldName) + "\"\r\n\r\n";

                body = Buffer.concat([
                    body,
                    Buffer.from(headers, "utf8"),
                    transferEncoding != "default" ?
                    Buffer.from(Buffer.from(form[fieldName]).toString(encoding), "utf8") :
                    Buffer.from(form[fieldName]),
                    Buffer.from("\r\n", "utf8")
                ]);
            });
        }

        // build multipart form body
        if (files != null) {
            Object.keys(files).forEach((fileName, fileIndex) => {
                if (typeof fileName !== "string") {
                    throw new Error("File name is not a string");
                }

                const fileExt = path.extname(fileName).toLowerCase();
                const mimeType = mime.hasOwnProperty(fileExt) ? mime[fileExt] : "application/octet-stream";

                let headers = "--" + boundary + "\r\n";
                if (transferEncoding != "default") {
                    headers += "MIME-Version: 1.0\r\n";
                    headers += "Content-Transfer-Encoding: " + transferEncoding + "\r\n";
                }
                headers += "Content-Type: " + mimeType + "; charset=utf-8\r\n";
                headers += "Content-Disposition: form-data; name=\"" +
                encodeURIComponent((
                    filesFieldNameFormat.includes("%") ?
                    util.format(filesFieldNameFormat, fileIndex) :
                    filesFieldNameFormat
                )) + "\"; filename=\"" + encodeURIComponent(fileName) + "\"\r\n\r\n";

                body = Buffer.concat([
                    body,
                    Buffer.from(headers, "utf8"),
                    transferEncoding != "default" ?
                    Buffer.from(Buffer.from(files[fileName]).toString(encoding), "utf8") :
                    Buffer.from(files[fileName]),
                    Buffer.from("\r\n", "utf8")
                ]);
            });
        }

        // append final multipart form boundary
        body = Buffer.concat([
            body,
            Buffer.from("--" + boundary + "--", "utf8")
        ]);

        return this.headers({
            "Content-Type": "multipart/form-data; boundary=" + boundary,
            "Content-Length": body.byteLength
        }).send(body);
    }

    sendJSON(data) {
        const body = Buffer.from(JSON.stringify(data), "utf8");

        return this.headers({
            "Content-Type": "application/json",
            "Content-Length": body.byteLength
        }).send(body);
    }
}

class ES6RequestMethods {}

methods.forEach(method => {
    ES6RequestMethods[method.toLowerCase()] = (urlStr, options) => {
        return new ES6Request(method, urlStr, options);
    }
});

ES6RequestMethods.Errors = {};
ES6RequestMethods.Errors.InvalidProtocol = InvalidProtocolError;
ES6RequestMethods.Errors.InvalidMethod = InvalidMethodError;
ES6RequestMethods.Errors.WriteOnReadOnlyMethod = WriteOnReadOnlyMethodError;

module.exports = ES6RequestMethods;
<commit_msg>Add Content-Length check<commit_after>"use strict";

const url = require("url");
const http = require("http");
const https = require("https");
const crypto = require("crypto");
const util = require("util");
const path = require("path");
const qs = require("querystring");
const stream = require("stream");
const methods = ["PUT", "POST", "PATCH", "DELETE", "GET", "HEAD", "OPTIONS"];
const writeMethods = ["PUT", "POST", "PATCH"];
const mime = require("./mime.json");

var InvalidProtocolError = new Error("Invalid protocol");
InvalidProtocolError.code = "invalid_protocol";

var InvalidMethodError = new Error("Invalid method");
InvalidMethodError.code = "invalid_method";

var WriteOnReadOnlyMethodError = new Error("Write on read-only method");
WriteOnReadOnlyMethodError.code = "write_on_read_method";

var InvalidResponseLengthError = new Error("Invalid response length");
InvalidResponseLengthError.code = "invalid_response_length";

class ES6Request extends stream.Duplex {
    constructor(method, urlStr, options) {
        // validate arguments
        if (typeof method !== "string") {
            throw new TypeError("Invalid type for argument \"method\"");
        }
        if (!methods.includes(method)) {
            throw InvalidMethodError;
        }
        if (typeof urlStr !== "string") {
            throw new TypeError("Invalid type for argument \"urlStr\"");
        }
        if (typeof options !== "undefined" && options !== undefined &&
            (typeof options !== "object" || Array.isArray(options))) {
            throw new TypeError("Invalid type for argument \"options\"");
        }

        // initialize duplex stream
        super();

        // parse url string
        this.url = url.parse(urlStr);

        // create base options object
        this._options = {
            hostname: this.url.hostname,
            path: this.url.pathname,
            method: method,
            headers: {},
            custom: {
                bodyAsBuffer: false,
                returnBody: true
            }
        };
        if (typeof options !== "undefined" && options !== undefined) {
            this.options(options);
        }

        this.qs = qs.parse(this.url.query) || {};

        return this;
    }

    headers(obj) {
        // validate arguments
        if (typeof obj !== "object" || Array.isArray(obj)) {
            throw new TypeError("Invalid type for argument \"obj\"");
        }

        Object.assign(this._options.headers, obj);
        return this;
    }

    header(key, value) {
        // validate arguments
        if (typeof key !== "string") {
            throw new TypeError("Invalid type for argument \"key\"");
        }
        if (typeof value !== "number" && typeof value !== "string" && (typeof value !== "object" && Array.isArray(value)) && typeof value !== "undefined") {
            throw new TypeError("Invalid type for argument \"value\"");
        }

        this._options.headers[key] = value;
        return this;
    }

    authBasic(username, password) {
        // validate arguments
        if (typeof username !== "string") {
            throw new TypeError("Invalid type for argument \"username\"");
        }
        if (typeof password !== "string") {
            throw new TypeError("Invalid type for argument \"password\"");
        }

        return this.header("Authorization", "Basic " + Buffer.from(username + ":" + password, "utf8").toString("base64"));
    }

    authBearer(bearer) {
        // validate arguments
        if (typeof bearer !== "string") {
            throw new TypeError("Invalid type for argument \"bearer\"");
        }

        return this.header("Authorization", "Bearer " + bearer);
    }

    options(obj) {
        // validate arguments
        if (typeof obj !== "object" || Array.isArray(obj)) {
            throw new TypeError("Invalid type for argument \"obj\"");
        }

        for (let key of ['hostname', 'path', 'method']) {
            if (obj.hasOwnProperty(key)) {
                this._options[key] = obj[key];
            }
        }
        for (let key of ['headers', 'custom']) {
            if (obj.hasOwnProperty(key)) {
                this._options[key] = Object.assign(this._options[key], obj[key]);
            }
        }
        for (let key of obj) {
            if (!['hostname', 'path', 'method', 'headers', 'custom'].includes(key)) {
                this._options[key] = obj[key];
            }
        }
        return this;
    }

    option(key, value) {
        // validate arguments
        if (typeof key !== "string") {
            throw new TypeError("Invalid type for argument \"key\"");
        }

        this._options[key] = value;
        return this;
    }

    query(key, value) {
        // validate arguments
        if (typeof key === "object" && !Array.isArray(key)) {
            if (typeof value !== "undefined" || value !== undefined) {
                throw new TypeError("Invalid type for argument \"value\"");
            }
            Object.assign(this.qs, key);
        } else {
            if (typeof key !== "string") {
                throw new TypeError("Invalid type for argument \"key\"");
            }
            if (typeof value !== "string") {
                throw new TypeError("Invalid type for argument \"value\"");
            }

            this.qs[key] = value;
        }

        return this;
    }

    start() {
        if (this._started) return this;

        if (Object.keys(this.qs).length > 0) this._options.path = this.url.pathname + "?" + qs.stringify(this.qs);

        // protocol switch
        switch (this.url.protocol) {
            case "https:":
            this._options.port = this.url.port || 443;
            this.req = https.request(this._options);
            this._started = true;
            break;

            case "http:":
            this._options.port = this.url.port || 80;
            this.req = http.request(this._options);
            this._started = true;
            break;

            default:
            throw InvalidProtocolError;
            break;
        }

        return this;
    }

    then(onSuccess, onFailure) {
        // validate arguments
        if (typeof onSuccess !== "undefined" &&
            onSuccess !== undefined &&
            typeof onSuccess !== "function") {
            throw new TypeError("Invalid type for argument \"onSuccess\"");
        }
        if (typeof onFailure !== "undefined" &&
            onFailure !== undefined &&
            typeof onFailure !== "function") {
            throw new TypeError("Invalid type for argument \"onFailure\"");
        }

        return this.perform().then(onSuccess, onFailure);
    }

    catch (onFailure) {
        // validate arguments
        if (typeof onFailure !== "undefined" &&
            onFailure !== undefined &&
            typeof onFailure !== "function") {
            throw new TypeError("Invalid type for argument \"onFailure\"");
        }

        return this.perform().catch(onFailure);
    }

    _destroy() {
        this._active = false;
        this._started = false;
        this.req = null;
        this.body = [];
    }

    perform() {
        return new Promise((resolve, reject) => {
            if (!this._started) {
                this.start();
            }

            this._active = true;

            const errorListener = e => {
                this.removeListener("error", errorListener);
                this.destroy();
                reject(e);
            };

            this.on("error", errorListener);
            this.req.on("error", e => this.emit("error", e));
            this.on("close", () => this.removeListener("error", errorListener));

            this.req.on("response", res => {
                this.body = [];
                let hasResponseLength = res.headers.hasOwnProperty('content-length');
                let responseLength = 0;
                let curLength = 0;
                if (hasResponseLength) {
                    responseLength = parseInt(res.headers['content-length']);
                    hasResponseLength = !isNaN(responseLength);
                }

                res.on("data", chunk => {
                    if (hasResponseLength && curLength + chunk.length > responseLength) {
                        this.emit('error', InvalidResponseLengthError);
                        return;
                    }
                    this.emit("data", chunk);
                    if (this._options.custom.returnBody) {
                        this.body.push(chunk);
                    }
                    if (hasResponseLength) {
                        curLength += chunk.length;
                        this.emit("progress", curLength / responseLength, curLength, responseLength);
                    }
                });

                res.on("end", () => {
                    this.emit("end");

                    if (this._options.custom.returnBody) {
                        if (this._options.custom.bodyAsBuffer) {
                            resolve([Buffer.concat(this.body), res]);
                        } else {
                            resolve([Buffer.concat(this.body).toString(), res]);
                        }
                    } else {
                        resolve([null, res]);
                    }

                    this.destroy();
                    this.emit("close");
                });

                res.on("error", e => this.emit("error", e));
            });

            this.req.end();
        });
    }

    write(chunk, encoding, callback) {
        // validate arguments
        if (typeof chunk !== "string" && !(typeof chunk === "object" && (chunk instanceof Buffer))) {
            throw new TypeError("Invalid type for argument \"chunk\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined && typeof encoding !== "string") {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }
        if (typeof callback !== "undefined" && callback !== undefined && typeof callback !== "function") {
            throw new TypeError("Invalid type for argument \"callback\"");
        }

        if (!writeMethods.includes(this._options.method)) {
            throw WriteOnReadOnlyMethodError;
        }

        if (!this._started) {
            this.start();
        }

        return this.req.write(chunk, encoding, callback);
    }

    pipe(dest, opt) {
        // validate arguments
        if (typeof dest !== "object" || !(dest instanceof stream.Writable)) {
            throw new TypeError("Invalid type for argument \"dest\"");
        }
        if (typeof opt !== "undefined" && opt !== undefined && typeof opt !== "object" || Array.isArray(opt)) {
            throw new TypeError("Invalid type for argument \"opt\"");
        }

        stream.Duplex.prototype.pipe.call(this, dest, opt);
        this._readStreamEnabled = true;
        return this;
    }

    _read(size) {}
    _write(chunk, encoding, callback) {
        // validate arguments
        if (typeof chunk !== "string" && !(typeof chunk === "object" && (chunk instanceof Buffer))) {
            throw new TypeError("Invalid type for argument \"chunk\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined && typeof encoding !== "string") {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }
        if (typeof callback !== "undefined" && callback !== undefined && typeof callback !== "function") {
            throw new TypeError("Invalid type for argument \"callback\"");
        }

        return this.write(chunk, encoding, callback);
    }

    send(body, encoding, callback) {
        // validate arguments
        if (typeof body !== "string" && !(typeof body === "object" && (body instanceof Buffer))) {
            throw new TypeError("Invalid type for argument \"body\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined && typeof encoding !== "string") {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }
        if (typeof callback !== "undefined" && callback !== undefined && typeof callback !== "function") {
            throw new TypeError("Invalid type for argument \"callback\"");
        }

        this.write(body, encoding, callback);
        return this.perform();
    }

    sendForm(form) {
        if (typeof form !== "object" || Array.isArray(form)) {
            throw new TypeError("Invalid type for argument \"form\"");
        }

        const body = Buffer.from(qs.stringify(form), "utf8");

        return this.headers({
            "Content-Type": "application/x-www-form-urlencoded",
            "Content-Length": body.length
        }).send(body);
    }

    sendMultipart(form, files, filesFieldNameFormat, encoding) {
        if (typeof form !== "undefined" && form !== undefined && typeof form !== "object") {
            throw new TypeError("Invalid type for argument \"form\"");
        }
        if (typeof files !== "undefined" && files !== undefined && typeof files !== "object") {
            throw new TypeError("Invalid type for argument \"files\"");
        }
        if (typeof filesFieldNameFormat !== "undefined" && filesFieldNameFormat !== undefined &&
            typeof filesFieldNameFormat !== "string") {
            throw new TypeError("Invalid type for argument \"filesFieldNameFormat\"");
        }
        if (typeof encoding !== "undefined" && encoding !== undefined &&
            (typeof encoding !== "string" || !["base64", "BASE64", "utf8", "UTF8", "utf-8", "UTF-8"].includes(encoding))) {
            throw new TypeError("Invalid type for argument \"encoding\"");
        }

        // define default file field name
        filesFieldNameFormat = filesFieldNameFormat || "files[%i]";

        // define default encoding
        encoding = encoding || "utf8";
        const transferEncoding = (["utf8", "utf-8", "UTF8", "UTF-8"].includes(encoding) ? "default" : encoding);

        // generate random multipart form boundary
        const boundary = "--------------------------" + crypto.randomBytes(6).toString("hex");
        let body = Buffer.alloc(0);

        // build multipart form body
        if (form != null) {
            Object.keys(form).forEach(fieldName => {
                if (typeof fieldName !== "string") {
                    throw new Error("Field name is not a string");
                }

                let headers = "--" + boundary + "\r\n";
                if (transferEncoding != "default") {
                    headers += "MIME-Version: 1.0\r\n";
                    headers += "Content-Transfer-Encoding: " + transferEncoding + "\r\n";
                }
                headers += "Content-Disposition: form-data; name=\"" +
                encodeURIComponent(fieldName) + "\"\r\n\r\n";

                body = Buffer.concat([
                    body,
                    Buffer.from(headers, "utf8"),
                    transferEncoding != "default" ?
                    Buffer.from(Buffer.from(form[fieldName]).toString(encoding), "utf8") :
                    Buffer.from(form[fieldName]),
                    Buffer.from("\r\n", "utf8")
                ]);
            });
        }

        // build multipart form body
        if (files != null) {
            Object.keys(files).forEach((fileName, fileIndex) => {
                if (typeof fileName !== "string") {
                    throw new Error("File name is not a string");
                }

                const fileExt = path.extname(fileName).toLowerCase();
                const mimeType = mime.hasOwnProperty(fileExt) ? mime[fileExt] : "application/octet-stream";

                let headers = "--" + boundary + "\r\n";
                if (transferEncoding != "default") {
                    headers += "MIME-Version: 1.0\r\n";
                    headers += "Content-Transfer-Encoding: " + transferEncoding + "\r\n";
                }
                headers += "Content-Type: " + mimeType + "; charset=utf-8\r\n";
                headers += "Content-Disposition: form-data; name=\"" +
                encodeURIComponent((
                    filesFieldNameFormat.includes("%") ?
                    util.format(filesFieldNameFormat, fileIndex) :
                    filesFieldNameFormat
                )) + "\"; filename=\"" + encodeURIComponent(fileName) + "\"\r\n\r\n";

                body = Buffer.concat([
                    body,
                    Buffer.from(headers, "utf8"),
                    transferEncoding != "default" ?
                    Buffer.from(Buffer.from(files[fileName]).toString(encoding), "utf8") :
                    Buffer.from(files[fileName]),
                    Buffer.from("\r\n", "utf8")
                ]);
            });
        }

        // append final multipart form boundary
        body = Buffer.concat([
            body,
            Buffer.from("--" + boundary + "--", "utf8")
        ]);

        return this.headers({
            "Content-Type": "multipart/form-data; boundary=" + boundary,
            "Content-Length": body.length
        }).send(body);
    }

    sendJSON(data) {
        const body = Buffer.from(JSON.stringify(data), "utf8");

        return this.headers({
            "Content-Type": "application/json",
            "Content-Length": body.length
        }).send(body);
    }
}

class ES6RequestMethods {}

methods.forEach(method => {
    ES6RequestMethods[method.toLowerCase()] = (urlStr, options) => {
        return new ES6Request(method, urlStr, options);
    }
});

ES6RequestMethods.Errors = {};
ES6RequestMethods.Errors.InvalidProtocol = InvalidProtocolError;
ES6RequestMethods.Errors.InvalidMethod = InvalidMethodError;
ES6RequestMethods.Errors.WriteOnReadOnlyMethod = WriteOnReadOnlyMethodError;

module.exports = ES6RequestMethods;
<|endoftext|>


<commit_before>var katex = require("katex");

module.exports = {
    book: {
        assets: "./static",
        js: [],
        css: [
            "katex.min.css"
        ]
    },
    ebook: {
        assets: "./static",
        css: [
            "katex.min.css"
        ]
    },
    blocks: {
        math: {
            shortcuts: {
                parsers: ["markdown", "asciidoc", "restructuredtext"],
                start: "$$",
                end: "$$"
            },
            process: function(blk) {
                var tex = blk.body;
                var isInline = !(tex[0] == "\n");
                var output = katex.renderToString(tex);

                if (!isInline) {
                    output = '
'+output+'
';
                }

                return output;
            }
        }
    }
};
<commit_msg>Fix display mode for block formulas<commit_after>var katex = require("katex");

module.exports = {
    book: {
        assets: "./static",
        js: [],
        css: [
            "katex.min.css"
        ]
    },
    ebook: {
        assets: "./static",
        css: [
            "katex.min.css"
        ]
    },
    blocks: {
        math: {
            shortcuts: {
                parsers: ["markdown", "asciidoc", "restructuredtext"],
                start: "$$",
                end: "$$"
            },
            process: function(blk) {
                var tex = blk.body;
                var isInline = !(tex[0] == "\n");
                var output = katex.renderToString(tex, {
                    displayMode: !isInline
                });

                if (!isInline) {
                    output = '
'+output+'
';
                }

                return output;
            }
        }
    }
};
<|endoftext|>


<commit_before>'use strict'

var React = require('react');

module.exports = React.createClass({
  // Define initial values
  getInitialState: function(){
    return {
      speedDivider: this.props.speedDivider ? this.props.speedDivider : 4, // Check if property speed is present if not set default value
      backgroundStyle: this.props.backgroundStyle ? this.props.backgroundStyle : '', // Check if thers is a direct style applied to the parallax background
    }
  },
  componentDidMount: function() {
    // Add event listener to the window
    window.addEventListener('scroll', this._calcTranslation, 10);

    // Set basic style rules for a parallax effect
    this.refs.background.setAttribute('style', 'width: 100%; top: 0; bottom: 0; background-size: cover ;background-position: 50% 0; background-repeat: no-repeat;');

    for(var key in this.state.backgroundStyle) {
      this.refs.background.style[key] = this.state.backgroundStyle[key];
    }

    // Set properties directly to avoid possible defined inline style
    this.refs.content.style.position = 'absolute';
    this.refs.content.style.left = 0;
    this.refs.content.style.right = 0;

  },
  componentWillUnmount: function() {
    // Remove event listener to the window
    window.removeEventListener('scroll', this._calcTranslation)
  },
  _calcTranslation: function() {
    // Calculate the translation CSS property
    var _window = window;
    var translateValue = _window.scrollY / this.state.speedDivider;
    if (translateValue < 0) {
      translateValue = 0;
    }

    // Aplly the transform to the background element
    var translate = 'translate3d(0px,' + translateValue + 'px, 0px)';
    this.refs.background.style.transform = translate;
  },
  render: function() {
    // Create and render elements
    var content = React.createElement('div', Object.assign({}, this.props,  { ref: 'content' }));
    var background = React.createElement('div', Object.assign({}, { ref: 'background', className: 'react-simple-parallax-bg' }));
    return(
      React.createElement('div', Object.assign({}, { ref: 'root' }), background, content)
    )
  }
});
<commit_msg>Fixed background style<commit_after>'use strict'

var React = require('react');

module.exports = React.createClass({
  // Define initial values
  getInitialState: function(){
    return {
      speedDivider: this.props.speedDivider ? this.props.speedDivider : 4, // Check if property speed is present if not set default value
      backgroundStyle: this.props.backgroundStyle ? this.props.backgroundStyle : '', // Check if thers is a direct style applied to the parallax background
    }
  },
  componentDidMount: function() {
    // Add event listener to the window
    window.addEventListener('scroll', this._calcTranslation, 10);

    // Set basic style rules for a parallax effect
    this.refs.background.setAttribute('style', 'width: 100%; top: 0; bottom: 0; background-size: cover ;background-position: 50% 0; background-repeat: no-repeat;');

    for(var key in this.state.backgroundStyle) {
      this.refs.background.style[key] = this.state.backgroundStyle[key];
    }

    // Set properties directly to avoid possible defined inline style
    this.refs.content.style.position = 'absolute';
    this.refs.content.style.left = 0;
    this.refs.content.style.right = 0;

  },
  componentWillUnmount: function() {
    // Remove event listener to the window
    window.removeEventListener('scroll', this._calcTranslation)
  },
  _calcTranslation: function() {
    // Calculate the translation CSS property
    var _window = window;
    var translateValue = _window.scrollY / this.state.speedDivider;
    if (translateValue < 0) {
      translateValue = 0;
    }

    // Aplly the transform to the background element
    var translate = 'translate3d(0px,' + translateValue + 'px, 0px)';

    // Check if background exists and then apply
    if (this.refs.background) {
      this.refs.background.style.transform = translate;
    }
  },
  render: function() {
    // Create and render elements
    var content = React.createElement('div', Object.assign({}, this.props,  { ref: 'content' }));
    var background = React.createElement('div', Object.assign({}, { ref: 'background', className: 'react-simple-parallax-bg' }));
    return(
      React.createElement('div', Object.assign({}, { ref: 'root' }), background, content)
    )
  }
});
<|endoftext|>


<commit_before>// !! This module implements the state object of a ProseMirror editor.

const {Mark} = require("../model")
const {Selection} = require("../selection")
const {Remapping} = require("../transform")

const {EditorTransform} = require("./transform")
exports.EditorTransform = EditorTransform

function currentMarks(doc, selection) {
  return selection.head == null ? Mark.none : doc.marksAt(selection.head)
}

const nullOptions = {}

function hasProp(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop)
}

function editorStateClass(fields) {
  const fieldNames = Object.keys(fields)

  class EditorState {
    constructor(doc, selection, storedMarks, updated, preserved) {
      this.doc = doc
      this.selection = selection
      this.storedMarks = storedMarks
      for (let i = 0; i < fieldNames.length; i++) {
        let name = fieldNames[i]
        this[name] = updated && hasProp(updated, name) ? updated[name] : preserved[name]
      }
    }

    // :: Schema
    get schema() {
      return this.doc.type.schema
    }

    applyTransform(transform, options = nullOptions) {
      if (!transform.docs[0].eq(this.doc))
        throw new RangeError("Applying a transform that does not start with the current document")
      let updated = {}
      for (let i = 0; i < fieldNames.length; i++) {
        let name = fieldNames[i], val = this[name]
        if (val && val.applyTransform)
          updated[name] = val.applyTransform(transform, options)
      }
      return new EditorState(transform.doc,
                             options.selection || this.selection.map(transform.doc, transform.mapping),
                             options.selection ? null : this.storedMarks,
                             updated, this)
    }

    applySelection(selection, options = nullOptions) {
      let updated = {}
      for (let i = 0; i < fieldNames.length; i++) {
        let name = fieldNames[i], val = this[name]
        if (val && val.applySelection)
          updated[name] = val.applySelection(selection, options)
      }
      return new EditorState(this.doc, selection, null, updated, this)
    }

    addActiveMark(mark) {
      if (!this.selection.empty) return this
      return new EditorState(this.doc, this.selection,
                             mark.addToSet(this.storedMarks || currentMarks(this.doc, this.selection)),
                             null, this)
    }

    removeActiveMark(markType) {
      if (!this.selection.empty) return this
      return new EditorState(this.doc, this.selection,
                             markType.removeFromSet(this.storedMarks || currentMarks(this.doc, this.selection)),
                             null, this)
    }

    // :: EditorTransform
    // Create a selection-aware `Transform` object.
    get tr() { return new EditorTransform(this) }

    update(fields) {
      return new EditorState(this.doc || this.doc,
                             this.selection || this.selection,
                             this.storedMarks || this.storedMarks,
                             fields, this)
    }

    static fromDoc(doc, selection) {
      if (!selection) selection = Selection.atStart(doc)
      let initial = {}
      fieldNames.forEach(name => {
        let value = fields[name]
        if (value instanceof Function) value = value(doc, selection)
        initial[name] = value
      })
      return new EditorState(doc, selection, null, initial)
    }

    static fromSchema(schema) {
      return this.fromDoc(schema.nodes.doc.createAndFill())
    }

    static extend(addFields) {
      let newFields = {}
      fieldNames.forEach(name => newFields[name] = fields[name])
      for (let name in addFields) if (hasProp(addFields, name)) newFields[name] = addFields[name]
      return editorStateClass(newFields)
    }

    static hasField(name) {
      return /^(doc|selection|storedMarks)$/.test(name) || fieldNames.indexOf(name) > -1
    }
  }
  return EditorState
}

class ViewState {
  constructor(inDOMChange, domChangeMapping, scrollToSelection) {
    this.inDOMChange = inDOMChange
    this.domChangeMapping = domChangeMapping
    this.scrollToSelection = scrollToSelection
  }

  startDOMChange(id) {
    return new ViewState(id, new Remapping, this.scrollToSelection)
  }

  endDOMChange() {
    return new ViewState(null, null, this.scrollToSelection)
  }

  applyTransform(transform, options) {
    return new ViewState(this.inDOMChange,
                         this.domChangeMapping && this.domChangeMapping.copy().appendMapping(transform.mapping),
                         options.scrollIntoView ? true : options.selection ? false : this.scrollToSelection)
  }

  applySelection(_selection, options) {
    return new ViewState(this.inDOMChange, this.domChangeMapping, !!options.scrollIntoView)
  }
}
ViewState.initial = new ViewState(null, null, false)
exports.ViewState = ViewState

exports.EditorState = editorStateClass({view: () => ViewState.initial})
<commit_msg>Crudely make history work again<commit_after>// !! This module implements the state object of a ProseMirror editor.

const {Mark} = require("../model")
const {Selection} = require("../selection")
const {Remapping} = require("../transform")

const {EditorTransform} = require("./transform")
exports.EditorTransform = EditorTransform

function currentMarks(doc, selection) {
  return selection.head == null ? Mark.none : doc.marksAt(selection.head)
}

const nullOptions = {}

function hasProp(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop)
}

function editorStateClass(fields) {
  const fieldNames = Object.keys(fields)

  class EditorState {
    constructor(doc, selection, storedMarks, updated, preserved) {
      this.doc = doc
      this.selection = selection
      this.storedMarks = storedMarks
      for (let i = 0; i < fieldNames.length; i++) {
        let name = fieldNames[i]
        this[name] = updated && hasProp(updated, name) ? updated[name] : preserved[name]
      }
    }

    // :: Schema
    get schema() {
      return this.doc.type.schema
    }

    applyTransform(transform, options = nullOptions) {
      if (!transform.docs[0].eq(this.doc))
        throw new RangeError("Applying a transform that does not start with the current document")
      let updated = {}
      for (let i = 0; i < fieldNames.length; i++) {
        let name = fieldNames[i], val = this[name]
        if (val && val.applyTransform)
          updated[name] = val.applyTransform(transform, options, this)
      }
      return new EditorState(transform.doc,
                             options.selection || this.selection.map(transform.doc, transform.mapping),
                             options.selection ? null : this.storedMarks,
                             updated, this)
    }

    applySelection(selection, options = nullOptions) {
      let updated = {}
      for (let i = 0; i < fieldNames.length; i++) {
        let name = fieldNames[i], val = this[name]
        if (val && val.applySelection)
          updated[name] = val.applySelection(selection, options)
      }
      return new EditorState(this.doc, selection, null, updated, this)
    }

    addActiveMark(mark) {
      if (!this.selection.empty) return this
      return new EditorState(this.doc, this.selection,
                             mark.addToSet(this.storedMarks || currentMarks(this.doc, this.selection)),
                             null, this)
    }

    removeActiveMark(markType) {
      if (!this.selection.empty) return this
      return new EditorState(this.doc, this.selection,
                             markType.removeFromSet(this.storedMarks || currentMarks(this.doc, this.selection)),
                             null, this)
    }

    // :: EditorTransform
    // Create a selection-aware `Transform` object.
    get tr() { return new EditorTransform(this) }

    update(fields) {
      return new EditorState(this.doc || this.doc,
                             this.selection || this.selection,
                             this.storedMarks || this.storedMarks,
                             fields, this)
    }

    static fromDoc(doc, selection) {
      if (!selection) selection = Selection.atStart(doc)
      let initial = {}
      fieldNames.forEach(name => {
        let value = fields[name]
        if (value instanceof Function) value = value(doc, selection)
        initial[name] = value
      })
      return new EditorState(doc, selection, null, initial)
    }

    static fromSchema(schema) {
      return this.fromDoc(schema.nodes.doc.createAndFill())
    }

    static extend(addFields) {
      let newFields = {}
      fieldNames.forEach(name => newFields[name] = fields[name])
      for (let name in addFields) if (hasProp(addFields, name)) newFields[name] = addFields[name]
      return editorStateClass(newFields)
    }

    static hasField(name) {
      return /^(doc|selection|storedMarks)$/.test(name) || fieldNames.indexOf(name) > -1
    }
  }
  return EditorState
}

class ViewState {
  constructor(inDOMChange, domChangeMapping, scrollToSelection) {
    this.inDOMChange = inDOMChange
    this.domChangeMapping = domChangeMapping
    this.scrollToSelection = scrollToSelection
  }

  startDOMChange(id) {
    return new ViewState(id, new Remapping, this.scrollToSelection)
  }

  endDOMChange() {
    return new ViewState(null, null, this.scrollToSelection)
  }

  applyTransform(transform, options) {
    return new ViewState(this.inDOMChange,
                         this.domChangeMapping && this.domChangeMapping.copy().appendMapping(transform.mapping),
                         options.scrollIntoView ? true : options.selection ? false : this.scrollToSelection)
  }

  applySelection(_selection, options) {
    return new ViewState(this.inDOMChange, this.domChangeMapping, !!options.scrollIntoView)
  }
}
ViewState.initial = new ViewState(null, null, false)
exports.ViewState = ViewState

exports.EditorState = editorStateClass({view: () => ViewState.initial})
<|endoftext|>


<commit_before>'use strict';

var path        = require('path');
var dirname     = path.dirname;
var relative    = path.relative;

var Promise     = require('bluebird');
var concat      = require('concat-stream');
var browserify  = require('browserify');
var watchify    = require('watchify');

function relativize(entry, requirement) {
  var expose = relative(dirname(entry), requirement);
  expose = expose.replace(/\.[a-z_\-]+$/, '');
  return "./" + expose;
}

function isBrowserify(x) {
  return x && (typeof x === 'object') &&
    (typeof x.bundle === 'function') &&
    (typeof x.plugin === 'function') &&
    (typeof x.transform === 'function');
}

function isString(x) {
  return Object.prototype.toString.call(x) === '[object String]';
}

function serve(options, maybeOptions) {
  var b;
  var rendered;

  if (!maybeOptions) {
    maybeOptions = {};
  }

  var contentType = options.contentType || 'application/javascript';
  var pipes = options.pipes || function(x) { return x; };
  var onError = options.onError || function() { };

  if (isBrowserify(options)) {
    b = options;
    options = maybeOptions;
  } else if (isString(options)) {
    b = serve.bundle({entry: options});
    options = maybeOptions;
  } else {
    b = serve.bundle(options);
  }

  function make() {
    rendered = new Promise(function(resolve, reject) {
      var output = pipes(b.bundle(options));
      output.on('error', reject);
      output.pipe(concat({encoding: 'string'}, resolve));
    });
    rendered.catch(onError);
  }

  make();

  var middleware = function(req, res, next) {
    res.setHeader('Content-type', contentType);
    return rendered.then(function(result) {
      return res.end(result);
    }).catch(next);
  };

  if (options.watch !== false) {
    var w = watchify(b);
    w.on('update', make);
    middleware.watchify = w;
  }

  return middleware;
}

function bundle(options) {
  var b = browserify({
    entries: [options.entry],
    extensions: options.extensions
  });

  b.delay = options.bundleDelay || 300;

  if (options.transforms) {
    options.transforms.forEach(function(transform) {
      b.transform(transform);
    });
  }

  if (options.requirements) {
    options.requirements.forEach(function(requirement) {
      var expose = relativize(options.entry, requirement);
      b.require(requirement, {
        expose: expose
      });
    });
  }

  if (options.bundle) {
    b = options.bundle(b);
  }

  return b;
}

module.exports = serve;
module.exports.serve = serve;
module.exports.bundle = bundle;
<commit_msg>Handle transform options<commit_after>'use strict';

var path        = require('path');
var dirname     = path.dirname;
var relative    = path.relative;

var Promise     = require('bluebird');
var concat      = require('concat-stream');
var browserify  = require('browserify');
var watchify    = require('watchify');

function relativize(entry, requirement) {
  var expose = relative(dirname(entry), requirement);
  expose = expose.replace(/\.[a-z_\-]+$/, '');
  return "./" + expose;
}

function isBrowserify(x) {
  return x && (typeof x === 'object') &&
    (typeof x.bundle === 'function') &&
    (typeof x.plugin === 'function') &&
    (typeof x.transform === 'function');
}

function isString(x) {
  return Object.prototype.toString.call(x) === '[object String]';
}

function serve(options, maybeOptions) {
  var b;
  var rendered;

  if (!maybeOptions) {
    maybeOptions = {};
  }

  var contentType = options.contentType || 'application/javascript';
  var pipes = options.pipes || function(x) { return x; };
  var onError = options.onError || function() { };

  if (isBrowserify(options)) {
    b = options;
    options = maybeOptions;
  } else if (isString(options)) {
    b = serve.bundle({entry: options});
    options = maybeOptions;
  } else {
    b = serve.bundle(options);
  }

  function make() {
    rendered = new Promise(function(resolve, reject) {
      var output = pipes(b.bundle(options));
      output.on('error', reject);
      output.pipe(concat({encoding: 'string'}, resolve));
    });
    rendered.catch(onError);
  }

  make();

  var middleware = function(req, res, next) {
    res.setHeader('Content-type', contentType);
    return rendered.then(function(result) {
      return res.end(result);
    }).catch(next);
  };

  if (options.watch !== false) {
    var w = watchify(b);
    w.on('update', make);
    middleware.watchify = w;
  }

  return middleware;
}

function bundle(options) {
  var b = browserify({
    entries: [options.entry],
    extensions: options.extensions
  });

  b.delay = options.bundleDelay || 300;

  if (options.transforms) {
    options.transforms.forEach(function(transform) {
      if (Array.isArray(transform)) {
        b.transform.apply(b, transform);
      } else {
        b.transform(transform);
      }
    });
  }

  if (options.requirements) {
    options.requirements.forEach(function(requirement) {
      var expose = relativize(options.entry, requirement);
      b.require(requirement, {
        expose: expose
      });
    });
  }

  if (options.bundle) {
    b = options.bundle(b);
  }

  return b;
}

module.exports = serve;
module.exports.serve = serve;
module.exports.bundle = bundle;
<|endoftext|>


<commit_before>var postcss = require('postcss');
var reporter = require('./lib/reporter');

module.exports = postcss.plugin('postcss-stats-reporter', reporter);
<commit_msg>Removes semicolons<commit_after>var postcss = require('postcss')
var reporter = require('./lib/reporter')

module.exports = postcss.plugin('postcss-stats-reporter', reporter)
<|endoftext|>


<commit_before>require('coffee-script/register');
require('./index.coffee');
<commit_msg>removed index.js<commit_after><|endoftext|>


<commit_before>'use strict';

const { handle } = require('./lib/events');
const fetchFactory = require('./lib/fetchFactory');
const fs = require('fs');
const Headers = require('./lib/Headers');
const indexedDB = require('fake-indexeddb');
const MessageChannel = require('./lib/MessageChannel');
const path = require('path');
const Request = require('./lib/Request');
const Response = require('./lib/Response');
const ServiceWorker = require('./lib/ServiceWorker');
const ServiceWorkerContainer = require('./lib/ServiceWorkerContainer');
const ServiceWorkerGlobalScope = require('./lib/ServiceWorkerGlobalScope');
const ServiceWorkerRegistration = require('./lib/ServiceWorkerRegistration');
const url = require('url');
const vm = require('vm');

const DEFAULT_ORIGIN = 'http://localhost:3333/';
const DEFAULT_SCOPE = './';

const containers = new Set();
const contexts = new Map();
const nativeRequire = require;
const parentPath = path.dirname(module.parent.filename);

module.exports = {
  Headers,
  MessageChannel,
  Request,
  Response,

  /**
   * Create/retrieve ServiceWorkerContainer instance for 'domain'
   * @param {String} [url]
   * @param {String} [webroot]
   * @returns {ServiceWorkerContainer}
   */
  connect(url = DEFAULT_ORIGIN, webroot = parentPath) {
    const container = new ServiceWorkerContainer(url, webroot, register, trigger);

    containers.add(container);

    // TODO: check if active context and apply state

    return container;
  },

  /**
   * Destroy all active containers/contexts
   * @returns {void}
   */
  destroy() {
    for (const container of containers) {
      container._destroy();
    }
    for (const context of contexts.values()) {
      context.registration._destroy();
      context.sw._destroy();
      context.scope._destroy();
    }
    containers.clear();
    contexts.clear();
  }
};

/**
 * Register ServiceWorker script at 'scriptURL'
 * @param {ServiceWorkerContainer} container
 * @param {String} scriptURL
 * @param {Object} [options]
 *  - {String} scope
 * @returns {Promise<ServiceWorkerRegistration>}
 */
function register(container, scriptURL, { scope = DEFAULT_SCOPE } = {}) {
  const origin = getOrigin(container._url);
  const urlScope = url.resolve(origin, scope);
  let context;

  if (contexts.has(urlScope)) {
    context = contexts.get(urlScope);
  } else {
    const isPath = !~scriptURL.indexOf('\n');
    const contextpath = isPath ? getResolvedPath(parentPath, scriptURL) : parentPath;
    const location = url.parse(path.join(origin, isPath ? scriptURL : 'sw.js').replace(/:\//, '://'));
    const fetch = fetchFactory(origin);
    const registration = new ServiceWorkerRegistration(unregister.bind(null, urlScope));
    const globalScope = new ServiceWorkerGlobalScope(registration, fetch);
    const sw = new ServiceWorker(isPath ? scriptURL : '', swPostMessage.bind(null, container));
    const script = isPath
      ? fs.readFileSync(isRelativePath(scriptURL) ? path.resolve(parentPath, scriptURL) : scriptURL, 'utf8')
      : scriptURL;
    const scriptModule = { exports: {} };

    location.origin = origin;
    location._webroot = container._webroot;
    context = Object.assign(globalScope, {
      clearImmediate,
      clearInterval,
      clearTimeout,
      console,
      fetch,
      Request,
      Response,
      Headers,
      indexedDB,
      location,
      module: scriptModule,
      exports: scriptModule.exports,
      process,
      setImmediate,
      setTimeout,
      setInterval,
      self: globalScope,
      require: getRequire(contextpath)
    });
    context.importScripts = getImportScripts(context);

    const sandbox = vm.createContext(context);

    vm.runInContext(script, sandbox);

    context = {
      api: scriptModule.exports,
      registration,
      scope: sandbox,
      sw
    };
    contexts.set(urlScope, context);
  }

  getContainersForUrlScope(urlScope).forEach(container => {
    container._registration = context.registration;
    container._sw = context.sw;
    container.api = context.api;
    container.scope = context.scope;

    // Create client for container
    container.scope.clients._connect(container._url, clientPostMessage.bind(null, container));
  });

  return Promise.resolve(container._registration);
}

/**
 * Unregister a ServiceWorker registration
 * @param {String} contextKey
 * @returns {Promise<Boolean>}
 */
function unregister(contextKey) {
  const context = contexts.get(contextKey);

  if (!context) {
    return Promise.resolve(false);
  }

  getContainersForContext(context).forEach(container => {
    container._registration = null;
    container._sw = null;
    container.api = null;
    container.scope = null;
    container.controller = null;
  });

  context.registration._destroy();
  context.sw._destroy();
  context.scope._destroy();

  contexts.delete(contextKey);

  return Promise.resolve(true);
}

/**
 * Send 'message' to client listeners
 * @param {ServiceWorkerContainer} container
 * @param {*} message
 * @param {Array} transferList
 * @returns {void}
 */
function clientPostMessage(container, message, transferList) {
  handle(container, 'message', message, transferList, container.controller);
}

/**
 * Send 'message' to active ServiceWorker
 * @param {ServiceWorkerContainer} container
 * @param {*} message
 * @param {Array} transferList
 * @returns {void}
 * */
function swPostMessage(container, message, transferList) {
  trigger(container, 'message', message, transferList);
}

/**
 * Trigger 'eventType' in current scope
 * @param {ServiceWorkerContainer} container
 * @param {String} eventType
 * @returns {Promise}
 */
function trigger(container, eventType, ...args) {
  const context = getContextForContainer(container);

  if (!context) {
    throw Error('no script registered yet');
  }

  const containers = getContainersForContext(context);

  switch (eventType) {
    case 'install':
      setState('installing', context, containers);
      break;
    case 'activate':
      setState('activating', context, containers);
      break;
    default:
    // No state mgmt necessary
  }

  const done = () => {
    switch (eventType) {
      case 'install':
        setState('installed', context, containers);
        break;
      case 'activate':
        setState('activated', context, containers);
        break;
      default:
      // No state mgmt necessary
    }
  };

  return handle(context.scope, eventType, ...args).then(result => {
    done();
    return result;
  });
}

/**
 * Store 'state'
 * @param {String} state
 * @param {Object} context
 * @param {Array} containers
 * @returns {void}
 */
function setState(state, context, containers) {
  // TODO: emit serviceworker.onstatechange events
  switch (state) {
    case 'installing':
      if (context.sw.state !== state) {
        throw Error('ServiceWorker already installed');
      }
      context.registration.installing = context.sw;
      setControllerForContainers(null, containers);
      break;
    case 'installed':
      context.sw.state = state;
      context.registration.installing = null;
      context.registration.waiting = context.sw;
      break;
    case 'activating':
      if (context.sw.state !== 'installed') {
        throw Error('ServiceWorker not yet installed');
      }
      context.sw.state = state;
      context.registration.activating = context.sw;
      setControllerForContainers(null, containers);
      break;
    case 'activated':
      context.sw.state = state;
      context.registration.waiting = null;
      context.registration.active = context.sw;
      setControllerForContainers(context.sw, containers);
      break;
    default:
      if (context.sw.state !== 'activated') {
        throw Error('ServiceWorker not yet active');
      }
  }
}

/**
 * Set 'controller' for 'containers'
 * @param {ServiceWorker} controller
 * @param {Array} containers
 * @returns {void}
 */
function setControllerForContainers(controller, containers) {
  for (const container of containers) {
    container.controller = controller;
  }
}

/**
 * Retrieve all containers associated with 'context'
 * @param {Object} context
 * @returns {Array}
 */
function getContainersForContext(context) {
  const results = [];

  for (const container of containers) {
    if (container._sw === context.sw) {
      results.push(container);
    }
  }

  return results;
}

/**
 * Retrieve all containers that fall under 'urlScope'
 * @param {String} urlScope
 * @returns {Array}
 */
function getContainersForUrlScope(urlScope) {
  const results = [];

  for (const container of containers) {
    if (container._url.indexOf(urlScope) === 0) {
      results.push(container);
    }
  }

  return results;
}

/**
 * Retrieve context for 'container'
 * @param {ServiceWorkerContainer} container
 * @returns {Object}
 */
function getContextForContainer(container) {
  for (const context of contexts.values()) {
    if (context.sw === container._sw) {
      return context;
    }
  }
  return null;
}

/**
 * Retrieve origin from 'urlString'
 * @param {String} urlString
 * @returns {String}
 */
function getOrigin(urlString) {
  const parsedUrl = url.parse(urlString);

  return `${parsedUrl.protocol}//${parsedUrl.host}`;
}

/**
 * Retrieve 'require' function for 'contextpath'
 * @param {String} contextpath
 * @returns {Function}
 */
function getRequire(contextpath) {
  const r = function require(requiredpath) {
    return nativeRequire(getResolvedPath(contextpath, requiredpath));
  };

  r.resolve = function resolve(requiredpath) {
    return nativeRequire.resolve(getResolvedPath(contextpath, requiredpath));
  };

  return r;
}

/**
 * Retrieve 'importScripts' function
 * @param {Object} context
 * @returns {Function}
 */
function getImportScripts(context) {
  function importScript(script) {
    const caches = context.caches;
    const clients = context.clients;
    const clearImmediate = context.clearImmediate;
    const clearInterval = context.clearInterval;
    const clearTimeout = context.clearTimeout;
    const console = context.console;
    const fetch = context.fetch;
    const Request = context.Request;
    const Response = context.Response;
    const Headers = context.Headers;
    const indexedDB = context.indexedDB;
    const location = context.location;
    const module = context.module;
    const exports = context.exports;
    const process = context.process;
    const registration = context.registration;
    const setImmediate = context.setImmediate;
    const setTimeout = context.setTimeout;
    const setInterval = context.setInterval;
    const self = context;
    const require = context.require;

    eval(script);
  }

  return function importScripts(...scripts) {
    scripts.forEach(script => {
      importScript.call(context, fs.readFileSync(path.join(context.location._webroot, script), 'utf8'));
    });
  };
}

/**
 * Retrieve the fully resolved path
 * @param {String} contextpath
 * @param {String} p
 * @returns {String}
 */
function getResolvedPath(contextpath, p) {
  return isRelativePath(p) ? path.resolve(contextpath, p) : p;
}

/**
 * Determine if 'p' is relative path
 * @param {String} p
 * @returns {Boolean}
 */
function isRelativePath(p) {
  return p.indexOf('.') === 0;
}
<commit_msg>validate passed url<commit_after>'use strict';

const { handle } = require('./lib/events');
const fetchFactory = require('./lib/fetchFactory');
const fs = require('fs');
const Headers = require('./lib/Headers');
const indexedDB = require('fake-indexeddb');
const MessageChannel = require('./lib/MessageChannel');
const path = require('path');
const Request = require('./lib/Request');
const Response = require('./lib/Response');
const ServiceWorker = require('./lib/ServiceWorker');
const ServiceWorkerContainer = require('./lib/ServiceWorkerContainer');
const ServiceWorkerGlobalScope = require('./lib/ServiceWorkerGlobalScope');
const ServiceWorkerRegistration = require('./lib/ServiceWorkerRegistration');
const url = require('url');
const vm = require('vm');

const DEFAULT_ORIGIN = 'http://localhost:3333/';
const DEFAULT_SCOPE = './';

const containers = new Set();
const contexts = new Map();
const nativeRequire = require;
const parentPath = path.dirname(module.parent.filename);

module.exports = {
  Headers,
  MessageChannel,
  Request,
  Response,

  /**
   * Create/retrieve ServiceWorkerContainer instance for 'domain'
   * @param {String} [url]
   * @param {String} [webroot]
   * @returns {ServiceWorkerContainer}
   */
  connect(url = DEFAULT_ORIGIN, webroot = parentPath) {
    if (url.slice(-1) !== '/') {
      url += '/';
    }

    const container = new ServiceWorkerContainer(url, webroot, register, trigger);

    containers.add(container);

    // TODO: check if active context and apply state

    return container;
  },

  /**
   * Destroy all active containers/contexts
   * @returns {void}
   */
  destroy() {
    for (const container of containers) {
      container._destroy();
    }
    for (const context of contexts.values()) {
      context.registration._destroy();
      context.sw._destroy();
      context.scope._destroy();
    }
    containers.clear();
    contexts.clear();
  }
};

/**
 * Register ServiceWorker script at 'scriptURL'
 * @param {ServiceWorkerContainer} container
 * @param {String} scriptURL
 * @param {Object} [options]
 *  - {String} scope
 * @returns {Promise<ServiceWorkerRegistration>}
 */
function register(container, scriptURL, { scope = DEFAULT_SCOPE } = {}) {
  const origin = getOrigin(container._url);
  const urlScope = url.resolve(origin, scope);
  let context;

  if (contexts.has(urlScope)) {
    context = contexts.get(urlScope);
  } else {
    const isPath = !~scriptURL.indexOf('\n');
    const contextpath = isPath ? getResolvedPath(parentPath, scriptURL) : parentPath;
    const location = url.parse(path.join(origin, isPath ? scriptURL : 'sw.js').replace(/:\//, '://'));
    const fetch = fetchFactory(origin);
    const registration = new ServiceWorkerRegistration(unregister.bind(null, urlScope));
    const globalScope = new ServiceWorkerGlobalScope(registration, fetch);
    const sw = new ServiceWorker(isPath ? scriptURL : '', swPostMessage.bind(null, container));
    const script = isPath
      ? fs.readFileSync(isRelativePath(scriptURL) ? path.resolve(parentPath, scriptURL) : scriptURL, 'utf8')
      : scriptURL;
    const scriptModule = { exports: {} };

    location.origin = origin;
    location._webroot = container._webroot;
    context = Object.assign(globalScope, {
      clearImmediate,
      clearInterval,
      clearTimeout,
      console,
      fetch,
      Request,
      Response,
      Headers,
      indexedDB,
      location,
      module: scriptModule,
      exports: scriptModule.exports,
      process,
      setImmediate,
      setTimeout,
      setInterval,
      self: globalScope,
      require: getRequire(contextpath)
    });
    context.importScripts = getImportScripts(context);

    const sandbox = vm.createContext(context);

    vm.runInContext(script, sandbox);

    context = {
      api: scriptModule.exports,
      registration,
      scope: sandbox,
      sw
    };
    contexts.set(urlScope, context);
  }

  getContainersForUrlScope(urlScope).forEach(container => {
    container._registration = context.registration;
    container._sw = context.sw;
    container.api = context.api;
    container.scope = context.scope;

    // Create client for container
    container.scope.clients._connect(container._url, clientPostMessage.bind(null, container));
  });

  return Promise.resolve(container._registration);
}

/**
 * Unregister a ServiceWorker registration
 * @param {String} contextKey
 * @returns {Promise<Boolean>}
 */
function unregister(contextKey) {
  const context = contexts.get(contextKey);

  if (!context) {
    return Promise.resolve(false);
  }

  getContainersForContext(context).forEach(container => {
    container._registration = null;
    container._sw = null;
    container.api = null;
    container.scope = null;
    container.controller = null;
  });

  context.registration._destroy();
  context.sw._destroy();
  context.scope._destroy();

  contexts.delete(contextKey);

  return Promise.resolve(true);
}

/**
 * Send 'message' to client listeners
 * @param {ServiceWorkerContainer} container
 * @param {*} message
 * @param {Array} transferList
 * @returns {void}
 */
function clientPostMessage(container, message, transferList) {
  handle(container, 'message', message, transferList, container.controller);
}

/**
 * Send 'message' to active ServiceWorker
 * @param {ServiceWorkerContainer} container
 * @param {*} message
 * @param {Array} transferList
 * @returns {void}
 * */
function swPostMessage(container, message, transferList) {
  trigger(container, 'message', message, transferList);
}

/**
 * Trigger 'eventType' in current scope
 * @param {ServiceWorkerContainer} container
 * @param {String} eventType
 * @returns {Promise}
 */
function trigger(container, eventType, ...args) {
  const context = getContextForContainer(container);

  if (!context) {
    throw Error('no script registered yet');
  }

  const containers = getContainersForContext(context);

  switch (eventType) {
    case 'install':
      setState('installing', context, containers);
      break;
    case 'activate':
      setState('activating', context, containers);
      break;
    default:
    // No state mgmt necessary
  }

  const done = () => {
    switch (eventType) {
      case 'install':
        setState('installed', context, containers);
        break;
      case 'activate':
        setState('activated', context, containers);
        break;
      default:
      // No state mgmt necessary
    }
  };

  return handle(context.scope, eventType, ...args).then(result => {
    done();
    return result;
  });
}

/**
 * Store 'state'
 * @param {String} state
 * @param {Object} context
 * @param {Array} containers
 * @returns {void}
 */
function setState(state, context, containers) {
  // TODO: emit serviceworker.onstatechange events
  switch (state) {
    case 'installing':
      if (context.sw.state !== state) {
        throw Error('ServiceWorker already installed');
      }
      context.registration.installing = context.sw;
      setControllerForContainers(null, containers);
      break;
    case 'installed':
      context.sw.state = state;
      context.registration.installing = null;
      context.registration.waiting = context.sw;
      break;
    case 'activating':
      if (context.sw.state !== 'installed') {
        throw Error('ServiceWorker not yet installed');
      }
      context.sw.state = state;
      context.registration.activating = context.sw;
      setControllerForContainers(null, containers);
      break;
    case 'activated':
      context.sw.state = state;
      context.registration.waiting = null;
      context.registration.active = context.sw;
      setControllerForContainers(context.sw, containers);
      break;
    default:
      if (context.sw.state !== 'activated') {
        throw Error('ServiceWorker not yet active');
      }
  }
}

/**
 * Set 'controller' for 'containers'
 * @param {ServiceWorker} controller
 * @param {Array} containers
 * @returns {void}
 */
function setControllerForContainers(controller, containers) {
  for (const container of containers) {
    container.controller = controller;
  }
}

/**
 * Retrieve all containers associated with 'context'
 * @param {Object} context
 * @returns {Array}
 */
function getContainersForContext(context) {
  const results = [];

  for (const container of containers) {
    if (container._sw === context.sw) {
      results.push(container);
    }
  }

  return results;
}

/**
 * Retrieve all containers that fall under 'urlScope'
 * @param {String} urlScope
 * @returns {Array}
 */
function getContainersForUrlScope(urlScope) {
  const results = [];

  for (const container of containers) {
    if (container._url.indexOf(urlScope) === 0) {
      results.push(container);
    }
  }

  return results;
}

/**
 * Retrieve context for 'container'
 * @param {ServiceWorkerContainer} container
 * @returns {Object}
 */
function getContextForContainer(container) {
  for (const context of contexts.values()) {
    if (context.sw === container._sw) {
      return context;
    }
  }
  return null;
}

/**
 * Retrieve origin from 'urlString'
 * @param {String} urlString
 * @returns {String}
 */
function getOrigin(urlString) {
  const parsedUrl = url.parse(urlString);

  return `${parsedUrl.protocol}//${parsedUrl.host}`;
}

/**
 * Retrieve 'require' function for 'contextpath'
 * @param {String} contextpath
 * @returns {Function}
 */
function getRequire(contextpath) {
  const r = function require(requiredpath) {
    return nativeRequire(getResolvedPath(contextpath, requiredpath));
  };

  r.resolve = function resolve(requiredpath) {
    return nativeRequire.resolve(getResolvedPath(contextpath, requiredpath));
  };

  return r;
}

/**
 * Retrieve 'importScripts' function
 * @param {Object} context
 * @returns {Function}
 */
function getImportScripts(context) {
  function importScript(script) {
    const caches = context.caches;
    const clients = context.clients;
    const clearImmediate = context.clearImmediate;
    const clearInterval = context.clearInterval;
    const clearTimeout = context.clearTimeout;
    const console = context.console;
    const fetch = context.fetch;
    const Request = context.Request;
    const Response = context.Response;
    const Headers = context.Headers;
    const indexedDB = context.indexedDB;
    const location = context.location;
    const module = context.module;
    const exports = context.exports;
    const process = context.process;
    const registration = context.registration;
    const setImmediate = context.setImmediate;
    const setTimeout = context.setTimeout;
    const setInterval = context.setInterval;
    const self = context;
    const require = context.require;

    eval(script);
  }

  return function importScripts(...scripts) {
    scripts.forEach(script => {
      importScript.call(context, fs.readFileSync(path.join(context.location._webroot, script), 'utf8'));
    });
  };
}

/**
 * Retrieve the fully resolved path
 * @param {String} contextpath
 * @param {String} p
 * @returns {String}
 */
function getResolvedPath(contextpath, p) {
  return isRelativePath(p) ? path.resolve(contextpath, p) : p;
}

/**
 * Determine if 'p' is relative path
 * @param {String} p
 * @returns {Boolean}
 */
function isRelativePath(p) {
  return p.indexOf('.') === 0;
}
<|endoftext|>