<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict'

const firewalla = require('../net2/Firewalla.js')
const log = require("../net2/logger.js")(__filename)

const fc = require("../net2/config.js")
const f = require("../net2/Firewalla.js")

const fs = require('fs')
const exec = require('child-process-promise').exec

const Promise = require('bluebird');
Promise.promisifyAll(fs);

const Sensor = require('./Sensor.js').Sensor

const rclient = require('../util/redis_manager.js').getRedisClient()

const HostManager = require('../net2/HostManager')
const hostManager = new HostManager('cli', 'server');

const sem = require('../sensor/SensorEventManager.js').getInstance();

const monkeyPrefix = "monkey";

class NaughtyMonkeySensor extends Sensor {

  async job() {
    // Disable auto monkey for production or beta
    if (f.isProductionOrBeta()) {
      return;
    }

    if (fc.isFeatureOn("naughty_monkey")) {
      await this.delay(this.getRandomTime())
      await this.release({
        monkeyType: "malware"
      })
    }
  }

  async randomFindDevice() {
    let hosts = await rclient.keysAsync("host:ip4:*");
    hosts = hosts.map((h) => h.replace("host:ip4:", ""));

    const hostCount = hosts.length
    if (hostCount > 0) {
      let randomHostIndex = Math.floor(Math.random() * hostCount)
      if (randomHostIndex == hostCount) {
        randomHostIndex = hostCount - 1
      }
      return hosts[randomHostIndex];
    } else {
      return null
    }
  }

  randomFindTarget() {
    const list = ["204.85.191.30",
      "46.235.227.70",
      "193.107.85.56",
      "5.79.68.161",
      "204.8.156.142",
      "37.48.120.196",
      "37.187.7.74",
      "162.247.72.199"
    ]

    return list[Math.floor(Math.random() * list.length)]

  }

  async prepareVideoEnvironment(ip) {
    const dnsInfo = {
      host: "v.qq.com",
      lastActive: "1528268891",
      count: 44,
      ssl: 1,
      established: true
    }

    await rclient.hmset(`dns:ip:${ip}`, dnsInfo);
  }

  async prepareGameEnvironment(ip) {
    const dnsInfo = {
      host: "battle.net",
      lastActive: "1528268891",
      count: 44,
      ssl: 1,
      established: true
    }

    await rclient.hmset(`dns:ip:${ip}`, dnsInfo);
  }

  async preparePornEnvironment(ip) {
    const dnsInfo = {
      host: "pornhub.com",
      lastActive: "1528268891",
      count: 44,
      ssl: 1,
      established: true
    }

    await rclient.hmset(`dns:ip:${ip}`, dnsInfo);
  }

  async release(event) {
    switch (event.monkeyType) {
      case "video":
        await this.video();
        break;
      case "game":
        await this.game();
        break;
      case "porn":
        await this.porn();
        break;
      case "ssh_scan":
        await this.ssh_scan();
        break;
      case "port_scan":
        await this.port_scan();
        break;
      case "abnormal_upload":
        await this.abnormal_upload();
        break;
      case "heartbleed":
        await this.heartbleed();
        break;
      case "malware":
      default:
        await this.malware();
        break;
    }
  }

  async recordMonkey(ip) {
    const key = `${monkeyPrefix}:${ip}`;
    await rclient.setAsync(key, 1);
    await rclient.expireAsync(key, 300); // only live for 60 seconds
  }

  async abnormal_upload() {
    // do not know how to trigger...
  }

  async ssh_scan() {
    const ip = await this.randomFindDevice();
    const remoteIP = "116.62.163.43";

    const payload = {
      "ts": new Date() / 1000,
      "note": "SSH::Password_Guessing",
      "msg": `${remoteIP} appears to be guessing SSH passwords (seen in 30 connections).`,
      "sub": `Sampled servers:  ${ip}, ${ip}, ${ip}`,
      "src": remoteIP,
      "peer_descr": "bro",
      "actions": ["Notice::ACTION_LOG"],
      "suppress_for": 1800.0,
      "dropped": false
    }

    await this.appendNotice(payload);
  }

  async appendNotice(payload) {
    const tmpfile = "/tmp/monkey";
    await fs.writeFileAsync(tmpfile, JSON.stringify(payload));

    const file = "/blog/current/notice.log";
    const cmd = `sudo cat ${tmpfile} >> ${file}`;
    await exec(cmd);
  }

  async port_scan() {

  }

  async video() {
    const remoteIP = "180.153.105.174";

    await this.prepareVideoEnvironment(remoteIP);

    const ip = await this.randomFindDevice();

    await this.monkey(ip, remoteIP, "video");
    await this.recordMonkey(remoteIP);
  }

  async game() {
    const remoteIP = "24.105.29.30";

    await this.prepareGameEnvironment(remoteIP);

    const ip = await this.randomFindDevice()

    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");

    await this.recordMonkey(remoteIP);
  }

  async porn() {
    const remoteIP = "146.112.61.106";
    await this.preparePornEnvironment(remoteIP);
    const ip = await this.randomFindDevice();
    await this.monkey(ip, remoteIP, "porn");
    await this.recordMonkey(remoteIP);
  }

  async malware() {
    const ip = await this.randomFindDevice()
    const remote = this.randomFindTarget()

    await this.monkey(remote, ip, "malware");
    await this.recordMonkey(remote);
  }

  async heartbleed() {
    const ip = await this.randomFindDevice();

    const heartbleedJSON = require("../extension/monkey/heartbleed.json");
    heartbleedJSON["id.resp_h"] = ip;
    heartbleedJSON["dst"] = ip;

    const remote = heartbleedJSON["id.orig_h"];

    await this.appendNotice(heartbleedJSON);
    await this.recordMonkey(remote);
  }

  async monkey(src, dst, tag, options) {
    options = options || {};

    const duration = options.duration || 10000;
    const length = options.length || 10000000;

    const cmd = `node malware_simulator.js --src ${src}  --dst ${dst} --duration ${duration} --length ${length}`
    log.info(`Release a ${tag} monkey for ${src} and ${dst}: ${cmd}`);
    await exec(cmd, {
      cwd: f.getFirewallaHome() + "/testLegacy/"
    }).catch((err) => {
      log.error("Failed to release monkey", cmd, err, {})
    })

  }

  run() {

    // if(!f.isDevelopmentVersion()) {
    //   return // do nothing if non dev version
    // }
    this.job()

    sem.on('ReleaseMonkey', (event) => {
      if (fc.isFeatureOn("naughty_monkey")) {
        this.release(event)
      }
    })

    setInterval(() => {
      this.job()
    }, 1000 * 3600 * 24) // release a monkey once every day
  }

  // in milli seconds
  getRandomTime() {
    return Math.floor(Math.random() * 1000 * 3600 * 24) // anytime random within a day
  }
}

module.exports = NaughtyMonkeySensor<commit_msg>bug fix<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict'

const firewalla = require('../net2/Firewalla.js')
const log = require("../net2/logger.js")(__filename)

const fc = require("../net2/config.js")
const f = require("../net2/Firewalla.js")

const fs = require('fs')
const exec = require('child-process-promise').exec

const Promise = require('bluebird');
Promise.promisifyAll(fs);

const Sensor = require('./Sensor.js').Sensor

const rclient = require('../util/redis_manager.js').getRedisClient()

const HostManager = require('../net2/HostManager')
const hostManager = new HostManager('cli', 'server');

const sem = require('../sensor/SensorEventManager.js').getInstance();

const monkeyPrefix = "monkey";

class NaughtyMonkeySensor extends Sensor {

  async job() {
    // Disable auto monkey for production or beta
    if (f.isProductionOrBeta()) {
      return;
    }

    if (fc.isFeatureOn("naughty_monkey")) {
      await this.delay(this.getRandomTime())
      await this.release({
        monkeyType: "malware"
      })
    }
  }

  async randomFindDevice() {
    let hosts = await rclient.keysAsync("host:ip4:*");
    hosts = hosts.map((h) => h.replace("host:ip4:", ""));

    const hostCount = hosts.length
    if (hostCount > 0) {
      let randomHostIndex = Math.floor(Math.random() * hostCount)
      if (randomHostIndex == hostCount) {
        randomHostIndex = hostCount - 1
      }
      return hosts[randomHostIndex];
    } else {
      return null
    }
  }

  randomFindTarget() {
    const list = ["204.85.191.30",
      "46.235.227.70",
      "193.107.85.56",
      "5.79.68.161",
      "204.8.156.142",
      "37.48.120.196",
      "37.187.7.74",
      "162.247.72.199"
    ]

    return list[Math.floor(Math.random() * list.length)]

  }

  async prepareVideoEnvironment(ip) {
    const dnsInfo = {
      host: "v.qq.com",
      lastActive: "1528268891",
      count: 44,
      ssl: 1,
      established: true
    }

    await rclient.hmset(`dns:ip:${ip}`, dnsInfo);
  }

  async prepareGameEnvironment(ip) {
    const dnsInfo = {
      host: "battle.net",
      lastActive: "1528268891",
      count: 44,
      ssl: 1,
      established: true
    }

    await rclient.hmset(`dns:ip:${ip}`, dnsInfo);
  }

  async preparePornEnvironment(ip) {
    const dnsInfo = {
      host: "pornhub.com",
      lastActive: "1528268891",
      count: 44,
      ssl: 1,
      established: true
    }

    await rclient.hmset(`dns:ip:${ip}`, dnsInfo);
  }

