<commit_before>/*eslint no-unused-vars: [2, "local"] */
/*global ParameterChanged:true, HandleMIDI:true,
  SetParameter, UpdatePluginParameters, MIDI, Note */

const MODELS = {
  'Ace Clone FR-2L': [
    ['BD', 'KICK'],
    ['SD', 'SNARE'],
    'CH',
    'OH',
    ['HT', 'PERC 3'],
    ['MT', 'PERC 2'],
    ['LT', 'PERC 1'],
    ['Cymbal', 'CRASH|RIDE'],
    ['Cowbell', 'RIM'],
    ['Clave', 'CLAP'],
    ['Electro Cowbell', 'FX'],
    ['LT2', 'PERC 3'],
    ['Bleep 1', 'FX'],
    ['Bleep 2', 'FX'],
    ['Buzz Synth', 'FX'],
    ['Acid Lead', 'FX']
  ],
}

// This object defines Drum Machine Designer's sound groups and
// each sound's pitch offset from C1. E.g. KICK 1 is C1+0 while
// KICK 2 is F2+17. It's used for a few things internally as
// well as specifying the groups' dropdown order in the UI.
const DRUMMER = {
  'KICK': [0, 17],
  'SNARE': [2, 4, 16],
  'RIM': [1],
  'CLAP': [3, 22],
  'PERC': [5, 7, 9, 11, 12],
  'CH': [6],
  'PH': [8],
  'OH': [10],
  'CRASH': [13],
  'RIDE': [15],
  'SHAKER': [18, 14],
  'FX': [19, 21, 23, 20]
}

const PITCH_INPUT_OFFSET = -36 // C1 will become array index 0
const PITCH_OUTPUT_OFFSET = 60 // C3

// begin UI with first two dropdowns
var PluginParameters = [{
  name: 'Model',
  type: 'menu',
  valueStrings: Object.keys(MODELS),
  defaultValue: 0
}, {
  name: 'Randomize',
  type: 'menu',
  defaultValue: 1,
  valueStrings: ['Reset default', '-', 'KICK', 'SNARE', 'CLAP', 'PERC', 'FX']
}]

var App = (function () {
  var model, modelName
  var Sounds = []
  var _initComplete
  var _skipUpdates

  // one-time init
  for (var group in DRUMMER) {
    DRUMMER[group].forEach(function (pitchOffset, alt) {
      // sound map dropdown placeholders prevent Logic
      // from complaining about param changes
      PluginParameters.push({
        name: group + (DRUMMER[group].length > 1 ? ' ' + (alt + 1) : ''),
        type: 'menu',
        defaultValue: 0,
        valueStrings: ['-'].concat(Array(16).fill(''))
      })

      // array of objects 0-indexed relative to C1's MIDI pitch
      Sounds[pitchOffset] = {
        group,
        alt,
        param: PluginParameters.length - 1, // current PluginParameter index
        pad: false // not mapped yet
      }
    })
  }

  /**
   * map a sound's pad using the specified method
   * @param  {[type]} sound  [description]
   * @param  {[type]} method [description]
   * @return {[type]}        [description]
   */
  function mapSound (sound, method) {
    if (Array.isArray(sound)) {
      sound.forEach(function (s) {
        mapSound(s, method)
      })

      return
    }

    var altOut = (function (altIn, group, method) {
      // if there's not much choice...
      if (model[group].length === 0) return false
      if (model[group].length === 1) return 0

      if (method === 'random') {
        return Math.floor(Math.random() * model[group].length)
      }

      // default (linear transform)
      return Math.floor(
        altIn / (DRUMMER[group].length - 1) * (model[group].length - 1)
      )
    })(sound.alt, sound.group, method)

    sound.pad = (altOut === false) ? false : model[sound.group][altOut]
    SetParameter(sound.param, (sound.pad === false) ? 0 : sound.pad + 1)
  }

  function setModel (key, setDefaults) {
    var opts = ['-']

    // avoid doing anything if we can
    if (key === modelName) return
    modelName = key

    // group model sounds like in DRUMMER
    model = {}
    for (var group in DRUMMER) {
      model[group] = []
    }

    // parse model definition
    MODELS[modelName].forEach(function (arr, pad) {
      // allow for arrr-ayless shorthand, matey
      if (!Array.isArray(arr)) arr = [arr, arr]

      // sound assignments
      arr[1].toUpperCase().split('|').forEach(function (txt) {
        var [group, num] = txt.split(' ')
        if (!group) return

        // set pad's index/position (0-15)
        if (num && typeof model[group][num - 1] === 'undefined') {
          model[group][num - 1] = pad
        } else {
          // add to next available alt slot
          model[group].push(pad)
        }
      })

      // add pad to dropdown options
      opts.push(arr[0] + '   [' + MIDI.noteName(pad + PITCH_OUTPUT_OFFSET) + ']')
    })

      // update pad selection/dropdowns for this model
    for (var sound in Sounds) {
      PluginParameters[Sounds[sound].param].valueStrings = opts
    }

    // set default mapping?
    if (setDefaults) mapSound(Sounds, 'default')

    // need Logic to update dropdown options but don't
    // care about the ParameterChanged() call for each
    _skipUpdates = true
    UpdatePluginParameters()
  }

  function ParameterChanged (param, value) {
    if (_initComplete && _skipUpdates) {
      if (param === PluginParameters.length - 1) {
        _skipUpdates = false // update sweep is complete
      }
      return
    }

    // when Logic has finished restoring params for the first time
    if (!_initComplete && param === PluginParameters.length - 1) {
      _initComplete = true
    }

    if (param === 0) {
      // change model and set defaults if user initiated change
      setModel(PluginParameters[param].valueStrings[value], _initComplete)
    } else if (param === 1) {
      // "Edit" dropdown
      var group = PluginParameters[param].valueStrings[value]

      if (value === 0) {
        mapSound(Sounds, 'default') // reset defaults
      } else if (value > 1) {
        // randomize group
        mapSound(Sounds.filter(function (sound) {
          return sound.group === group
        }), 'random')
      }

      // set dropdown back to '-'
      SetParameter(param, 1)
    } else {
      // find the sound with this param index
      Sounds.find(function (sound) {
        return sound.param === param
      }).pad = (value === 0) ? false : value - 1
    }
  }

  function HandleMIDI (event) {
    var sound

    // non-note events are passed through
    if (event instanceof Note) {
      // notes outside DMD's range are NOT passed through
      sound = Sounds[event.pitch + PITCH_INPUT_OFFSET]
      if (!sound || sound.pad === false) return

      event.pitch = sound.pad + PITCH_OUTPUT_OFFSET
    }

    event.send()
  }

  // public methods
  return {
    ParameterChanged,
    HandleMIDI
  }
})()

// let Logic reign...
ParameterChanged = App.ParameterChanged
HandleMIDI = App.HandleMIDI
<commit_msg>(feat) Add map for 'DR-606'<commit_after>/*eslint no-unused-vars: [2, "local"] */
/*global ParameterChanged:true, HandleMIDI:true,
  SetParameter, UpdatePluginParameters, MIDI, Note */

const MODELS = {
  'Ace Clone FR-2L': [
    ['BD', 'KICK'],
    ['SD', 'SNARE'],
    'CH',
    'OH',
    ['HT', 'PERC 3'],
    ['MT', 'PERC 2'],
    ['LT', 'PERC 1'],
    ['Cymbal', 'CRASH|RIDE'],
    ['Cowbell', 'RIM'],
    ['Clave', 'CLAP'],
    ['Electro Cowbell', 'FX'],
    ['LT2', 'PERC 3'],
    ['Bleep 1', 'FX'],
    ['Bleep 2', 'FX'],
    ['Buzz Synth', 'FX'],
    ['Acid Lead', 'FX']
  ],
  'DR-606': [
    ['BD1', 'KICK 1'],
    ['SD1', 'SNARE 1'],
    ['CH1', 'CH'],
    ['OH1', 'OH'],
    ['HT1', 'PERC 3'],
    ['MT1', 'PERC 2'],
    ['LT1', 'PERC 1'],
    ['CY1', 'CRASH'],
    ['BD2', 'KICK 2'],
    ['SD2', 'SNARE 2'],
    ['CH2', 'PH'],
    ['OH2', 'FX'],
    ['HT2', 'FX'],
    ['MT2', 'PERC 1'],
    ['LT2', 'PERC 4'],
    ['CY2', 'RIDE']
  ],
}

// This object defines Drum Machine Designer's sound groups and
// each sound's pitch offset from C1. E.g. KICK 1 is C1+0 while
// KICK 2 is F2+17. It's used for a few things internally as
// well as specifying the groups' dropdown order in the UI.
const DRUMMER = {
  'KICK': [0, 17],
  'SNARE': [2, 4, 16],
  'RIM': [1],
  'CLAP': [3, 22],
  'PERC': [5, 7, 9, 11, 12],
  'CH': [6],
  'PH': [8],
  'OH': [10],
  'CRASH': [13],
  'RIDE': [15],
  'SHAKER': [18, 14],
  'FX': [19, 21, 23, 20]
}

const PITCH_INPUT_OFFSET = -36 // C1 will become array index 0
const PITCH_OUTPUT_OFFSET = 60 // C3

// begin UI with first two dropdowns
var PluginParameters = [{
  name: 'Model',
  type: 'menu',
  valueStrings: Object.keys(MODELS),
  defaultValue: 0
}, {
  name: 'Randomize',
  type: 'menu',
  defaultValue: 1,
  valueStrings: ['Reset default', '-', 'KICK', 'SNARE', 'CLAP', 'PERC', 'FX']
}]

