<commit_before>var EventEmitter = require("events").EventEmitter;
require('rootpath')();
var mcu = require('hardware/mcu.js');
var power = require('hardware/power.js');
var watchdog = require('system/watchdog.js');
var _ = require('underscore');
var net = require('net');

var core = new EventEmitter();

var cbStore = {};
var cbIndex = 0;

console.log(">>>>>>>>>>>>>>>>>>>>> starting intervalometer client >>>>>>>>>>>>>>>>>>>");

function getCallbackId(callerName, cb) {
    if (!cb) return 0;
    cbIndex++;
    if (cbIndex > 300) cbIndex = 1;
    cbStore[cbIndex.toString()] = {callback: cb, name: callerName};
    return cbIndex;
}

function runCallback(cbData) {
    if (cbData && cbData.id && cbStore[cbData.id.toString()]) {
        var cb = cbStore[cbData.id.toString()];
        console.log("running callback for ", cb.name);
        cb && cb.callback && cb.callback(cbData.err, cbData.data);
        delete cbStore[cbData.id.toString()];
    }
}

function errorCallbacks(err, port) {
    console.log("running remaining callbacks:", JSON.stringify(cbStore));
    for (var i in cbStore) {
        runCallback({
            id: i,
            err: err,
            data: null
        });
    }
}

// buffered data
core.cameraSettings = {};
core.cameraConnected = false;
core.cameraModel = "";
core.cameraCount = 0;
core.cameraSupports = {};
core.intervalometerStatus = {};
core.histogram = [];
core.motionStatus = {};

var dataBuf = new Buffer(0);
var client;
function connect() {
    client = net.connect('/tmp/intervalometer.sock', function() {
      console.log('connected to server!');
      client.ready = true;
    });
    client.on('data', function(rawChunk) {
      var data;
      try {
        if(!rawChunk.length) return;
        dataBuf = Buffer.concat([dataBuf, rawChunk]);
        if(rawChunk[rawChunk.length - 1] != 0) {
            return;
        }
        var chunk = dataBuf.toString('utf8');
        dataBuf = new Buffer(0);
        var pieces = chunk.split('\0');
        for(var i = 0; i < pieces.length; i++) {
            pieces[i] = pieces[i].trim();
            if(!pieces[i]) continue;
            data = JSON.parse(pieces[i]);
            if(data.type == 'callback') {
                runCallback(data.data);
            } else if(data.type == 'watchdog') {
                var pid = parseInt(data.data);
                watchdog.watch(pid);
            } else {
                if(data.type == 'camera.photo') {
                    if(data.data.base64) {
                        data.data.jpeg = new Buffer(data.data.base64, 'base64');
                    }
                    core.photo = data.data;
                    data.data = null;
                } else if(data.type == 'camera.settings') {
                    core.cameraSettings = data.data;
                } else if(data.type == 'camera.histogram') {
                    core.histogram = data.data;
                } else if(data.type == 'camera.connected') {
                    core.cameraConnected = data.data.connected;
                    core.cameraModel = data.data.model;
                    core.cameraCount = data.data.count;
                    console.log("CORE: camera count: ", core.cameraCount);
                    core.cameraSupports = data.data.supports;
                    core.getSettings();
                } else if(data.type == 'camera.exiting') {
                    core.cameraConnected = data.data.connected;
                    core.cameraModel = data.data.model;
                    core.cameraCount = data.data.count;
                    console.log("CORE: camera count: ", core.cameraCount);
                    core.cameraSupports = data.data.supports;
                } else if(data.type == 'motion.status') {
                    core.motionStatus = data.data;
                } else if(data.type == 'media.present') {
                    core.sdMounted = data.data;
                    core.sdPresent = true;
                } else if(data.type == 'media.insert') {
                    core.sdPresent = true;
                } else if(data.type == 'media.remove') {
                    core.sdPresent = false;
                } else if(data.type == 'intervalometer.status') {
                    core.intervalometerStatus = data.data;
                    if(core.intervalometerStatus.running == false) {
                        power.performance('low');
                    }
                }
                console.log("CORE:", data.type, "event");
                core.emit(data.type, data.data);
            }
        }
      } catch(e) {
        console.log("Error parsing data:", data, e);
      }
    });
    client.on('end', function() {
      client.ready = false;
      console.log('disconnected from server');
      setTimeout(connect, 2000);
    });
    client.on('error', function(err) {
        if(err && err.code && (err.code == 'ECONNREFUSED' || err.code == 'ENOENT')) {
            console.log("Error: server not ready");
            setTimeout(connect, 1000);
        } else {
            console.log("error: ", err);
        }
        client.ready = false;
        client.end();
    });
}
setTimeout(connect, 5000);

function call(method, args, callback) {
    if(!client.ready) return;
    var cbId = getCallbackId(method, callback);
    var payload = JSON.stringify({
        type: method,
        args: args,
        id: cbId
    });
    if(client.ready) client.write(payload+'\0');
}

core.connectSonyWifi = function(callback) {
    call('camera.ptp.connectSonyWifi', {}, callback);
};
core.lvOff = function(callback) {
    call('camera.ptp.lvOff', {}, callback);
};
core.zoom = function(xPercent, yPercent, callback) {
    call('camera.ptp.zoom', {x:xPercent,y:yPercent}, function(err, data) {
        core.cameraZoomed = data.zoomed;
        callback(err, data);
    });
};
core.focus = function(step, repeat, callback) {
    call('camera.ptp.focus', {step:step, repeat:repeat}, callback);
};
core.setEv = function(ev, options, callback) {
    call('camera.setEv', {ev:ev, options:options}, callback);
};
core.preview = function(callback) {
    call('camera.ptp.preview', {}, callback);
};
core.getSettings = function(callback) {
    call('camera.ptp.getSettings', {}, callback);
};
core.cameraList = function(callback) {
    call('camera.ptp.cameraList', {}, callback);
};
core.switchPrimary = function(cameraObject, callback) {
    call('camera.ptp.switchPrimary', {cameraObject:cameraObject}, callback);
};
core.capture = function(options, callback) {
    call('camera.ptp.capture', {options:options}, callback);
};
core.captureTest = function(callback) {
    call('camera.ptp.capture-test', {}, callback);
};
core.runSupportTest = function(callback) {
    call('camera.ptp.runSupportTest', {}, callback);
};
core.set = function(key, val, callback) {
    call('camera.ptp.set', {key:key, val:val}, callback);
};
core.mountSd = function(callback) {
    call('camera.ptp.mountSd', {}, function(err, res){
        core.sdMounted = res;
        callback(err, res)
    });
};
core.unmountSd = function(callback) {
    call('camera.ptp.unmountSd', {}, callback);
};

core.moveMotion = function(driver, motor, steps, callback) {
    call('motion.move', {driver:driver, motor:motor, steps:steps}, callback);
};

core.zeroMotion = function(driver, motor, callback) {
    call('motion.zero', {driver:driver, motor:motor}, callback);
};

core.motionStatusUpdate = function(callback) {
    call('motion.status', {}, callback);
};

core.moveMotionJoystick = function(driver, motor, speed, callback) {
    call('motion.joystick', {driver:driver, motor:motor, speed:speed}, callback);
};

core.getCurrentTimelapseFrames = function(cameraIndex, callback) {
    call('db.currentTimelapseFrames', {cameraIndex:cameraIndex}, callback);
};

var wdtInterval = null;
core.watchdogEnable = function(callback) {
    if(!wdtInterval) {
        wdtInterval = setInterval(core.watchdogEnable, 5000); // this will have the server kill this process if it ever gets stuck
    }
    call('watchdog.set', {pid:process.pid}, callback);
};

core.watchdogDisable = function(callback) {
    if(wdtInterval) {
        clearInterval(wdtInterval);
        wdtInterval = null;
    }
    call('watchdog.disable', {pid:process.pid}, function(err){
        callback && callback(err);
    });
};

core.resetBt = function(callback) {
    call('bt.reset', {}, callback);
};

defaultProgram = {
    rampMode: "fixed",
    intervalMode: "fixed",
    rampAlgorithm: "lum",
    highlightProtection: true,
    interval: 5,
    dayInterval: 5,
    nightInterval: 35,
    frames: 300,
    destination: 'camera',
    nightCompensation: -1,
    isoMax: -6,
    isoMin:  0,
    rampParameters:  'S+I',
    apertureMax: -2,
    apertureMin:  -4,
    manualAperture: -5,
    hdrCount: 0,
    hdrStops: 1,
    exposurePlans: [],
    keyframes: [{
        focus: 0,
        ev: "not set",
        motor: {}
    }]
};

core.intervalometerStatus = {
    running: false,
    frames: 0,
    framesRemaining: 0,
    rampRate: 0,
    intervalMs: 0,
    message: "",
    rampEv: null,
    autoSettings: {
        paddingTimeMs: 5000
    }
}

core.moveTracking = function(axis, degrees, callback) {
    call('intervalometer.moveTracking', {axis:axis, degrees:degrees}, callback);
}

core.dynamicChange = function(parameter, newValue, frames, callback) {
    call('intervalometer.dynamicChange', {parameter:parameter, newValue:newValue, frames:frames}, callback);
}

core.loadProgram = function(program, callback) {
    if(!program.frames) program.frames = Infinity; // Infinity comes back as null from the DB
    if(program.keyframes) { // arrays come back as object in the VIEW db
        var kfs = [];
        for(var key in program.keyframes) {
            if(program.keyframes.hasOwnProperty(key)) {
                kfs.push(program.keyframes[key]);
            }
        }
        if(kfs.length > 0) program.keyframes = kfs;
    }
    if(program.exposurePlans) { // arrays come back as object in the VIEW db
        var plans = [];
        for(var key in program.exposurePlans) {
            if(program.exposurePlans.hasOwnProperty(key)) {
                plans.push(program.exposurePlans[key]);
            }
        }
        if(plans.length > 0) program.exposurePlans = plans;
    }

    core.currentProgram = _.extendOwn(defaultProgram, core.currentProgram, program);
    callback && callback();
}
core.loadProgram(defaultProgram);