  async release(event) {
    switch (event.monkeyType) {
      case "video":
        await this.video();
        break;
      case "game":
        await this.game();
        break;
      case "porn":
        await this.porn();
        break;
      case "ssh_scan":
        await this.ssh_scan();
        break;
      case "port_scan":
        await this.port_scan();
        break;
      case "abnormal_upload":
        await this.abnormal_upload();
        break;
      case "heartbleed":
        await this.heartbleed();
        break;
      case "malware":
      default:
        await this.malware();
        break;
    }
  }

  async recordMonkey(ip) {
    const key = `${monkeyPrefix}:${ip}`;
    await rclient.setAsync(key, 1);
    await rclient.expireAsync(key, 300); // only live for 60 seconds
  }

  async abnormal_upload() {
    // do not know how to trigger...
  }

  async ssh_scan() {
    const ip = await this.randomFindDevice();
    const remoteIP = "116.62.163.43";

    const payload = {
      "ts": new Date() / 1000,
      "note": "SSH::Password_Guessing",
      "msg": `${remoteIP} appears to be guessing SSH passwords (seen in 30 connections).`,
      "sub": `Sampled servers:  ${ip}, ${ip}, ${ip}`,
      "src": remoteIP,
      "peer_descr": "bro",
      "actions": ["Notice::ACTION_LOG"],
      "suppress_for": 1800.0,
      "dropped": false
    }

    await this.appendNotice(payload);
  }

  async appendNotice(payload) {
    const tmpfile = "/tmp/monkey";
    await fs.writeFileAsync(tmpfile, JSON.stringify(payload));

    const file = "/blog/current/notice.log";
    const cmd = `sudo bash -c 'cat ${tmpfile} >> ${file}'`;
    await exec(cmd);
  }

  async port_scan() {

  }

  async video() {
    const remoteIP = "180.153.105.174";

    await this.prepareVideoEnvironment(remoteIP);

    const ip = await this.randomFindDevice();

    await this.monkey(ip, remoteIP, "video");
    await this.recordMonkey(remoteIP);
  }

  async game() {
    const remoteIP = "24.105.29.30";

    await this.prepareGameEnvironment(remoteIP);

    const ip = await this.randomFindDevice()

    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");
    await this.monkey(ip, remoteIP, "game");

    await this.recordMonkey(remoteIP);
  }

  async porn() {
    const remoteIP = "146.112.61.106";
    await this.preparePornEnvironment(remoteIP);
    const ip = await this.randomFindDevice();
    await this.monkey(ip, remoteIP, "porn");
    await this.recordMonkey(remoteIP);
  }

  async malware() {
    const ip = await this.randomFindDevice()
    const remote = this.randomFindTarget()

    await this.monkey(remote, ip, "malware");
    await this.recordMonkey(remote);
  }

  async heartbleed() {
    const ip = await this.randomFindDevice();

    const heartbleedJSON = require("../extension/monkey/heartbleed.json");
    heartbleedJSON["id.resp_h"] = ip;
    heartbleedJSON["dst"] = ip;

    const remote = heartbleedJSON["id.orig_h"];

    await this.appendNotice(heartbleedJSON);
    await this.recordMonkey(remote);
  }

  async monkey(src, dst, tag, options) {
    options = options || {};

    const duration = options.duration || 10000;
    const length = options.length || 10000000;

    const cmd = `node malware_simulator.js --src ${src}  --dst ${dst} --duration ${duration} --length ${length}`
    log.info(`Release a ${tag} monkey for ${src} and ${dst}: ${cmd}`);
    await exec(cmd, {
      cwd: f.getFirewallaHome() + "/testLegacy/"
    }).catch((err) => {
      log.error("Failed to release monkey", cmd, err, {})
    })

  }

  run() {

    // if(!f.isDevelopmentVersion()) {
    //   return // do nothing if non dev version
    // }
    this.job()

    sem.on('ReleaseMonkey', (event) => {
      if (fc.isFeatureOn("naughty_monkey")) {
        this.release(event)
      }
    })

    setInterval(() => {
      this.job()
    }, 1000 * 3600 * 24) // release a monkey once every day
  }

  // in milli seconds
  getRandomTime() {
    return Math.floor(Math.random() * 1000 * 3600 * 24) // anytime random within a day
  }
}

module.exports = NaughtyMonkeySensor<|endoftext|>


<commit_before>'use strict';

var config = require('../config'),
    format = require('./format');

/*
 * function colorize (opts)
 * Returns a new instance of the CLI format TransformStream
 * with applies level colors to `info` objects.
 */
module.exports = format(function (info, opts) {
  var level = info.level;

  if (opts.level || opts.all || !opts.message) {
    info.level = config.colorize(level);
  }

  if (opts.all || opts.message) {
    info.message = config.colorize(level, info.message);
  }

  return info;
});
<commit_msg>[refactor breaking] Begin to move in colorize logic from winston itself.<commit_after>'use strict';

const colors = require('colors/safe');
const format = require('./format');

//
// Fix colors not appearing in non-tty environments
//
colors.enabled = true;

/**
 * @property {RegExp} hasSpace
 * Simple regex to check for presence of spaces.
 */
const hasSpace = /\s+/;

/*
 * function colorize (opts)
 * Returns a new instance of the CLI format TransformStream
 * with applies level colors to `info` objects.
 */
module.exports = function createColorize(opts) {
  return new Colorizer(opts);
};

/*
 * function setupColors(opts)
 * Attaches a Colorizer instance to the format.
 */
function Colorizer(opts) {
  opts = opts || {};
  if (!opts.colors) {
    throw new Error('Cannot create colorize format: missing { colors } in options.');
  }

  this.options = opts;
  this.addColors(opts.colors);
}

/**
 * Adds the colors Object to the set of allColors
 * known by winston
 *
 * @param {Object} colors Set of color mappings to add.
 */
Colorizer.prototype.addColors = function addColors(colors) {
  this.allColors = this.allColors || {};
  Object.assign(this.allColors, colors);

  //
  // Eagerly set any colors that happen to be `/s`
  // separated strings into an Array to be used later.
  //
  Object.keys(this.allColors).forEach(level => {
    if (hasSpace.test(this.allColors[level])) {
      this.allColors[level] = this.allColors[level].split(hasSpace);
    }
  });
};

/*
 * function colorize (level, message)
 * Performs multi-step colorization using colors/safe
 */
Colorizer.prototype.colorize = function colorize(level, message) {
  if (typeof message === 'undefined') {
    message = level;
  }

  //
  // If the color for the level is just a string
  // then attempt to colorize the message with it.
  //
  if (!Array.isArray(this.allColors[level])) {
    return colors[this.allColors[level]](message);
  }

  //
  // If it is an Array then iterate over that Array, applying
  // the colors function for each item.
  //
  for (var i = 0, len = this.allColors[level].length; i < len; i++) {
    message = this.colors[allColors[level][i]](message);
  }

  return message;
};

/*
 * function transform (info, opts)
 * Attempts to colorize the { level, message } of the given
 * `longform` info object.
 */
Colorizer.prototype.transform = function transform(info, opts) {
  var level = info.level;

  if (opts.level || opts.all || !opts.message) {
    info.level = this.colorize(level);
  }

  if (opts.all || opts.message) {
    info.message = this.colorize(level, info.message);
  }

  return info;
};

<|endoftext|>


<commit_before>function Path (path) {
    this.family = 'unix'
    this.path = path
}

Path.prototype.toString = function () {
    return this.path
}

Path.prototype.options = function (connect) {
    return { path: this.path, ...connect }
}

function Bindable (host, port) {
    this.family = 'IPv4'
    this.host = host
    this.port = port
}

Bindable.prototype.toString = function () {
    return this.host + ':' + this.port
}

Bindable.prototype.options = function (connect) {
    return { host: this.host, port: this.port, ...connect }
}

function isNumeric (value) {
    return !isNaN(parseFloat(value)) && isFinite(value)
}

// TODO IPv6.
function parse (value) {
    if (/^[.\/]/.test(value)) {
        return new Path(value)
    }
    var bind = value.split(':')
    if (bind.length == 1) {
        bind.unshift('0.0.0.0')
    }
// TODO You'll want string aliases so that `"foo is required" -> "generic required"
    if (!isNumeric(bind[1])) {
        return null
    }
    var parts = bind[0].split('.')
    if (parts.length != 4) {
        return null
    }
    if (parts.filter(function (part) {
        return isNumeric(part) && 0 <= +part && +part <= 255
    }).length != 4) {
        return null
    }
    var bind = value.split(':')
    if (bind.length == 1) {
        bind.unshift('0.0.0.0')
    }
    return new Bindable(bind[0], +bind[1])
}

module.exports = function (value, name) {
    value = String(value)
    value = parse(value)
    if (value == null) {
        throw '%s is not bindable'
    }
    return value
}
<commit_msg>Convert `Bindable` to ES6.<commit_after>class Path {
    constructor (path) {
        this.family = 'unix'
        this.path = path
    }

    toString () {
        return this.path
    }

    options (connect) {
        return { path: this.path, ...connect }
    }
}

class Bindable {
    constructor (host, port) {
        this.family = 'IPv4'
        this.host = host
        this.port = port
    }

    toString () {
        return this.host + ':' + this.port
    }

    options (connect) {
        return { host: this.host, port: this.port, ...connect }
    }
}

function isNumeric (value) {
    return !isNaN(parseFloat(value)) && isFinite(value)
}