var App = (function () {
  var model, modelName
  var Sounds = []
  var _initComplete
  var _skipUpdates

  // one-time init
  for (var group in DRUMMER) {
    DRUMMER[group].forEach(function (pitchOffset, alt) {
      // sound map dropdown placeholders prevent Logic
      // from complaining about param changes
      PluginParameters.push({
        name: group + (DRUMMER[group].length > 1 ? ' ' + (alt + 1) : ''),
        type: 'menu',
        defaultValue: 0,
        valueStrings: ['-'].concat(Array(16).fill(''))
      })

      // array of objects 0-indexed relative to C1's MIDI pitch
      Sounds[pitchOffset] = {
        group,
        alt,
        param: PluginParameters.length - 1, // current PluginParameter index
        pad: false // not mapped yet
      }
    })
  }

  /**
   * map a sound's pad using the specified method
   * @param  {[type]} sound  [description]
   * @param  {[type]} method [description]
   * @return {[type]}        [description]
   */
  function mapSound (sound, method) {
    if (Array.isArray(sound)) {
      sound.forEach(function (s) {
        mapSound(s, method)
      })

      return
    }

    var altOut = (function (altIn, group, method) {
      // if there's not much choice...
      if (model[group].length === 0) return false
      if (model[group].length === 1) return 0

      if (method === 'random') {
        return Math.floor(Math.random() * model[group].length)
      }

      // default (linear transform)
      return Math.floor(
        altIn / (DRUMMER[group].length - 1) * (model[group].length - 1)
      )
    })(sound.alt, sound.group, method)

    sound.pad = (altOut === false) ? false : model[sound.group][altOut]
    SetParameter(sound.param, (sound.pad === false) ? 0 : sound.pad + 1)
  }

  function setModel (key, setDefaults) {
    var opts = ['-']

    // avoid doing anything if we can
    if (key === modelName) return
    modelName = key

    // group model sounds like in DRUMMER
    model = {}
    for (var group in DRUMMER) {
      model[group] = []
    }

    // parse model definition
    MODELS[modelName].forEach(function (arr, pad) {
      // allow for arrr-ayless shorthand, matey
      if (!Array.isArray(arr)) arr = [arr, arr]

      // sound assignments
      arr[1].toUpperCase().split('|').forEach(function (txt) {
        var [group, num] = txt.split(' ')
        if (!group) return

        // set pad's index/position (0-15)
        if (num && typeof model[group][num - 1] === 'undefined') {
          model[group][num - 1] = pad
        } else {
          // add to next available alt slot
          model[group].push(pad)
        }
      })

      // add pad to dropdown options
      opts.push(arr[0] + '   [' + MIDI.noteName(pad + PITCH_OUTPUT_OFFSET) + ']')
    })

      // update pad selection/dropdowns for this model
    for (var sound in Sounds) {
      PluginParameters[Sounds[sound].param].valueStrings = opts
    }

    // set default mapping?
    if (setDefaults) mapSound(Sounds, 'default')

    // need Logic to update dropdown options but don't
    // care about the ParameterChanged() call for each
    _skipUpdates = true
    UpdatePluginParameters()
  }

  function ParameterChanged (param, value) {
    if (_initComplete && _skipUpdates) {
      if (param === PluginParameters.length - 1) {
        _skipUpdates = false // update sweep is complete
      }
      return
    }

    // when Logic has finished restoring params for the first time
    if (!_initComplete && param === PluginParameters.length - 1) {
      _initComplete = true
    }

    if (param === 0) {
      // change model and set defaults if user initiated change
      setModel(PluginParameters[param].valueStrings[value], _initComplete)
    } else if (param === 1) {
      // "Edit" dropdown
      var group = PluginParameters[param].valueStrings[value]

      if (value === 0) {
        mapSound(Sounds, 'default') // reset defaults
      } else if (value > 1) {
        // randomize group
        mapSound(Sounds.filter(function (sound) {
          return sound.group === group
        }), 'random')
      }

      // set dropdown back to '-'
      SetParameter(param, 1)
    } else {
      // find the sound with this param index
      Sounds.find(function (sound) {
        return sound.param === param
      }).pad = (value === 0) ? false : value - 1
    }
  }

  function HandleMIDI (event) {
    var sound

    // non-note events are passed through
    if (event instanceof Note) {
      // notes outside DMD's range are NOT passed through
      sound = Sounds[event.pitch + PITCH_INPUT_OFFSET]
      if (!sound || sound.pad === false) return

      event.pitch = sound.pad + PITCH_OUTPUT_OFFSET
    }

    event.send()
  }

  // public methods
  return {
    ParameterChanged,
    HandleMIDI
  }
})()

// let Logic reign...
ParameterChanged = App.ParameterChanged
HandleMIDI = App.HandleMIDI
<|endoftext|>


<commit_before>'use strict'

let cli = require('..')
let errors = require('./errors')

function action (message, options, promise) {
  if (options.then) [options, promise] = [{}, options]

  let task = module.exports.task = {
    promise: promise,
    spinner: new cli.Spinner({spinner: options.spinner, text: `${message}...`}),
    stream: options.stream
  }
  task.spinner.start()

  return promise.then(function (result) {
    if (options.success !== false) done(options.success || 'done', options)
    else done(null, options)
    return result
  }).catch(function (err) {
    if (err.body && err.body.id === 'two_factor') done(cli.color.yellow.bold('!'), options)
    else done(cli.color.red.bold('!!!'), options)
    throw err
  })
}

function warn (msg) {
  let task = module.exports.task
  if (task) task.spinner.warn(msg)
  else errors.warn(msg)
}

function status (status) {
  let task = module.exports.task
  if (task) task.spinner.status = status
}

function done (msg, options) {
  options = options || {}
  let task = module.exports.task
  if (task) {
    task.spinner.stop(msg)
    module.exports.task = null
    if (options.clear) task.spinner.clear()
  }
}

module.exports = action
module.exports.warn = warn
module.exports.status = status
module.exports.done = done
<commit_msg>subtler errors<commit_after>'use strict'

let cli = require('..')
let errors = require('./errors')

function action (message, options, promise) {
  if (options.then) [options, promise] = [{}, options]

  let task = module.exports.task = {
    promise: promise,
    spinner: new cli.Spinner({spinner: options.spinner, text: `${message}...`}),
    stream: options.stream
  }
  task.spinner.start()

  return promise.then(function (result) {
    if (options.success !== false) done(options.success || 'done', options)
    else done(null, options)
    return result
  }).catch(function (err) {
    if (err.body && err.body.id === 'two_factor') done(cli.color.yellow.bold('!'), options)
    else done(cli.color.red.bold('!'), options)
    throw err
  })
}

function warn (msg) {
  let task = module.exports.task
  if (task) task.spinner.warn(msg)
  else errors.warn(msg)
}

function status (status) {
  let task = module.exports.task
  if (task) task.spinner.status = status
}

function done (msg, options) {
  options = options || {}
  let task = module.exports.task
  if (task) {
    task.spinner.stop(msg)
    module.exports.task = null
    if (options.clear) task.spinner.clear()
  }
}

module.exports = action
module.exports.warn = warn
module.exports.status = status
module.exports.done = done
<|endoftext|>


<commit_before>var fs = require('fs');
var net = require('net');
var path = require('path');
var util = require('util');
var actorify = require('actorify');
var spawn = require('child_process').spawn;
var EventEmitter = require('events').EventEmitter;

var VERBOSE = false;

function info(message) {
  if (VERBOSE) {
    process.stdout.write(message);
  }
}

function Angelo(args) {
  this.filePath = '';
  this.nodeBinary = 'node';

  if (args) {
    // If args is a string, we're going to assume the
    // common case that it's a file path
    if (typeof args === 'string') {
      this.filePath = args;
    } else {
      var keys = Object.keys(args);
      keys.forEach(function(key) {
        this[key] = args[key];
      }, this);
    }
    if (args.verbose) {
      VERBOSE = true;
    }
  }
  this.init();
}

util.inherits(Angelo, EventEmitter);

Angelo.prototype.init = function() {
  this.initPlayer();

  setTimeout(function(_this) {
    _this.initConnection();
    _this.initActor();
    _this.actor.send('ready');
  }, 250, this);
};

Angelo.prototype.initPlayer = function() {
  var binFullPath = fs.realpathSync(__filename);
  var binDir = path.dirname(binFullPath);
  var libDir = path.join(binDir, '../lib');
  var playerPath = path.join(libDir, 'mocha-player.js');

  this.player  = spawn(this.nodeBinary, [playerPath, 3000], {detached:false});

  if (!process.hasOwnProperty('children')) {
    process.children = {};
  }
  process.children[this.player.pid] = this.player;

  this.player.on('exit', function() {
    delete process.children[this.pid];
  });

  this.player.stdout.on('data', function(data) {
    info(data);
  });
};

Angelo.prototype.initConnection = function() {
  this.sock = net.connect(3000);

  this.sock.on('error', function() {
    console.log('socket error');
  });
};

Angelo.prototype.initActor = function() {
  this.actor = actorify(this.sock);
  this.send = this.actor.send;

  this.actor.on('readied', function() {
    info('Client received message: readied\n');
    if (this.filePath !== '') {
      this.load();
    }
  }.bind(this));

  this.actor.on('echoed', function(message) {
    info('Client received message: echoed\n');
    if (message) {
      info('Client received echoed content: ' + util.inspect(message) + '\n');
    }
  });

  this.actor.on('loaded', function(playlists) {
    info('Client received message: loaded\n');
    if (playlists) {
      info('Client received load: ' + JSON.stringify(playlists,null,2) + '\n');
    }
    this.playlists = playlists;
    this.emit('loaded');
  }.bind(this));

  this.actor.on('started', function() {
    info('Client received message: started\n');
  });

  this.actor.on('logged', function(message) {
    if (message) {
      info('Client received log: ')
      process.stdout.write(message + '\n');
    }
  });

  this.actor.on('stopped', function(restart) {
    info('Client received message: stopped\n');
    // Re-init everything
    if (restart) {
      setTimeout(function(_this) {
        _this.init();
      }, 250, this);
  }
  }.bind(this));
};

Angelo.prototype.echo = function(message) {
  this.actor.send('echo', message);
};

Angelo.prototype.load = function(newFilePath) {
  if (newFilePath) {
    this.filePath = newFilePath;
  }
  if (this.filePath) {
    this.actor.send('load', this.filePath);
  }
};

Angelo.prototype.play = function() {
  var restart = true;
  this.actor.send('start', restart);
};

Angelo.prototype.playOnce = function() {
  var restart = false;
  this.actor.send('start', restart);
};

Angelo.prototype.stop = function() {
  var restart = false;
  this.actor.send('stop', restart);
};

Angelo.prototype.restart = function() {
  var restart = true;
  this.actor.send('stop', restart);
};

process.on("SIGINT", function() {
  info('Exiting...\n');
  process.exit();
});

process.on('SIGTERM', function() {
  info('Exiting...\n');
  process.exit();
});

process.on('exit', function () {
  // info('Event: "exit"\n');
  if (process.hasOwnProperty('children')) {
    // info('Child processes:');
    for (var key in process.children) {
      if (process.children.hasOwnProperty(key)) {
        // info('  pid: ' + process.children[key].pid);
        // info('  exitCode: ' + process.children[key].exitCode);
        // info('  killed: ' + process.children[key].killed);
        if ((process.children[key].exitCode === null) && (process.children[key].killed === false)) {
          process.children[key].kill('SIGINT');
          // info("  ... but now it's dead!");
        }
      }
    }
  }
  process.exit();
});