core.stopIntervalometer = function(callback) {
    call('intervalometer.cancel', {}, callback);
}
core.startIntervalometer = function(program, callback) {
    power.performance('high');
    core.addGpsData(mcu.validCoordinates());
    call('intervalometer.run', {program:program}, callback);
}
core.addGpsData = function(gpsData, callback) {
    call('gps', {gpsData:gpsData}, callback);
}

core.getFilesList = function(callback) {
    call('camera.ptp.getFilesList', {}, callback);
}

core.downloadThumbnail = function(filePath, callback) {
    call('camera.ptp.downloadThumbnail', {filePath: filePath}, callback);
}

core.downloadFile = function(filePath, callback) {
    call('camera.ptp.downloadFile', {filePath: filePath}, callback);
}

mcu.on('gps', function(status){
    if(status == 2) {
        core.addGpsData(mcu.gps);
    }
});

module.exports = core;<commit_msg>startup time testing<commit_after>var EventEmitter = require("events").EventEmitter;
require('rootpath')();
var mcu = require('hardware/mcu.js');
var power = require('hardware/power.js');
var watchdog = require('system/watchdog.js');
var _ = require('underscore');
var net = require('net');

var core = new EventEmitter();

var cbStore = {};
var cbIndex = 0;

console.log(">>>>>>>>>>>>>>>>>>>>> starting intervalometer client >>>>>>>>>>>>>>>>>>>");

function getCallbackId(callerName, cb) {
    if (!cb) return 0;
    cbIndex++;
    if (cbIndex > 300) cbIndex = 1;
    cbStore[cbIndex.toString()] = {callback: cb, name: callerName};
    return cbIndex;
}

function runCallback(cbData) {
    if (cbData && cbData.id && cbStore[cbData.id.toString()]) {
        var cb = cbStore[cbData.id.toString()];
        console.log("running callback for ", cb.name);
        cb && cb.callback && cb.callback(cbData.err, cbData.data);
        delete cbStore[cbData.id.toString()];
    }
}

function errorCallbacks(err, port) {
    console.log("running remaining callbacks:", JSON.stringify(cbStore));
    for (var i in cbStore) {
        runCallback({
            id: i,
            err: err,
            data: null
        });
    }
}

// buffered data
core.cameraSettings = {};
core.cameraConnected = false;
core.cameraModel = "";
core.cameraCount = 0;
core.cameraSupports = {};
core.intervalometerStatus = {};
core.histogram = [];
core.motionStatus = {};

var dataBuf = new Buffer(0);
var client;
function connect() {
    client = net.connect('/tmp/intervalometer.sock', function() {
      console.log('connected to server!');
      client.ready = true;
    });
    client.on('data', function(rawChunk) {
      var data;
      try {
        if(!rawChunk.length) return;
        dataBuf = Buffer.concat([dataBuf, rawChunk]);
        if(rawChunk[rawChunk.length - 1] != 0) {
            return;
        }
        var chunk = dataBuf.toString('utf8');
        dataBuf = new Buffer(0);
        var pieces = chunk.split('\0');
        for(var i = 0; i < pieces.length; i++) {
            pieces[i] = pieces[i].trim();
            if(!pieces[i]) continue;
            data = JSON.parse(pieces[i]);
            if(data.type == 'callback') {
                runCallback(data.data);
            } else if(data.type == 'watchdog') {
                var pid = parseInt(data.data);
                watchdog.watch(pid);
            } else {
                if(data.type == 'camera.photo') {
                    if(data.data.base64) {
                        data.data.jpeg = new Buffer(data.data.base64, 'base64');
                    }
                    core.photo = data.data;
                    data.data = null;
                } else if(data.type == 'camera.settings') {
                    core.cameraSettings = data.data;
                } else if(data.type == 'camera.histogram') {
                    core.histogram = data.data;
                } else if(data.type == 'camera.connected') {
                    core.cameraConnected = data.data.connected;
                    core.cameraModel = data.data.model;
                    core.cameraCount = data.data.count;
                    console.log("CORE: camera count: ", core.cameraCount);
                    core.cameraSupports = data.data.supports;
                    core.getSettings();
                } else if(data.type == 'camera.exiting') {
                    core.cameraConnected = data.data.connected;
                    core.cameraModel = data.data.model;
                    core.cameraCount = data.data.count;
                    console.log("CORE: camera count: ", core.cameraCount);
                    core.cameraSupports = data.data.supports;
                } else if(data.type == 'motion.status') {
                    core.motionStatus = data.data;
                } else if(data.type == 'media.present') {
                    core.sdMounted = data.data;
                    core.sdPresent = true;
                } else if(data.type == 'media.insert') {
                    core.sdPresent = true;
                } else if(data.type == 'media.remove') {
                    core.sdPresent = false;
                } else if(data.type == 'intervalometer.status') {
                    core.intervalometerStatus = data.data;
                    if(core.intervalometerStatus.running == false) {
                        power.performance('low');
                    }
                }
                console.log("CORE:", data.type, "event");
                core.emit(data.type, data.data);
            }
        }
      } catch(e) {
        console.log("Error parsing data:", data, e);
      }
    });
    client.on('end', function() {
      client.ready = false;
      console.log('disconnected from server');
      setTimeout(connect, 2000);
    });
    client.on('error', function(err) {
        if(err && err.code && (err.code == 'ECONNREFUSED' || err.code == 'ENOENT')) {
            console.log("Error: server not ready");
            setTimeout(connect, 1000);
        } else {
            console.log("error: ", err);
        }
        client.ready = false;
        client.end();
    });
}
setTimeout(connect, 5000);

function call(method, args, callback) {
    if(!client || !client.ready) {
        (function(m, a, c){
            setTimeout(function(){
                call(m, a, c);
            }, 1000);
        })(method, args, callback);
        return;
    }
    var cbId = getCallbackId(method, callback);
    var payload = JSON.stringify({
        type: method,
        args: args,
        id: cbId
    });
    if(client.ready) client.write(payload+'\0');
}

core.connectSonyWifi = function(callback) {
    call('camera.ptp.connectSonyWifi', {}, callback);
};
core.lvOff = function(callback) {
    call('camera.ptp.lvOff', {}, callback);
};
core.zoom = function(xPercent, yPercent, callback) {
    call('camera.ptp.zoom', {x:xPercent,y:yPercent}, function(err, data) {
        core.cameraZoomed = data.zoomed;
        callback(err, data);
    });
};
core.focus = function(step, repeat, callback) {
    call('camera.ptp.focus', {step:step, repeat:repeat}, callback);
};
core.setEv = function(ev, options, callback) {
    call('camera.setEv', {ev:ev, options:options}, callback);
};
core.preview = function(callback) {
    call('camera.ptp.preview', {}, callback);
};
core.getSettings = function(callback) {
    call('camera.ptp.getSettings', {}, callback);
};
core.cameraList = function(callback) {
    call('camera.ptp.cameraList', {}, callback);
};
core.switchPrimary = function(cameraObject, callback) {
    call('camera.ptp.switchPrimary', {cameraObject:cameraObject}, callback);
};
core.capture = function(options, callback) {
    call('camera.ptp.capture', {options:options}, callback);
};
core.captureTest = function(callback) {
    call('camera.ptp.capture-test', {}, callback);
};
core.runSupportTest = function(callback) {
    call('camera.ptp.runSupportTest', {}, callback);
};
core.set = function(key, val, callback) {
    call('camera.ptp.set', {key:key, val:val}, callback);
};
core.mountSd = function(callback) {
    call('camera.ptp.mountSd', {}, function(err, res){
        core.sdMounted = res;
        callback(err, res)
    });
};
core.unmountSd = function(callback) {
    call('camera.ptp.unmountSd', {}, callback);
};

core.moveMotion = function(driver, motor, steps, callback) {
    call('motion.move', {driver:driver, motor:motor, steps:steps}, callback);
};

core.zeroMotion = function(driver, motor, callback) {
    call('motion.zero', {driver:driver, motor:motor}, callback);
};

core.motionStatusUpdate = function(callback) {
    call('motion.status', {}, callback);
};

core.moveMotionJoystick = function(driver, motor, speed, callback) {
    call('motion.joystick', {driver:driver, motor:motor, speed:speed}, callback);
};

core.getCurrentTimelapseFrames = function(cameraIndex, callback) {
    call('db.currentTimelapseFrames', {cameraIndex:cameraIndex}, callback);
};

var wdtInterval = null;
core.watchdogEnable = function(callback) {
    if(!wdtInterval) {
        wdtInterval = setInterval(core.watchdogEnable, 5000); // this will have the server kill this process if it ever gets stuck
    }
    call('watchdog.set', {pid:process.pid}, callback);
};

core.watchdogDisable = function(callback) {
    if(wdtInterval) {
        clearInterval(wdtInterval);
        wdtInterval = null;
    }
    call('watchdog.disable', {pid:process.pid}, function(err){
        callback && callback(err);
    });
};

core.resetBt = function(callback) {
    call('bt.reset', {}, callback);
};

defaultProgram = {
    rampMode: "fixed",
    intervalMode: "fixed",
    rampAlgorithm: "lum",
    highlightProtection: true,
    interval: 5,
    dayInterval: 5,
    nightInterval: 35,
    frames: 300,
    destination: 'camera',
    nightCompensation: -1,
    isoMax: -6,
    isoMin:  0,
    rampParameters:  'S+I',
    apertureMax: -2,
    apertureMin:  -4,
    manualAperture: -5,
    hdrCount: 0,
    hdrStops: 1,
    exposurePlans: [],
    keyframes: [{
        focus: 0,
        ev: "not set",
        motor: {}
    }]
};

core.intervalometerStatus = {
    running: false,
    frames: 0,
    framesRemaining: 0,
    rampRate: 0,
    intervalMs: 0,
    message: "",
    rampEv: null,
    autoSettings: {
        paddingTimeMs: 5000
    }
}