// TODO IPv6.
function parse (value) {
    if (/^[.\/]/.test(value)) {
        return new Path(value)
    }
    const bind = value.split(':')
    if (bind.length == 1) {
        bind.unshift('0.0.0.0')
    }
// TODO You'll want string aliases so that `"foo is required" -> "generic required"
    if (!isNumeric(bind[1])) {
        return null
    }
    const parts = bind[0].split('.')
    if (parts.length != 4) {
        return null
    }
    if (parts.filter(function (part) {
        return isNumeric(part) && 0 <= +part && +part <= 255
    }).length != 4) {
        return null
    }
    if (bind.length == 1) {
        bind.unshift('0.0.0.0')
    }
    return new Bindable(bind[0], +bind[1])
}

module.exports = function (value, name) {
    value = String(value)
    value = parse(value)
    if (value == null) {
        throw '%s is not bindable'
    }
    return value
}
<|endoftext|>


<commit_before>var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');
//导入配置文件
var settings = require('./settings');
//支持会话信息
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);

//生成一个express实例app
var app = express();

//设置views文件夹为存放视图文件的目录，即存放模版文件的地方
//__dirname为全局变量，存储当前正在执行的脚本所在的目录。
// view engine setup
app.set('views', path.join(__dirname, 'views'));
//设置视图模版引擎为jade
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
// 设置/public/favicon.ico为favicon图标
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
//加载日志中间件
app.use(logger('dev'));
//加载解析json的中间件
app.use(bodyParser.json());
//加载解析urlencoded请求体中的中间件
app.use(bodyParser.urlencoded({ extended: false }));
//加载解析cookie的中间件
app.use(cookieParser());
// 设置public文件夹为存放静态文件的目录.
app.use(express.static(path.join(__dirname, 'public')));

//路由控制器
app.use('/', routes);
app.use('/users', users);

//捕获404错误，并转发到错误处理器.
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

//错误处理器
// error handlers

//开发环境下的错误处理器，
// 将错误信息渲染到error模版并显示到浏览器中（打印堆栈信息）.
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

//生产环境下的错误处理器
//将错误信息渲染到error模版并显示到浏览器中（不打印堆栈信息）
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

app.use(session({
  secret: setting.cookieSecret,
  key: setting.db,//cookie name
  cookie: {maxAge: 1000 * 60 * 60 * 24 * 30}, //30 days
  store: new MongoStore({
    db: settings.db,
    host: settings.host,
    port: settings.prot
  })
}));

//导出app实例供其他模块调用。
module.exports = app;
<commit_msg>添加参数，使session的会话信息更完善<commit_after>var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');
//导入配置文件
var settings = require('./settings');
//支持会话信息
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);

//生成一个express实例app
var app = express();

//设置views文件夹为存放视图文件的目录，即存放模版文件的地方
//__dirname为全局变量，存储当前正在执行的脚本所在的目录。
// view engine setup
app.set('views', path.join(__dirname, 'views'));
//设置视图模版引擎为jade
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
// 设置/public/favicon.ico为favicon图标
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
//加载日志中间件
app.use(logger('dev'));
//加载解析json的中间件
app.use(bodyParser.json());
//加载解析urlencoded请求体中的中间件
app.use(bodyParser.urlencoded({ extended: false }));
//加载解析cookie的中间件
app.use(cookieParser());
// 设置public文件夹为存放静态文件的目录.
app.use(express.static(path.join(__dirname, 'public')));

//路由控制器
app.use('/', routes);
app.use('/users', users);

//捕获404错误，并转发到错误处理器.
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

//错误处理器
// error handlers

//开发环境下的错误处理器，
// 将错误信息渲染到error模版并显示到浏览器中（打印堆栈信息）.
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

//生产环境下的错误处理器
//将错误信息渲染到error模版并显示到浏览器中（不打印堆栈信息）
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

app.use(session({
  resave:false,//resave ——重新保存：强制会话保存即使是未修改的，默认为true
  saveUninitialized: true,//强制保存未初始化的会话到存储器
  secret: settings.cookieSecret,
  key: settings.db,//cookie name
  cookie: {maxAge: 1000 * 60 * 60 * 24 * 30}, //30 days
  store: new MongoStore({
    db: settings.db,
    host: settings.host,
    port: settings.prot
  })
}));

//导出app实例供其他模块调用。
module.exports = app;
<|endoftext|>


<commit_before>function isImage(mime) {
    switch (mime) {
    case "image/gif":
    case "image/jpeg":
    case "image/png":
    case "image/tiff":
    case "image/x-ms-bmp":
    case "image/bmp":
        return true;
    default:
        return false;
    }
}

function numberWithCommas(n) {
    n = n.toString();
    var pattern = /(-?\d+)(\d{3})/;
    while (pattern.test(n)) {
        n = n.replace(pattern, "$1,$2");
    }
    return n;
}

function bytesToSizeStr(bytes) {
    if(bytes === 0) return '0 Byte';
    var k = 1000;
    var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    var i = Math.floor(Math.log(bytes) / Math.log(k));
    return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];

}

function endsWith(str, suffix) {
    return str.indexOf(suffix, str.length - suffix.length) !== -1;
}

function _add_json_callback(url) {
    return url + "&callback=JSON_CALLBACK";
}

function _add_json_callback2(url) {
    var qIndex = url.indexOf("?");
    var argSeparator = "&";
    if (qIndex == -1) {
        argSeparator = "?";
    }

    return url + argSeparator + "callback=JSON_CALLBACK";
}

function differenceById(from, others) {
    var idsFrom = from.map(function(entry) {
        return entry.id;
    });
    var idsOthers = others.map(function(entry) {
        return entry.id;
    });

    var diff = _.difference(idsFrom, idsOthers);
    return from.filter(function(entry) {
        return _.indexOf(diff, function(e) {
            return e == entry.id;
        }) !== -1;
    })
}

String.prototype.capitalize = function () {
    return this.replace(/(?:^|\s)\S/g, function (a) {
        return a.toUpperCase();
    });
};

// save a reference to the core implementation
var indexOfValue = _.indexOf;

// using .mixin allows both wrapped and unwrapped calls:
// _(array).indexOf(...) and _.indexOf(array, ...)
_.mixin({

    // return the index of the first array element passing a test
    indexOf: function (array, test) {
        // delegate to standard indexOf if the test isn't a function
        if (!_.isFunction(test)) return indexOfValue(array, test);
        // otherwise, look for the index
        for (var x = 0; x < array.length; x++) {
            if (test(array[x])) return x;
        }
        // not found, return fail value
        return -1;
    }
});
<commit_msg>Add removeById function to remove an item from an array that matches a particular id. Assumes the object passed in and the array of objects all have id fields.<commit_after>function isImage(mime) {
    switch (mime) {
    case "image/gif":
    case "image/jpeg":
    case "image/png":
    case "image/tiff":
    case "image/x-ms-bmp":
    case "image/bmp":
        return true;
    default:
        return false;
    }
}

function numberWithCommas(n) {
    n = n.toString();
    var pattern = /(-?\d+)(\d{3})/;
    while (pattern.test(n)) {
        n = n.replace(pattern, "$1,$2");
    }
    return n;
}

function bytesToSizeStr(bytes) {
    if(bytes === 0) return '0 Byte';
    var k = 1000;
    var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    var i = Math.floor(Math.log(bytes) / Math.log(k));
    return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];

}

function endsWith(str, suffix) {
    return str.indexOf(suffix, str.length - suffix.length) !== -1;
}

function _add_json_callback(url) {
    return url + "&callback=JSON_CALLBACK";
}

function _add_json_callback2(url) {
    var qIndex = url.indexOf("?");
    var argSeparator = "&";
    if (qIndex == -1) {
        argSeparator = "?";
    }

    return url + argSeparator + "callback=JSON_CALLBACK";
}

function differenceById(from, others) {
    var idsFrom = from.map(function(entry) {
        return entry.id;
    });
    var idsOthers = others.map(function(entry) {
        return entry.id;
    });

    var diff = _.difference(idsFrom, idsOthers);
    return from.filter(function(entry) {
        return _.indexOf(diff, function(e) {
            return e == entry.id;
        }) !== -1;
    })
}

function removeById(from, what) {
    var i = _.indexOf(from, function(item) {
        return item.id === what.id;
    });

    if (i !== -1) {
        from.splice(i, 1);
    }
}

String.prototype.capitalize = function () {
    return this.replace(/(?:^|\s)\S/g, function (a) {
        return a.toUpperCase();
    });
};

// save a reference to the core implementation
var indexOfValue = _.indexOf;

// using .mixin allows both wrapped and unwrapped calls:
// _(array).indexOf(...) and _.indexOf(array, ...)
_.mixin({

    // return the index of the first array element passing a test
    indexOf: function (array, test) {
        // delegate to standard indexOf if the test isn't a function
        if (!_.isFunction(test)) return indexOfValue(array, test);
        // otherwise, look for the index
        for (var x = 0; x < array.length; x++) {
            if (test(array[x])) return x;
        }
        // not found, return fail value
        return -1;
    }
});
<|endoftext|>


<commit_before>var Util         = require('util');
var BaseService = require('./BaseService');

module.exports = HTTaxinvoiceService;
Util.inherits(HTTaxinvoiceService,BaseService);

function HTTaxinvoiceService(configs) {
  BaseService.call(this,configs);
  this._scopes.push('111');
};

HTTaxinvoiceService.prototype.getChargeInfo = function (CorpNum, UserID, success, error){
  if(!CorpNum || 0 === CorpNum.length) {
    this._throwError(-99999999,'팝빌회원 사업자번호가 입력되지 않았습니다.',error);
    return;
  }
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/ChargeInfo',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
};