module.exports.Angelo = Angelo;
<commit_msg>Now messages show up in the devtools console.<commit_after>var fs = require('fs');
var net = require('net');
var path = require('path');
var util = require('util');
var actorify = require('actorify');
var spawn = require('child_process').spawn;
var EventEmitter = require('events').EventEmitter;

var VERBOSE = false;

function info(message) {
  if (VERBOSE) {
    console.log(message.toString())
  }
}

function Angelo(args) {
  this.filePath = '';
  this.nodeBinary = 'node';

  if (args) {
    // If args is a string, we're going to assume the
    // common case that it's a file path
    if (typeof args === 'string') {
      this.filePath = args;
    } else {
      var keys = Object.keys(args);
      keys.forEach(function(key) {
        this[key] = args[key];
      }, this);
    }
    if (args.verbose) {
      VERBOSE = true;
    }
  }
  this.init();
}

util.inherits(Angelo, EventEmitter);

Angelo.prototype.init = function() {
  this.initPlayer();

  setTimeout(function(_this) {
    _this.initConnection();
    _this.initActor();
    _this.actor.send('ready');
  }, 250, this);
};

Angelo.prototype.initPlayer = function() {
  var binFullPath = fs.realpathSync(__filename);
  var binDir = path.dirname(binFullPath);
  var libDir = path.join(binDir, '../lib');
  var playerPath = path.join(libDir, 'mocha-player.js');

  this.player  = spawn(this.nodeBinary, [playerPath, 3000], {detached:false});

  if (!process.hasOwnProperty('children')) {
    process.children = {};
  }
  process.children[this.player.pid] = this.player;

  this.player.on('exit', function() {
    delete process.children[this.pid];
  });

  this.player.stdout.on('data', function(data) {
    info(data);
  });
};

Angelo.prototype.initConnection = function() {
  this.sock = net.connect(3000);

  this.sock.on('error', function() {
    console.log('socket error');
  });
};

Angelo.prototype.initActor = function() {
  this.actor = actorify(this.sock);
  this.send = this.actor.send;

  this.actor.on('readied', function() {
    info('Client received message: readied\n');
    if (this.filePath !== '') {
      this.load();
    }
  }.bind(this));

  this.actor.on('echoed', function(message) {
    info('Client received message: echoed\n');
    if (message) {
      info('Client received echoed content: ' + util.inspect(message) + '\n');
    }
  });

  this.actor.on('loaded', function(playlists) {
    info('Client received message: loaded\n');
    if (playlists) {
      info('Client received load: ' + JSON.stringify(playlists,null,2) + '\n');
    }
    this.playlists = playlists;
    this.emit('loaded');
  }.bind(this));

  this.actor.on('started', function() {
    info('Client received message: started\n');
  });

  this.actor.on('logged', function(message) {
    if (message) {
      info('Client received log: ')
      process.stdout.write(message + '\n');
    }
  });

  this.actor.on('stopped', function(restart) {
    info('Client received message: stopped\n');
    // Re-init everything
    if (restart) {
      setTimeout(function(_this) {
        _this.init();
      }, 250, this);
  }
  }.bind(this));
};

Angelo.prototype.echo = function(message) {
  this.actor.send('echo', message);
};

Angelo.prototype.load = function(newFilePath) {
  if (newFilePath) {
    this.filePath = newFilePath;
  }
  if (this.filePath) {
    this.actor.send('load', this.filePath);
  }
};

Angelo.prototype.play = function() {
  var restart = true;
  this.actor.send('start', restart);
};

Angelo.prototype.playOnce = function() {
  var restart = false;
  this.actor.send('start', restart);
};

Angelo.prototype.stop = function() {
  var restart = false;
  this.actor.send('stop', restart);
};

Angelo.prototype.restart = function() {
  var restart = true;
  this.actor.send('stop', restart);
};

process.on("SIGINT", function() {
  info('Exiting...\n');
  process.exit();
});

process.on('SIGTERM', function() {
  info('Exiting...\n');
  process.exit();
});

process.on('exit', function () {
  // info('Event: "exit"\n');
  if (process.hasOwnProperty('children')) {
    // info('Child processes:');
    for (var key in process.children) {
      if (process.children.hasOwnProperty(key)) {
        // info('  pid: ' + process.children[key].pid);
        // info('  exitCode: ' + process.children[key].exitCode);
        // info('  killed: ' + process.children[key].killed);
        if ((process.children[key].exitCode === null) && (process.children[key].killed === false)) {
          process.children[key].kill('SIGINT');
          // info("  ... but now it's dead!");
        }
      }
    }
  }
  process.exit();
});

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


<commit_before>//
//  Author: Vlad Seryakov vseryakov@gmail.com
//  backendjs 2018
//

const url = require('url');
const path = require("path");
const mime = require('mime');
const qs = require('qs');
const http = require('http');
const https = require('https');
const logger = require(__dirname + '/logger');
const core = require(__dirname + '/core');
const lib = require(__dirname + '/lib');
const aws = require(__dirname + '/aws');

// Sign S3 AWS request, returns url to be send to S3 server, options will have all updated headers to be sent as well
aws.signS3 = function(method, bucket, path, body, options)
{
    if (!options) options = {};
    if (!options.headers) options.headers = {};

    var region = options.region || this.region || 'us-east-1';
    if (!options.headers["content-type"]) options.headers["content-type"] = "binary/octet-stream";
    // For text files to prevent encoding/decoding issues
    if (/text|html|xml|json/.test(options.headers["content-type"]) &&
        options.headers["content-type"].indexOf("charset=") == -1) {
        options.headers["content-type"] += "; charset=utf-8";
    }

    // Run through the encoding so our signature match the real url sent by core.httpGet
    path = url.parse(path || "/").pathname;
    if (path[0] != "/") path = "/" + path;

    // DNS compatible or not, use path-style if not for access otherwise virtual host style
    var dns = bucket.match(/[a-z0-9][a-z0-9-]*[a-z0-9]/) ? true : false;

    var host = (dns ? bucket + "." : "") + "s3" + (region != "us-east-1" ? "-" + region : "") + ".amazonaws.com";
    var uri = (options.endpoint_protocol || "https") + "://" + host + (dns ? "" : "/" + bucket) + path;
    var credentials = options.credentials || this;

    if (!dns) path = "/" + bucket + path;
    var q = Object.keys(options.query || []).sort().map((x) => (aws.uriEscape(x) + (options.query[x] == null ? "" : "=" + aws.uriEscape(options.query[x])))).join("&");
    if (q) path += "?" + q;
    if (!options.headers["x-amz-content-sha256"]) {
        options.headers["x-amz-content-sha256"] = method != "GET" && options.postfile ? "UNSIGNED-PAYLOAD" : lib.hash(body || "", "sha256", "hex");
    }
    var opts = {};
    this.querySign(region, "s3", host, method || "GET", path, body, options.headers, options.credentials, opts);
    if (options.url) {
        uri += '&X-Amz-Date=' + opts.date;
        uri += '&X-Amz-Algorithm=AWS4-HMAC-SHA256';
        uri += '&X-Amz-Credential=' + opts.credential;
        uri += '&X-Amz-SignedHeaders=' + opts.signedHeaders;
        uri += '&X-Amz-Signature=' + opts.signature;
        if (options.expires) uri += "&X-Amz-Expires=" + options.expires;
        if (credentials.token) uri += '&X-Amz-Security-Token=' + credentials.token;
    }
    logger.debug('signS3:', uri, options, "opts:", opts);
    return uri;
}

// S3 requests
// Options may contain the following properties:
// - method - HTTP method
// - query - query parameters for the url as an object
// - postdata - any data to be sent with POST
// - postfile - file to be uploaded to S3 bucket
// - expires - absolute time when this request is expires
// - headers - HTTP headers to be sent with request
// - file - file name where to save downloaded contents
aws.queryS3 = function(bucket, path, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    if (!options.retryCount) options.retryCount = 3;
    if (!options.retryTimeout) options.retryTimeout = 1000;
    options.retryOnError = function() { return this.status == 503 || this.status == 500 }
    var uri = this.signS3(options.method, bucket, path, options.postdata, options);

    core.httpGet(uri, options, (err, params) => {
        if ((params.status < 200 || params.status > 299) && params.data) err = aws.parseError(params, options);
        lib.tryCall(callback, err, params);
    });
}

// Retrieve a list of files from S3 bucket, only files inside the path will be returned
aws.s3List = function(path, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    options.query = options.query || {};
    var uri = this.s3ParseUrl(path);
    for (const p in uri.query) options.query[p] = uri.query[p];
    if (uri.path) options.query.prefix = uri.path;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    options.query['list-type'] = 2;
    var rows = [], prefixes = [], truncated = false;

    lib.doWhilst(
      function(next) {
          aws.queryS3(uri.bucket, "", options, (err, params) => {
              if (err) return next(err);
              rows.push.apply(rows, lib.objGet(params.obj, "ListBucketResult.Contents", { list: 1 }));
              prefixes.push.apply(prefixes, lib.objGet(params.obj, "ListBucketResult.CommonPrefixes", { list: 1 }).map((x) => (x.Prefix.replace(uri.path, ""))));
              truncated = lib.toBool(params.obj.ListBucketResult.IsTruncated);
              options.query['continuation-token'] = params.obj.ListBucketResult.NextContinuationToken || "";
              next(err);
          });
      },
      function() {
          return truncated;
      }, (err) => {
          lib.tryCall(callback, err, rows, prefixes);
      });
}

// Retrieve a file from S3 bucket, root of the path is a bucket, path can have a protocol prepended like s3://, it will be ignored
aws.s3GetFile = function(path, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    var uri = this.s3ParseUrl(path);
    if (uri.query) options.query = uri.query;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    this.queryS3(uri.bucket, uri.path, options, (err, rc) => {
        if (rc.status == 200) {
            // A weird case when noSuchKey returned with status 200 as JSON
            if (/^"?<\?xml version=[\s\S]+<Error><Code>/.test(rc.data)) {
                if (rc.data[0] == '"') rc.data = lib.jsonParse(rc.data);
                if (!rc.obj) rc.obj = lib.xmlParse(rc.data);
                err = aws.parseError(rc);
                if (err) err.status = 404;
            }
        }
        lib.tryCall(callback, err, rc);
    });
}