core.moveTracking = function(axis, degrees, callback) {
    call('intervalometer.moveTracking', {axis:axis, degrees:degrees}, callback);
}

core.dynamicChange = function(parameter, newValue, frames, callback) {
    call('intervalometer.dynamicChange', {parameter:parameter, newValue:newValue, frames:frames}, callback);
}

core.loadProgram = function(program, callback) {
    if(!program.frames) program.frames = Infinity; // Infinity comes back as null from the DB
    if(program.keyframes) { // arrays come back as object in the VIEW db
        var kfs = [];
        for(var key in program.keyframes) {
            if(program.keyframes.hasOwnProperty(key)) {
                kfs.push(program.keyframes[key]);
            }
        }
        if(kfs.length > 0) program.keyframes = kfs;
    }
    if(program.exposurePlans) { // arrays come back as object in the VIEW db
        var plans = [];
        for(var key in program.exposurePlans) {
            if(program.exposurePlans.hasOwnProperty(key)) {
                plans.push(program.exposurePlans[key]);
            }
        }
        if(plans.length > 0) program.exposurePlans = plans;
    }

    core.currentProgram = _.extendOwn(defaultProgram, core.currentProgram, program);
    callback && callback();
}
core.loadProgram(defaultProgram);

core.stopIntervalometer = function(callback) {
    call('intervalometer.cancel', {}, callback);
}
core.startIntervalometer = function(program, callback) {
    power.performance('high');
    core.addGpsData(mcu.validCoordinates());
    call('intervalometer.run', {program:program}, callback);
}
core.addGpsData = function(gpsData, callback) {
    call('gps', {gpsData:gpsData}, callback);
}

core.getFilesList = function(callback) {
    call('camera.ptp.getFilesList', {}, callback);
}

core.downloadThumbnail = function(filePath, callback) {
    call('camera.ptp.downloadThumbnail', {filePath: filePath}, callback);
}

core.downloadFile = function(filePath, callback) {
    call('camera.ptp.downloadFile', {filePath: filePath}, callback);
}

mcu.on('gps', function(status){
    if(status == 2) {
        core.addGpsData(mcu.gps);
    }
});

module.exports = core;<|endoftext|>


<commit_before>'use strict';

const path = require('path').posix;
const Utils = require('./utils');

// node 0.11 urlencodes caret as %5E
const haveToFixCaretAtUrlParser = Utils.parseUrl('^').href !== '^';

class Canonicalizer {

  constructor(hashAlgo) {
    this._hashAlgo = hashAlgo;
  }

  _canonicalizeHeaders(headers) {
    return Object.keys(headers).map(function(key) {
      return key + ':' + headers[key];
    });
  }

  canonicalizeQuery(query) {
    function encodeComponent(component) {
      return encodeURIComponent(component).replace(/\(/g, "%28").replace(/\)/g, "%29");
    }

    function join(key, value) {
      if (haveToFixCaretAtUrlParser) {
        key = key.replace('%5E', '^');
        value = value.replace('%5E', '^');
      }

      return encodeComponent(key) + '=' + encodeComponent(value);
    }

    return Object.keys(query).map(function(key) {
      const value = query[key];
      if (typeof value === 'string') {
        return join(key, value);
      } else {
        return value.sort().map(function(oneValue) {
          return join(key, oneValue);
        }).join('&');
      }
    }).sort().join('&');
  }

  _prepareUri(uri) {
    // https://github.com/joyent/node/blob/4b59db008cec1bfcca2783f4b27c630c9c3fdd73/lib/url.js#L113-L117
    return uri.replace('#', '%23').replace('\\', '%5C');
  }

  _filterHeaders(headers, headersToSign) {
    const filteredHeaders = {};
    const normalizedSignedHeaders = headersToSign
      .map(header => header.toLowerCase());

    Object.keys(headers).forEach(function(headerName) {
      if (normalizedSignedHeaders.includes(headerName)) {
        filteredHeaders[headerName] = headers[headerName];
      }
    });

    return filteredHeaders;
  }

  canonicalizeRequest(requestOptions, body, headersToSign) {
    const parsedUrl = Utils.parseUrl(this._prepareUri(requestOptions.url), true);
    const headers = this._filterHeaders(Utils.normalizeHeaders(requestOptions.headers), headersToSign);
    const lines = [
      requestOptions.method,
      path.normalize(parsedUrl.pathname),
      this.canonicalizeQuery(parsedUrl.query),
      this._canonicalizeHeaders(headers).join('\n'),
      '',
      Object.keys(headers).join(';'),
      Utils.hash(this._hashAlgo, body)
    ];
    return lines.join('\n');
  }

  getCanonicalizedSignedHeaders(headers, headersToSign) {
    const normalizedHeaders = this._filterHeaders(Utils.normalizeHeaders(headers), headersToSign);
    return Object.keys(normalizedHeaders);
  }

}

module.exports = Canonicalizer;
<commit_msg>refactor(carets encoding): remove haveToFixCaretAtUrlParser, encodeComponent code style<commit_after>'use strict';

const path = require('path').posix;
const Utils = require('./utils');

class Canonicalizer {

  constructor(hashAlgo) {
    this._hashAlgo = hashAlgo;
  }

  _canonicalizeHeaders(headers) {
    return Object.keys(headers).map(function(key) {
      return key + ':' + headers[key];
    });
  }

  canonicalizeQuery(query) {
    function encodeComponent(component) {
      return encodeURIComponent(component)
        .replace(/\(/g, '%28')
        .replace(/\)/g, '%29');
    }

    function join(key, value) {
      return encodeComponent(key) + '=' + encodeComponent(value);
    }

    return Object.keys(query).map(function(key) {
      const value = query[key];
      if (typeof value === 'string') {
        return join(key, value);
      } else {
        return value.sort().map(function(oneValue) {
          return join(key, oneValue);
        }).join('&');
      }
    }).sort().join('&');
  }

  _prepareUri(uri) {
    // https://github.com/joyent/node/blob/4b59db008cec1bfcca2783f4b27c630c9c3fdd73/lib/url.js#L113-L117
    return uri.replace('#', '%23').replace('\\', '%5C');
  }

  _filterHeaders(headers, headersToSign) {
    const filteredHeaders = {};
    const normalizedSignedHeaders = headersToSign
      .map(header => header.toLowerCase());

    Object.keys(headers).forEach(function(headerName) {
      if (normalizedSignedHeaders.includes(headerName)) {
        filteredHeaders[headerName] = headers[headerName];
      }
    });

    return filteredHeaders;
  }

  canonicalizeRequest(requestOptions, body, headersToSign) {
    const parsedUrl = Utils.parseUrl(this._prepareUri(requestOptions.url), true);
    const headers = this._filterHeaders(Utils.normalizeHeaders(requestOptions.headers), headersToSign);
    const lines = [
      requestOptions.method,
      path.normalize(parsedUrl.pathname),
      this.canonicalizeQuery(parsedUrl.query),
      this._canonicalizeHeaders(headers).join('\n'),
      '',
      Object.keys(headers).join(';'),
      Utils.hash(this._hashAlgo, body)
    ];
    return lines.join('\n');
  }

  getCanonicalizedSignedHeaders(headers, headersToSign) {
    const normalizedHeaders = this._filterHeaders(Utils.normalizeHeaders(headers), headersToSign);
    return Object.keys(normalizedHeaders);
  }

}

module.exports = Canonicalizer;
<|endoftext|>


<commit_before>window.config = {
	GA_code     : "",
	API_HOST    : "http://develop.codedoodl.es",
	SITE_URL    : "http://codedoodl.es",
	assets_url  : "http://assets.codedoodl.es",
	doodles_url : "http://source.codedoodl.es",
	hostname    : document.location.protocol + "//" + document.location.host,
	localeCode  : "en-gb",
	routes      : {"HOME":"","ABOUT":"about","CONTRIBUTE":"contribute","DOODLES":"~","LOGIN":"login"}
};
<commit_msg>Switch API host to production<commit_after>window.config = {
	GA_code     : "",
	API_HOST    : "http://codedoodl.es",
	SITE_URL    : "http://codedoodl.es",
	assets_url  : "http://assets.codedoodl.es",
	doodles_url : "http://source.codedoodl.es",
	hostname    : document.location.protocol + "//" + document.location.host,
	localeCode  : "en-gb",
	routes      : {"HOME":"","ABOUT":"about","CONTRIBUTE":"contribute","DOODLES":"~","LOGIN":"login"}
};
<|endoftext|>


<commit_before>import {Box, Flex, Image, PseudoBox, useDisclosure} from '@chakra-ui/core'
import {
  faChartArea,
  faCompass,
  faCubes,
  faDatabase,
  faExclamationCircle,
  faGlobe,
  faLayerGroup,
  faMap,
  faPencilAlt,
  faQuestionCircle,
  faServer,
  faSignOutAlt,
  faTh,
  faWifi
} from '@fortawesome/free-solid-svg-icons'
import get from 'lodash/get'
import {useRouter} from 'next/router'
import React from 'react'
import {useSelector} from 'react-redux'

import {CB_DARK, CB_HEX, LOGO_URL} from 'lib/constants'
import useRouteChanging from 'lib/hooks/use-route-changing'
import {routeTo} from 'lib/router'

import {CREATING_ID} from '../constants/region'
import message from '../message'
import selectOutstandingRequests from '../selectors/outstanding-requests'

import Icon from './icon'

const sidebarWidth = '40px'

function NavTip(p) {
  return (
    <Box pos='relative'>
      <Box pos='absolute' left={sidebarWidth} top='-20px'>
        <Box as='span' className='tooltip' px='5px' ml='3px'>
          <Box
            as='span'
            className='tooltip-arrow'
            borderRightColor='#333'
            borderWidth='5px 5px 5px 0'
            left='0'
            mt='-5px'
            top='50%'
          />
          {p.label}
        </Box>
      </Box>
    </Box>
  )
}