BaseService.addMethod(HTTaxinvoiceService.prototype, "requestJob", function(CorpNum, Type, DType, SDate, EDate, success, error){
  this.requestJob(CorpNum, Type, DType, SDate, EDate, "", success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "requestJob", function(CorpNum, Type, DType, SDate, EDate, UserID, success, error){
  targetURI = '/HomeTax/Taxinvoice/' + Type;
  targetURI += '?DType=' + DType;
  targetURI += '&SDate=' + SDate;
  targetURI += '&EDate=' + EDate;

  this._executeAction({
    uri : targetURI,
    CorpNum : CorpNum,
    UserID : UserID,
    Method : 'POST',
    success : function(response){
      if(success) success(response.jobID);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getJobState", function(CorpNum, JobID, success, error){
  this.getJobState(CorpNum, JobID, "", success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getJobState", function(CorpNum, JobID, UserID, success, error){
  if ( JobID.length !== 18 ) {
    this._throwError(-99999999, '작업아이디(jobID)가 올바르지 않습니다.', error);
    return;
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice/' + JobID + '/State',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "listActiveJob", function(CorpNum, success, error){
  this.listActiveJob(CorpNum, "", success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "listActiveJob", function(CorpNum, UserID, success, error){
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/JobList',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "search", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, Page, PerPage, Order, success, error){
  this.search(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, Page, PerPage, Order, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "search", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, Page, PerPage, Order, UserID, success, error){
  if ( JobID.length !== 18 ){
    this._throwError(-99999999, '작업아이디(jobID)가 올바르지 않습니다.', error);
  }

  targetURI = '/HomeTax/Taxinvoice/'+JobID;
  targetURI += '?Type=' + Type.toString();
  targetURI += '&TaxType=' + TaxType.toString();
  targetURI += '&PurposeType=' + PurposeType.toString();
  targetURI += '&TaxRegIDType=' + TaxRegIDType;
  targetURI += '&TaxRegID=' + TaxRegID;

  if ( TaxRegIDYN !== '')
    targetURI += '&TaxRegIDYN=' + TaxRegIDYN;

  targetURI += '&Page=' + Page;
  targetURI += '&PerPage=' + PerPage;
  targetURI += '&Order=' + Order;

  console.log(targetURI);

  this._executeAction({
    uri : targetURI,
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "summary", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, success, error){
  this.summary(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "summary", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, UserID, success, error){
  if ( JobID.length !== 18 ){
    this._throwError(-99999999, '작업아이디(jobID)가 올바르지 않습니다.', error);
  }

  targetURI = '/HomeTax/Taxinvoice/'+JobID +'/Summary';
  targetURI += '?Type=' + Type.toString();
  targetURI += '&TaxType=' + TaxType.toString();
  targetURI += '&PurposeType=' + PurposeType.toString();
  targetURI += '&TaxRegIDType=' + TaxRegIDType;
  targetURI += '&TaxRegID=' + TaxRegID;

  if ( TaxRegIDYN !== '')
    targetURI += '&TaxRegIDYN=' + TaxRegIDYN;

  console.log(targetURI);

  this._executeAction({
    uri : targetURI,
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getTaxinvoice", function(CorpNum, NTSConfirmNum, success, error){
  this.getTaxinvoice(CorpNum, NTSConfirmNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getTaxinvoice", function(CorpNum, NTSConfirmNum, UserID, success, error){
  if ( NTSConfirmNum.length !== 24 ){
    this._throwError(-99999999, '국세청승인번호(NTSConfirmNum)가 올바르지 않습니다.', error);
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice/' + NTSConfirmNum,
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getXML", function(CorpNum, NTSConfirmNum, success, error){
  this.getXML(CorpNum, NTSConfirmNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getXML", function(CorpNum, NTSConfirmNum, UserID, success, error){
  if ( NTSConfirmNum.length !== 24 ){
    this._throwError(-99999999, '국세청승인번호(NTSConfirmNum)가 올바르지 않습니다.', error);
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice/' + NTSConfirmNum + '?T=xml',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

HTTaxinvoiceService.prototype.getFlatRatePopUpURL = function (CorpNum, UserID, success, error){
  if(!CorpNum || 0 === CorpNum.length) {
    this._throwError(-99999999,'팝빌회원 사업자번호가 입력되지 않았습니다.',error);
    return;
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice?TG=CHRG',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response.url);
    },
    error : error
  });
};

HTTaxinvoiceService.prototype.getCertificatePopUpURL = function (CorpNum, UserID, success, error){
  if(!CorpNum || 0 === CorpNum.length) {
    this._throwError(-99999999,'팝빌회원 사업자번호가 입력되지 않았습니다.',error);
    return;
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice?TG=CERT',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response.url);
    },
    error : error
  });
};

BaseService.addMethod(HTTaxinvoiceService.prototype, "getFlatRateState", function(CorpNum, success, error){
  this.getFlatRateState(CorpNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getFlatRateState", function(CorpNum, UserID, success, error){
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/Contract',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getCertificateExpireDate", function(CorpNum, success, error){
  this.getCertificateExpireDate(CorpNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getCertificateExpireDate", function(CorpNum, UserID, success, error){
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/CertInfo',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response.certificateExpiration);
    },
    error : error
  });
});
<commit_msg>remove console log<commit_after>var Util         = require('util');
var BaseService = require('./BaseService');

module.exports = HTTaxinvoiceService;
Util.inherits(HTTaxinvoiceService,BaseService);

function HTTaxinvoiceService(configs) {
  BaseService.call(this,configs);
  this._scopes.push('111');
};

HTTaxinvoiceService.prototype.getChargeInfo = function (CorpNum, UserID, success, error){
  if(!CorpNum || 0 === CorpNum.length) {
    this._throwError(-99999999,'팝빌회원 사업자번호가 입력되지 않았습니다.',error);
    return;
  }
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/ChargeInfo',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
};

BaseService.addMethod(HTTaxinvoiceService.prototype, "requestJob", function(CorpNum, Type, DType, SDate, EDate, success, error){
  this.requestJob(CorpNum, Type, DType, SDate, EDate, "", success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "requestJob", function(CorpNum, Type, DType, SDate, EDate, UserID, success, error){
  targetURI = '/HomeTax/Taxinvoice/' + Type;
  targetURI += '?DType=' + DType;
  targetURI += '&SDate=' + SDate;
  targetURI += '&EDate=' + EDate;

  this._executeAction({
    uri : targetURI,
    CorpNum : CorpNum,
    UserID : UserID,
    Method : 'POST',
    success : function(response){
      if(success) success(response.jobID);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getJobState", function(CorpNum, JobID, success, error){
  this.getJobState(CorpNum, JobID, "", success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getJobState", function(CorpNum, JobID, UserID, success, error){
  if ( JobID.length !== 18 ) {
    this._throwError(-99999999, '작업아이디(jobID)가 올바르지 않습니다.', error);
    return;
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice/' + JobID + '/State',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "listActiveJob", function(CorpNum, success, error){
  this.listActiveJob(CorpNum, "", success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "listActiveJob", function(CorpNum, UserID, success, error){
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/JobList',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "search", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, Page, PerPage, Order, success, error){
  this.search(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, Page, PerPage, Order, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "search", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, Page, PerPage, Order, UserID, success, error){
  if ( JobID.length !== 18 ){
    this._throwError(-99999999, '작업아이디(jobID)가 올바르지 않습니다.', error);
  }

  targetURI = '/HomeTax/Taxinvoice/'+JobID;
  targetURI += '?Type=' + Type.toString();
  targetURI += '&TaxType=' + TaxType.toString();
  targetURI += '&PurposeType=' + PurposeType.toString();
  targetURI += '&TaxRegIDType=' + TaxRegIDType;
  targetURI += '&TaxRegID=' + TaxRegID;

  if ( TaxRegIDYN !== '')
    targetURI += '&TaxRegIDYN=' + TaxRegIDYN;

  targetURI += '&Page=' + Page;
  targetURI += '&PerPage=' + PerPage;
  targetURI += '&Order=' + Order;

  this._executeAction({
    uri : targetURI,
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "summary", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, success, error){
  this.summary(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "summary", function(CorpNum, JobID, Type, TaxType, PurposeType, TaxRegIDType, TaxRegIDYN, TaxRegID, UserID, success, error){
  if ( JobID.length !== 18 ){
    this._throwError(-99999999, '작업아이디(jobID)가 올바르지 않습니다.', error);
  }

  targetURI = '/HomeTax/Taxinvoice/'+JobID +'/Summary';
  targetURI += '?Type=' + Type.toString();
  targetURI += '&TaxType=' + TaxType.toString();
  targetURI += '&PurposeType=' + PurposeType.toString();
  targetURI += '&TaxRegIDType=' + TaxRegIDType;
  targetURI += '&TaxRegID=' + TaxRegID;

  if ( TaxRegIDYN !== '')
    targetURI += '&TaxRegIDYN=' + TaxRegIDYN;

  this._executeAction({
    uri : targetURI,
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getTaxinvoice", function(CorpNum, NTSConfirmNum, success, error){
  this.getTaxinvoice(CorpNum, NTSConfirmNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getTaxinvoice", function(CorpNum, NTSConfirmNum, UserID, success, error){
  if ( NTSConfirmNum.length !== 24 ){
    this._throwError(-99999999, '국세청승인번호(NTSConfirmNum)가 올바르지 않습니다.', error);
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice/' + NTSConfirmNum,
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getXML", function(CorpNum, NTSConfirmNum, success, error){
  this.getXML(CorpNum, NTSConfirmNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getXML", function(CorpNum, NTSConfirmNum, UserID, success, error){
  if ( NTSConfirmNum.length !== 24 ){
    this._throwError(-99999999, '국세청승인번호(NTSConfirmNum)가 올바르지 않습니다.', error);
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice/' + NTSConfirmNum + '?T=xml',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

HTTaxinvoiceService.prototype.getFlatRatePopUpURL = function (CorpNum, UserID, success, error){
  if(!CorpNum || 0 === CorpNum.length) {
    this._throwError(-99999999,'팝빌회원 사업자번호가 입력되지 않았습니다.',error);
    return;
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice?TG=CHRG',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response.url);
    },
    error : error
  });
};

HTTaxinvoiceService.prototype.getCertificatePopUpURL = function (CorpNum, UserID, success, error){
  if(!CorpNum || 0 === CorpNum.length) {
    this._throwError(-99999999,'팝빌회원 사업자번호가 입력되지 않았습니다.',error);
    return;
  }

  this._executeAction({
    uri : '/HomeTax/Taxinvoice?TG=CERT',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response.url);
    },
    error : error
  });
};

BaseService.addMethod(HTTaxinvoiceService.prototype, "getFlatRateState", function(CorpNum, success, error){
  this.getFlatRateState(CorpNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getFlatRateState", function(CorpNum, UserID, success, error){
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/Contract',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response);
    },
    error : error
  });
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getCertificateExpireDate", function(CorpNum, success, error){
  this.getCertificateExpireDate(CorpNum, '', success, error);
});

BaseService.addMethod(HTTaxinvoiceService.prototype, "getCertificateExpireDate", function(CorpNum, UserID, success, error){
  this._executeAction({
    uri : '/HomeTax/Taxinvoice/CertInfo',
    CorpNum : CorpNum,
    UserID : UserID,
    success : function(response){
      if(success) success(response.certificateExpiration);
    },
    error : error
  });
});
<|endoftext|>


<commit_before>
// http://stackoverflow.com/questions/4911577/jquery-click-toggle-between-two-functions
// $().toggle() no longer exists in JQuery

$.fn.clickToggle = function(func1, func2) {
  var funcs = [func1, func2];
  this.data('toggleclicked', 0);
  this.click(function() {
	var data = $(this).data();
	var tc = data.toggleclicked;
	$.proxy(funcs[tc], this)();
	data.toggleclicked = (tc + 1) % 2;
  });
  return this;
};
<commit_msg>refactoring<commit_after>
// http://stackoverflow.com/questions/4911577/jquery-click-toggle-between-two-functions
// $().toggle() no longer exists in JQuery
// Used in app/assets/javascripts/cyber-dojo_dialog_diff.js
// to toggle added/removed lines

$.fn.clickToggle = function(func1, func2) {
  var funcs = [func1, func2];
  this.data('toggle_clicked', 0);
  this.click(function() {
	var data = $(this).data();
	var tc = data.toggle_clicked;
	$.proxy(funcs[tc], this)();
	data.toggle_clicked = (tc + 1) % 2;
  });
  return this;
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Platform,
  TouchableHighlight,
  Button,
  ScrollView,
} from 'react-native';

import {
  AdMobBanner,
  AdMobRewarded,
  AdMobInterstitial,
  PublisherBanner,
} from 'react-native-admob';

const BannerExample = ({ style, title, children, ...props }) => (
  <View {...props} style={[styles.example, style]}>
    <Text style={styles.title}>{title}</Text>
    <View>
      {children}
    </View>
  </View>
);

const bannerWidths = [200, 250, 320];

export default class Example extends Component {

  constructor() {
    super();
    this.state = {
      fluidSizeIndex: 0,
    };
  }

  componentDidMount() {
    AdMobRewarded.setTestDevices([AdMobRewarded.simulatorId]);
    AdMobRewarded.setAdUnitID('ca-app-pub-3940256099942544/1033173712');

    AdMobRewarded.addEventListener('rewardedVideoDidRewardUser',
      (type, amount) => console.log('rewardedVideoDidRewardUser', type, amount)
    );
    AdMobRewarded.addEventListener('rewardedVideoDidLoad',
      () => console.log('rewardedVideoDidLoad')
    );
    AdMobRewarded.addEventListener('rewardedVideoDidFailToLoad',
      (error) => console.log('rewardedVideoDidFailToLoad', error)
    );
    AdMobRewarded.addEventListener('rewardedVideoDidOpen',
      () => console.log('rewardedVideoDidOpen')
    );
    AdMobRewarded.addEventListener('rewardedVideoDidStartPlaying',
      () => console.log('rewardedVideoDidStartPlaying')
    );
    AdMobRewarded.addEventListener('rewardedVideoDidClose',
      () => {
        console.log('rewardedVideoDidClose');
        AdMobRewarded.requestAd().catch(error => console.log(error));
      }
    );
    AdMobRewarded.addEventListener('rewardedVideoWillLeaveApplication',
      () => console.log('rewardedVideoWillLeaveApplication')
    );

    AdMobRewarded.requestAd().catch(error => console.log(error));

    AdMobInterstitial.setTestDevices([AdMobInterstitial.simulatorId]);
    AdMobInterstitial.setAdUnitID('ca-app-pub-3940256099942544/4411468910');

    AdMobInterstitial.addEventListener('interstitialDidLoad',
      () => console.log('interstitialDidLoad')
    );
    AdMobInterstitial.addEventListener('interstitialDidFailToLoad',
      (error) => console.log('interstitialDidFailToLoad', error)
    );
    AdMobInterstitial.addEventListener('interstitialDidOpen',
      () => console.log('interstitialDidOpen')
    );
    AdMobInterstitial.addEventListener('interstitialDidClose',
      () => {
        console.log('interstitialDidClose');
        AdMobInterstitial.requestAd().catch(error => console.log(error));
      }
    );
    AdMobInterstitial.addEventListener('interstitialWillLeaveApplication',
      () => console.log('interstitialWillLeaveApplication')
    );

    AdMobInterstitial.requestAd().catch(error => console.log(error));
  }

  componentWillUnmount() {
    AdMobRewarded.removeAllListeners();
    AdMobInterstitial.removeAllListeners();
  }

  showRewarded() {
    AdMobRewarded.showAd().catch(error => console.log(error));
  }

  showInterstitial() {
    AdMobInterstitial.showAd().catch(error => console.log(error));
  }

  render() {
    return (
      <View style={styles.container}>
        <ScrollView>
          <BannerExample title="AdMob - Basic">
            <AdMobBanner
              adSize="banner"
              adUnitID="ca-app-pub-3940256099942544/2934735716"
              ref={el => (this._basicExample = el)}
            />
            <Button
              title="Reload"
              onPress={() => this._basicExample.loadBanner()}
            />
          </BannerExample>
          <BannerExample title="Rewarded">
            <Button
              title="Show Rewarded Video and preload next"
              onPress={this.showRewarded}
            />
          </BannerExample>
          <BannerExample title="Interstitial">
            <Button
              title="Show Interstitial and preload next"
              onPress={this.showInterstitial}
            />
          </BannerExample>
          <BannerExample title="DFP - Multiple Ad Sizes">
            <PublisherBanner
              adSize="banner"
              validAdSizes={['banner', 'largeBanner', 'mediumRectangle']}
              adUnitID="/6499/example/APIDemo/AdSizes"
              ref={el => (this._adSizesExample = el)}
            />
            <Button
              title="Reload"
              onPress={() => this._adSizesExample.loadBanner()}
            />
          </BannerExample>
          <BannerExample title="DFP - App Events" style={this.state.appEventsExampleStyle}>
            <PublisherBanner
              style={{ height: 50 }}
              adUnitID="/6499/example/APIDemo/AppEvents"
              onAdmobDispatchAppEvent={(event) => {
                if (event.name === 'color') {
                  this.setState({
                    appEventsExampleStyle: { backgroundColor: event.info },
                  });
                }
              }}
              ref={el => (this._appEventsExample = el)}
            />
            <Button
              title="Reload"
              onPress={() => this._appEventsExample.loadBanner()}
              style={styles.button}
            />
          </BannerExample>
          <BannerExample title="DFP - Fluid Ad Size">
            <View
              style={[
                { backgroundColor: '#f3f', paddingVertical: 10 },
                this.state.fluidAdSizeExampleStyle,
              ]}
            >
              <PublisherBanner
                adSize="fluid"
                adUnitID="/6499/example/APIDemo/Fluid"
                ref={el => (this._appFluidAdSizeExample = el)}
                style={{ flex: 1 }}
              />
            </View>
            <Button
              title="Change Banner Width"
              onPress={() => this.setState(prevState => ({
                fluidSizeIndex: prevState.fluidSizeIndex + 1,
                fluidAdSizeExampleStyle: { width: bannerWidths[prevState.fluidSizeIndex % bannerWidths.length] },
              }))}
              style={styles.button}
            />
            <Button
              title="Reload"
              onPress={() => this._appFluidAdSizeExample.loadBanner()}
              style={styles.button}
            />
          </BannerExample>
        </ScrollView>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    marginTop: (Platform.OS === 'ios') ? 30 : 10,
  },
  example: {
    paddingVertical: 10,
  },
  title: {
    margin: 10,
    fontSize: 20,
  },
});

AppRegistry.registerComponent('Example', () => Example);
<commit_msg>fix reward example<commit_after>import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Platform,
  TouchableHighlight,
  Button,
  ScrollView,
} from 'react-native';

import {
  AdMobBanner,
  AdMobRewarded,
  AdMobInterstitial,
  PublisherBanner,
} from 'react-native-admob';

const BannerExample = ({ style, title, children, ...props }) => (
  <View {...props} style={[styles.example, style]}>
    <Text style={styles.title}>{title}</Text>
    <View>
      {children}
    </View>
  </View>
);

const bannerWidths = [200, 250, 320];

export default class Example extends Component {

  constructor() {
    super();
    this.state = {
      fluidSizeIndex: 0,
    };
  }

  componentDidMount() {
    AdMobRewarded.setTestDevices([AdMobRewarded.simulatorId]);
    AdMobRewarded.setAdUnitID('ca-app-pub-3940256099942544/1033173712');

    AdMobRewarded.addEventListener('rewardedVideoDidRewardUser',
      (reward) => console.log('rewardedVideoDidRewardUser', reward)
    );
    AdMobRewarded.addEventListener('rewardedVideoDidLoad',
      () => console.log('rewardedVideoDidLoad')
    );
    AdMobRewarded.addEventListener('rewardedVideoDidFailToLoad',
      (error) => console.log('rewardedVideoDidFailToLoad', error)
    );
    AdMobRewarded.addEventListener('rewardedVideoDidOpen',
      () => console.log('rewardedVideoDidOpen')
    );
    AdMobRewarded.addEventListener('rewardedVideoDidStartPlaying',
      () => console.log('rewardedVideoDidStartPlaying')
    );
    AdMobRewarded.addEventListener('rewardedVideoDidClose',
      () => {
        console.log('rewardedVideoDidClose');
        AdMobRewarded.requestAd().catch(error => console.log(error));
      }
    );
    AdMobRewarded.addEventListener('rewardedVideoWillLeaveApplication',
      () => console.log('rewardedVideoWillLeaveApplication')
    );

    AdMobRewarded.requestAd().catch(error => console.log(error));

    AdMobInterstitial.setTestDevices([AdMobInterstitial.simulatorId]);
    AdMobInterstitial.setAdUnitID('ca-app-pub-3940256099942544/4411468910');

    AdMobInterstitial.addEventListener('interstitialDidLoad',
      () => console.log('interstitialDidLoad')
    );
    AdMobInterstitial.addEventListener('interstitialDidFailToLoad',
      (error) => console.log('interstitialDidFailToLoad', error)
    );
    AdMobInterstitial.addEventListener('interstitialDidOpen',
      () => console.log('interstitialDidOpen')
    );
    AdMobInterstitial.addEventListener('interstitialDidClose',
      () => {
        console.log('interstitialDidClose');
        AdMobInterstitial.requestAd().catch(error => console.log(error));
      }
    );
    AdMobInterstitial.addEventListener('interstitialWillLeaveApplication',
      () => console.log('interstitialWillLeaveApplication')
    );

    AdMobInterstitial.requestAd().catch(error => console.log(error));
  }

  componentWillUnmount() {
    AdMobRewarded.removeAllListeners();
    AdMobInterstitial.removeAllListeners();
  }

  showRewarded() {
    AdMobRewarded.showAd().catch(error => console.log(error));
  }

  showInterstitial() {
    AdMobInterstitial.showAd().catch(error => console.log(error));
  }

  render() {
    return (
      <View style={styles.container}>
        <ScrollView>
          <BannerExample title="AdMob - Basic">
            <AdMobBanner
              adSize="banner"
              adUnitID="ca-app-pub-3940256099942544/2934735716"
              ref={el => (this._basicExample = el)}
            />
            <Button
              title="Reload"
              onPress={() => this._basicExample.loadBanner()}
            />
          </BannerExample>
          <BannerExample title="Rewarded">
            <Button
              title="Show Rewarded Video and preload next"
              onPress={this.showRewarded}
            />
          </BannerExample>
          <BannerExample title="Interstitial">
            <Button
              title="Show Interstitial and preload next"
              onPress={this.showInterstitial}
            />
          </BannerExample>
          <BannerExample title="DFP - Multiple Ad Sizes">
            <PublisherBanner
              adSize="banner"
              validAdSizes={['banner', 'largeBanner', 'mediumRectangle']}
              adUnitID="/6499/example/APIDemo/AdSizes"
              ref={el => (this._adSizesExample = el)}
            />
            <Button
              title="Reload"
              onPress={() => this._adSizesExample.loadBanner()}
            />
          </BannerExample>
          <BannerExample title="DFP - App Events" style={this.state.appEventsExampleStyle}>
            <PublisherBanner
              style={{ height: 50 }}
              adUnitID="/6499/example/APIDemo/AppEvents"
              onAdmobDispatchAppEvent={(event) => {
                if (event.name === 'color') {
                  this.setState({
                    appEventsExampleStyle: { backgroundColor: event.info },
                  });
                }
              }}
              ref={el => (this._appEventsExample = el)}
            />
            <Button
              title="Reload"
              onPress={() => this._appEventsExample.loadBanner()}
              style={styles.button}
            />
          </BannerExample>
          <BannerExample title="DFP - Fluid Ad Size">
            <View
              style={[
                { backgroundColor: '#f3f', paddingVertical: 10 },
                this.state.fluidAdSizeExampleStyle,
              ]}
            >
              <PublisherBanner
                adSize="fluid"
                adUnitID="/6499/example/APIDemo/Fluid"
                ref={el => (this._appFluidAdSizeExample = el)}
                style={{ flex: 1 }}
              />
            </View>
            <Button
              title="Change Banner Width"
              onPress={() => this.setState(prevState => ({
                fluidSizeIndex: prevState.fluidSizeIndex + 1,
                fluidAdSizeExampleStyle: { width: bannerWidths[prevState.fluidSizeIndex % bannerWidths.length] },
              }))}
              style={styles.button}
            />
            <Button
              title="Reload"
              onPress={() => this._appFluidAdSizeExample.loadBanner()}
              style={styles.button}
            />
          </BannerExample>
        </ScrollView>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    marginTop: (Platform.OS === 'ios') ? 30 : 10,
  },
  example: {
    paddingVertical: 10,
  },
  title: {
    margin: 10,
    fontSize: 20,
  },
});

AppRegistry.registerComponent('Example', () => Example);
<|endoftext|>


<commit_before>const puppeteer = require('puppeteer');
const Spinner = require('cli-spinner').Spinner;
const get = require('lodash.get');
const mkdir = require('mkdirp');
const { writeFile: write, readFile: read } = require('fs').promises;
const { existsSync: exists } = require('fs');
const path = require('path');

const jsDir = path.join(__dirname, '../public/js');
const outputFile = path.join(jsDir, 'data.json');

/**
 *
 * @param {string[]} d - Documents to filter
 * @return {string[]}
 */
function filterResourceURLs(d) {
  if (d.includes(' ')) {
    const items = d.split(' ').filter((i) => i.startsWith('http://') || i.startsWith('https://'));

    if (items.length) {
      return items.map((i) => ({
        url: i,
        origin: new URL(items[0]).origin,
      }));
    }
  } else {
    try {
      return [
        {
          url: d,
          origin: new URL(d).origin,
        },
      ];
    } catch (e) {
      return [];
    }
  }

  return [];
}

module.exports = async function () {
  if (exists(outputFile)) {
    return JSON.parse(await read(outputFile, 'utf-8'));
  }

  const spinner = new Spinner('%s Downloading data');
  spinner.setSpinnerString(2);
  spinner.start();

  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  spinner.setSpinnerTitle('Downloading Chrome version data');

  await page.goto('https://chromiumdash.appspot.com/fetch_milestone_schedule?offset=-1&n=3', {
    waitUntil: 'networkidle0',
  });
  const versions = await page.evaluate(() => {
    const { mstones } = JSON.parse(document.querySelector('body').innerText);
    return {
      stable: mstones[0].mstone,
      min: mstones[0].mstone,
      beta: mstones[1].mstone,
      dev: mstones[2].mstone,
      max: mstones[2].mstone,
    };
  });

  // Get OT info
  spinner.setSpinnerTitle('Downloading Origin Trial info');
  let trials = [];
  page.on('requestfinished', async (request) => {
    if (request.url().startsWith('https://content-chromeorigintrials-pa.googleapis.com/v')) {
      const response = await request.response();
      trials = JSON.parse(await (await response.buffer()).toString()).trials;
    }
  });
  await page.goto('https://developers.chrome.com/origintrials/#/trials/active', {
    waitUntil: 'networkidle2',
  });

  // Get Feature Info
  spinner.setSpinnerTitle('Downloading feature statuses');
  await page.goto('https://chromestatus.com/features', {
    waitUntil: 'networkidle0',
  });
  const features = await page.evaluate(async () => await fetch('/features_v2.json').then((i) => i.json()));

  // log(features.find((f) => f.browsers.chrome.bug.includes('651661')));

  // Get Fugu bugs
  spinner.setSpinnerTitle('Downloading Fugu bugs');
  await page.goto('https://bugs.chromium.org/p/chromium/issues/list?sort=pri&colspec=ID%20Target%20M%20Component%20Status%20Owner%20Summary%20OS%20Modified%20Stars%20DevTrial%20OriginTrial%20OriginTrialEnd&num=1000&q=proj%3Dfugu%20-Type%3DFLT-Launch%20-Proj%3Dfugu-efforts%20-Restrict%3DView-Google&can=1', { waitUntil: 'networkidle0' });

  const content = await page.evaluate(() => {
    const headers = [...document.querySelector('body > mr-app').shadowRoot.querySelector('main > mr-list-page').shadowRoot.querySelector('mr-issue-list').shadowRoot.querySelectorAll('thead mr-dropdown')].map((item) => item.shadowRoot.querySelector('button').innerText.split('\n')[0]);

    const rows = [...document.querySelector('body > mr-app').shadowRoot.querySelector('main > mr-list-page').shadowRoot.querySelector('mr-issue-list').shadowRoot.querySelectorAll('tbody > tr')].map((item) => {
      const columns = [...item.querySelectorAll('td[class]:not(.ignore-navigation)')].map((column) => {
        const text = column.innerText;

        if (text) return text;
        return column.querySelector('mr-issue-link').shadowRoot.querySelector('#bugLink').innerText;
      });

      return columns;
    });

    return {
      headers,
      rows,
    };
  });

  const platforms = [];

  const rows = content.rows
    .map((row) => {
      return row.reduce((acc, cur, i) => {
        const header = content.headers[i].includes('Summary') ? 'Summary' : content.headers[i];
        const rowObj = { [header]: cur };

        if (header === 'OS' && cur !== '----') {
          let where = cur.split(', ').map((i) => (i === 'Chrome' ? 'Chrome OS' : i));
          if (where.includes('All')) {
            where = ['Linux', 'Windows', 'Chrome OS', 'Mac', 'Android'];
          }

          rowObj.Where = Array.isArray(acc.Where) ? acc.Where : [];
          rowObj.Where = rowObj.Where.concat(where.sort());
        }

        if (header === 'Component' && cur.includes('WebAppInstalls')) {
          rowObj.PWA = true;
        }

        if (header === 'Owner') {
          const owner = RegExp(/\w*\.*@(\w*)\.\w*/).exec(cur);
          rowObj[header] = owner ? (owner[1] === 'chromium' ? 'Google' : owner[1].charAt(0).toUpperCase() + owner[1].slice(1)) : cur;
        }

        let release = false;

        if (header === 'DevTrial' && cur !== '----') {
          rowObj.Shipping = acc.Shipping || {};
          rowObj.Shipping.dev = parseInt(cur);
          release = parseInt(cur);
        }

        if (header === 'OriginTrial' && cur !== '----') {
          rowObj.Shipping = acc.Shipping || {};
          rowObj.Shipping.ot = rowObj.Shipping.ot || {};
          rowObj.Shipping.ot.start = parseInt(cur);
          release = parseInt(cur);
        }

        if (header === 'OriginTrialEnd' && cur !== '----') {
          rowObj.Shipping = acc.Shipping || {};
          rowObj.Shipping.ot = rowObj.Shipping.ot || {};
          rowObj.Shipping.ot.end = parseInt(cur);
          release = parseInt(cur);
        }

        if (header === 'M' || header === 'Target') {
          rowObj.Shipping = acc.Shipping || {};
          if (!rowObj.Shipping.ship && cur !== '----') {
            rowObj.Shipping.ship = parseInt(cur);
            release = parseInt(cur);
          }
        }

        return Object.assign(acc, rowObj);
      }, {});
    })
    .filter((i) => i.Status !== 'WontFix' && i.Status !== 'Duplicate' && i.Status !== 'Archived')
    .map((i) => {
      const feature = features.find((f) => get(f, 'browsers.chrome.bug', '').includes(i.ID));
      let docs = [];
      let demos = [];

      if (feature && feature.resources) {
        if (feature.resources.docs) {
          docs = feature.resources.docs
            .map(filterResourceURLs)
            .reduce((acc, cur) => acc.concat(cur), [])
            .filter((i) => i.origin !== 'https://docs.google.com' && i.origin !== 'https://bit.ly');
        }
        if (feature.resources.samples) {
          demos = feature.resources.samples
            .map(filterResourceURLs)
            .reduce((acc, cur) => acc.concat(cur), [])
            .filter((i) => i.origin !== 'https://docs.google.com' && i.origin !== 'https://bit.ly');
        }
      }

      const ot = feature?.id ? trials.find((t) => t.chromestatusUrl && t.chromestatusUrl.includes(feature.id)) : false;

      return {
        who: i.Owner !== '----' ? i.Owner : false,
        where: i.Where || false,
        pwa: i.PWA || false,
        stars: parseInt(i.Stars) || false,
        title: feature?.name || i.Summary || false,
        summary: feature?.summary || false,
        updated: i.Modified || false,
        shipping: Object.keys(i.Shipping).length ? i.Shipping : false,
        status: i.Status || false,
        id: i.ID || false,
        feature,
        docs,
        demos,
        ot,
      };
    })
    .reduce(
      (acc, cur) => {
        if (cur.where) {
          cur.where = cur.where.filter((w) => w !== 'Fuchsia');
          const pform = cur.pwa ? cur.where.concat(['PWA']) : cur.where;
          for (const p of pform) {
            if (!platforms.includes(p)) {
              platforms.push(p);
            }
          }
        }

        if (cur.ot) {
          cur.shipping.ot = {
            start: parseInt(cur.ot.startMilestone),
            end: parseInt(cur.ot.endMilestone),
          };
          cur.ot = {
            feedback: cur.ot?.feedbackUrl || false,
            feature: cur.ot?.originTrialFeatureName || false,
            name: cur.ot?.displayName || false,
            desc: cur.ot?.description || false,
            id: cur.ot.id,
            status: cur.ot?.status || false,
          };
        }

        if (cur.shipping) {
          const max = cur.shipping?.ship || cur.shipping?.ot?.end || cur.shipping?.ot?.start || cur.shipping?.dev || versions.stable;
          const min = cur.shipping?.dev || cur.shipping?.ot?.start || cur.shipping?.ship || versions.stable;

          if (min < versions.min) {
            versions.min = min;
          }
          if (max > versions.max) {
            versions.max = max;
          }
        }

        if ((cur.shipping.ship && cur.shipping.ship <= versions.stable) || cur.status === 'Fixed') {
          acc.shipped.push(cur);
          acc.shipped = acc.shipped.sort((a, b) => (a.shipping.ship > b.shipping.ship ? 1 : -1));
        } else if (cur.shipping.ot && cur.shipping.ot.start <= versions.stable) {
          acc.ot.push(cur);
          acc.ot = acc.ot.sort((a, b) => (a.shipping.ot.start > b.shipping.ot.start ? 1 : -1));
        } else if (cur.shipping.dev) {
          acc.dev.push(cur);
          acc.dev = acc.dev.sort((a, b) => (a.shipping.dev > b.shipping.dev ? 1 : -1));
        } else if (cur.status === 'Started') {
          acc.started.push(cur);
          acc.started = acc.started.sort((a, b) => (a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1));
          // } else if (cur.status === 'Assigned') {
          //   acc.assigned.push(cur);
          //   acc.assigned = acc.assigned.sort((a, b) => (a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1));
        } else {
          acc.consideration.push(cur);
          acc.consideration = acc.consideration.sort((a, b) => (a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1));
        }

        return acc;
      },
      {
        shipped: [],
        ot: [],
        dev: [],
        started: [],
        assigned: [],
        consideration: [],
      },
    );

  spinner.setSpinnerTitle('Downloading Chrome releases');

  await page.goto(`https://chromiumdash.appspot.com/fetch_milestone_schedule?offset=${versions.min - versions.stable}&n=${versions.max - versions.min}`, {
    waitUntil: 'networkidle2',
  });

  const releases = await page.evaluate(() => {
    const { mstones } = JSON.parse(document.querySelector('body').innerText);
    return mstones.map((m) => ({
      release: m.mstone,
      date: m.stable_date,
    }));
  });

  await browser.close();
  spinner.setSpinnerTitle('Finishing up');

  if (!exists(jsDir)) {
    await mkdir(jsDir);
  }

  await write(
    outputFile,
    JSON.stringify({
      versions,
      releases,
      rows,
      platforms,
    }),
  );
  spinner.stop(true);

  return {
    versions,
    releases,
    platforms,
    rows,
  };
};
<commit_msg>Don't include bugs w/o a release<commit_after>const puppeteer = require('puppeteer');
const Spinner = require('cli-spinner').Spinner;
const get = require('lodash.get');
const mkdir = require('mkdirp');
const { writeFile: write, readFile: read } = require('fs').promises;
const { existsSync: exists } = require('fs');
const path = require('path');

const jsDir = path.join(__dirname, '../public/js');
const outputFile = path.join(jsDir, 'data.json');

/**
 *
 * @param {string[]} d - Documents to filter
 * @return {string[]}
 */
function filterResourceURLs(d) {
  if (d.includes(' ')) {
    const items = d.split(' ').filter((i) => i.startsWith('http://') || i.startsWith('https://'));

    if (items.length) {
      return items.map((i) => ({
        url: i,
        origin: new URL(items[0]).origin,
      }));
    }
  } else {
    try {
      return [
        {
          url: d,
          origin: new URL(d).origin,
        },
      ];
    } catch (e) {
      return [];
    }
  }

  return [];
}

module.exports = async function () {
  if (exists(outputFile)) {
    return JSON.parse(await read(outputFile, 'utf-8'));
  }

  const spinner = new Spinner('%s Downloading data');
  spinner.setSpinnerString(2);
  spinner.start();

  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  spinner.setSpinnerTitle('Downloading Chrome version data');

  await page.goto('https://chromiumdash.appspot.com/fetch_milestone_schedule?offset=-1&n=3', {
    waitUntil: 'networkidle0',
  });
  const versions = await page.evaluate(() => {
    const { mstones } = JSON.parse(document.querySelector('body').innerText);
    return {
      stable: mstones[0].mstone,
      min: mstones[0].mstone,
      beta: mstones[1].mstone,
      dev: mstones[2].mstone,
      max: mstones[2].mstone,
    };
  });

  // Get OT info
  spinner.setSpinnerTitle('Downloading Origin Trial info');
  let trials = [];
  page.on('requestfinished', async (request) => {
    if (request.url().startsWith('https://content-chromeorigintrials-pa.googleapis.com/v')) {
      const response = await request.response();
      trials = JSON.parse(await (await response.buffer()).toString()).trials;
    }
  });
  await page.goto('https://developers.chrome.com/origintrials/#/trials/active', {
    waitUntil: 'networkidle2',
  });

  // Get Feature Info
  spinner.setSpinnerTitle('Downloading feature statuses');
  await page.goto('https://chromestatus.com/features', {
    waitUntil: 'networkidle0',
  });
  const features = await page.evaluate(async () => await fetch('/features_v2.json').then((i) => i.json()));

  // log(features.find((f) => f.browsers.chrome.bug.includes('651661')));

  // Get Fugu bugs
  spinner.setSpinnerTitle('Downloading Fugu bugs');
  await page.goto('https://bugs.chromium.org/p/chromium/issues/list?sort=pri&colspec=ID%20Target%20M%20Component%20Status%20Owner%20Summary%20OS%20Modified%20Stars%20DevTrial%20OriginTrial%20OriginTrialEnd&num=1000&q=proj%3Dfugu%20-Type%3DFLT-Launch%20-Proj%3Dfugu-efforts%20-Restrict%3DView-Google&can=1', { waitUntil: 'networkidle0' });

  const content = await page.evaluate(() => {
    const headers = [...document.querySelector('body > mr-app').shadowRoot.querySelector('main > mr-list-page').shadowRoot.querySelector('mr-issue-list').shadowRoot.querySelectorAll('thead mr-dropdown')].map((item) => item.shadowRoot.querySelector('button').innerText.split('\n')[0]);

    const rows = [...document.querySelector('body > mr-app').shadowRoot.querySelector('main > mr-list-page').shadowRoot.querySelector('mr-issue-list').shadowRoot.querySelectorAll('tbody > tr')].map((item) => {
      const columns = [...item.querySelectorAll('td[class]:not(.ignore-navigation)')].map((column) => {
        const text = column.innerText;

        if (text) return text;
        return column.querySelector('mr-issue-link').shadowRoot.querySelector('#bugLink').innerText;
      });

      return columns;
    });

    return {
      headers,
      rows,
    };
  });

  const platforms = [];

  const rows = content.rows
    .map((row) => {
      return row.reduce((acc, cur, i) => {
        const header = content.headers[i].includes('Summary') ? 'Summary' : content.headers[i];
        const rowObj = { [header]: cur };

        if (header === 'OS' && cur !== '----') {
          let where = cur.split(', ').map((i) => (i === 'Chrome' ? 'Chrome OS' : i));
          if (where.includes('All')) {
            where = ['Linux', 'Windows', 'Chrome OS', 'Mac', 'Android'];
          }

          rowObj.Where = Array.isArray(acc.Where) ? acc.Where : [];
          rowObj.Where = rowObj.Where.concat(where.sort());
        }

        if (header === 'Component' && cur.includes('WebAppInstalls')) {
          rowObj.PWA = true;
        }

        if (header === 'Owner') {
          const owner = RegExp(/\w*\.*@(\w*)\.\w*/).exec(cur);
          rowObj[header] = owner ? (owner[1] === 'chromium' ? 'Google' : owner[1].charAt(0).toUpperCase() + owner[1].slice(1)) : cur;
        }

        let release = false;

        if (header === 'DevTrial' && cur !== '----') {
          rowObj.Shipping = acc.Shipping || {};
          rowObj.Shipping.dev = parseInt(cur);
          release = parseInt(cur);
        }

        if (header === 'OriginTrial' && cur !== '----') {
          rowObj.Shipping = acc.Shipping || {};
          rowObj.Shipping.ot = rowObj.Shipping.ot || {};
          rowObj.Shipping.ot.start = parseInt(cur);
          release = parseInt(cur);
        }

        if (header === 'OriginTrialEnd' && cur !== '----') {
          rowObj.Shipping = acc.Shipping || {};
          rowObj.Shipping.ot = rowObj.Shipping.ot || {};
          rowObj.Shipping.ot.end = parseInt(cur);
          release = parseInt(cur);
        }

        if (header === 'M' || header === 'Target') {
          rowObj.Shipping = acc.Shipping || {};
          if (!rowObj.Shipping.ship && cur !== '----') {
            rowObj.Shipping.ship = parseInt(cur);
            release = parseInt(cur);
          }
        }

        return Object.assign(acc, rowObj);
      }, {});
    })
    .filter((i) => i.Status !== 'WontFix' && i.Status !== 'Duplicate' && i.Status !== 'Archived')
    .map((i) => {
      const feature = features.find((f) => get(f, 'browsers.chrome.bug', '').includes(i.ID));
      let docs = [];
      let demos = [];

      if (feature && feature.resources) {
        if (feature.resources.docs) {
          docs = feature.resources.docs
            .map(filterResourceURLs)
            .reduce((acc, cur) => acc.concat(cur), [])
            .filter((i) => i.origin !== 'https://docs.google.com' && i.origin !== 'https://bit.ly');
        }
        if (feature.resources.samples) {
          demos = feature.resources.samples
            .map(filterResourceURLs)
            .reduce((acc, cur) => acc.concat(cur), [])
            .filter((i) => i.origin !== 'https://docs.google.com' && i.origin !== 'https://bit.ly');
        }
      }

      const ot = feature?.id ? trials.find((t) => t.chromestatusUrl && t.chromestatusUrl.includes(feature.id)) : false;

      return {
        who: i.Owner !== '----' ? i.Owner : false,
        where: i.Where || false,
        pwa: i.PWA || false,
        stars: parseInt(i.Stars) || false,
        title: feature?.name || i.Summary || false,
        summary: feature?.summary || false,
        updated: i.Modified || false,
        shipping: Object.keys(i.Shipping).length ? i.Shipping : false,
        status: i.Status || false,
        id: i.ID || false,
        feature,
        docs,
        demos,
        ot,
      };
    })
    .reduce(
      (acc, cur) => {
        if (cur.where) {
          cur.where = cur.where.filter((w) => w !== 'Fuchsia');
          const pform = cur.pwa ? cur.where.concat(['PWA']) : cur.where;
          for (const p of pform) {
            if (!platforms.includes(p)) {
              platforms.push(p);
            }
          }
        }

        if (cur.ot) {
          cur.shipping.ot = {
            start: parseInt(cur.ot.startMilestone),
            end: parseInt(cur.ot.endMilestone),
          };
          cur.ot = {
            feedback: cur.ot?.feedbackUrl || false,
            feature: cur.ot?.originTrialFeatureName || false,
            name: cur.ot?.displayName || false,
            desc: cur.ot?.description || false,
            id: cur.ot.id,
            status: cur.ot?.status || false,
          };
        }

        if (cur.shipping) {
          const max = cur.shipping?.ship || cur.shipping?.ot?.end || cur.shipping?.ot?.start || cur.shipping?.dev || versions.stable;
          const min = cur.shipping?.dev || cur.shipping?.ot?.start || cur.shipping?.ship || versions.stable;

          if (min < versions.min) {
            versions.min = min;
          }
          if (max > versions.max) {
            versions.max = max;
          }
        }

        if (cur.shipping.ship && cur.shipping.ship <= versions.stable) {
          acc.shipped.push(cur);
          acc.shipped = acc.shipped.sort((a, b) => (a.shipping.ship > b.shipping.ship ? 1 : -1));
        } else if (cur.shipping.ot && cur.shipping.ot.start <= versions.stable) {
          acc.ot.push(cur);
          acc.ot = acc.ot.sort((a, b) => (a.shipping.ot.start > b.shipping.ot.start ? 1 : -1));
        } else if (cur.shipping.dev) {
          acc.dev.push(cur);
          acc.dev = acc.dev.sort((a, b) => (a.shipping.dev > b.shipping.dev ? 1 : -1));
        } else if (cur.status === 'Started') {
          acc.started.push(cur);
          acc.started = acc.started.sort((a, b) => (a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1));
          // } else if (cur.status === 'Assigned') {
          //   acc.assigned.push(cur);
          //   acc.assigned = acc.assigned.sort((a, b) => (a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1));
        } else {
          acc.consideration.push(cur);
          acc.consideration = acc.consideration.sort((a, b) => (a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1));
        }

        return acc;
      },
      {
        shipped: [],
        ot: [],
        dev: [],
        started: [],
        assigned: [],
        consideration: [],
      },
    );

  spinner.setSpinnerTitle('Downloading Chrome releases');

  await page.goto(`https://chromiumdash.appspot.com/fetch_milestone_schedule?offset=${versions.min - versions.stable}&n=${versions.max - versions.min}`, {
    waitUntil: 'networkidle2',
  });

  const releases = await page.evaluate(() => {
    const { mstones } = JSON.parse(document.querySelector('body').innerText);
    return mstones.map((m) => ({
      release: m.mstone,
      date: m.stable_date,
    }));
  });

  await browser.close();
  spinner.setSpinnerTitle('Finishing up');

  if (!exists(jsDir)) {
    await mkdir(jsDir);
  }

  await write(
    outputFile,
    JSON.stringify({
      versions,
      releases,
      rows,
      platforms,
    }),
  );
  spinner.stop(true);

  return {
    versions,
    releases,
    platforms,
    rows,
  };
};
<|endoftext|>