// Upload a file to S3 bucket, `file` can be a Buffer or a file name
aws.s3PutFile = function(path, file, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    options.method = "PUT";
    if (!options.headers) options.headers = {};
    if (options.acl) options.headers['x-amz-acl'] = options.acl;
    if (options.contentType) options.headers['content-type'] = options.contentType;
    if (!options.headers['content-type']) options.headers['content-type'] = mime.getType(path);
    options[Buffer.isBuffer(file) ? 'postdata' : 'postfile'] = file;
    var uri = this.s3ParseUrl(path);
    if (uri.query) options.query = uri.query;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    logger.debug("s3PutFile:", uri, options);
    this.queryS3(uri.bucket, uri.path, options, callback);
}

// Copy existing S3 file, source must be in the format `bucket/path`
aws.s3CopyFile = function(path, source, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    options.method = "PUT";
    if (!options.headers) options.headers = {};
    options.headers["x-amz-copy-source"] = String(source).replace("s3://", "");
    if (options.acl) options.headers['x-amz-acl'] = options.acl;
    var uri = this.s3ParseUrl(path);
    if (uri.query) options.query = uri.query;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    logger.debug("s3CopyFile:", uri, options);
    this.queryS3(uri.bucket, uri.path, options, callback);
}

// Parse an S3 URL and return an object with bucket and path
aws.s3ParseUrl = function(link)
{
    var rc = {};
    if (!link) return rc;
    link = link.split("?");
    // Remove the protocol part and leading slashes
    link[0] = link[0].replace(/(^.+:\/\/|^\/+)/, "");
    var path = link[0].replace("//", "/").split("/");
    rc.bucket = path[0];
    // Access key and secret as auth
    var d = rc.bucket.match(/^([^:]+):([^@]+)@(.+)$/);
    if (d) {
        rc.key = d[1];
        rc.secret = d[2];
        rc.bucket = d[3];
    }
    rc.path = path.slice(1).join("/");
    if (link[1]) rc.query = qs.parse(link[1]);
    return rc;
}

// Proxy a file from S3 bucket into the existing HTTP response `res`
aws.s3Proxy = function(res, bucket, file, options, callback)
{
    if (typeof options == "function") callback = options, options = null;
    var opts = lib.objClone(options);
    var params = url.parse(this.signS3("GET", bucket, file, "", opts));
    params.headers = opts.headers;
    var mod = params.protocol == "https:" ? https : http;
    var s3req = mod.request(params, (s3res) => {
        if (options.attachment) s3res.headers["Content-Disposition"] = "attachment; filename=" + path.basename(file);
        res.writeHead(s3res.statusCode, s3res.headers);
        s3res.pipe(res, { end: true });
    }).on("error", (err) => {
        logger.error('s3Proxy:', bucket, file, err);
        s3req.abort();
    }).on("close", () => {
        lib.tryCall(null, callback);
    });
    s3req.setTimeout(options.httpTimeout || 10000, function() { s3req.abort() });
    s3req.end();
}

<commit_msg>s3 path in error<commit_after>//
//  Author: Vlad Seryakov vseryakov@gmail.com
//  backendjs 2018
//

const url = require('url');
const path = require("path");
const mime = require('mime');
const qs = require('qs');
const http = require('http');
const https = require('https');
const logger = require(__dirname + '/logger');
const core = require(__dirname + '/core');
const lib = require(__dirname + '/lib');
const aws = require(__dirname + '/aws');

// Sign S3 AWS request, returns url to be send to S3 server, options will have all updated headers to be sent as well
aws.signS3 = function(method, bucket, path, body, options)
{
    if (!options) options = {};
    if (!options.headers) options.headers = {};

    var region = options.region || this.region || 'us-east-1';
    if (!options.headers["content-type"]) options.headers["content-type"] = "binary/octet-stream";
    // For text files to prevent encoding/decoding issues
    if (/text|html|xml|json/.test(options.headers["content-type"]) &&
        options.headers["content-type"].indexOf("charset=") == -1) {
        options.headers["content-type"] += "; charset=utf-8";
    }

    // Run through the encoding so our signature match the real url sent by core.httpGet
    path = url.parse(path || "/").pathname;
    if (path[0] != "/") path = "/" + path;

    // DNS compatible or not, use path-style if not for access otherwise virtual host style
    var dns = bucket.match(/[a-z0-9][a-z0-9-]*[a-z0-9]/) ? true : false;

    var host = (dns ? bucket + "." : "") + "s3" + (region != "us-east-1" ? "-" + region : "") + ".amazonaws.com";
    var uri = (options.endpoint_protocol || "https") + "://" + host + (dns ? "" : "/" + bucket) + path;
    var credentials = options.credentials || this;

    if (!dns) path = "/" + bucket + path;
    var q = Object.keys(options.query || []).sort().map((x) => (aws.uriEscape(x) + (options.query[x] == null ? "" : "=" + aws.uriEscape(options.query[x])))).join("&");
    if (q) path += "?" + q;
    if (!options.headers["x-amz-content-sha256"]) {
        options.headers["x-amz-content-sha256"] = method != "GET" && options.postfile ? "UNSIGNED-PAYLOAD" : lib.hash(body || "", "sha256", "hex");
    }
    var opts = {};
    this.querySign(region, "s3", host, method || "GET", path, body, options.headers, options.credentials, opts);
    if (options.url) {
        uri += '&X-Amz-Date=' + opts.date;
        uri += '&X-Amz-Algorithm=AWS4-HMAC-SHA256';
        uri += '&X-Amz-Credential=' + opts.credential;
        uri += '&X-Amz-SignedHeaders=' + opts.signedHeaders;
        uri += '&X-Amz-Signature=' + opts.signature;
        if (options.expires) uri += "&X-Amz-Expires=" + options.expires;
        if (credentials.token) uri += '&X-Amz-Security-Token=' + credentials.token;
    }
    logger.debug('signS3:', uri, options, "opts:", opts);
    return uri;
}

// S3 requests
// Options may contain the following properties:
// - method - HTTP method
// - query - query parameters for the url as an object
// - postdata - any data to be sent with POST
// - postfile - file to be uploaded to S3 bucket
// - expires - absolute time when this request is expires
// - headers - HTTP headers to be sent with request
// - file - file name where to save downloaded contents
aws.queryS3 = function(bucket, path, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    if (!options.retryCount) options.retryCount = 3;
    if (!options.retryTimeout) options.retryTimeout = 1000;
    options.retryOnError = function() { return this.status == 503 || this.status == 500 }
    var uri = this.signS3(options.method, bucket, path, options.postdata, options);

    core.httpGet(uri, options, (err, params) => {
        if ((params.status < 200 || params.status > 299) && params.data) err = aws.parseError(params, options);
        if (err) err.path = uri;
        lib.tryCall(callback, err, params);
    });
}

// Retrieve a list of files from S3 bucket, only files inside the path will be returned
aws.s3List = function(path, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    options.query = options.query || {};
    var uri = this.s3ParseUrl(path);
    for (const p in uri.query) options.query[p] = uri.query[p];
    if (uri.path) options.query.prefix = uri.path;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    options.query['list-type'] = 2;
    var rows = [], prefixes = [], truncated = false;

    lib.doWhilst(
      function(next) {
          aws.queryS3(uri.bucket, "", options, (err, params) => {
              if (err) return next(err);
              rows.push.apply(rows, lib.objGet(params.obj, "ListBucketResult.Contents", { list: 1 }));
              prefixes.push.apply(prefixes, lib.objGet(params.obj, "ListBucketResult.CommonPrefixes", { list: 1 }).map((x) => (x.Prefix.replace(uri.path, ""))));
              truncated = lib.toBool(params.obj.ListBucketResult.IsTruncated);
              options.query['continuation-token'] = params.obj.ListBucketResult.NextContinuationToken || "";
              next(err);
          });
      },
      function() {
          return truncated;
      }, (err) => {
          lib.tryCall(callback, err, rows, prefixes);
      });
}

// Retrieve a file from S3 bucket, root of the path is a bucket, path can have a protocol prepended like s3://, it will be ignored
aws.s3GetFile = function(path, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    var uri = this.s3ParseUrl(path);
    if (uri.query) options.query = uri.query;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    this.queryS3(uri.bucket, uri.path, options, (err, rc) => {
        if (rc.status == 200) {
            // A weird case when noSuchKey returned with status 200 as JSON
            if (/^"?<\?xml version=[\s\S]+<Error><Code>/.test(rc.data)) {
                if (rc.data[0] == '"') rc.data = lib.jsonParse(rc.data);
                if (!rc.obj) rc.obj = lib.xmlParse(rc.data);
                err = aws.parseError(rc);
                if (err) err.status = 404;
            }
        }
        lib.tryCall(callback, err, rc);
    });
}

// Upload a file to S3 bucket, `file` can be a Buffer or a file name
aws.s3PutFile = function(path, file, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    options.method = "PUT";
    if (!options.headers) options.headers = {};
    if (options.acl) options.headers['x-amz-acl'] = options.acl;
    if (options.contentType) options.headers['content-type'] = options.contentType;
    if (!options.headers['content-type']) options.headers['content-type'] = mime.getType(path);
    options[Buffer.isBuffer(file) ? 'postdata' : 'postfile'] = file;
    var uri = this.s3ParseUrl(path);
    if (uri.query) options.query = uri.query;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    logger.debug("s3PutFile:", uri, options);
    this.queryS3(uri.bucket, uri.path, options, callback);
}

// Copy existing S3 file, source must be in the format `bucket/path`
aws.s3CopyFile = function(path, source, options, callback)
{
    if (typeof options == "function") callback = options, options = {};
    if (!options) options = {};

    options.method = "PUT";
    if (!options.headers) options.headers = {};
    options.headers["x-amz-copy-source"] = String(source).replace("s3://", "");
    if (options.acl) options.headers['x-amz-acl'] = options.acl;
    var uri = this.s3ParseUrl(path);
    if (uri.query) options.query = uri.query;
    if (uri.key) options = lib.objClone(options, "credentials", { key: uri.key, secret: uri.secret });
    logger.debug("s3CopyFile:", uri, options);
    this.queryS3(uri.bucket, uri.path, options, callback);
}

// Parse an S3 URL and return an object with bucket and path
aws.s3ParseUrl = function(link)
{
    var rc = {};
    if (!link) return rc;
    link = link.split("?");
    // Remove the protocol part and leading slashes
    link[0] = link[0].replace(/(^.+:\/\/|^\/+)/, "");
    var path = link[0].replace("//", "/").split("/");
    rc.bucket = path[0];
    // Access key and secret as auth
    var d = rc.bucket.match(/^([^:]+):([^@]+)@(.+)$/);
    if (d) {
        rc.key = d[1];
        rc.secret = d[2];
        rc.bucket = d[3];
    }
    rc.path = path.slice(1).join("/");
    if (link[1]) rc.query = qs.parse(link[1]);
    return rc;
}