function NavItemContents({children, label, ...p}) {
  const {isOpen, onOpen, onClose} = useDisclosure()

  return (
    <PseudoBox
      borderBottom='2px solid rgba(0, 0, 0, 0)'
      cursor='pointer'
      color={CB_HEX}
      fontSize='14px'
      lineHeight='20px'
      onMouseOver={onOpen}
      onMouseOut={onClose}
      py={3}
      textAlign='center'
      width={sidebarWidth}
      _focus={{
        outline: 'none'
      }}
      _hover={{
        color: CB_DARK
      }}
      {...p}
    >
      {children}
      {isOpen && <NavTip label={label} />}
    </PseudoBox>
  )
}

export default function Sidebar() {
  const router = useRouter()
  const [routeChanging, pathname] = useRouteChanging()
  const username = useSelector(s => get(s, 'user.email'))
  const {projectId, regionId} = router.query

  /**
   * Render an ItemLink. Created internally to capture the pathname so it
   * doesn't need to be passed to each individually.
   */
  const ItemLink = React.useMemo(() => {
    function isActive({to, ...p}) {
      let {href, as} = routeTo(to, p)
      href = href.split('?')[0]
      as = as.split('?')[0]
      const pathOnly = pathname.split('?')[0]
      // Server === 'href', client === 'as'
      return pathOnly === href || pathOnly === as
    }

    return React.memo(function ItemLink(p) {
      function goToLink() {
        const {to, ...rest} = p.link
        const {href, as, query} = routeTo(to, rest)
        router.push({pathname: href, query}, as)
      }

      const navItemProps = isActive(p.link)
        ? {
            bg: '#fff',
            borderBottom: `2px solid ${CB_HEX}`,
            boxShadow: '0 6px 6px 0 rgba(51, 122, 183, 0.1'
          }
        : {onClick: goToLink}

      return (
        <NavItemContents {...navItemProps} label={p.label}>
          <Icon icon={p.icon} />
        </NavItemContents>
      )
    })
  }, [pathname, router])

  return (
    <Flex
      bg='#ddd'
      direction='column'
      height='100vh'
      justify='space-between'
      width='40px'
    >

        <Box fontSize='20px' py={10} textAlign='center' width='40px'>
          <LogoSpinner routeChanging={routeChanging} />
        </Box>

        <ItemLink
          icon={faGlobe}
          label={message('nav.regions')}
          link={{to: 'regions'}}
        />
        {regionId && regionId !== CREATING_ID && (
          <>
            <ItemLink
              icon={faMap}
              label={message('nav.regionSettings')}
              link={{
                to: 'regionSettings',
                regionId
              }}
            />
            <ItemLink
              icon={faCubes}
              label={message('nav.projects')}
              link={{
                to: 'projects',
                regionId
              }}
            />
            <ItemLink
              icon={faDatabase}
              label={message('nav.gtfsBundles')}
              link={{
                to: 'bundles',
                regionId
              }}
            />
            <ItemLink
              icon={faTh}
              label={message('nav.opportunityDatasets')}
              link={{
                to: 'opportunities',
                regionId
              }}
            />
            <Box className='DEV'>
              <ItemLink
                icon={faLayerGroup}
                label={message('nav.resources')}
                link={{
                  to: 'resources',
                  regionId
                }}
              />
            </Box>
            <ItemLink
              icon={faPencilAlt}
              label={message('nav.editModifications')}
              link={{
                to: 'modifications',
                regionId,
                projectId: projectId ? projectId : 'undefined'
              }}
            />
            <ItemLink
              icon={faChartArea}
              label={message('nav.analyze')}
              link={{
                to: 'analysis',
                projectId,
                regionId
              }}
            />
            <ItemLink
              icon={faServer}
              label='Regional Analyses'
              link={{
                to: 'regionalAnalyses',
                regionId
              }}
            />
          </>
        )}

        {username && (
          <ItemLink
            icon={faSignOutAlt}
            label={
              message('authentication.logOut') +
              ' - ' +
              message('authentication.username', {username: username})
            }
            link={{to: 'logout'}}
          />
        )}
        <ExternalLink
          icon={faQuestionCircle}
          label={message('nav.help')}
          href='http://docs.analysis.conveyal.com/'
        />

        <ErrorTip />
        <OnlineIndicator />

    </Flex>
  )
}

const isServer = typeof window === 'undefined'
const fn = () => {}
const addListener = isServer ? fn : window.addEventListener
const removeListener = isServer ? fn : window.removeEventListener

function LogoSpinner(p) {
  const outstandingRequests = useSelector(selectOutstandingRequests)

  // Handle outstanding requests
  React.useEffect(() => {
    if (outstandingRequests) {
      const onBeforeUnload = e => {
        const returnValue = (e.returnValue = message('nav.unfinishedRequests'))
        return returnValue
      }

      addListener('beforeunload', onBeforeUnload)

      return () => removeListener('beforeunload', onBeforeUnload)
    }
  }, [outstandingRequests])

  if (outstandingRequests || p.routeChanging) {
    return <Icon color={CB_HEX} icon={faCompass} spin />
  }

  return <Image display='inline-block' size='20px' src={LOGO_URL} />
}

const isOnline = () => (isServer ? true : navigator.onLine)
function OnlineIndicator() {
  const [online, setOnline] = React.useState(isOnline())

  React.useEffect(() => {
    const onOnline = () => setOnline(true)
    const onOffline = () => setOnline(false)
    // TODO: Check to see if it can communicate with the backend, not just the
    // internet (for local development)
    addListener('online', onOnline)
    addListener('offline', onOffline)
    return () => {
      removeListener('online', onOnline)
      removeListener('offline', onOffline)
    }
  }, [setOnline])

  if (online) return null
  return (
    <NavItemContents label={message('nav.notConnectedToInternet')}>
      <Icon icon={faWifi} />
    </NavItemContents>
  )
}

function ErrorTip() {
  const [error, setError] = React.useState()
  // Handle error events
  React.useEffect(() => {
    const onError = e => setError(e.message)
    const onUnhandledRejection = e => setError(e.reason.stack)

    addListener('error', onError)
    addListener('unhandledrejection', onUnhandledRejection)

    return () => {
      removeListener('error', onError)
      removeListener('unhandledrejection', onUnhandledRejection)
    }
  }, [setError])

  if (!error) return null
  return (
    <NavItemContents color='red' label={message('error.script') + error}>
      <Icon icon={faExclamationCircle} />
    </NavItemContents>
  )
}

function ExternalLink(p) {
  return (

      <NavItemContents label={p.label}>
        <Icon icon={p.icon} />
      </NavItemContents>

  )
}
<commit_msg>Ensure errors are red in sidebar<commit_after>import {Box, Flex, Image, PseudoBox, useDisclosure} from '@chakra-ui/core'
import {
  faChartArea,
  faCompass,
  faCubes,
  faDatabase,
  faExclamationCircle,
  faGlobe,
  faLayerGroup,
  faMap,
  faPencilAlt,
  faQuestionCircle,
  faServer,
  faSignOutAlt,
  faTh,
  faWifi
} from '@fortawesome/free-solid-svg-icons'
import get from 'lodash/get'
import {useRouter} from 'next/router'
import React from 'react'
import {useSelector} from 'react-redux'

import {CB_DARK, CB_HEX, LOGO_URL} from 'lib/constants'
import useRouteChanging from 'lib/hooks/use-route-changing'
import {routeTo} from 'lib/router'

import {CREATING_ID} from '../constants/region'
import message from '../message'
import selectOutstandingRequests from '../selectors/outstanding-requests'

import Icon from './icon'

const sidebarWidth = '40px'

function NavTip(p) {
  return (
    <Box pos='relative'>
      <Box pos='absolute' left={sidebarWidth} top='-20px'>
        <Box as='span' className='tooltip' px='5px' ml='3px'>
          <Box
            as='span'
            className='tooltip-arrow'
            borderRightColor='#333'
            borderWidth='5px 5px 5px 0'
            left='0'
            mt='-5px'
            top='50%'
          />
          {p.label}
        </Box>
      </Box>
    </Box>
  )
}

function NavItemContents({children, label, ...p}) {
  const {isOpen, onOpen, onClose} = useDisclosure()

  return (
    <PseudoBox
      borderBottom='2px solid rgba(0, 0, 0, 0)'
      cursor='pointer'
      color={CB_HEX}
      fontSize='14px'
      lineHeight='20px'
      onMouseOver={onOpen}
      onMouseOut={onClose}
      py={3}
      textAlign='center'
      width={sidebarWidth}
      _focus={{
        outline: 'none'
      }}
      _hover={{
        color: CB_DARK
      }}
      {...p}
    >
      {children}
      {isOpen && <NavTip label={label} />}
    </PseudoBox>
  )
}

export default function Sidebar() {
  const router = useRouter()
  const [routeChanging, pathname] = useRouteChanging()
  const username = useSelector(s => get(s, 'user.email'))
  const {projectId, regionId} = router.query

  /**
   * Render an ItemLink. Created internally to capture the pathname so it
   * doesn't need to be passed to each individually.
   */
  const ItemLink = React.useMemo(() => {
    function isActive({to, ...p}) {
      let {href, as} = routeTo(to, p)
      href = href.split('?')[0]
      as = as.split('?')[0]
      const pathOnly = pathname.split('?')[0]
      // Server === 'href', client === 'as'
      return pathOnly === href || pathOnly === as
    }

    return React.memo(function ItemLink(p) {
      function goToLink() {
        const {to, ...rest} = p.link
        const {href, as, query} = routeTo(to, rest)
        router.push({pathname: href, query}, as)
      }

      const navItemProps = isActive(p.link)
        ? {
            bg: '#fff',
            borderBottom: `2px solid ${CB_HEX}`,
            boxShadow: '0 6px 6px 0 rgba(51, 122, 183, 0.1'
          }
        : {onClick: goToLink}

      return (
        <NavItemContents {...navItemProps} label={p.label}>
          <Icon icon={p.icon} />
        </NavItemContents>
      )
    })
  }, [pathname, router])

  return (
    <Flex
      bg='#ddd'
      direction='column'
      height='100vh'
      justify='space-between'
      width='40px'
    >

        <Box fontSize='20px' py={10} textAlign='center' width='40px'>
          <LogoSpinner routeChanging={routeChanging} />
        </Box>

        <ItemLink
          icon={faGlobe}
          label={message('nav.regions')}
          link={{to: 'regions'}}
        />
        {regionId && regionId !== CREATING_ID && (
          <>
            <ItemLink
              icon={faMap}
              label={message('nav.regionSettings')}
              link={{
                to: 'regionSettings',
                regionId
              }}
            />
            <ItemLink
              icon={faCubes}
              label={message('nav.projects')}
              link={{
                to: 'projects',
                regionId
              }}
            />
            <ItemLink
              icon={faDatabase}
              label={message('nav.gtfsBundles')}
              link={{
                to: 'bundles',
                regionId
              }}
            />
            <ItemLink
              icon={faTh}
              label={message('nav.opportunityDatasets')}
              link={{
                to: 'opportunities',
                regionId
              }}
            />
            <Box className='DEV'>
              <ItemLink
                icon={faLayerGroup}
                label={message('nav.resources')}
                link={{
                  to: 'resources',
                  regionId
                }}
              />
            </Box>
            <ItemLink
              icon={faPencilAlt}
              label={message('nav.editModifications')}
              link={{
                to: 'modifications',
                regionId,
                projectId: projectId ? projectId : 'undefined'
              }}
            />
            <ItemLink
              icon={faChartArea}
              label={message('nav.analyze')}
              link={{
                to: 'analysis',
                projectId,
                regionId
              }}
            />
            <ItemLink
              icon={faServer}
              label='Regional Analyses'
              link={{
                to: 'regionalAnalyses',
                regionId
              }}
            />
          </>
        )}

        {username && (
          <ItemLink
            icon={faSignOutAlt}
            label={
              message('authentication.logOut') +
              ' - ' +
              message('authentication.username', {username: username})
            }
            link={{to: 'logout'}}
          />
        )}
        <ExternalLink
          icon={faQuestionCircle}
          label={message('nav.help')}
          href='http://docs.analysis.conveyal.com/'
        />

        <ErrorTip />
        <OnlineIndicator />

    </Flex>
  )
}

const isServer = typeof window === 'undefined'
const fn = () => {}
const addListener = isServer ? fn : window.addEventListener
const removeListener = isServer ? fn : window.removeEventListener

function LogoSpinner(p) {
  const outstandingRequests = useSelector(selectOutstandingRequests)

  // Handle outstanding requests
  React.useEffect(() => {
    if (outstandingRequests) {
      const onBeforeUnload = e => {
        const returnValue = (e.returnValue = message('nav.unfinishedRequests'))
        return returnValue
      }

      addListener('beforeunload', onBeforeUnload)

      return () => removeListener('beforeunload', onBeforeUnload)
    }
  }, [outstandingRequests])

  if (outstandingRequests || p.routeChanging) {
    return <Icon color={CB_HEX} icon={faCompass} spin />
  }

  return <Image display='inline-block' size='20px' src={LOGO_URL} />
}

const isOnline = () => (isServer ? true : navigator.onLine)
function OnlineIndicator() {
  const [online, setOnline] = React.useState(isOnline())

  React.useEffect(() => {
    const onOnline = () => setOnline(true)
    const onOffline = () => setOnline(false)
    // TODO: Check to see if it can communicate with the backend, not just the
    // internet (for local development)
    addListener('online', onOnline)
    addListener('offline', onOffline)
    return () => {
      removeListener('online', onOnline)
      removeListener('offline', onOffline)
    }
  }, [setOnline])

  if (online) return null
  return (
    <NavItemContents
      color='red.500'
      label={message('nav.notConnectedToInternet')}
    >
      <Icon icon={faWifi} />
    </NavItemContents>
  )
}

function ErrorTip() {
  const [error, setError] = React.useState()
  // Handle error events
  React.useEffect(() => {
    const onError = e => setError(e.message)
    const onUnhandledRejection = e => setError(e.reason.stack)

    addListener('error', onError)
    addListener('unhandledrejection', onUnhandledRejection)

    return () => {
      removeListener('error', onError)
      removeListener('unhandledrejection', onUnhandledRejection)
    }
  }, [setError])

  if (!error) return null
  return (
    <NavItemContents color='red.500' label={message('error.script') + error}>
      <Icon icon={faExclamationCircle} />
    </NavItemContents>
  )
}

function ExternalLink(p) {
  return (

      <NavItemContents label={p.label}>
        <Icon icon={p.icon} />
      </NavItemContents>

  )
}
<|endoftext|>


<commit_before>/**
 * @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
 *
 * @author Joas Schilling <coding@schilljs.com>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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/>.
 *
 */
import Vue from 'vue'
import {
	promoteToModerator,
	demoteFromModerator,
	removeUserFromConversation,
	removeGuestFromConversation,
} from '../services/participantsService'
import {
	joinCall,
	leaveCall,
} from '../services/callsService'
import { PARTICIPANT } from '../constants'

const state = {
	participants: {
	},
}

const getters = {
	/**
	 * Gets the participants array
	 * @param {object} state the state object.
	 * @returns {array} the participants array (if there are participants in the store)
	 */
	participantsList: (state) => (token) => {
		if (state.participants[token]) {
			return state.participants[token]
		}
		return []
	},
	getParticipant: (state) => (token, index) => {
		if (state.participants[token] && state.participants[token][index]) {
			return state.participants[token][index]
		}
		return {}
	},
	getParticipantIndex: (state) => (token, participantIdentifier) => {
		if (!state.participants[token]) {
			return -1
		}

		let index

		if (participantIdentifier.hasOwnProperty('participant')) {
			index = state.participants[token].findIndex(participant => participant.userId === participantIdentifier.participant)
		} else {
			index = state.participants[token].findIndex(participant => participant.sessionId === participantIdentifier.sessionId)
		}

		return index
	},
}

const mutations = {
	/**
	 * Adds a message to the store.
	 * @param {object} state current store state;
	 * @param {object} token the token of the conversation;
	 * @param {object} participant the participant;
	 */
	addParticipant(state, { token, participant }) {
		if (!state.participants[token]) {
			Vue.set(state.participants, token, [])
		}
		state.participants[token].push(participant)
	},
	updateParticipant(state, { token, index, updatedData }) {
		if (state.participants[token] && state.participants[token][index]) {
			state.participants[token][index] = Object.assign(state.participants[token][index], updatedData)
		} else {
			console.error('Error while updating the participant')
		}
	},
	deleteParticipant(state, { token, index }) {
		if (state.participants[token] && state.participants[token][index]) {
			Vue.delete(state.participants[token], index)
		} else {
			console.error(`The conversation you are trying to purge doesn't exist`)
		}
	},
	/**
	 * Purges a given conversation from the previously added participants
	 * @param {object} state current store state;
	 * @param {string} token the conversation to purge;
	 */
	purgeParticipantsStore(state, token) {
		if (state.participants[token]) {
			Vue.delete(state.participants, token)
		}
	},
}