// Proxy a file from S3 bucket into the existing HTTP response `res`
aws.s3Proxy = function(res, bucket, file, options, callback)
{
    if (typeof options == "function") callback = options, options = null;
    var opts = lib.objClone(options);
    var params = url.parse(this.signS3("GET", bucket, file, "", opts));
    params.headers = opts.headers;
    var mod = params.protocol == "https:" ? https : http;
    var s3req = mod.request(params, (s3res) => {
        if (options.attachment) s3res.headers["Content-Disposition"] = "attachment; filename=" + path.basename(file);
        res.writeHead(s3res.statusCode, s3res.headers);
        s3res.pipe(res, { end: true });
    }).on("error", (err) => {
        logger.error('s3Proxy:', bucket, file, err);
        s3req.abort();
    }).on("close", () => {
        lib.tryCall(null, callback);
    });
    s3req.setTimeout(options.httpTimeout || 10000, function() { s3req.abort() });
    s3req.end();
}

<|endoftext|>


<commit_before>var Buffer = require("binary").Buffer;
var alphabetString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var alphabetBuffer = new Buffer(alphabetString, "utf-8");

exports.encode = function(input) {
	if (!(input instanceof Buffer)) { input = new Buffer(input, "utf-8"); }

	var output = new Buffer(4 * Math.ceil(input.length / 3));
	var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
	var i=0, j=0;
	system.stdout(input.length);
	do {
		chr1 = (i < input.length ? input[i++] : NaN);
		chr2 = (i < input.length ? input[i++] : NaN);
		chr3 = (i < input.length ? input[i++] : NaN);

		enc1 = chr1 >> 2;
		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		enc4 = chr3 & 63;

		if (isNaN(chr2)) {
			enc3 = enc4 = 64;
		} else if (isNaN(chr3)) {
			enc4 = 64;
		}

		output[j++] = alphabetBuffer[enc1];
		output[j++] = alphabetBuffer[enc2];
		output[j++] = alphabetBuffer[enc3];
		output[j++] = alphabetBuffer[enc4];

	} while (i < input.length);

	return output;
};

exports.decode = function(input) {
	if (!(input instanceof Buffer)) { input = new Buffer(input, "utf-8"); }

	var output = new Buffer(Math.ceil(3 * input.length / 4));
	var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
	var i=0, j=0;

	try {

		do {
			enc1 = alphabetBuffer.indexOf(input[i++]);
			enc2 = alphabetBuffer.indexOf(input[i++]);
			enc3 = alphabetBuffer.indexOf(input[i++]);
			enc4 = alphabetBuffer.indexOf(input[i++]);

			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;

			output[j++] = chr1;

			if (enc3 != 64) { output[j++] = chr2; }
			if (enc4 != 64) { output[j++] = chr3; }
		} while (i < input.length);

	} catch (e) {
		throw new Error("Malformed base64 sequence at char " + i);
	}

	output = output.range(0, j);
	return output;
};
<commit_msg>b64 typo<commit_after>var Buffer = require("binary").Buffer;
var alphabetString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var alphabetBuffer = new Buffer(alphabetString, "utf-8");

exports.encode = function(input) {
	if (!(input instanceof Buffer)) { input = new Buffer(input, "utf-8"); }

	var output = new Buffer(4 * Math.ceil(input.length / 3));
	var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
	var i=0, j=0;
	do {
		chr1 = (i < input.length ? input[i++] : NaN);
		chr2 = (i < input.length ? input[i++] : NaN);
		chr3 = (i < input.length ? input[i++] : NaN);

		enc1 = chr1 >> 2;
		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		enc4 = chr3 & 63;

		if (isNaN(chr2)) {
			enc3 = enc4 = 64;
		} else if (isNaN(chr3)) {
			enc4 = 64;
		}

		output[j++] = alphabetBuffer[enc1];
		output[j++] = alphabetBuffer[enc2];
		output[j++] = alphabetBuffer[enc3];
		output[j++] = alphabetBuffer[enc4];

	} while (i < input.length);

	return output;
};

exports.decode = function(input) {
	if (!(input instanceof Buffer)) { input = new Buffer(input, "utf-8"); }

	var output = new Buffer(Math.ceil(3 * input.length / 4));
	var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
	var i=0, j=0;

	try {

		do {
			enc1 = alphabetBuffer.indexOf(input[i++]);
			enc2 = alphabetBuffer.indexOf(input[i++]);
			enc3 = alphabetBuffer.indexOf(input[i++]);
			enc4 = alphabetBuffer.indexOf(input[i++]);

			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;

			output[j++] = chr1;

			if (enc3 != 64) { output[j++] = chr2; }
			if (enc4 != 64) { output[j++] = chr3; }
		} while (i < input.length);

	} catch (e) {
		throw new Error("Malformed base64 sequence at char " + i);
	}

	output = output.range(0, j);
	return output;
};
<|endoftext|>


<commit_before>/**
 *
 *  base64 encode / decode
 *  http://www.webtoolkit.info/
 *
 **/

var base64 = {

  // private property
  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

  // public method for encoding
  encode : function (input) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    input = base64._utf8_encode(input);

    while (i < input.length) {

      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }

      output = output +
        this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
        this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

    }

    return output;
  },

  // public method for decoding
  decode : function (input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    while (i < input.length) {

      enc1 = this._keyStr.indexOf(input.charAt(i++));
      enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++));
      enc4 = this._keyStr.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      output = output + String.fromCharCode(chr1);

      if (enc3 != 64) {
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output = output + String.fromCharCode(chr3);
      }

    }

    output = base64._utf8_decode(output);

    return output;

  },

  // private method for UTF-8 encoding
  _utf8_encode : function (string) {
    string = string.replace(/\r\n/g,"\n");
    var utftext = "";

    for (var n = 0; n < string.length; n++) {

      var c = string.charCodeAt(n);

      if (c < 128) {
        utftext += String.fromCharCode(c);
      }
      else if((c > 127) && (c < 2048)) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      }
      else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }

    }

    return utftext;
  },

  // private method for UTF-8 decoding
  _utf8_decode : function (utftext) {
    var string = "";
    var i = 0;
    var c = c1 = c2 = 0;

    while ( i < utftext.length ) {

      c = utftext.charCodeAt(i);

      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      }
      else if((c > 191) && (c < 224)) {
        c2 = utftext.charCodeAt(i+1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      }
      else {
        c2 = utftext.charCodeAt(i+1);
        c3 = utftext.charCodeAt(i+2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }

    }

    return string;
  }

}

module.exports = base64;
<commit_msg>Ignore missing = at end of encoded string while decoding<commit_after>/**
 *
 *  base64 encode / decode
 *  http://www.webtoolkit.info/
 *
 **/

var base64 = {

  // private property
  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

  // public method for encoding
  encode : function (input) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    input = base64._utf8_encode(input);

    while (i < input.length) {

      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }

      output = output +
        this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
        this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

    }

    return output;
  },

  // public method for decoding
  decode : function (input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    input += new Array(5 - (input.length % 4 || 4)).join('=');

    while (i < input.length) {

      enc1 = this._keyStr.indexOf(input.charAt(i++));
      enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++));
      enc4 = this._keyStr.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      output = output + String.fromCharCode(chr1);

      if (enc3 != 64) {
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output = output + String.fromCharCode(chr3);
      }

    }

    output = base64._utf8_decode(output);

    return output;

  },

  // private method for UTF-8 encoding
  _utf8_encode : function (string) {
    string = string.replace(/\r\n/g,"\n");
    var utftext = "";

    for (var n = 0; n < string.length; n++) {

      var c = string.charCodeAt(n);

      if (c < 128) {
        utftext += String.fromCharCode(c);
      }
      else if((c > 127) && (c < 2048)) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      }
      else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }

    }

    return utftext;
  },

  // private method for UTF-8 decoding
  _utf8_decode : function (utftext) {
    var string = "";
    var i = 0;
    var c = c1 = c2 = 0;

    while ( i < utftext.length ) {

      c = utftext.charCodeAt(i);

      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      }
      else if((c > 191) && (c < 224)) {
        c2 = utftext.charCodeAt(i+1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      }
      else {
        c2 = utftext.charCodeAt(i+1);
        c3 = utftext.charCodeAt(i+2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }

    }

    return string;
  }

}

module.exports = base64;
<|endoftext|>


<commit_before>/*
 * Copyright 2017 Pascal Mainini
 * Licensed under MIT license, see included file LICENSE or
 * http://opensource.org/licenses/MIT
 */

/**
 * This module wraps Leemon BigInt and provides additional, more performant
 * conversion functions.
 *
 * Kudos to Rolf Haenni for the initial implementation of the algorithms.
 *
 * @module famodulus-client/bigint
 */

'use strict';

const leemon = require('BigInt');
const randomBytes = require('randombytes');

/**
 * A BigInt representing the value 0.
 * @type BigInt
 */
const ZERO = [0];

/**
 * A BigInt representing the value 1.
 * @type BigInt
 */
const ONE = [1, 0];

var maxIntBits = 52; // bit size of mantissa of JS numbers

// max bit size to perform integer multiplications
const maxMulBits = (() => {
  let bpi = 0;
  while ((1 << (bpi + 1)) > (1 << bpi)) {
    bpi++;
  }
  return bpi >> 1;
})();

const zeros = (() => {
  let retval = new Array(maxIntBits);
  let zeros = '';

  for (var i = 0; i < maxIntBits; i++) {
    retval[i] = zeros;
    zeros += '0';
  }

  return retval;
})();

function hex2BigInt (h) {
  return bin2BigInt(hex2Bin(h));
}

function hex2Bin (h) {
  var b = '';
  var r = h.length % (maxIntBits / 4);
  if (r > 0) r = r - (maxIntBits / 4);
  for (var i = r; i < h.length; i = i + maxIntBits / 4) {
    var g = parseInt(h.substring(i, i + (maxIntBits / 4)), 16).toString(2);
    if (i > r) {
      b += zeros[maxIntBits - g.length];
    }
    b += g;
  }
  return b;
}

function bin2BigInt (b) {
  var x = [];
  for (var i = b.length; i > 0; i = i - maxMulBits) {
    x.push(parseInt(b.substring(i - maxMulBits, i), 2));
  }
  x.push(0);
  return x;
}

function bigInt2Hex (x) {
  return bin2Hex(bigInt2Bin(x));
}

function bigInt2Bin (x) {
  var s = '';
  for (var i = x.length - 2; i >= 0; i--) {
    var b = x[i].toString(2);
    if (i < x.length - 2) {
      s += zeros[maxMulBits - b.length];
    }
    s += b;
  }
  return s;
}

function bin2Hex (b) {
  var h = '';
  var r = b.length % maxIntBits;
  if (r > 0) r = r - maxIntBits;
  for (var i = r; i < b.length; i = i + maxIntBits) {
    var g = parseInt(b.substring(i, i + maxIntBits), 2).toString(16);
    if (i > r) {
      h += zeros[(maxIntBits / 4) - g.length];
    }
    h += g;
  }
  return h;
}

/**
 * Generate a random BigInt smaller than bound.
 * This uses either window.crypto (Web Crypto API) or crypto.randomBytes (nodejs).
 *
 * @param {BigInt} bound        Upper bound for the random number
 * @returns {BigInt}            A random BigInt smaller than bound.
 */
function random (bound) {
  let bitsize = leemon.bitSize(bound);
  let bytesize = Math.floor(bitsize / 8) === 0 ? 1 : Math.floor(bitsize / 8);
  let randhex = new Array(bytesize);
  let rand = ZERO;

  while (leemon.equals(rand, ZERO)) {
    let randbytes = randomBytes(bytesize);

    for (let i = 0; i < bytesize; i++) {
      let bhex = randbytes[i].toString(16);
      randhex[i] = randbytes[i] < 0x10 ? '0' + bhex : bhex;
    }
    rand = leemon.mod(hex2BigInt(randhex.join(''), 16, 0), bound);
  }
  return rand;
}

module.exports = leemon;
module.exports.ZERO = ZERO;
module.exports.ONE = ONE;
module.exports.hex2BigInt = hex2BigInt;
module.exports.bigInt2Hex = bigInt2Hex;
module.exports.random = random;
<commit_msg>initial code cleanup<commit_after>/*
 * Copyright 2017 Pascal Mainini
 * Licensed under MIT license, see included file LICENSE or
 * http://opensource.org/licenses/MIT
 */

/**
 * This module wraps Leemon BigInt and provides additional, more performant
 * conversion functions.
 *
 * Kudos to Rolf Haenni for the initial implementation of the algorithms.
 *
 * @module famodulus-client/bigint
 */

'use strict';

const leemon = require('BigInt');
const randomBytes = require('randombytes');

/**
 * A BigInt representing the value 0.
 * @type BigInt
 */
const ZERO = [0];

/**
 * A BigInt representing the value 1.
 * @type BigInt
 */
const ONE = [1, 0];

// bit size of mantissa of JS numbers
const maxIntBits = 52;

// max bit size to perform integer multiplications
const maxMulBits = (() => {
  let bpi = 0;
  while ((1 << (bpi + 1)) > (1 << bpi)) {
    bpi++;
  }
  return bpi >> 1;
})();

const zeros = (() => {
  let retval = new Array(maxIntBits);
  let zeros = '';

  for (let i = 0; i < maxIntBits; i++) {
    retval[i] = zeros;
    zeros += '0';
  }

  return retval;
})();

function hex2Bin (hexstr) {
  let binstr = '';
  let maxBinBits = maxIntBits / 4;
  let r = hexstr.length % maxBinBits;
  r = r > 0 ? r - maxBinBits : r;

  for (let i = r; i < hexstr.length; i += maxBinBits) {
    let binpart = Number.parseInt(hexstr.substring(i, i + maxBinBits), 16).toString(2);
    if (i > r) {
      binstr += zeros[maxIntBits - binpart.length];
    }
    binstr += binpart;
  }

  return binstr;
}

function bin2Hex (binstr) {
  let hexstr = '';
  let r = binstr.length % maxIntBits;
  r = r > 0 ? r - maxIntBits : r;

  for (let i = r; i < binstr.length; i = i + maxIntBits) {
    let g = Number.parseInt(binstr.substring(i, i + maxIntBits), 2).toString(16);
    if (i > r) {
      hexstr += zeros[(maxIntBits / 4) - g.length];
    }
    hexstr += g;
  }

  return hexstr;
}

function hex2BigInt (hexstr) {
  if (hexstr === '0' || hexstr === '00') {
    return [0];
  } else {
    return bin2BigInt(hex2Bin(hexstr));
  }
}

function bin2BigInt (binstr) {
  let x = [];

  for (let i = binstr.length; i > 0; i -= maxMulBits) {
    x.push(Number.parseInt(binstr.substring(i - maxMulBits, i), 2));
  }

  x.push(0);
  return x;
}

function bigInt2Hex (bigint) {
  if (bigint.length === 1 && bigint[0] === 0) {
    return '0';
  } else {
    return bin2Hex(bigInt2Bin(bigint));
  }
}

function bigInt2Bin (bigint) {
  let s = '';

  for (let i = bigint.length - 2; i >= 0; i--) {
    let b = bigint[i].toString(2);
    if (i < bigint.length - 2) {
      s += zeros[maxMulBits - b.length];
    }
    s += b;
  }

  return s;
}

/**
 * Generate a random BigInt smaller than bound.
 * This uses either window.crypto (Web Crypto API) or crypto.randomBytes (nodejs).
 *
 * @param {BigInt} bound        Upper bound for the random number
 * @returns {BigInt}            A random BigInt smaller than bound.
 */
function random (bound) {
  let bytesize = Math.floor(leemon.bitSize(bound) / 8);
  bytesize = bytesize === 0 ? 1 : bytesize;

  let randhex = new Array(bytesize);
  let rand = ZERO;

  while (leemon.equals(rand, ZERO)) {
    randhex = randomBytes(bytesize).map(byte => byte.toString(16));
    rand = leemon.mod(hex2BigInt(randhex.join('')), bound);
  }

  return rand;
}

module.exports = leemon;
module.exports.ZERO = ZERO;
module.exports.ONE = ONE;
module.exports.hex2BigInt = hex2BigInt;
module.exports.bigInt2Hex = bigInt2Hex;
module.exports.random = random;
<|endoftext|>


<commit_before>var B = exports.Binary;
var BS = exports.ByteString;
var BA = exports.ByteArray;

var get = function(index) {
	return this[index];
}

/* Binary stuff */

B.prototype.toArray = function(charset) {
	var arr = [];
	var source = this;
	if (charset) { source = this.decodeToString(charset); }

	for (var i=0;i<source.length;i++) {
		arr.push(source.codeAt(i));
	}
	return arr;
}

/* BS stuff */

BS.prototype.get = BS.prototype.valueAt = BS.prototype.byteAt = get;

BS.prototype.toString = function() {
	return "[ByteString "+this.length + "]";
}

BS.prototype.toSource = function() {
	return "ByteString([" + this.toArray().join(", ") + "])";
}

BS.prototype.substring = function(index1, index2) {
	/* swap if necessary */
	var start = Math.min(index1, index2);
	var end = Math.max(index1, index2);

	/* both non-negative */
	start = Math.max(start, 0);
	end = Math.max(end, 0);

	return this.slice(start, end);
}

BS.prototype.substr = function(index, length) {
	/* non-negative start, default end */
	var start = Math.max(index, 0);
	var end = this.length;

	/* length specified */
	if (arguments.length > 1) {
		end = start + length;
		/* length nonnegative */
		end = Math.max(start, end);
	}

	return this.slice(start, end);
}

/* BA stuff */

BA.prototype.get = BA.prototype.valueAt = BA.prototype.byteAt = get;

BA.prototype.toString = function() {
	return "[ByteArray "+this.length + "]";
}

BA.prototype.toSource = function() {
	return "ByteArray([" + this.toArray().join(", ") + "])";
}

/* String stuff */

String.prototype.toByteArray = function(charset) {
	return new BA(this, charset);
}

String.prototype.toByteString = function(charset) {
	return new BS(this, charset);
}

String.prototype.charCode = function() {
	var arr = [];
	for (var i=0;i<this.length;i++) { arr.push(this.charCodeAt(i)); }
	return arr;
}

String.prototype.codeAt = function(index) {
	return this.charCodeAt(index);
}

String.prototype.get = function(index) {
	return this[index];
}

/* Array stuff */

Array.prototype.toByteArray = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BA(str, charset);
}

Array.prototype.toByteString = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BS(str, charset);
}

Array.prototype.get = function(index) {
	return this[index];
}
<commit_msg>more binary<commit_after>var B = exports.Binary;
var BS = exports.ByteString;
var BA = exports.ByteArray;

var get = function(index) {
	return this[index];
}

/* Binary stuff */

B.prototype.toArray = function(charset) {
	var arr = [];
	var source = this;
	if (charset) { source = this.decodeToString(charset); }

	for (var i=0;i<source.length;i++) {
		arr.push(source.codeAt(i));
	}
	return arr;
}

/* BS stuff */

BS.prototype.get = BS.prototype.valueAt = BS.prototype.byteAt = get;

BS.prototype.toString = function() {
	return "[ByteString "+this.length + "]";
}

BS.prototype.toSource = function() {
	return "ByteString([" + this.toArray().join(", ") + "])";
}

BS.prototype.substring = function(index1, index2) {
	/* swap if necessary */
	var start = Math.min(index1, index2);
	var end = Math.max(index1, index2);

	/* both non-negative */
	start = Math.max(start, 0);
	end = Math.max(end, 0);

	return this.slice(start, end);
}

BS.prototype.substr = function(index, length) {
	/* non-negative start, default end */
	var start = Math.max(index, 0);
	var end = this.length;

	/* length specified */
	if (arguments.length > 1) {
		end = start + length;
		/* length nonnegative */
		end = Math.max(start, end);
	}

	return this.slice(start, end);
}

/* BA stuff */

BA.prototype.get = BA.prototype.valueAt = BA.prototype.byteAt = get;

BA.prototype.toString = function() {
	return "[ByteArray "+this.length + "]";
}

BA.prototype.toSource = function() {
	return "ByteArray([" + this.toArray().join(", ") + "])";
}

/* BA JS 1.6 Array methods */

BA.prototype.forEach = function(cb, _this) {
	var len = this.length;
	for (var i=0;i<len;i++) {
		if (i in this) { cb.call(_this, this[i], i, this); }
	}
}

BA.prototype.every = function(cb, _this) {
	var len = this.length;
	for (var i=0;i<len;i++) {
		if (i in this && !cb.call(_this, this[i], i, this)) { return false; }
	}
	return true;
}

BA.prototype.some = function(cb, _this) {
	var len = this.length;
	for (var i=0;i<len;i++) {
		if (i in this && cb.call(_this, this[i], i, this)) { return true; }
	}
	return false;
}