const actions = {

	/**
	 * Adds participant to the store.
	 *
	 * Only call this after purgeParticipantsStore, otherwise use addParticipantOnce
	 *
	 * @param {object} context default store context;
	 * @param {string} token the conversation to add the participant;
	 * @param {object} participant the participant;
	 */
	addParticipant({ commit }, { token, participant }) {
		commit('addParticipant', { token, participant })
	},
	/**
	 * Only add a participant when they are not there yet
	 *
	 * @param {object} context default store context;
	 * @param {string} token the conversation to add the participant;
	 * @param {object} participant the participant;
	 */
	addParticipantOnce({ commit }, { token, participant }) {
		const index = getters.getParticipantIndex(token, participant)
		if (index === -1) {
			commit('addParticipant', { token, participant })
		}
	},
	async promoteToModerator({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		await promoteToModerator(token, participantIdentifier)

		const participant = getters.getParticipant(token, index)
		const updatedData = {
			participantType: participant.participantType === PARTICIPANT.TYPE.GUEST ? PARTICIPANT.TYPE.GUEST_MODERATOR : PARTICIPANT.TYPE.MODERATOR,
		}
		commit('updateParticipant', { token, index, updatedData })
	},
	async demoteFromModerator({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		await demoteFromModerator(token, participantIdentifier)

		const participant = getters.getParticipant(token, index)
		const updatedData = {
			participantType: participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR ? PARTICIPANT.TYPE.GUEST : PARTICIPANT.TYPE.USER,
		}
		commit('updateParticipant', { token, index, updatedData })
	},
	async removeParticipant({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		const participant = getters.getParticipant(token, index)
		if (participant.userId) {
			await removeUserFromConversation(token, participant.userId)
		} else {
			await removeGuestFromConversation(token, participant.sessionId)
		}
		commit('deleteParticipant', { token, index })
	},
	/**
	 * Purges a given conversation from the previously added participants
	 * @param {object} context default store context;
	 * @param {string} token the conversation to purge;
	 */
	purgeParticipantsStore({ commit }, token) {
		commit('purgeParticipantsStore', token)
	},

	async joinCall({ commit, getters }, { token, participantIdentifier, flags }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			console.error('Participant not found')
			console.error(participantIdentifier)
			console.error(flags)
			return
		}

		await joinCall(token, flags)

		const updatedData = {
			inCall: flags,
		}
		commit('updateParticipant', { token, index, updatedData })
	},

	async leaveCall({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		await leaveCall(token)

		const updatedData = {
			inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED,
		}
		commit('updateParticipant', { token, index, updatedData })
	},
}

export default { state, mutations, getters, actions }
<commit_msg>Reduce the number of logs<commit_after>/**
 * @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
 *
 * @author Joas Schilling <coding@schilljs.com>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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/>.
 *
 */
import Vue from 'vue'
import {
	promoteToModerator,
	demoteFromModerator,
	removeUserFromConversation,
	removeGuestFromConversation,
} from '../services/participantsService'
import {
	joinCall,
	leaveCall,
} from '../services/callsService'
import { PARTICIPANT } from '../constants'

const state = {
	participants: {
	},
}

const getters = {
	/**
	 * Gets the participants array
	 * @param {object} state the state object.
	 * @returns {array} the participants array (if there are participants in the store)
	 */
	participantsList: (state) => (token) => {
		if (state.participants[token]) {
			return state.participants[token]
		}
		return []
	},
	getParticipant: (state) => (token, index) => {
		if (state.participants[token] && state.participants[token][index]) {
			return state.participants[token][index]
		}
		return {}
	},
	getParticipantIndex: (state) => (token, participantIdentifier) => {
		if (!state.participants[token]) {
			return -1
		}

		let index

		if (participantIdentifier.hasOwnProperty('participant')) {
			index = state.participants[token].findIndex(participant => participant.userId === participantIdentifier.participant)
		} else {
			index = state.participants[token].findIndex(participant => participant.sessionId === participantIdentifier.sessionId)
		}

		return index
	},
}

const mutations = {
	/**
	 * Adds a message to the store.
	 * @param {object} state current store state;
	 * @param {object} token the token of the conversation;
	 * @param {object} participant the participant;
	 */
	addParticipant(state, { token, participant }) {
		if (!state.participants[token]) {
			Vue.set(state.participants, token, [])
		}
		state.participants[token].push(participant)
	},
	updateParticipant(state, { token, index, updatedData }) {
		if (state.participants[token] && state.participants[token][index]) {
			state.participants[token][index] = Object.assign(state.participants[token][index], updatedData)
		} else {
			console.error('Error while updating the participant')
		}
	},
	deleteParticipant(state, { token, index }) {
		if (state.participants[token] && state.participants[token][index]) {
			Vue.delete(state.participants[token], index)
		} else {
			console.error(`The conversation you are trying to purge doesn't exist`)
		}
	},
	/**
	 * Purges a given conversation from the previously added participants
	 * @param {object} state current store state;
	 * @param {string} token the conversation to purge;
	 */
	purgeParticipantsStore(state, token) {
		if (state.participants[token]) {
			Vue.delete(state.participants, token)
		}
	},
}

const actions = {

	/**
	 * Adds participant to the store.
	 *
	 * Only call this after purgeParticipantsStore, otherwise use addParticipantOnce
	 *
	 * @param {object} context default store context;
	 * @param {string} token the conversation to add the participant;
	 * @param {object} participant the participant;
	 */
	addParticipant({ commit }, { token, participant }) {
		commit('addParticipant', { token, participant })
	},
	/**
	 * Only add a participant when they are not there yet
	 *
	 * @param {object} context default store context;
	 * @param {string} token the conversation to add the participant;
	 * @param {object} participant the participant;
	 */
	addParticipantOnce({ commit }, { token, participant }) {
		const index = getters.getParticipantIndex(token, participant)
		if (index === -1) {
			commit('addParticipant', { token, participant })
		}
	},
	async promoteToModerator({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		await promoteToModerator(token, participantIdentifier)

		const participant = getters.getParticipant(token, index)
		const updatedData = {
			participantType: participant.participantType === PARTICIPANT.TYPE.GUEST ? PARTICIPANT.TYPE.GUEST_MODERATOR : PARTICIPANT.TYPE.MODERATOR,
		}
		commit('updateParticipant', { token, index, updatedData })
	},
	async demoteFromModerator({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		await demoteFromModerator(token, participantIdentifier)

		const participant = getters.getParticipant(token, index)
		const updatedData = {
			participantType: participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR ? PARTICIPANT.TYPE.GUEST : PARTICIPANT.TYPE.USER,
		}
		commit('updateParticipant', { token, index, updatedData })
	},
	async removeParticipant({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		const participant = getters.getParticipant(token, index)
		if (participant.userId) {
			await removeUserFromConversation(token, participant.userId)
		} else {
			await removeGuestFromConversation(token, participant.sessionId)
		}
		commit('deleteParticipant', { token, index })
	},
	/**
	 * Purges a given conversation from the previously added participants
	 * @param {object} context default store context;
	 * @param {string} token the conversation to purge;
	 */
	purgeParticipantsStore({ commit }, token) {
		commit('purgeParticipantsStore', token)
	},

	async joinCall({ commit, getters }, { token, participantIdentifier, flags }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			console.error('Participant not found', participantIdentifier)
			return
		}

		await joinCall(token, flags)

		const updatedData = {
			inCall: flags,
		}
		commit('updateParticipant', { token, index, updatedData })
	},

	async leaveCall({ commit, getters }, { token, participantIdentifier }) {
		const index = getters.getParticipantIndex(token, participantIdentifier)
		if (index === -1) {
			return
		}

		await leaveCall(token)

		const updatedData = {
			inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED,
		}
		commit('updateParticipant', { token, index, updatedData })
	},
}

export default { state, mutations, getters, actions }
<|endoftext|>


<commit_before>var Nohm = null;

exports.setNohm = function (originalNohm) {
  Nohm = originalNohm;
  initialize();
};

var EventEmitter = require('events').EventEmitter;
var h = require(__dirname + '/helpers');

/**
 * Seperate redis client for pubSub.
 */
var pub_sub_client = false;

var pub_sub_all_pattern = false;

var pub_sub_event_emitter = false;

var do_publish = false;

/**
 *  Publish something on the nohm client.
 */
var publish = function (channel, payload, parse) {
  var message;
  var client = Nohm.client;

  if (parse || typeof payload !== 'string') {
    message = JSON.stringify(payload);
  }

  if (!client) {
    Nohm.logError("No redis client specified. Please provide one (Nohm.setClient()).");
  } else if (!client.publish) {
    Nohm.logError("Specified client does not support pubsub.");
  } else {
    client.publish( Nohm.prefix.channel + channel, message );
  }
};

var initializePubSub = function initializePubSub (callback) {

  if (!pub_sub_client) {
    return Nohm.logError('A second redis client must be specified to use pub/sub methods. Please declare one.');
  } else if (pub_sub_client.subscriptionsa === true) {
    /**
     *  This will break in the next node_redis version and we'll have to update to
     *    pub_sub_client.pub_sub_mode
     *
     *  Should cause all pubsub tests to fail, so will be relatively easy to spot.
     */
    // already in pubsub mode, don't need to initialize it again.
    if (typeof(callback) === 'function') {
      callback();
    }
    return;
  }

  pub_sub_all_pattern = Nohm.prefix.channel + '*:*';
  pub_sub_event_emitter = new EventEmitter();
  pub_sub_event_emitter.setMaxListeners(0);

  pub_sub_client.psubscribe(pub_sub_all_pattern, callback);

  pub_sub_client.on('pmessage', function(pattern, channel, message){
    var modelName;
    var action;
    var payload;
    var suffix = channel.slice(Nohm.prefix.channel.length);
    var parts = suffix.match(/([^:]+)/g); // Pattern = _prefix_:channel:_modelname_:_action_

    if (!parts) {
      Nohm.logError("An erroneous channel has been captured.");
      return;
    }

    modelName = parts[0];
    action = parts[1];

    payload = {};

    try {
      payload = message ? JSON.parse(message) : {};
    } catch (e) {
      Nohm.logError('A published message is not valid JSON. Was : "'+message+'"');
      return;
    }

    pub_sub_event_emitter.emit(modelName+':'+action, payload);
    pub_sub_event_emitter.emit(modelName+':all', action, payload);

  });
};

var initialize = function () {

  /**
   * Set the pubSub client and initialize the subscriptions and event emitters.
   *
   * @param {Object} client Redis client to use. This client will be set to pubSub and cannot be used for normal commands after that.
   * @param {Function} callback Called after the provided redis client is subscribed to the necessary channels.
   */
  Nohm.setPubSubClient = function (client, callback) {
    pub_sub_client = client;
    initializePubSub(callback);
  };

  /**
   * Return the PubSub Client, if set.
   */
  Nohm.getPubSubClient = function () {
    return pub_sub_client;
  };

  /**
   * Unsubscribes from the nohm redis pubsub channel.
   *
   * @param {Function} callback Called after the unsubscibe. Parameters: redisClient
   */
  Nohm.closePubSub = function closePubSub (callback) {
    if (pub_sub_client.subscriptions === true) {
      pub_sub_client.punsubscribe(pub_sub_all_pattern, function () {
        callback(null, pub_sub_client);
      });
    } else {
      callback(null, pub_sub_client);
    }
  };

  var messageComposers = {

    // The default (base) message creator
    defaultComposer: function defaultComposer (action) {
      return {
        target: {
          id: this.id,
          modelName: this.modelName,
          properties: this.allProperties()
        }
      };
    }

  };

  // This populates the diff property for `save` and `update` events.
  messageComposers.save = messageComposers.update = function changeComposer (action, diff) {
    var result = messageComposers.defaultComposer.apply(this, arguments);
    result.target.diff = diff;
    return result;
  };

  // This sets the id and properties
  messageComposers.remove = function removeComposer (action, id) {
    var result = messageComposers.defaultComposer.apply(this, arguments);
    result.target.id = id;
    return result;
  };

  messageComposers.link = messageComposers.unlink = function relationComposer (action, parent, relationName) {
    var result = {};
    result.child = messageComposers.defaultComposer.call(this, action).target;
    result.parent = messageComposers.defaultComposer.call(parent, action).target;
    result.relation = relationName;
    return result;
  };

  var supportedActions = [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ];

  /**
   * Fires an event to be published to the redis db by the internal publisher.
   *
   * @param {String} event Name of the event to be published. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   */
  Nohm.prototype.fireEvent = function (event) {
    var channel;
    var composer;
    var payload;
    var supported;

    if ( ! this.getPublish() ) {
      // global or model specific setting for publishing events is false.
      return false;
    }

    if (supportedActions.indexOf(event) < 0) {
      supported = supportedActions.join(', ');
      Nohm.logError(
        'Cannot fire an unsupported action. Was "' + event + '" ' +
        'and must be one of ' + supported
        );
      return false;
    }

    channel = this.modelName + ':' + event;
    composer = messageComposers[event] || messageComposers.defaultComposer;

    payload = composer.apply(this, arguments);

    publish(channel, payload);
  };

  /**
   * Set global boolean to publish events or not.
   * By default publishing is disabled globally.
   * The model-specific setting overwrites the global setting.
   *
   * @param {Boolean} publish Whether nohm should publish its events.
   */
  Nohm.setPublish = function (publish) {
    do_publish = !!publish;
  }

  /**
   * Get the model-specific status of whether event should be published or not.
   * If no model-specific setting is found, the global setting is returned.
   *
   * @returns {Boolean} True if this model will publish its events, False if not.
   */
  Nohm.prototype.getPublish = function () {
    if (this.hasOwnProperty('publish')) {
      return !!this.publish;
    }
    return do_publish;
  };

  /**
   * Subscribe to events of nohm models.
   *
   * @param {String} event_name Name of the event to be listened to. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   * @param {Function} callback Called every time an event of the provided name is published on this model.
   */
  Nohm.prototype.subscribe = function (event_name, callback) {
    initializePubSub();
    pub_sub_event_emitter.on(this.modelName+':'+event_name, callback);
  };

  /**
   * Subscribe to an event of nohm models only once.
   *
   * @param {String} event_name Name of the event to be listened to. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   * @param {Function} callback Called once when an event of the provided name is published on this model and then never again.
   */
  Nohm.prototype.subscribeOnce = function (event_name, callback) {
    initializePubSub();
    pub_sub_event_emitter.once(this.modelName+':'+event_name, callback);
  };

  /**
   * Unsubscribe from a nohm model event.
   *
   * @param {String} event_name Name of the event to be unsubscribed from. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   * @param {Function} fn Function to unsubscribe. If none is provided all subscriptions of the given event are unsubscribed!
   */
  Nohm.prototype.unsubscribe = function (event_name, fn) {
    if (pub_sub_event_emitter !== false) {
      if (! fn) {
        pub_sub_event_emitter.removeAllListeners(self.modelName+':'+event_name);
      } else {
        pub_sub_event_emitter.removeListener(self.modelName+':'+event_name, fn);
      }
    }
  };

};

<commit_msg>Fix test error. /omfg<commit_after>var Nohm = null;

exports.setNohm = function (originalNohm) {
  Nohm = originalNohm;
  initialize();
};

var EventEmitter = require('events').EventEmitter;
var h = require(__dirname + '/helpers');

/**
 * Seperate redis client for pubSub.
 */
var pub_sub_client = false;

var pub_sub_all_pattern = false;

var pub_sub_event_emitter = false;

var do_publish = false;

/**
 *  Publish something on the nohm client.
 */
var publish = function (channel, payload, parse) {
  var message;
  var client = Nohm.client;

  if (parse || typeof payload !== 'string') {
    message = JSON.stringify(payload);
  }

  if (!client) {
    Nohm.logError("No redis client specified. Please provide one (Nohm.setClient()).");
  } else if (!client.publish) {
    Nohm.logError("Specified client does not support pubsub.");
  } else {
    client.publish( Nohm.prefix.channel + channel, message );
  }
};

var initializePubSub = function initializePubSub (callback) {

  if (!pub_sub_client) {
    return Nohm.logError('A second redis client must be specified to use pub/sub methods. Please declare one.');
  } else if (pub_sub_client.subscriptions === true) {
    /**
     *  This will break in the next node_redis version and we'll have to update to
     *    pub_sub_client.pub_sub_mode
     *
     *  Should cause all pubsub tests to fail, so will be relatively easy to spot.
     */
    // already in pubsub mode, don't need to initialize it again.
    if (typeof(callback) === 'function') {
      callback();
    }
    return;
  }

  pub_sub_all_pattern = Nohm.prefix.channel + '*:*';
  pub_sub_event_emitter = new EventEmitter();
  pub_sub_event_emitter.setMaxListeners(0);

  pub_sub_client.psubscribe(pub_sub_all_pattern, callback);

  pub_sub_client.on('pmessage', function(pattern, channel, message){
    var modelName;
    var action;
    var payload;
    var suffix = channel.slice(Nohm.prefix.channel.length);
    var parts = suffix.match(/([^:]+)/g); // Pattern = _prefix_:channel:_modelname_:_action_

    if (!parts) {
      Nohm.logError("An erroneous channel has been captured.");
      return;
    }

    modelName = parts[0];
    action = parts[1];

    payload = {};

    try {
      payload = message ? JSON.parse(message) : {};
    } catch (e) {
      Nohm.logError('A published message is not valid JSON. Was : "'+message+'"');
      return;
    }

    pub_sub_event_emitter.emit(modelName+':'+action, payload);
    pub_sub_event_emitter.emit(modelName+':all', action, payload);

  });
};

var initialize = function () {

  /**
   * Set the pubSub client and initialize the subscriptions and event emitters.
   *
   * @param {Object} client Redis client to use. This client will be set to pubSub and cannot be used for normal commands after that.
   * @param {Function} callback Called after the provided redis client is subscribed to the necessary channels.
   */
  Nohm.setPubSubClient = function (client, callback) {
    pub_sub_client = client;
    initializePubSub(callback);
  };

  /**
   * Return the PubSub Client, if set.
   */
  Nohm.getPubSubClient = function () {
    return pub_sub_client;
  };

  /**
   * Unsubscribes from the nohm redis pubsub channel.
   *
   * @param {Function} callback Called after the unsubscibe. Parameters: redisClient
   */
  Nohm.closePubSub = function closePubSub (callback) {
    if (pub_sub_client.subscriptions === true) {
      pub_sub_client.punsubscribe(pub_sub_all_pattern, function () {
        callback(null, pub_sub_client);
      });
    } else {
      callback(null, pub_sub_client);
    }
  };

  var messageComposers = {

    // The default (base) message creator
    defaultComposer: function defaultComposer (action) {
      return {
        target: {
          id: this.id,
          modelName: this.modelName,
          properties: this.allProperties()
        }
      };
    }

  };

  // This populates the diff property for `save` and `update` events.
  messageComposers.save = messageComposers.update = function changeComposer (action, diff) {
    var result = messageComposers.defaultComposer.apply(this, arguments);
    result.target.diff = diff;
    return result;
  };

  // This sets the id and properties
  messageComposers.remove = function removeComposer (action, id) {
    var result = messageComposers.defaultComposer.apply(this, arguments);
    result.target.id = id;
    return result;
  };

  messageComposers.link = messageComposers.unlink = function relationComposer (action, parent, relationName) {
    var result = {};
    result.child = messageComposers.defaultComposer.call(this, action).target;
    result.parent = messageComposers.defaultComposer.call(parent, action).target;
    result.relation = relationName;
    return result;
  };

  var supportedActions = [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ];

  /**
   * Fires an event to be published to the redis db by the internal publisher.
   *
   * @param {String} event Name of the event to be published. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   */
  Nohm.prototype.fireEvent = function (event) {
    var channel;
    var composer;
    var payload;
    var supported;

    if ( ! this.getPublish() ) {
      // global or model specific setting for publishing events is false.
      return false;
    }

    if (supportedActions.indexOf(event) < 0) {
      supported = supportedActions.join(', ');
      Nohm.logError(
        'Cannot fire an unsupported action. Was "' + event + '" ' +
        'and must be one of ' + supported
        );
      return false;
    }

    channel = this.modelName + ':' + event;
    composer = messageComposers[event] || messageComposers.defaultComposer;

    payload = composer.apply(this, arguments);

    publish(channel, payload);
  };

  /**
   * Set global boolean to publish events or not.
   * By default publishing is disabled globally.
   * The model-specific setting overwrites the global setting.
   *
   * @param {Boolean} publish Whether nohm should publish its events.
   */
  Nohm.setPublish = function (publish) {
    do_publish = !!publish;
  }

  /**
   * Get the model-specific status of whether event should be published or not.
   * If no model-specific setting is found, the global setting is returned.
   *
   * @returns {Boolean} True if this model will publish its events, False if not.
   */
  Nohm.prototype.getPublish = function () {
    if (this.hasOwnProperty('publish')) {
      return !!this.publish;
    }
    return do_publish;
  };

  /**
   * Subscribe to events of nohm models.
   *
   * @param {String} event_name Name of the event to be listened to. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   * @param {Function} callback Called every time an event of the provided name is published on this model.
   */
  Nohm.prototype.subscribe = function (event_name, callback) {
    initializePubSub();
    pub_sub_event_emitter.on(this.modelName+':'+event_name, callback);
  };

  /**
   * Subscribe to an event of nohm models only once.
   *
   * @param {String} event_name Name of the event to be listened to. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   * @param {Function} callback Called once when an event of the provided name is published on this model and then never again.
   */
  Nohm.prototype.subscribeOnce = function (event_name, callback) {
    initializePubSub();
    pub_sub_event_emitter.once(this.modelName+':'+event_name, callback);
  };

  /**
   * Unsubscribe from a nohm model event.
   *
   * @param {String} event_name Name of the event to be unsubscribed from. Allowed are: [ 'create', 'update', 'save', 'remove', 'unlink', 'link' ]
   * @param {Function} fn Function to unsubscribe. If none is provided all subscriptions of the given event are unsubscribed!
   */
  Nohm.prototype.unsubscribe = function (event_name, fn) {
    if (pub_sub_event_emitter !== false) {
      if (! fn) {
        pub_sub_event_emitter.removeAllListeners(self.modelName+':'+event_name);
      } else {
        pub_sub_event_emitter.removeListener(self.modelName+':'+event_name, fn);
      }
    }
  };

};

<|endoftext|>


<commit_before>'use strict';

var moment = require('moment');
var _ = require('lodash');

function configure (app, wares, ctx, env) {
    var entries = ctx.entries;
    var express = require('express')
        , api = express.Router( )
        ;

    // invoke common middleware
    api.use(wares.sendJSONStatus);
    // text body types get handled as raw buffer stream
    api.use(wares.bodyParser.raw());
    // json body types get handled as parsed json
    api.use(wares.bodyParser.json());

    ctx.plugins.eachEnabledPlugin(function each(plugin){
        if (plugin.alexa) {
            if (plugin.alexa.intentHandlers) {
                console.log(plugin.name + ' is Alexa enabled');
                _.each(plugin.alexa.intentHandlers, function (route) {
                    if (route) {
                        ctx.alexa.configureIntentHandler(route.intent, route.intentHandler, route.routableSlot, route.slots);
                    }
                });
            }
            if (plugin.alexa.rollupHandlers) {
                console.log(plugin.name + ' is Alexa rollup enabled');
                _.each(plugin.alexa.rollupHandlers, function (route) {
                    console.log('Route');
                    console.log(route);
                    if (route) {
                        ctx.alexa.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName);
                    }
                });
            }
        } else {
            console.log('Plugin ' + plugin.name + ' is not Alexa enabled');
        }
    });

    api.post('/alexa', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) {
        console.log('Incoming request from Alexa');
        switch (req.body.request.type) {
            case 'IntentRequest':
                onIntent(req.body.request.intent, function (title, response) {
                    res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true'));
                    next( );
                });
                break;
            case 'LaunchRequest':
                onLaunch(req.body.request.intent, function (title, response) {
                    res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true'));
                    next( );
                });
                break;
            case 'SessionEndedRequest':
                onSessionEnded(req.body.request.intent, function (alexaResponse) {
                    res.json(alexaResponse);
                    next( );
                });
                break;
        }
    });

    ctx.alexa.addToRollup('Status', function bgRollupHandler(slots, sbx, callback) {

        entries.list({count: 1}, function(err, records) {
            var direction = '';

            console.log('addToRollup: direction' + records[0].direction);

            if (records[0].direction === 'FortyFiveDown') {
                direction = ' and slightly dropping';
            } else if (records[0].direction === 'FortyFiveUp') {
                direction = ' and slightly rising';
            } else if (records[0].direction === 'Flat') {
                direction = ' and holding';
            } else if (records[0].direction === 'SingleUp') {
                direction = ' and rising';
            } else if (records[0].direction === 'SingleDown') {
                direction = ' and dropping';
            } else if (records[0].direction === 'DoubleDown') {
                direction = ' and rapidly dropping';
            } else if (records[0].direction === 'DoubleUp') {
                direction = ' and rapidly rising';
            } else {
                direction = records[0].direction;
            }
            var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time)) + '.';
            console.log('addToRollup: status: ' + status);
            callback(null, {results: status, priority: -1});
        });

        //console.log('BG results called');
        //callback(null, 'BG results');
    }, 'BG Status');

    ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx) {
        entries.list({count: 1}, function(err, records) {
            var direction = '';
            if (records[0].direction === 'FortyFiveDown') {
                direction = ' and slightly dropping';
            } else if (records[0].direction === 'FortyFiveUp') {
                direction = ' and slightly rising';
            } else if (records[0].direction === 'Flat') {
                direction = ' and holding';
            } else if (records[0].direction === 'SingleUp') {
                direction = ' and rising';
            } else if (records[0].direction === 'SingleDown') {
                direction = ' and dropping';
            } else if (records[0].direction === 'DoubleDown') {
                direction = ' and rapidly dropping';
            } else if (records[0].direction === 'DoubleUp') {
                direction = ' and rapidly rising';
            }
            var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time));
            console.log('configureIntentHandler: status: ' + status);
            callback('Current blood glucose', status);
        });
    }, 'metric', ['bg', 'blood glucose', 'number']);

    ctx.alexa.configureIntentHandler('NSStatus', function(callback, slots, sbx) {
        ctx.alexa.getRollup('Status', sbx, slots, function (status) {
            callback('Full status', status);
        });
    });

    function onLaunch() {
        console.log('Session launched');
    }

    function onIntent(intent, next) {
        console.log('Received intent request');
        console.log(JSON.stringify(intent));
        handleIntent(intent.name, intent.slots, next);
    }

    function onSessionEnded() {
        console.log('Session ended');
    }

    function handleIntent(intentName, slots, next) {
        var handler = ctx.alexa.getIntentHandler(intentName, slots);
        if (handler){
            var sbx = initializeSandbox();
            handler(next, slots, sbx);
        } else {
            next('Unknown Intent', 'I\'m sorry I don\'t know what you\'re asking for');
        }
    }

    function initializeSandbox() {
        var sbx = require('../../sandbox')();
        sbx.serverInit(env, ctx);
        ctx.plugins.setProperties(sbx);
        return sbx;
    }

    return api;
}