BA.prototype.map = function(cb, _this) {
	var len = this.length;
	var res = new Array(len);
	for (var i=0;i<len;i++) {
		if (i in this) { res[i] = cb.call(_this, this[i], i, this); }
	}
	return res;
}

BA.prototype.filter = function(cb, _this) {
	var len = this.length;
	var res = [];
		for (var i=0;i<len;i++) {
			if (i in this) {
				var val = this[i];
				if (cb.call(_this, val, i, this)) { res.push(val); }
			}
		}
	return res;
}

/* String stuff */

String.prototype.toByteArray = function(charset) {
	return new BA(this, charset);
}

String.prototype.toByteString = function(charset) {
	return new BS(this, charset);
}

String.prototype.charCode = function() {
	var arr = [];
	for (var i=0;i<this.length;i++) { arr.push(this.charCodeAt(i)); }
	return arr;
}

String.prototype.codeAt = function(index) {
	return this.charCodeAt(index);
}

String.prototype.get = function(index) {
	return this[index];
}

/* Array stuff */

Array.prototype.toByteArray = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BA(str, charset);
}

Array.prototype.toByteString = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BS(str, charset);
}

Array.prototype.get = function(index) {
	return this[index];
}
<|endoftext|>


<commit_before>"use strict";
var q = require("q");
var matrixLib = require("./mxlib/matrix");
var ircLib = require("./irclib/irc");
var models = require("./models");
var store = new models.RoomStore();
module.exports.store = store;

var msgTypes = {
    message: "m.text",
    privmsg: "m.emote",
    notice: "m.notice",
    "m.text": "message",
    "m.emote": "privmsg",
    "m.notice": "notice"
};

var createIrcUserWithUserId = function(userId) {
    var defer = q.defer();

    ircLib.checkNickForUserIdExists(userId).then(function(info) {
        // make the user
        var server = info.server;
        var nick = info.nick;
        console.log("Creating virtual user for %s on %s",
            nick, server.domain);
        return matrixLib.getMatrixUser(server.userPrefix+nick);
    }).then(function(user) {
        console.log("Created virtual user %s", user.userId);
        defer.resolve(user);
    }, function(err) {
        console.error("Virtual user creation for %s failed: %s",
            userId, err);
        defer.reject({});
    }).done();

    return defer.promise;
}

module.exports.hooks = {
    matrix: {
        onInvite: function(event) {
            console.log("onInvite: %s", JSON.stringify(event));
            var errFn = function(err) {
                console.error(
                    "onInvite: Failed to handle invite from %s to room %s : %s",
                    userId, event.room_id, err);
            };
            var userId = event.state_key;
            createIrcUserWithUserId(userId).then(function(user) {
                return matrixLib.joinRoom(event.room_id, user);
            }).then(function() {
                console.log("Joined %s to room %s", userId, event.room_id);
                return matrixLib.isPmRoom(userId, event.room_id, event.user_id);
            }).then(function(isPmRoom) {
                if (isPmRoom) {
                    // Clobber the PM room with the invited room ID
                    // Store the PM room (IRC user / Matrix user tuple)
                    // store.setPmRoom(server, nick, event.user_id);
                }
                else {
                    // whine that you don't do group chats and leave.
                    matrixLib.sendNoticeRaw(
                        event.room_id, userId, "Group chat not supported."
                    ).done(undefined, errFn);
                    matrixLib.leaveRoom(userId, event.room_id).done(
                        undefined, errFn
                    );
                }
            });
        },
        onJoin: function(event) {
            // if this is another Matrix user joining a PM room:
            //  - Whine that you don't do group chats and leave (virtual user)
        },
        onMessage: function(event) {
            // if message is in a tracked room, echo to IRC room.

            // TODO: if message is in a PM room, PM IRC user (from Matrix user)
            // else complain and send an error back (could be a stale PM room)
            console.log("[M->I]onMessage usr=%s rm=%s", event.user_id,
                event.room_id);

            var ircRooms = store.getRoomsForRoomId(event.room_id);

            if (ircRooms.length == 0) {
                console.log("No mapped channels.");
                return;
            }

            ircRooms.forEach(function(ircRoom) {
                ircLib.getVirtualIrcUser(ircRoom.server, event.user_id).then(
                    function(ircUser) {
                        if (!ircUser) {
                            console.error("Unknown IRC user for user ID %s",
                                event.user_id);
                            return;
                        }
                        console.log("Obtained virtual IRC user: %s",
                            ircUser.nick);
                        var msgtype = msgTypes[event.content.msgtype];
                        var msg = event.content.body;
                        return ircUser.sendMessage(ircRoom, msgtype, msg);
                }).done(function() {
                    console.log("[M->I] Sent message.");
                }, function(err) {
                    console.error("[M->I]Failed to relay Matrix message: %s",
                        JSON.stringify(err));
                });
            });
        },
        onAliasQuery: function(roomAlias) {
            // if alias maps to a valid IRC server and channel:
            //  - create a matrix room
            //  - join the irc server (if haven't already)
            //  - join the channel
            //  - STORE THE NEW DYNAMIC MAPPING FOREVERMORE (so if you get
            //    restarted, you know to track this room)
            //  - respond OK
            return q.reject({});
        },
        onUserQuery: function(userId) {
            console.log("onUserQuery: %s", userId);
            return createIrcUserWithUserId(userId);
        }
    },
    irc: {
        onMessage: function(server, from, to, kind, msg) {
            console.log("[I->M]onMessage: from=%s to=%s kind=%s msg=%s",
                from, to, kind, msg);

            // TODO if message is a PM to a Matrix user, send message in PM
            // room, creating one if need be.

            var matrixRooms = store.getRoomsForChannel(server, to);

            if (ircLib.isNickVirtualUser(server, from)) {
                console.log("Virtual user: bailing.");
                return;
            }
            if (matrixRooms.length == 0) {
                console.log("No mapped rooms.");
                return; // nothing to do.
            }

            var errFn = function(err) {
                console.error("[I->M]Failed to relay IRC message: %s",
                    JSON.stringify(err));
            };

            matrixLib.getMatrixUser(server.userPrefix+from).done(function(user) {
                matrixRooms.forEach(function(room) {
                    matrixLib.sendMessage(
                        room, user, msgTypes[kind], msg
                    ).done(undefined, errFn);
                });
            }, errFn);

        }
    }
};
<commit_msg>Wrap in finally clause to avoid racing between leaving the room and sending the notice.<commit_after>"use strict";
var q = require("q");
var matrixLib = require("./mxlib/matrix");
var ircLib = require("./irclib/irc");
var models = require("./models");
var store = new models.RoomStore();
module.exports.store = store;

var msgTypes = {
    message: "m.text",
    privmsg: "m.emote",
    notice: "m.notice",
    "m.text": "message",
    "m.emote": "privmsg",
    "m.notice": "notice"
};

var createIrcUserWithUserId = function(userId) {
    var defer = q.defer();

    ircLib.checkNickForUserIdExists(userId).then(function(info) {
        // make the user
        var server = info.server;
        var nick = info.nick;
        console.log("Creating virtual user for %s on %s",
            nick, server.domain);
        return matrixLib.getMatrixUser(server.userPrefix+nick);
    }).then(function(user) {
        console.log("Created virtual user %s", user.userId);
        defer.resolve(user);
    }, function(err) {
        console.error("Virtual user creation for %s failed: %s",
            userId, err);
        defer.reject({});
    }).done();

    return defer.promise;
}

module.exports.hooks = {
    matrix: {
        onInvite: function(event) {
            console.log("onInvite: %s", JSON.stringify(event));
            var errFn = function(err) {
                console.error(
                    "onInvite: Failed to handle invite from %s to room %s : %s",
                    userId, event.room_id, err);
            };
            var userId = event.state_key;
            createIrcUserWithUserId(userId).then(function(user) {
                return matrixLib.joinRoom(event.room_id, user);
            }).then(function() {
                console.log("Joined %s to room %s", userId, event.room_id);
                return matrixLib.isPmRoom(userId, event.room_id, event.user_id);
            }).then(function(isPmRoom) {
                if (isPmRoom) {
                    // Clobber the PM room with the invited room ID
                    // Store the PM room (IRC user / Matrix user tuple)
                    // store.setPmRoom(server, nick, event.user_id);
                }
                else {
                    // whine that you don't do group chats and leave.
                    matrixLib.sendNoticeRaw(
                        event.room_id, userId, "Group chat not supported."
                    ).finally(function() {
                        matrixLib.leaveRoom(userId, event.room_id).done(
                            undefined, errFn
                        );
                    });
                }
            });
        },
        onJoin: function(event) {
            // if this is another Matrix user joining a PM room:
            //  - Whine that you don't do group chats and leave (virtual user)
        },
        onMessage: function(event) {
            // if message is in a tracked room, echo to IRC room.

            // TODO: if message is in a PM room, PM IRC user (from Matrix user)
            // else complain and send an error back (could be a stale PM room)
            console.log("[M->I]onMessage usr=%s rm=%s", event.user_id,
                event.room_id);

            var ircRooms = store.getRoomsForRoomId(event.room_id);

            if (ircRooms.length == 0) {
                console.log("No mapped channels.");
                return;
            }

            ircRooms.forEach(function(ircRoom) {
                ircLib.getVirtualIrcUser(ircRoom.server, event.user_id).then(
                    function(ircUser) {
                        if (!ircUser) {
                            console.error("Unknown IRC user for user ID %s",
                                event.user_id);
                            return;
                        }
                        console.log("Obtained virtual IRC user: %s",
                            ircUser.nick);
                        var msgtype = msgTypes[event.content.msgtype];
                        var msg = event.content.body;
                        return ircUser.sendMessage(ircRoom, msgtype, msg);
                }).done(function() {
                    console.log("[M->I] Sent message.");
                }, function(err) {
                    console.error("[M->I]Failed to relay Matrix message: %s",
                        JSON.stringify(err));
                });
            });
        },
        onAliasQuery: function(roomAlias) {
            // if alias maps to a valid IRC server and channel:
            //  - create a matrix room
            //  - join the irc server (if haven't already)
            //  - join the channel
            //  - STORE THE NEW DYNAMIC MAPPING FOREVERMORE (so if you get
            //    restarted, you know to track this room)
            //  - respond OK
            return q.reject({});
        },
        onUserQuery: function(userId) {
            console.log("onUserQuery: %s", userId);
            return createIrcUserWithUserId(userId);
        }
    },
    irc: {
        onMessage: function(server, from, to, kind, msg) {
            console.log("[I->M]onMessage: from=%s to=%s kind=%s msg=%s",
                from, to, kind, msg);

            // TODO if message is a PM to a Matrix user, send message in PM
            // room, creating one if need be.

            var matrixRooms = store.getRoomsForChannel(server, to);

            if (ircLib.isNickVirtualUser(server, from)) {
                console.log("Virtual user: bailing.");
                return;
            }
            if (matrixRooms.length == 0) {
                console.log("No mapped rooms.");
                return; // nothing to do.
            }

            var errFn = function(err) {
                console.error("[I->M]Failed to relay IRC message: %s",
                    JSON.stringify(err));
            };

            matrixLib.getMatrixUser(server.userPrefix+from).done(function(user) {
                matrixRooms.forEach(function(room) {
                    matrixLib.sendMessage(
                        room, user, msgTypes[kind], msg
                    ).done(undefined, errFn);
                });
            }, errFn);

        }
    }
};
<|endoftext|>


<commit_before>"use strict";
var button = module.exports;
var jQuery = require('jquery');

var each = require('lodash/collection/each');
var isFunction = require('lodash/lang/isFunction');
var isArray = require('lodash/lang/isArray');
var extend = require('lodash/object/extend');

/**
 * Execute a series of user provided action(s), available to them
 * in the helper utilities
 * @param  {*} actions   null|Object (with action property)|Function||Array[Function]
 * @return {undefined}
 */
var execAction = function(actions) {
    if (!actions) { return; }
    actions = actions.action || actions;
    if (!isArray(actions)) { actions = [actions]; }
    each(actions, function applyActions(action) {
        if (isFunction(action)) { return action(); }
        throw new TypeError('action not a function');
    });
};

button.btnClasses = {
    "default": 'btn-default button-default ui-button-default',
    "danger": 'btn-danger button-danger ui-button-danger',
    "info": 'btn-info button-info ui-button-info',
    "primary": 'btn-primary button-primary ui-button-primary',
    "success": 'btn-success button-success ui-button-success',
    "warning": 'btn-warning button-warning ui-button-warning'
};

// Button Definitions

/**
 * Base jQuery button generator
 * @param  {object} options
 * @return {object|jQuery}
 */
button.base = function(options) {
    if (!options) {
        throw new Error("options must defined for jquery ui button");
    }
    var $el;
    var el = options.el;
    var click = options.click;
    var action = options.action;
    var returnConfig = options.returnConfig;
    var closeDialog = options.closeDialog;
    var destroyDialog = options.destroyDialog;
    var wrappedClick;

    if (destroyDialog) {
        // force close handlers to execute prior to destruction
        closeDialog = true;
    }

    // purge all supported configs that belong to jquu, not button module
    delete options.closeDialog;
    delete options.destroyDialog;
    delete options.returnConfig;
    delete options.action;
    delete options.el;
    delete options.click;

    // retain original click func, but overwrite with extended version
    options.click = function() {
        if (click) { click.call(this.el); }
        if (action) { execAction(action); }
        if (closeDialog) { jQuery(this).dialog("close"); }
        if (destroyDialog) { jQuery(this).dialog("destroy"); }
    };
    options.class = (options.class && button.btnClasses[options.class]) ?
        button.btnClasses[options.class] : button.btnClasses["default"];

    if (el) {
        $el = jQuery(el);
        $el.addClass(options.class);
        delete options.class;
    }

    if (returnConfig || !el) {
        return options;
    }

    $el = $el.button(options);
    $el.on('click', options.click);
    return $el;
};

/**
 * Closes the dialog
 * @param  {jQuery} dialogInstance    dialog instance
 * @param  {Object} see `base` options
 * @return {Object}
 */
button.close = function(options) {
    options = options || {};
    options.closeDialog = options.closeDialog === false ? false : true;
    options.text = options.text || 'Close';
    return button.base(options);
};

/**
 * Destroys the dialog
 * @param  {jQuery} dialogInstance    dialog instance
 * @param  {Object} see `close` options
 * @return {Object}
 */
button.destroy = function(options) {
    options = options || {};
    options.destroyDialog = options.destroyDialog === false ? false : true;
    return button.close(options);
};

/**
 * Short-hand for button.close() with delete text.  Expects delete
 * functionality to be specified in `click` or `action` property
 * @param  {Object} options
 * @return {Object}
 */
button.delete = function(options) {
    options = options || {};
    var deleteOps = {};
    deleteOps.text = "Delete";
    deleteOps.class = "danger";
    if (!options.click && !options.action) {
        throw new Error('requested delete button, but no delete action specified');
    }
    deleteOps = extend(deleteOps, options);
    return button.base(deleteOps);
};
<commit_msg>Pass event object to handler scope<commit_after>"use strict";
var button = module.exports;
var jQuery = require('jquery');

var each = require('lodash/collection/each');
var isFunction = require('lodash/lang/isFunction');
var isArray = require('lodash/lang/isArray');
var extend = require('lodash/object/extend');

/**
 * Execute a series of user provided action(s), available to them
 * in the helper utilities
 * @param  {*} actions   null|Object (with action property)|Function||Array[Function]
 * @return {undefined}
 */
var execAction = function(actions) {
    if (!actions) { return; }
    actions = actions.action || actions;
    if (!isArray(actions)) { actions = [actions]; }
    each(actions, function applyActions(action) {
        if (isFunction(action)) { return action(); }
        throw new TypeError('action not a function');
    });
};

button.btnClasses = {
    "default": 'btn-default button-default ui-button-default',
    "danger": 'btn-danger button-danger ui-button-danger',
    "info": 'btn-info button-info ui-button-info',
    "primary": 'btn-primary button-primary ui-button-primary',
    "success": 'btn-success button-success ui-button-success',
    "warning": 'btn-warning button-warning ui-button-warning'
};

// Button Definitions

/**
 * Base jQuery button generator
 * @param  {object} options
 * @return {object|jQuery}
 */
button.base = function(options) {
    if (!options) {
        throw new Error("options must defined for jquery ui button");
    }
    var $el;
    var el = options.el;
    var click = options.click;
    var action = options.action;
    var returnConfig = options.returnConfig;
    var closeDialog = options.closeDialog;
    var destroyDialog = options.destroyDialog;
    var wrappedClick;

    if (destroyDialog) {
        // force close handlers to execute prior to destruction
        closeDialog = true;
    }

    // purge all supported configs that belong to jquu, not button module
    delete options.closeDialog;
    delete options.destroyDialog;
    delete options.returnConfig;
    delete options.action;
    delete options.el;
    delete options.click;

    // retain original click func, but overwrite with extended version
    options.click = function(event) {
        if (click) { click.call(this.el); }
        if (action) { execAction(action); }
        if (closeDialog) { jQuery(this).dialog("close"); }
        if (destroyDialog) { jQuery(this).dialog("destroy"); }
    };
    options.class = (options.class && button.btnClasses[options.class]) ?
        button.btnClasses[options.class] : button.btnClasses["default"];

    if (el) {
        $el = jQuery(el);
        $el.addClass(options.class);
        delete options.class;
    }

    if (returnConfig || !el) {
        return options;
    }

    $el = $el.button(options);
    $el.on('click', options.click);
    return $el;
};

/**
 * Closes the dialog
 * @param  {jQuery} dialogInstance    dialog instance
 * @param  {Object} see `base` options
 * @return {Object}
 */
button.close = function(options) {
    options = options || {};
    options.closeDialog = options.closeDialog === false ? false : true;
    options.text = options.text || 'Close';
    return button.base(options);
};

/**
 * Destroys the dialog
 * @param  {jQuery} dialogInstance    dialog instance
 * @param  {Object} see `close` options
 * @return {Object}
 */
button.destroy = function(options) {
    options = options || {};
    options.destroyDialog = options.destroyDialog === false ? false : true;
    return button.close(options);
};

/**
 * Short-hand for button.close() with delete text.  Expects delete
 * functionality to be specified in `click` or `action` property
 * @param  {Object} options
 * @return {Object}
 */
button.delete = function(options) {
    options = options || {};
    var deleteOps = {};
    deleteOps.text = "Delete";
    deleteOps.class = "danger";
    if (!options.click && !options.action) {
        throw new Error('requested delete button, but no delete action specified');
    }
    deleteOps = extend(deleteOps, options);
    return button.base(deleteOps);
};
<|endoftext|>


<commit_before>import React from "react";
import { ActivityIndicator, View } from "react-native";
import { BrowserRouter as Router, Route } from "react-router-dom";

import Header from "../components/Header";
import DynamicImport from "../components/DynamicImport";

function Loading() {
  return (
    <View>
      <ActivityIndicator size="large" color="#0000ff" />
    </View>
  );
}

function LazyHome(props) {
  return (
    <DynamicImport load={() => import("../screens/Home")}>
      {Component =>
        Component === null ? <Loading /> : <Component {...props} />
      }
    </DynamicImport>
  );
}

function LazyListingResolver(props) {
  return (
    <DynamicImport load={() => import("../screens/ListingResolver")}>
      {Component =>
        Component === null ? <Loading /> : <Component {...props} />
      }
    </DynamicImport>
  );
}

function Screen(props) {
  return (
    <View>
      {props.children}
      {props.children}
    </View>
  );
}

function AppRoutes() {
  return (
    <Router>
      <React.Fragment>
        <Screen>
          <Route path="/" component={LazyHome} />
          <Route component={LazyListingResolver} />
        </Screen>
      </React.Fragment>
    </Router>
  );
}

export default AppRoutes;
<commit_msg>Wrap routes in switch<commit_after>import React from "react";
import { ActivityIndicator, View } from "react-native";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

import Header from "../components/Header";
import DynamicImport from "../components/DynamicImport";

function Loading() {
  return (
    <View>
      <ActivityIndicator size="large" color="#0000ff" />
    </View>
  );
}

function LazyHome(props) {
  return (
    <DynamicImport load={() => import("../screens/Home")}>
      {Component =>
        Component === null ? <Loading /> : <Component {...props} />
      }
    </DynamicImport>
  );
}

function LazyListingResolver(props) {
  return (
    <DynamicImport load={() => import("../screens/ListingResolver")}>
      {Component =>
        Component === null ? <Loading /> : <Component {...props} />
      }
    </DynamicImport>
  );
}

function Screen(props) {
  return (
    <View>
      {props.children}
      {props.children}
    </View>
  );
}

function AppRoutes() {
  return (
    <Router>
      <React.Fragment>
        <Screen>
          <Switch>
            <Route path="/" exact component={LazyHome} />
            <Route component={LazyListingResolver} />
          </Switch>
        </Screen>
      </React.Fragment>
    </Router>
  );
}

export default AppRoutes;
<|endoftext|>