module.exports = configure;
<commit_msg>Identify why status BG being recorded as Nan<commit_after>'use strict';

var moment = require('moment');
var _ = require('lodash');

function configure (app, wares, ctx, env) {
    var entries = ctx.entries;
    var express = require('express')
        , api = express.Router( )
        ;

    // invoke common middleware
    api.use(wares.sendJSONStatus);
    // text body types get handled as raw buffer stream
    api.use(wares.bodyParser.raw());
    // json body types get handled as parsed json
    api.use(wares.bodyParser.json());

    ctx.plugins.eachEnabledPlugin(function each(plugin){
        if (plugin.alexa) {
            if (plugin.alexa.intentHandlers) {
                console.log(plugin.name + ' is Alexa enabled');
                _.each(plugin.alexa.intentHandlers, function (route) {
                    if (route) {
                        ctx.alexa.configureIntentHandler(route.intent, route.intentHandler, route.routableSlot, route.slots);
                    }
                });
            }
            if (plugin.alexa.rollupHandlers) {
                console.log(plugin.name + ' is Alexa rollup enabled');
                _.each(plugin.alexa.rollupHandlers, function (route) {
                    console.log('Route');
                    console.log(route);
                    if (route) {
                        ctx.alexa.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName);
                    }
                });
            }
        } else {
            console.log('Plugin ' + plugin.name + ' is not Alexa enabled');
        }
    });

    api.post('/alexa', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) {
        console.log('Incoming request from Alexa');
        switch (req.body.request.type) {
            case 'IntentRequest':
                onIntent(req.body.request.intent, function (title, response) {
                    res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true'));
                    next( );
                });
                break;
            case 'LaunchRequest':
                onLaunch(req.body.request.intent, function (title, response) {
                    res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true'));
                    next( );
                });
                break;
            case 'SessionEndedRequest':
                onSessionEnded(req.body.request.intent, function (alexaResponse) {
                    res.json(alexaResponse);
                    next( );
                });
                break;
        }
    });

    ctx.alexa.addToRollup('Status', function bgRollupHandler(slots, sbx, callback) {

        entries.list({count: 1}, function(err, records) {
            var direction = '';
            if (records[0].direction === 'FortyFiveDown') {
                direction = ' and slightly dropping';
            } else if (records[0].direction === 'FortyFiveUp') {
                direction = ' and slightly rising';
            } else if (records[0].direction === 'Flat') {
                direction = ' and holding';
            } else if (records[0].direction === 'SingleUp') {
                direction = ' and rising';
            } else if (records[0].direction === 'SingleDown') {
                direction = ' and dropping';
            } else if (records[0].direction === 'DoubleDown') {
                direction = ' and rapidly dropping';
            } else if (records[0].direction === 'DoubleUp') {
                direction = ' and rapidly rising';
            } else {
                direction = records[0].direction;
            }

            console.log('addToRollup: svg: ' + records[0].svg);
            console.log('addToRollup: direction: ' + direction);
            console.log('addToRollup: date: ' + records[0].date);
            console.log('addToRollup: time: ' + sbx.time);

            var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time)) + '.';
            console.log('addToRollup: status: ' + status);
            callback(null, {results: status, priority: -1});
        });

        //console.log('BG results called');
        //callback(null, 'BG results');
    }, 'BG Status');

    ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx) {
        entries.list({count: 1}, function(err, records) {
            var direction = '';
            if (records[0].direction === 'FortyFiveDown') {
                direction = ' and slightly dropping';
            } else if (records[0].direction === 'FortyFiveUp') {
                direction = ' and slightly rising';
            } else if (records[0].direction === 'Flat') {
                direction = ' and holding';
            } else if (records[0].direction === 'SingleUp') {
                direction = ' and rising';
            } else if (records[0].direction === 'SingleDown') {
                direction = ' and dropping';
            } else if (records[0].direction === 'DoubleDown') {
                direction = ' and rapidly dropping';
            } else if (records[0].direction === 'DoubleUp') {
                direction = ' and rapidly rising';
            }
            var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time));
            console.log('configureIntentHandler: status: ' + status);
            callback('Current blood glucose', status);
        });
    }, 'metric', ['bg', 'blood glucose', 'number']);

    ctx.alexa.configureIntentHandler('NSStatus', function(callback, slots, sbx) {
        ctx.alexa.getRollup('Status', sbx, slots, function (status) {
            callback('Full status', status);
        });
    });

    function onLaunch() {
        console.log('Session launched');
    }

    function onIntent(intent, next) {
        console.log('Received intent request');
        console.log(JSON.stringify(intent));
        handleIntent(intent.name, intent.slots, next);
    }

    function onSessionEnded() {
        console.log('Session ended');
    }

    function handleIntent(intentName, slots, next) {
        var handler = ctx.alexa.getIntentHandler(intentName, slots);
        if (handler){
            var sbx = initializeSandbox();
            handler(next, slots, sbx);
        } else {
            next('Unknown Intent', 'I\'m sorry I don\'t know what you\'re asking for');
        }
    }

    function initializeSandbox() {
        var sbx = require('../../sandbox')();
        sbx.serverInit(env, ctx);
        ctx.plugins.setProperties(sbx);
        return sbx;
    }

    return api;
}

module.exports = configure;
<|endoftext|>