<commit_before>var express     = require('express'),
    app         = express(),
    server      = require('http').createServer(app),
    winston     = require('winston'),
    each        = require('tiny-each-async'),
    request     = require('request'),
    OpenStates  = require('openstates'),
    plivo       = require('plivo'),
    config      = require('./config.js'),
    port        = config.port;

const plivoPhoneNumber = config.plivoPhoneNumber;

//
// Init APIs
//

var p = plivo.RestAPI({
  authId: config.plivoAuthId,
  authToken: config.plivoAuthToken
});

var openstates = new OpenStates(config.sunlightAPI);

//
// Set Up Logging
//

var logger = new (winston.Logger)({
  transports: [
    new (winston.transports.Console)({ json: false, timestamp: true }),
    // new winston.transports.File({ filename: __dirname + '/log/debug.log', json: false })
  ],
  exceptionHandlers: [
    new (winston.transports.Console)({ json: false, timestamp: true }),
    // new winston.transports.File({ filename: __dirname + '/log/exceptions.log', json: false })
  ],
  exitOnError: false
});

//
// Express configure
//

var bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
server.listen(port, function () {
  logger.info('Server listening at port %d', port);
});

app.post('/sms', receiveSMS);

//
// Receive Text Message
//

function receiveSMS(req, res) {
  var incoming = {
    'text': req.body.Text,
    'id': req.body.MessageUUID,
    'from': req.body.From,
    'debug': req.body.Debug,
  };
  res.sendStatus(200);

  logger.info('incomingSMS:',incoming);

  var testWords = ['','hi','hello','text','test','info','rep info','congressional representatives','state representatives'];
  var niceWords = ['nice','cool','neat','this is awesome'];
  var thanksWords = ['thanks','thank you','awesome thanks','thx'];

  if ( niceWords.indexOf( incoming.text.toLowerCase().replace(/[.!]/g,"") ) > -1 ) {
    sendResponse( incoming.from, "Thanks!", null, incoming.debug );

  } else if ( thanksWords.indexOf( incoming.text.toLowerCase().replace(/[.!]/g,"") ) > -1 ) {
    sendResponse( incoming.from, "You're welcome!", null, incoming.debug );

  } else if ( (testWords.indexOf( incoming.text.toLowerCase().replace(/[.!]/g,"") ) > -1 ) || ( incoming.text.length < 4 ) ) {
    sendResponse( incoming.from, 'Hi! Text me a US postal address and I will send back phone numbers for your state and federal legislators.', null, incoming.debug );

  } else if ( incoming.text.match(/\d+/g) == null ) { // no numbers at all.
    sendResponse( incoming.from, 'I’m sorry, I only speak postal. Try texting me a US postal address and I will send back phone numbers for your state and federal legislators.', null, incoming.debug );

  } else {
    geocode( incoming.from, incoming.text, incoming.debug );
  }

} // end receiveSMS

//
// Geocode Postal Address
//

function geocode( recipient, address, debug ) {

  logger.info('geocoder got address:',address);

  // first try geocode with Google:

  var url = 'https://maps.googleapis.com/maps/api/geocode/json?address='+address+'&key='+config.googleAPI;

  request(url, function (err, response, body) {
    if (err) {
      logger.info('Google maps error:', body);
      sendResponse( recipient, "I'm sorry, I don't understand that address. Would you try writing it a different way?", null, debug );
    }
    data = JSON.parse(body);

    //logger.info('geocoder response:',data);

    if (data.results.length) {

        var address_components = JSON.stringify(data.results[0].address_components);

        if ( address_components.indexOf("United States") > -1)  {
          var geo = data.results[0].geometry.location;
          logger.info('geo:',geo);
          getReps( recipient, geo, address, debug );
        } else {
          logger.info('Not US. Google address components:',address_components);
          sendResponse( recipient, "I'm sorry, I can't find that address within the U.S.", null, debug);
        }

    } else {

      // if that didn't work, try geocode with MapQuest:

      logger.info('Google couldn\'t find it. Trying MapQuest.');

      url = 'http://open.mapquestapi.com/geocoding/v1/address?key='+config.mapquestAPI+'&location='+address;
      request(url, function (err, response, body) {
        if (err) {
          logger.info('MapQuest error:', body);
          sendResponse( recipient, "I'm sorry, I don't understand that address. Would you try writing it a different way?", null, debug );
        }
        data = JSON.parse(body);

        if (typeof data.results !== 'undefined') {

          var address_components = JSON.stringify(data.results[0].locations[0]);

          if (address_components !== 'undefined') {
            if ( address_components.indexOf("US") > -1)  {
              var geo = data.results[0].locations[0].latLng;
              logger.info('geo:',geo);
              getReps( recipient, geo, address, debug );
            } else {
              logger.info('Not US. MapQuest address components:',address_components);
              sendResponse( recipient, "I'm sorry, I can't find that address within the U.S.", null, debug );
            } // end address_components.indexOf("US") > -1
          } else {
              logger.info('Not address_components:',address_components);
              sendResponse( recipient, "I'm sorry, I don't understand that address. Would you try writing it a different way?", null, debug );
          } // end address_components !== 'undefined'

        } else {

          logger.info('MapQuest got no results.');
          sendResponse( recipient, "I'm sorry, I can't find that address.", null, debug );

        } // end typeof data.results !== 'undefined'

      }); // end mapquest request

    } // end no results from Google

  }); // end Google request

}

//
// Get Representatives
//

function getReps( recipient, geo, address, debug ) {

    var url = 'https://www.googleapis.com/civicinfo/v2/representatives?address='+geo.lat+'%2C'+geo.lng+'&levels=country&levels=administrativeArea1&roles=legislatorLowerBody&roles=legislatorUpperBody&fields=offices%2Cofficials(name%2Cparty%2Cphones)&key='+config.googleAPI;

    request(url, function (err, response, body) {

        body = JSON.parse(body);

        if ((err) || (typeof body.error !== 'undefined')) {
          logger.info('getReps can\'t find reps for that address.');

          if ( /^\d+$/.test(address) ) {
            sendResponse( recipient, "I'm sorry, sometimes zip code alone does not always work. Try again with a postal address?", null, debug );
          } else {
            sendResponse( recipient, "I'm sorry, I can't find representatives for that address.", null, debug );
          }

        } else {

          logger.info('Got reps.');
          //debug(body);

          var reps = [];
          var n = -1;
          var hasState = 0;

          each(body.officials, function ( thisRep, thisRepCallback ){
              var title, order = '';
              n++;

              if (thisRep.name == 'Vacant') {

                thisRepCallback();

              } else {

                each(body.offices, function ( thisOffice, thisOfficeCallback ){

                  if (typeof thisOffice.officialIndices !== 'undefined') {

                    if (thisOffice.officialIndices.indexOf(n) > -1 ) {
                      if ((thisOffice.levels[0] == 'administrativeArea1') && (thisOffice.roles[0] == 'legislatorUpperBody')) {
                        title = 'State Sen.';
                        order = 2;
                        hasState = 1;
                      }
                      if ((thisOffice.levels[0] == 'administrativeArea1') && (thisOffice.roles[0] == 'legislatorLowerBody')) {
                        title = 'State Rep.';
                        order = 3;
                        hasState = 1;
                      }
                      if ((thisOffice.levels[0] == 'country') && (thisOffice.roles[0] == 'legislatorUpperBody')) {
                        title = 'Senator';
                        order = 0;
                      }
                      if ((thisOffice.levels[0] == 'country') && (thisOffice.roles[0] == 'legislatorLowerBody')) {
                        title = 'Representative';
                        order = 1;
                      }
                      thisOfficeCallback();
                    } else {
                      thisOfficeCallback();
                    }

                  } else {
                    thisOfficeCallback();
                  }

              }, function(err) {
                if (err) { logger.info("async thisOffice:",err); }

                if (typeof thisRep.phones !== 'undefined') {
                  // change phone format
                  var phone = thisRep.phones[0];
                  phone = phone.replace(/[()]/g, '').replace(/ /,'-');

                  var rep = {
                    "name": title + ' ' + thisRep.name,
                    "phone": phone,
                    "order": order };
                  reps.push(rep);
                  thisRepCallback();
                } else {
                  thisRepCallback();
                }
              });

            } // if is seat vacant

          }, function(err) {
            if (err) { logger.info("async thisRep:",err); }

            if (hasState) {
              sendResponse( recipient, '', reps, debug);
            } else {
              getState( recipient, address, geo, reps, debug );
            }
          });

      } // end if error
    }); // end civicAPI request
}

//
// Get State Representatives
//

function getState(recipient, address, geo, reps, debug) {

    logger.info('Google didn\'t have state, so try OpenStates.');

    openstates.geoLookup(geo.lat, geo.lng, function(err, stateReps) {
      if (err) {
          logger.info('getState had an error. Sending without.');
          sendResponse( recipient, '', reps, debug );
      }

      // debug('openstates response',stateReps);

      logger.info('OpenStates has reps.');

      if (typeof stateReps !== 'undefined') {
        each(stateReps, function ( item, eachStateRepCallback ){

          var title, order = '';
          if (item.chamber == 'upper') {
            title = 'State Sen.';
            order = 3;
          } else {
            title = 'State Rep.';
            order = 4;
          }

          // DC has city council, not state legislators
          if ( item.state == 'dc' ) {
            title = 'Councilmember';
          }

          if (typeof item.offices !== 'undefined') {

            // need a better way to iterate to find phone
            var phone = item.offices[0].phone;
            if ((phone == null) && (typeof item.offices[1] !== 'undefined')) {
              phone = item.offices[1].phone;
            }
            if ((phone == null) && (typeof item.offices[2] !== 'undefined')) {
              phone = item.offices[2].phone;
            }

            reps.push({
              "name": title + ' ' + item.first_name + ' ' + item.last_name,
              "phone": phone,
              "order": order
            });

            eachStateRepCallback();
          } else {
            eachStateRepCallback();
          }

        }, function done() {
          //debug('stateReps',reps);
          sendResponse( recipient, '', reps, debug );
        });

      } else {
          // log address and error.
          logger.info('OpenStates can\'t find reps for that address. Send without.');
          sendResponse( recipient, '', reps, debug );
      }
    });

}

//
// Send Text Message Response
//

function sendResponse( recipient, message, reps, debug ) {

  if (reps) {
    reps = reps.sort(function(a, b) { return (a.order > b.order) ? 1 : ((a.order < b.order) ? -1 : 0); });

    if (reps.length == 1) {
      message = 'Call your representative:\n';
    } else {
      message = 'Call your representatives:\n';
    }

    reps.forEach( function ( item, pos ){
        message += item.name + ': ' + item.phone + "\n";
    });
  }

  logger.info("textResponse:\n",message);

  var params = {
      'src': plivoPhoneNumber,
      'dst' : recipient,
      'text' : message
  };

  if (debug) {
      logger.info('Debug flag. No message sent.');
  } else {

    // Send the SMS to Plivo
    p.send_message(params, function (status, response) {
        logger.info('Status: ', status);
        logger.info('API Response:\n', response);
    });

  } // end if debug

}
<commit_msg>Fix some undefined errors.<commit_after>var express     = require('express'),
    app         = express(),
    server      = require('http').createServer(app),
    winston     = require('winston'),
    each        = require('tiny-each-async'),
    request     = require('request'),
    OpenStates  = require('openstates'),
    plivo       = require('plivo'),
    config      = require('./config.js'),
    port        = config.port;

const plivoPhoneNumber = config.plivoPhoneNumber;

//
// Init APIs
//

var p = plivo.RestAPI({
  authId: config.plivoAuthId,
  authToken: config.plivoAuthToken
});

var openstates = new OpenStates(config.sunlightAPI);

//
// Set Up Logging
//

var logger = new (winston.Logger)({
  transports: [
    new (winston.transports.Console)({ json: false, timestamp: true }),
    // new winston.transports.File({ filename: __dirname + '/log/debug.log', json: false })
  ],
  exceptionHandlers: [
    new (winston.transports.Console)({ json: false, timestamp: true }),
    // new winston.transports.File({ filename: __dirname + '/log/exceptions.log', json: false })
  ],
  exitOnError: false
});

//
// Express configure
//

var bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
server.listen(port, function () {
  logger.info('Server listening at port %d', port);
});

app.post('/sms', receiveSMS);

//
// Receive Text Message
//

function receiveSMS(req, res) {
  var incoming = {
    'text': req.body.Text,
    'id': req.body.MessageUUID,
    'from': req.body.From,
    'debug': req.body.Debug,
  };
  res.sendStatus(200);

  logger.info('incomingSMS:',incoming);

  var testWords = ['','hi','hello','text','test','info','rep info','congressional representatives','state representatives'];
  var niceWords = ['nice','cool','neat','this is awesome'];
  var thanksWords = ['thanks','thank you','awesome thanks','thx'];

  if ( niceWords.indexOf( incoming.text.toLowerCase().replace(/[.!]/g,"") ) > -1 ) {
    sendResponse( incoming.from, "Thanks!", null, incoming.debug );

  } else if ( thanksWords.indexOf( incoming.text.toLowerCase().replace(/[.!]/g,"") ) > -1 ) {
    sendResponse( incoming.from, "You're welcome!", null, incoming.debug );

  } else if ( (testWords.indexOf( incoming.text.toLowerCase().replace(/[.!]/g,"") ) > -1 ) || ( incoming.text.length < 4 ) ) {
    sendResponse( incoming.from, 'Hi! Text me a US postal address and I will send back phone numbers for your state and federal legislators.', null, incoming.debug );

  } else if ( incoming.text.match(/\d+/g) == null ) { // no numbers at all.
    sendResponse( incoming.from, 'I’m sorry, I only speak postal. Try texting me a US postal address and I will send back phone numbers for your state and federal legislators.', null, incoming.debug );

  } else {
    geocode( incoming.from, incoming.text, incoming.debug );
  }

} // end receiveSMS

//
// Geocode Postal Address
//

function geocode( recipient, address, debug ) {

  logger.info('geocoder got address:',address);

  // first try geocode with Google:

  var url = 'https://maps.googleapis.com/maps/api/geocode/json?address='+address+'&key='+config.googleAPI;

  request(url, function (err, response, body) {
    if (err) {
      logger.info('Google maps error:', body);
      sendResponse( recipient, "I'm sorry, I don't understand that address. Would you try writing it a different way?", null, debug );
    }
    data = JSON.parse(body);

    //logger.info('geocoder response:',data);

    if (data.results.length) {

        var address_components = JSON.stringify(data.results[0].address_components);

        if ( address_components.indexOf("United States") > -1)  {
          var geo = data.results[0].geometry.location;
          logger.info('geo:',geo);
          getReps( recipient, geo, address, debug );
        } else {
          logger.info('Not US. Google address components:',address_components);
          sendResponse( recipient, "I'm sorry, I can't find that address within the U.S.", null, debug);
        }

    } else {

      // if that didn't work, try geocode with MapQuest:

      logger.info('Google couldn\'t find it. Trying MapQuest.');

      url = 'http://open.mapquestapi.com/geocoding/v1/address?key='+config.mapquestAPI+'&location='+address;
      request(url, function (err, response, body) {
        if (err) {
          logger.info('MapQuest error:', body);
          sendResponse( recipient, "I'm sorry, I don't understand that address. Would you try writing it a different way?", null, debug );
        }
        data = JSON.parse(body);

        if (typeof data.results !== 'undefined') {

          var address_components = JSON.stringify(data.results[0].locations[0]);

          if (address_components !== 'undefined') {
            if ( address_components.indexOf("US") > -1)  {
              var geo = data.results[0].locations[0].latLng;
              logger.info('geo:',geo);
              getReps( recipient, geo, address, debug );
            } else {
              logger.info('Not US. MapQuest address components:',address_components);
              sendResponse( recipient, "I'm sorry, I can't find that address within the U.S.", null, debug );
            } // end address_components.indexOf("US") > -1
          } else {
              logger.info('Not address_components:',address_components);
              sendResponse( recipient, "I'm sorry, I don't understand that address. Would you try writing it a different way?", null, debug );
          } // end address_components !== 'undefined'

        } else {

          logger.info('MapQuest got no results.');
          sendResponse( recipient, "I'm sorry, I can't find that address.", null, debug );

        } // end typeof data.results !== 'undefined'

      }); // end mapquest request

    } // end no results from Google

  }); // end Google request

}

//
// Get Representatives
//

function getReps( recipient, geo, address, debug ) {

    var url = 'https://www.googleapis.com/civicinfo/v2/representatives?address='+geo.lat+'%2C'+geo.lng+'&levels=country&levels=administrativeArea1&roles=legislatorLowerBody&roles=legislatorUpperBody&fields=offices%2Cofficials(name%2Cparty%2Cphones)&key='+config.googleAPI;

    request(url, function (err, response, body) {

        body = JSON.parse(body);

        if ((err) || (typeof body.error !== 'undefined') || typeof body.officials == 'undefined') {
          logger.info('getReps can\'t find reps for that address.');

          if ( /^\d+$/.test(address) ) {

            // try http://whoismyrepresentative.com/getall_mems.php?zip=47803

            sendResponse( recipient, "I'm sorry, I can't find representatives for that ZIP code. Sometimes ZIP code alone does not always work. Try again with a postal address?", null, debug );
          } else {
            sendResponse( recipient, "I'm sorry, I can't find representatives for that address.", null, debug );
          }

        } else {

          logger.info('Got reps.');
          //debug(body);

          var reps = [];
          var n = -1;
          var hasState = 0;

          each(body.officials, function ( thisRep, thisRepCallback ){
              var title, order = '';
              n++;

              if (thisRep.name == 'Vacant') {

                thisRepCallback();

              } else {

                each(body.offices, function ( thisOffice, thisOfficeCallback ){

                  if (typeof thisOffice.officialIndices !== 'undefined') {

                    if (thisOffice.officialIndices.indexOf(n) > -1 ) {
                      if ((thisOffice.levels[0] == 'administrativeArea1') && (thisOffice.roles[0] == 'legislatorUpperBody')) {
                        title = 'State Sen.';
                        order = 2;
                        hasState = 1;
                      }
                      if ((thisOffice.levels[0] == 'administrativeArea1') && (thisOffice.roles[0] == 'legislatorLowerBody')) {
                        title = 'State Rep.';
                        order = 3;
                        hasState = 1;
                      }
                      if ((thisOffice.levels[0] == 'country') && (thisOffice.roles[0] == 'legislatorUpperBody')) {
                        title = 'Senator';
                        order = 0;
                      }
                      if ((thisOffice.levels[0] == 'country') && (thisOffice.roles[0] == 'legislatorLowerBody')) {
                        title = 'Representative';
                        order = 1;
                      }
                      thisOfficeCallback();
                    } else {
                      thisOfficeCallback();
                    }

                  } else {
                    thisOfficeCallback();
                  }

              }, function(err) {
                if (err) { logger.info("async thisOffice:",err); }

                if (typeof thisRep.phones !== 'undefined') {
                  // change phone format
                  var phone = thisRep.phones[0];
                  phone = phone.replace(/[()]/g, '').replace(/ /,'-');

                  var rep = {
                    "name": title + ' ' + thisRep.name,
                    "phone": phone,
                    "order": order };
                  reps.push(rep);
                  thisRepCallback();
                } else {
                  thisRepCallback();
                }
              });

            } // if is seat vacant

          }, function(err) {
            if (err) { logger.info("async thisRep:",err); }

            if (hasState) {
              sendResponse( recipient, '', reps, debug);
            } else {
              getState( recipient, address, geo, reps, debug );
            }
          });

      } // end if error
    }); // end civicAPI request
}

//
// Get State Representatives
//

function getState(recipient, address, geo, reps, debug) {

    logger.info('Google didn\'t have state, so try OpenStates.');

    openstates.geoLookup(geo.lat, geo.lng, function(err, stateReps) {
      if (err) {
          logger.info('getState had an error. Sending without.');
          sendResponse( recipient, '', reps, debug );
      }

      // debug('openstates response',stateReps);

      logger.info('OpenStates has reps.');

      if (typeof stateReps !== 'undefined') {
        each(stateReps, function ( item, eachStateRepCallback ){

          var title, order = '';
          if (item.chamber == 'upper') {
            title = 'State Sen.';
            order = 3;
          } else {
            title = 'State Rep.';
            order = 4;
          }

          // DC has city council, not state legislators
          if ( item.state == 'dc' ) {
            title = 'Councilmember';
          }

          if (typeof item.offices !== 'undefined' && typeof item.offices[0] !== 'undefined') {

            // need a better way to iterate to find phone
            var phone = item.offices[0].phone;
            if ((phone == null) && (typeof item.offices[1] !== 'undefined')) {
              phone = item.offices[1].phone;
            }
            if ((phone == null) && (typeof item.offices[2] !== 'undefined')) {
              phone = item.offices[2].phone;
            }

            reps.push({
              "name": title + ' ' + item.first_name + ' ' + item.last_name,
              "phone": phone,
              "order": order
            });

            eachStateRepCallback();
          } else {
            eachStateRepCallback();
          }

        }, function done() {
          //debug('stateReps',reps);
          sendResponse( recipient, '', reps, debug );
        });

      } else {
          // log address and error.
          logger.info('OpenStates can\'t find reps for that address. Send without.');
          sendResponse( recipient, '', reps, debug );
      }
    });

}

//
// Send Text Message Response
//

function sendResponse( recipient, message, reps, debug ) {

  if (reps) {
    reps = reps.sort(function(a, b) { return (a.order > b.order) ? 1 : ((a.order < b.order) ? -1 : 0); });

    if (reps.length == 1) {
      message = 'Call your representative:\n';
    } else {
      message = 'Call your representatives:\n';
    }

    reps.forEach( function ( item, pos ){
        message += item.name + ': ' + item.phone + "\n";
    });
  }

  logger.info("textResponse:\n",message);

  var params = {
      'src': plivoPhoneNumber,
      'dst' : recipient,
      'text' : message
  };

  if (debug) {
      logger.info('Debug flag. No message sent.');
  } else {

    // Send the SMS to Plivo
    p.send_message(params, function (status, response) {
        logger.info('Status: ', status);
        logger.info('API Response:\n', response);
    });

  } // end if debug

}
<|endoftext|>


<commit_before>// TODO this isn't JSON - is this limiting?
// TODO if this is minified we should still be allowed to pass options to override it - since the minified code can't be modified
define(function () {
  // TODO we need to extend this for other languages and override
  var lang = {
    info: {
      name: 'Default'
    },
    elements: {
      comment: /(\/\*.*?\*\/)|(\/\/.*?$)/,
      preprocessor: /(#.*?$)/,
      tag: null,
      string: /([^\\]|^)(["']).*?\1\2/, // TODO this matches the first character, we should ignore it,
      // TODO provide array function for this
      keyword: ['enddeclare', 'endforeach', 'endswitch', 'continue', 'endwhile', 'foreach', 'finally', 'default', 'elseif', 'endfor', 'return', 'switch', 'assert', 'break', 'catch', 'endif', 'throw', 'while', 'then', 'case', 'else', 'goto', 'each', 'and', 'for', 'try', 'use', 'xor', 'and', 'not', 'end', 'as', 'do', 'if', 'or', 'in', 'is', 'to'],
      statement: null, // TODO
      reserved: null,
      type: null,
      modifier: null,
      entity: null,
      variable: null,
      identifier: /\b[A-Za-z_]\w*\b/,
      constant: /\b[0-9][\.\w]*/, // TODO
      operator: null,
      symbol: null
    },
    cssPrefix: 'crayon', // TODO repeat of pluginId in defaults, load from there?
    // Stores a list of the elements used during compilation. The order allows us to determine which group was matched.
    _elementsArray: null,
    regex: {
      backref: /\\(\d)\b/g,
      // TODO this isn't perfect, doesn't check that ( is not escaped.
      group: /\(.*?[^\\]\)/g,
      groupRemove: /((?:[^\\]|^)\()/g,
      expandBackrefs: function (regexStr) {
        var backrefMatches = this.matchAll(this.backref, regexStr);
        var backrefs = {};
        if (backrefMatches.length) {
          for (var i = 0; i < backrefMatches.length; i++) {
            backrefs[parseInt(backrefMatches[i][1])] = backrefMatches[i][0];
          }
          var groups = regexStr.match(this.group);
          for (var groupId in backrefs) {
            var group = groups[groupId - 1];
            regexStr = regexStr.replace(backrefs[groupId], group);
          }
        }
        return regexStr;
      },
      removeGroups: function (regexStr) {
        return regexStr.replace(this.groupRemove, '$1?:');
      },
      // TODO put in utils
      regexToString: function (re) {
        var str = re.toString().replace(/\\/g, '\\\\');
//        console.error('re', re, str.substring(1, str.length - 1));
        return str.substring(1, str.length - 1);
      },
      matchAll: function (regex, string) {
        var match = null, matches = [];
        while (match = regex.exec(string)) {
          var matchArray = [];
          for (i in match) {
            if (parseInt(i) == i) {
              matchArray.push(match[i]);
            }
          }
          matchArray.index = match.index;
          matches.push(matchArray);
        }
        return matches;
      },
//      _reLookbehind: //g,
      convertLookbehinds: function () {
        // TODO needs some more thought
        // TODO convert "(?<!a)b" -> "[^a]b" but only works for a single character
        // TODO see http://stackoverflow.com/questions/641407/javascript-negative-lookbehind-equivalent
      },
      alternation: function (array) {
        return new RegExp('\\b(' + array.join('|') + ')\\b');
      }
    },
    compile: function (me) { // TODO remove me arg
      var regexStr = '';
      me._elementsArray = [];
      for (var id in me.elements) {
        console.log('reElem', me.elements[id]);
        // TODO rather than remove groups, change algorithm to allow them for more complex regex with functions in elements
        var elem = me.elements[id];
        if (elem) {
          me._elementsArray.push(id);
          if (elem instanceof Array) {
            elem = me.regex.alternation(elem);
          }
          elem = elem.toString();
          elem = elem.substring(1, elem.length - 1);
          elem = me.regex.expandBackrefs(elem);
          elem = me.regex.removeGroups(elem);
          regexStr += '(' + elem + ')|';
        }
      }
      if (regexStr.length) {
        // Remove trailing character
        regexStr = regexStr.substring(0, regexStr.length - 1);
      } else {
        console.error('No elements compiled', me);
      }
      console.error('regexStr', regexStr, new RegExp(regexStr, 'gmi'));
      return new RegExp(regexStr, 'gmi');
    },
    spacesInTabString: function () {
      return new Array(this.spacesInTab).join(' ');
    },
    convertTabs: function (value) {
      return value.replace(/\t/g, this.spacesInTabString());
    },
    convertSpaces: function (value) {
      return value.replace(this.spacesInTabString(), '\\t');
    },
    preTransform: function (value) {
      if (this.indent == 'spaces') {
        value = this.convertTabs(value);
      } else if (this.indent == 'tabs') {
        value = this.convertSpaces(value);
      }
      return value;
    },
    // TODO use better name for value variable
    transform: function (matchValue, args) {
      console.error('matchValue, args', matchValue, args);
      return '' + matchValue + '';
    },
    getMatchIndex: function (matches) {
      console.error('matches', matches, matches.length);
      if (matches.length > 1) {
        for (var i = 1; i < matches.length; i++) {
          var match = matches[i];
          if (match) {
            return i;
          }
        }
      }
      return null;
    }
  };
  return lang;
});
<commit_msg>Added regex for other elements, improved alternation by sorting and escaping.<commit_after>// TODO this isn't JSON - is this limiting?
// TODO if this is minified we should still be allowed to pass options to override it - since the minified code can't be modified
define(function () {
  // TODO we need to extend this for other languages and override
  var lang = {
    info: {
      name: 'Default'
    },
    elements: {
      comment: /(\/\*.*?\*\/)|(\/\/.*?$)/,
      preprocessor: /(#.*?$)/,
      tag: null,
      string: /([^\\]|^)(["']).*?\1\2/, // TODO this matches the first character, we should ignore it,
      keyword: null,
      statement: ['enddeclare', 'endforeach', 'endswitch', 'continue', 'endwhile', 'foreach', 'finally', 'default', 'elseif', 'endfor', 'return', 'switch', 'assert', 'break', 'catch', 'endif', 'throw', 'while', 'then', 'case', 'else', 'goto', 'each', 'and', 'for', 'try', 'use', 'xor', 'and', 'not', 'end', 'as', 'do', 'if', 'or', 'in', 'is', 'to'],
      reserved: ['implements', 'instanceof', 'declare', 'default', 'extends', 'typedef', 'parent', 'super', 'child', 'clone', 'self', 'this', 'new'],
      type: ['cfunction', 'interface', 'namespace', 'function', 'unsigned', 'boolean', 'integer', 'package', 'double', 'struct', 'string', 'signed', 'object', 'class', 'array', 'float', 'short', 'false', 'char', 'long', 'void', 'word', 'byte', 'bool', 'null', 'true', 'enum', 'var', 'int'],
      modifier: ['protected', 'abstract', 'property', 'private', 'global', 'public', 'static', 'native', 'const', 'final'],
      entity: /(\b[a-z_]\w*\b(?=\s*\([^\)]*\)))|(\b[a-z_]\w+\b\s+(?=\b[a-z_]\w+\b))/,
      variable: /([A-Za-z_]\w*(?=\s*[=\[\.]))/,
      identifier: /\b[A-Za-z_]\w*\b/,
      constant: /\b[0-9][\.\w]*/,
//      operator: ['=&', '<<<', '>>>', '<<', '>>', '<<=', '=>>', '!==', '!=', '^=', '*=', '&=', '%=', '|=', '/=', '===', '==', '<>', '->', '<=', '>=', '++', '--', '&&', '||', '::', '', '#', '+', '-', '*', '/', '%', '=', '&', '|', '^', '~', '!', '<', '>', ':'],
      symbol: ['~', '`', '!', '@', '#', '$', '%', '(', ')', '_', '{', '}', '[', ']', '|', '\\', ':', ';', ',', '.', '?']
    },
    cssPrefix: 'crayon', // TODO repeat of pluginId in defaults, load from there?
    // Stores a list of the elements used during compilation. The order allows us to determine which group was matched.
    _elementsArray: null,
    regex: {
      // Whether to compile each element regex string into a RegExp object. Helps find invalid regexes.
      debug: true,
      backref: /\\(\d)\b/g,
      // TODO this isn't perfect, doesn't check that ( is not escaped.
      group: /\(.*?[^\\]\)/g,
      groupRemove: /((?:[^\\]|^)\((?!\?))/g,
      expandBackrefs: function (regexStr) {
        var backrefMatches = this.matchAll(this.backref, regexStr);
        var backrefs = {};
        if (backrefMatches.length) {
          for (var i = 0; i < backrefMatches.length; i++) {
            backrefs[parseInt(backrefMatches[i][1])] = backrefMatches[i][0];
          }
          var groups = regexStr.match(this.group);
          for (var groupId in backrefs) {
            var group = groups[groupId - 1];
            regexStr = regexStr.replace(backrefs[groupId], group);
          }
        }
        return regexStr;
      },
      removeGroups: function (regexStr) {
        return regexStr.replace(this.groupRemove, '$1?:');
      },
      // TODO put in utils
      regexToString: function (re) {
        var str = re.toString().replace(/\\/g, '\\\\');
//        console.error('re', re, str.substring(1, str.length - 1));
        return str.substring(1, str.length - 1);
      },
      matchAll: function (regex, string) {
        var match = null, matches = [];
        while (match = regex.exec(string)) {
          var matchArray = [];
          for (i in match) {
            if (parseInt(i) == i) {
              matchArray.push(match[i]);
            }
          }
          matchArray.index = match.index;
          matches.push(matchArray);
        }
        return matches;
      },
//      _reLookbehind: //g,
      convertLookbehinds: function () {
        // TODO needs some more thought
        // TODO convert "(?<!a)b" -> "[^a]b" but only works for a single character
        // TODO see http://stackoverflow.com/questions/641407/javascript-negative-lookbehind-equivalent
      },
      alternation: function (array) {
        array.sort(function (a, b) {
          // Reverse sort the array of strings.
          return a.length  b.length ? -1 : 0);
        });
        var me = this;
        array = array.map(function (item) {
          // Escape regex characters.
          return me.escape(item);
        });
        return new RegExp('\\b(' + array.join('|') + ')\\b');
      },
      escape: function (str) {
//        return str.replace(/([\\|/*+?{}()\[\]^$.-])/g, '\\$1');
        return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
      }
    },
    compile: function (me) { // TODO remove me arg
      var regexStr = '';
      me._elementsArray = [];
      for (var id in me.elements) {
        console.log('reElem', me.elements[id]);
        // TODO rather than remove groups, change algorithm to allow them for more complex regex with functions in elements
        var elem = me.elements[id];
        if (elem) {
          me._elementsArray.push(id);
          if (elem instanceof Array) {
            elem = me.regex.alternation(elem);
          }
          elem = elem.toString();
          elem = elem.substring(1, elem.length - 1);
          elem = me.regex.expandBackrefs(elem);
          elem = me.regex.removeGroups(elem);
          if (me.debug) {
            elem = new RegExp(elem);
            regexStr += '(' + elem.toString() + ')|';
          } else {
            regexStr += '(' + elem + ')|';
          }
        }
      }
      if (regexStr.length) {
        // Remove trailing character
        regexStr = regexStr.substring(0, regexStr.length - 1);
      } else {
        console.error('No elements compiled', me);
      }
      console.error('regexStr', regexStr, new RegExp(regexStr, 'gmi'));
      return new RegExp(regexStr, 'gmi');
    },
    spacesInTabString: function () {
      return new Array(this.spacesInTab).join(' ');
    },
    convertTabs: function (value) {
      return value.replace(/\t/g, this.spacesInTabString());
    },
    convertSpaces: function (value) {
      return value.replace(this.spacesInTabString(), '\\t');
    },
    preTransform: function (value) {
      if (this.indent == 'spaces') {
        value = this.convertTabs(value);
      } else if (this.indent == 'tabs') {
        value = this.convertSpaces(value);
      }
      return value;
    },
    // TODO use better name for value variable
    transform: function (matchValue, args) {
      console.error('matchValue, args', matchValue, args);
      return '' + matchValue + '';
    },
    getMatchIndex: function (matches) {
      console.error('matches', matches, matches.length);
      if (matches.length > 1) {
        for (var i = 1; i < matches.length; i++) {
          var match = matches[i];
          if (typeof match != 'undefined') {
            return i;
          }
        }
      }
      return null;
    }
  };
  return lang;
});
<|endoftext|>


<commit_before>/**
 * Created by tungtouch on 2/5/15.
 */

/*
* Listen UDP from Device: 128.199.126.250
* */

var dgram = require("dgram");
var colors = require('colors');
var async = require('async');
var redis = require("redis");
var rClient = redis.createClient();
var client;
var d = new Date();

//var ports = [4000, 4001, 4002, 3333, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011];
var ports = [4444];
var r = 0;

rClient.on("error", function (err) {
    console.log("Error " + err);
});

var currentTime = d.getTime();
console.log("Current Time:", currentTime);

for(var i=0; i < ports.length; i++){

    client = dgram.createSocket('udp4', function(data){
        var ttl = d.getTime();

        console.log("[1. Data Raw]: ".green, data);
        console.log("[2. JSON Data]: ".yellow, JSON.stringify(data));
        //console.log("[3. Decoder:]".blue, typeof data, data.toString('utf8'));
        console.log("-------------------------------------------------");
        console.log("Total request: ".blue, r++, ttl - currentTime);

        rClient.hset("raw", "data"+r, data, redis.print);

    });

    console.log("Start listen port ",i,":",ports[i]);

    client.on("error", function (err) {
        console.log("Server error:\n".red + err.stack);
        client.close();
    });
    //
    client.on("message", function (msg, rinfo) {
        console.log("Server got: ".yellow + " IP: " +
        rinfo.address + " - Port:" + rinfo.port);

    });

    //client.on("listening", function () {
    //    var address = client.address();
    //    console.log("Server listening ".blue +
    //    address.address + ":" + address.port);
    //});

    client.bind(ports[i]);
}

<commit_msg>Update UDP<commit_after>/**
 * Created by tungtouch on 2/5/15.
 */

/*
* Listen UDP from Device: 128.199.126.250
* */

var dgram = require("dgram");
var colors = require('colors');
var async = require('async');
var redis = require("redis");
var rClient = redis.createClient();
var d = new Date();

//var ports = [4000, 4001, 4002, 3333, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011];
var ports = [4444];
var r = 0;

var client = dgram.createSocket('udp4');

rClient.on("error", function (err) {
    console.log("Error " + err);
});

for(var i=0; i < ports.length; i++){

    console.log("Start listen port ",i,":",ports[i]);

    client.on("error", function (err) {
        console.log("Server error:\n".red + err.stack);
        client.close();
    });
    //
    client.on("message", function (data, rinfo) {

        console.log("[1. Data Raw]: ".green, data);
        console.log("[2. JSON Data]: ".yellow, JSON.stringify(data));
        //console.log("[3. Decoder:]".blue, typeof data, data.toString('utf8'));
        console.log("-------------------------------------------------");
        console.log("Total request: ".blue, r++);

        //rClient.hset("raw", "data"+r, data, redis.print);

        console.log("Server got: ".yellow + " IP: " +
        rinfo.address + " - Port:" + rinfo.port);
    });

    //client.on("listening", function () {
    //    var address = client.address();
    //    console.log("Server listening ".blue +
    //    address.address + ":" + address.port);
    //});

    client.bind(ports[i]);
}

<|endoftext|>


<commit_before>module.exports = File;

var SHEBANG_REGEX = /^\#\!.*/;

var transform = require("./transformation/transform");
var generate  = require("./generation/generator");
var Scope     = require("./traverse/scope");
var util      = require("./util");
var t         = require("./types");
var _         = require("lodash");

function File(opts) {
  this.opts            = File.normaliseOptions(opts);
  this.moduleFormatter = this.getModuleFormatter(this.opts.modules);

  this.declarations = {};
  this.uids         = {};
  this.ast          = {};
}

File.declarations = ["extends", "class-props", "slice", "apply-constructor",
                     "tagged-template-literal"];

File.normaliseOptions = function (opts) {
  opts = _.cloneDeep(opts || {});

  _.defaults(opts, {
    whitespace: true,
    blacklist:  [],
    whitelist:  [],
    sourceMap:  false,
    comments:   true,
    filename:   "unknown",
    modules:    "common",
    runtime:    false,
    code:       true
  });

  // normalise windows path separators to unix
  opts.filename = opts.filename.replace(/\\/g, "/");

  _.defaults(opts, {
    filenameRelative: opts.filename
  });

  _.defaults(opts, {
    sourceFileName: opts.filenameRelative,
    sourceMapName:  opts.filenameRelative
  });

  if (opts.runtime === true) {
    opts.runtime = "to5Runtime";
  }

  transform._ensureTransformerNames("blacklist", opts.blacklist);
  transform._ensureTransformerNames("whitelist", opts.whitelist);

  return opts;
};

File.prototype.getModuleFormatter = function (type) {
  var ModuleFormatter = transform.moduleFormatters[type];

  if (!ModuleFormatter) {
    var loc = util.resolve(type);
    if (loc) ModuleFormatter = require(loc);
  }

  if (!ModuleFormatter) {
    throw new ReferenceError("unknown module formatter type " + type);
  }

  return new ModuleFormatter(this);
};

File.prototype.parseShebang = function (code) {
  var shebangMatch = code.match(SHEBANG_REGEX);
  if (shebangMatch) {
    this.shebang = shebangMatch[0];

    // remove shebang
    code = code.replace(SHEBANG_REGEX, "");
  }

  return code;
};

File.prototype.addDeclaration = function (name) {
  if (!_.contains(File.declarations, name)) {
    throw new ReferenceError("unknown declaration " + name);
  }

  var declar = this.declarations[name];
  if (declar) return declar.uid;

  var ref;
  var runtimeNamespace = this.opts.runtime;
  if (runtimeNamespace) {
    name = t.identifier(t.toIdentifier(name));
    return t.memberExpression(t.identifier(runtimeNamespace), name);
  } else {
    ref = util.template(name);
  }

  var uid = t.identifier(this.generateUid(name));
  this.declarations[name] = {
    uid: uid,
    node: ref
  };
  return uid;
};

File.prototype.errorWithNode = function (node, msg, Error) {
  Error = Error || SyntaxError;

  var loc = node.loc.start;
  var err = new Error("Line " + loc.line + ": " + msg);
  err.loc = loc;
  return err;
};

File.prototype.parse = function (code) {
  code = (code || "") + "";

  var self = this;

  this.code = code;

  code = this.parseShebang(code);

  return util.parse(this.opts, code, function (tree) {
    self.transform(tree);
    return self.generate();
  });
};

File.prototype.transform = function (ast) {
  this.ast = ast;
  this.scope = new Scope(null, ast.program);

  var self = this;

  _.each(transform.transformers, function (transformer) {
    transformer.transform(self);
  });
};

File.prototype.generate = function () {
  var opts = this.opts;
  var ast  = this.ast;

  if (!opts.code) {
    return {
      code: "",
      map:  null,
      ast:  ast
    };
  }

  var result = generate(ast, opts, this.code);

  if (this.shebang) {
    // add back shebang
    result.code = this.shebang + "\n" + result.code;
  }

  if (opts.sourceMap === "inline") {
    result.code += "\n" + util.sourceMapToComment(result.map);
  }

  result.ast = result;

  return result;
};

File.prototype.generateUid = function (name, scope) {
  name = t.toIdentifier(name);

  scope = scope || this.scope;

  var uid;
  do {
    uid = this._generateUid(name);
  } while (scope.has(uid));
  return uid;
};

File.prototype._generateUid = function (name) {
  var uids = this.uids;
  var i = uids[name] || 1;

  var id = name;
  if (i > 1) id += i;
  uids[name] = i + 1;
  return "_" + id;
};
<commit_msg>Allow module transformers to be directly passed into opts.<commit_after>module.exports = File;

var SHEBANG_REGEX = /^\#\!.*/;

var transform = require("./transformation/transform");
var generate  = require("./generation/generator");
var Scope     = require("./traverse/scope");
var util      = require("./util");
var t         = require("./types");
var _         = require("lodash");

function File(opts) {
  this.opts            = File.normaliseOptions(opts);
  this.moduleFormatter = this.getModuleFormatter(this.opts.modules);

  this.declarations = {};
  this.uids         = {};
  this.ast          = {};
}

File.declarations = ["extends", "class-props", "slice", "apply-constructor",
                     "tagged-template-literal"];

File.normaliseOptions = function (opts) {
  opts = _.cloneDeep(opts || {});

  _.defaults(opts, {
    whitespace: true,
    blacklist:  [],
    whitelist:  [],
    sourceMap:  false,
    comments:   true,
    filename:   "unknown",
    modules:    "common",
    runtime:    false,
    code:       true
  });

  // normalise windows path separators to unix
  opts.filename = opts.filename.replace(/\\/g, "/");

  _.defaults(opts, {
    filenameRelative: opts.filename
  });

  _.defaults(opts, {
    sourceFileName: opts.filenameRelative,
    sourceMapName:  opts.filenameRelative
  });

  if (opts.runtime === true) {
    opts.runtime = "to5Runtime";
  }

  transform._ensureTransformerNames("blacklist", opts.blacklist);
  transform._ensureTransformerNames("whitelist", opts.whitelist);

  return opts;
};

File.prototype.getModuleFormatter = function (type) {
  var ModuleFormatter = _.isFunction(type) ? type : transform.moduleFormatters[type];

  if (!ModuleFormatter) {
    var loc = util.resolve(type);
    if (loc) ModuleFormatter = require(loc);
  }

  if (!ModuleFormatter) {
    throw new ReferenceError("unknown module formatter type " + type);
  }

  return new ModuleFormatter(this);
};

File.prototype.parseShebang = function (code) {
  var shebangMatch = code.match(SHEBANG_REGEX);
  if (shebangMatch) {
    this.shebang = shebangMatch[0];

    // remove shebang
    code = code.replace(SHEBANG_REGEX, "");
  }

  return code;
};

File.prototype.addDeclaration = function (name) {
  if (!_.contains(File.declarations, name)) {
    throw new ReferenceError("unknown declaration " + name);
  }

  var declar = this.declarations[name];
  if (declar) return declar.uid;

  var ref;
  var runtimeNamespace = this.opts.runtime;
  if (runtimeNamespace) {
    name = t.identifier(t.toIdentifier(name));
    return t.memberExpression(t.identifier(runtimeNamespace), name);
  } else {
    ref = util.template(name);
  }

  var uid = t.identifier(this.generateUid(name));
  this.declarations[name] = {
    uid: uid,
    node: ref
  };
  return uid;
};

File.prototype.errorWithNode = function (node, msg, Error) {
  Error = Error || SyntaxError;

  var loc = node.loc.start;
  var err = new Error("Line " + loc.line + ": " + msg);
  err.loc = loc;
  return err;
};

File.prototype.parse = function (code) {
  code = (code || "") + "";

  var self = this;

  this.code = code;

  code = this.parseShebang(code);

  return util.parse(this.opts, code, function (tree) {
    self.transform(tree);
    return self.generate();
  });
};

File.prototype.transform = function (ast) {
  this.ast = ast;
  this.scope = new Scope(null, ast.program);

  var self = this;

  _.each(transform.transformers, function (transformer) {
    transformer.transform(self);
  });
};

File.prototype.generate = function () {
  var opts = this.opts;
  var ast  = this.ast;

  if (!opts.code) {
    return {
      code: "",
      map:  null,
      ast:  ast
    };
  }

  var result = generate(ast, opts, this.code);

  if (this.shebang) {
    // add back shebang
    result.code = this.shebang + "\n" + result.code;
  }

  if (opts.sourceMap === "inline") {
    result.code += "\n" + util.sourceMapToComment(result.map);
  }

  result.ast = result;

  return result;
};

File.prototype.generateUid = function (name, scope) {
  name = t.toIdentifier(name);

  scope = scope || this.scope;

  var uid;
  do {
    uid = this._generateUid(name);
  } while (scope.has(uid));
  return uid;
};

File.prototype._generateUid = function (name) {
  var uids = this.uids;
  var i = uids[name] || 1;

  var id = name;
  if (i > 1) id += i;
  uids[name] = i + 1;
  return "_" + id;
};
<|endoftext|>


<commit_before>require("./patch");

var estraverse = require("estraverse");
var traverse   = require("./traverse");
var acorn      = require("acorn-6to5");
var path       = require("path");
var util       = require("util");
var fs         = require("fs");
var t          = require("./types");
var _          = require("lodash");

exports.inherits = util.inherits;

exports.canCompile = function (filename, altExts) {
  var exts = altExts || [".js", ".jsx", ".es6"];
  var ext = path.extname(filename);
  return _.contains(exts, ext);
};

exports.isInteger = function (i) {
  return _.isNumber(i) && i % 1 === 0;
};

exports.resolve = function (loc) {
  try {
    return require.resolve(loc);
  } catch (err) {
    return null;
  }
};

exports.trimRight = function (str) {
  return str.replace(/[\n\s]+$/g, "");
};

exports.list = function (val) {
  return val ? val.split(",") : [];
};

exports.getUid = function (parent, file) {
  var node;

  if (t.isAssignmentExpression(parent)) {
    node = parent.left;
  } else if (t.isVariableDeclarator(parent)) {
    node = parent.id;
  }

  var id = "ref";

  if (t.isIdentifier(node)) id = node.name;

  return t.identifier(file.generateUid(id));
};

exports.isAbsolute = function (loc) {
  if (!loc) return false;
  if (loc[0] === "/") return true; // unix
  if (loc[1] === ":" && loc[2] === "\\") return true; // windows
  return false;
};

exports.sourceMapToComment = function (map) {
  var json = JSON.stringify(map);
  var base64 = new Buffer(json).toString("base64");
  return "//# sourceMappingURL=data:application/json;base64," + base64;
};

exports.pushMutatorMap = function (mutatorMap, key, kind, method) {
  var alias;

  if (t.isIdentifier(key)) {
    alias = key.name;
    if (method.computed) alias = "computed:" + alias;
  } else if (t.isLiteral(key)) {
    alias = String(key.value);
  } else {
    alias = JSON.stringify(traverse.removeProperties(_.cloneDeep(key)));
  }

  var map;
  if (_.has(mutatorMap, alias)) {
    map = mutatorMap[alias];
  } else {
    map = {};
  }
  mutatorMap[alias] = map;

  map._key = key;
  if (method.computed) {
    map._computed = true;
  }

  map[kind] = method;
};

exports.buildDefineProperties = function (mutatorMap) {
  var objExpr = t.objectExpression([]);

  _.each(mutatorMap, function (map) {
    var mapNode = t.objectExpression([]);

    var propNode = t.property("init", map._key, mapNode, map._computed);

    _.each(map, function (node, key) {
      if (key[0] === "_") return;

      node = _.clone(node);
      var inheritNode = node;
      if (t.isMethodDefinition(node)) node = node.value;

      var prop = t.property("init", t.identifier(key), node);
      t.inheritsComments(prop, inheritNode);
      t.removeComments(inheritNode);
      mapNode.properties.push(prop);
    });

    objExpr.properties.push(propNode);
  });

  return objExpr;
};

exports.template = function (name, nodes, keepExpression) {
  var template = exports.templates[name];
  if (!template) throw new ReferenceError("unknown template " + name);

  if (nodes === true) {
    keepExpression = true;
    nodes = null;
  }

  template = _.cloneDeep(template);

  if (!_.isEmpty(nodes)) {
    traverse(template, function (node) {
      if (t.isIdentifier(node) && _.has(nodes, node.name)) {
        var newNode = nodes[node.name];
        if (_.isString(newNode)) {
          node.name = newNode;
        } else {
          return newNode;
        }
      }
    });
  }

  var node = template.body[0];

  if (!keepExpression && t.isExpressionStatement(node)) {
    node = node.expression;

    if (t.isParenthesizedExpression(node)) node = node.expression;
  }

  return node;
};

exports.codeFrame = function (lines, lineNumber, colNumber) {
  colNumber = Math.max(colNumber, 0);

  lines = lines.split("\n");

  var start = Math.max(lineNumber - 3, 0);
  var end   = Math.min(lines.length, lineNumber + 3);
  var width = (end + "").length;

  if (!lineNumber && !colNumber) {
    start = 0;
    end = lines.length;
  }

  return "\n" + lines.slice(start, end).map(function (line, i) {
    var curr = i + start + 1;

    var gutter = curr === lineNumber ? "> " : "  ";

    var sep = curr + exports.repeat(width + 1);
    gutter += sep + "| ";

    var str = gutter + line;

    if (colNumber && curr === lineNumber) {
      str += "\n";
      str += exports.repeat(gutter.length - 2);
      str += "|" + exports.repeat(colNumber) + "^";
    }

    return str;
  }).join("\n");
};

exports.repeat = function (width, cha) {
  cha = cha || " ";
  return new Array(width + 1).join(cha);
};

exports.parse = function (opts, code, callback) {
  try {
    var comments = [];
    var tokens   = [];

    var ast = acorn.parse(code, {
      allowReturnOutsideFunction: true,
      preserveParens:             true,
      ecmaVersion:                Infinity,
      strictMode:                 true,
      onComment:                  comments,
      locations:                  true,
      onToken:                    tokens,
      ranges:                     true
    });

    estraverse.attachComments(ast, comments, tokens);

    ast = t.file(ast, comments, tokens);

    traverse(ast, function (node, parent) {
      node._parent = parent;
    });

    if (callback) {
      return callback(ast);
    } else {
      return ast;
    }
  } catch (err) {
    if (!err._6to5) {
      err._6to5 = true;
      var message = opts.filename + ": " + err.message;

      var loc = err.loc;
      if (loc) {
        var frame = exports.codeFrame(code, loc.line, loc.column);
        message += frame;
      }

      if (err.stack) err.stack = err.stack.replace(err.message, message);
      err.message = message;
    }

    throw err;
  }
};

exports.parseTemplate = function (loc, code) {
  var ast = exports.parse({ filename: loc }, code).program;
  return traverse.removeProperties(ast);
};

var loadTemplates = function () {
  var templates = {};

  var templatesLoc = __dirname + "/templates";
  if (!fs.existsSync(templatesLoc)) {
    throw new Error("no templates directory - this is most likely the " +
                    "result of a broken `npm publish`. Please report to " +
                    "https://githut.com/6to5/6to5/issues");
  }

  _.each(fs.readdirSync(templatesLoc), function (name) {
    if (name[0] === ".") return;

    var key  = path.basename(name, path.extname(name));
    var loc  = templatesLoc + "/" + name;
    var code = fs.readFileSync(loc, "utf8");

    templates[key] = exports.parseTemplate(loc, code);
  });

  return templates;
};

try {
  exports.templates = require("../../templates.json");
} catch (err) {
  if (err.code !== "MODULE_NOT_FOUND") throw err;

  Object.defineProperty(exports, "templates", {
    get: function () {
      return exports.templates = loadTemplates();
    }
  });
}
<commit_msg>don't lazy set templates, can't even remember why i did this<commit_after>require("./patch");

var estraverse = require("estraverse");
var traverse   = require("./traverse");
var acorn      = require("acorn-6to5");
var path       = require("path");
var util       = require("util");
var fs         = require("fs");
var t          = require("./types");
var _          = require("lodash");

exports.inherits = util.inherits;

exports.canCompile = function (filename, altExts) {
  var exts = altExts || [".js", ".jsx", ".es6"];
  var ext = path.extname(filename);
  return _.contains(exts, ext);
};

exports.isInteger = function (i) {
  return _.isNumber(i) && i % 1 === 0;
};

exports.resolve = function (loc) {
  try {
    return require.resolve(loc);
  } catch (err) {
    return null;
  }
};

exports.trimRight = function (str) {
  return str.replace(/[\n\s]+$/g, "");
};

exports.list = function (val) {
  return val ? val.split(",") : [];
};

exports.getUid = function (parent, file) {
  var node;

  if (t.isAssignmentExpression(parent)) {
    node = parent.left;
  } else if (t.isVariableDeclarator(parent)) {
    node = parent.id;
  }

  var id = "ref";

  if (t.isIdentifier(node)) id = node.name;

  return t.identifier(file.generateUid(id));
};

exports.isAbsolute = function (loc) {
  if (!loc) return false;
  if (loc[0] === "/") return true; // unix
  if (loc[1] === ":" && loc[2] === "\\") return true; // windows
  return false;
};

exports.sourceMapToComment = function (map) {
  var json = JSON.stringify(map);
  var base64 = new Buffer(json).toString("base64");
  return "//# sourceMappingURL=data:application/json;base64," + base64;
};

exports.pushMutatorMap = function (mutatorMap, key, kind, method) {
  var alias;

  if (t.isIdentifier(key)) {
    alias = key.name;
    if (method.computed) alias = "computed:" + alias;
  } else if (t.isLiteral(key)) {
    alias = String(key.value);
  } else {
    alias = JSON.stringify(traverse.removeProperties(_.cloneDeep(key)));
  }

  var map;
  if (_.has(mutatorMap, alias)) {
    map = mutatorMap[alias];
  } else {
    map = {};
  }
  mutatorMap[alias] = map;

  map._key = key;
  if (method.computed) {
    map._computed = true;
  }

  map[kind] = method;
};

exports.buildDefineProperties = function (mutatorMap) {
  var objExpr = t.objectExpression([]);

  _.each(mutatorMap, function (map) {
    var mapNode = t.objectExpression([]);

    var propNode = t.property("init", map._key, mapNode, map._computed);

    _.each(map, function (node, key) {
      if (key[0] === "_") return;

      node = _.clone(node);
      var inheritNode = node;
      if (t.isMethodDefinition(node)) node = node.value;

      var prop = t.property("init", t.identifier(key), node);
      t.inheritsComments(prop, inheritNode);
      t.removeComments(inheritNode);
      mapNode.properties.push(prop);
    });

    objExpr.properties.push(propNode);
  });

  return objExpr;
};

exports.template = function (name, nodes, keepExpression) {
  var template = exports.templates[name];
  if (!template) throw new ReferenceError("unknown template " + name);

  if (nodes === true) {
    keepExpression = true;
    nodes = null;
  }

  template = _.cloneDeep(template);

  if (!_.isEmpty(nodes)) {
    traverse(template, function (node) {
      if (t.isIdentifier(node) && _.has(nodes, node.name)) {
        var newNode = nodes[node.name];
        if (_.isString(newNode)) {
          node.name = newNode;
        } else {
          return newNode;
        }
      }
    });
  }

  var node = template.body[0];

  if (!keepExpression && t.isExpressionStatement(node)) {
    node = node.expression;

    if (t.isParenthesizedExpression(node)) node = node.expression;
  }

  return node;
};

exports.codeFrame = function (lines, lineNumber, colNumber) {
  colNumber = Math.max(colNumber, 0);

  lines = lines.split("\n");

  var start = Math.max(lineNumber - 3, 0);
  var end   = Math.min(lines.length, lineNumber + 3);
  var width = (end + "").length;

  if (!lineNumber && !colNumber) {
    start = 0;
    end = lines.length;
  }

  return "\n" + lines.slice(start, end).map(function (line, i) {
    var curr = i + start + 1;

    var gutter = curr === lineNumber ? "> " : "  ";

    var sep = curr + exports.repeat(width + 1);
    gutter += sep + "| ";

    var str = gutter + line;

    if (colNumber && curr === lineNumber) {
      str += "\n";
      str += exports.repeat(gutter.length - 2);
      str += "|" + exports.repeat(colNumber) + "^";
    }

    return str;
  }).join("\n");
};

exports.repeat = function (width, cha) {
  cha = cha || " ";
  return new Array(width + 1).join(cha);
};

exports.parse = function (opts, code, callback) {
  try {
    var comments = [];
    var tokens   = [];

    var ast = acorn.parse(code, {
      allowReturnOutsideFunction: true,
      preserveParens:             true,
      ecmaVersion:                Infinity,
      strictMode:                 true,
      onComment:                  comments,
      locations:                  true,
      onToken:                    tokens,
      ranges:                     true
    });

    estraverse.attachComments(ast, comments, tokens);

    ast = t.file(ast, comments, tokens);

    traverse(ast, function (node, parent) {
      node._parent = parent;
    });

    if (callback) {
      return callback(ast);
    } else {
      return ast;
    }
  } catch (err) {
    if (!err._6to5) {
      err._6to5 = true;
      var message = opts.filename + ": " + err.message;

      var loc = err.loc;
      if (loc) {
        var frame = exports.codeFrame(code, loc.line, loc.column);
        message += frame;
      }

      if (err.stack) err.stack = err.stack.replace(err.message, message);
      err.message = message;
    }

    throw err;
  }
};

exports.parseTemplate = function (loc, code) {
  var ast = exports.parse({ filename: loc }, code).program;
  return traverse.removeProperties(ast);
};

var loadTemplates = function () {
  var templates = {};

  var templatesLoc = __dirname + "/templates";
  if (!fs.existsSync(templatesLoc)) {
    throw new Error("no templates directory - this is most likely the " +
                    "result of a broken `npm publish`. Please report to " +
                    "https://githut.com/6to5/6to5/issues");
  }

  _.each(fs.readdirSync(templatesLoc), function (name) {
    if (name[0] === ".") return;

    var key  = path.basename(name, path.extname(name));
    var loc  = templatesLoc + "/" + name;
    var code = fs.readFileSync(loc, "utf8");

    templates[key] = exports.parseTemplate(loc, code);
  });

  return templates;
};

try {
  exports.templates = require("../../templates.json");
} catch (err) {
  if (err.code !== "MODULE_NOT_FOUND") throw err;

  exports.templates = loadTemplates();
}
<|endoftext|>


<commit_before>const {Cu} = require('chrome');

Cu.import('resource://gre/modules/Services.jsm'); /* globals Services */
Cu.import('resource://gre/modules/TelemetryController.jsm'); /* globals TelemetryController */
Cu.import('resource://gre/modules/Timer.jsm'); /* globals setTimeout, clearTimeout */

Cu.importGlobalProperties(['URL']); /* globals URL */

const {Log} = require('./Log.js');
const {EventEmitter} = require('./EventEmitter.js');

const PREF_SURVEY_DURATION = 'browser.uitour.surveyDuration';

/**
 * Show the Heartbeat UI to request user feedback. This function reports back to the
 * caller using |notify|. The notification event name reflects the current status the UI
 * is in (either "Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed",
 * "Heartbeat:LearnMore", "Heartbeat:Engaged", "Heartbeat:Voted",
 * "Heartbeat:SurveyExpired" or "Heartbeat:WindowClosed").
 * When a "Heartbeat:Voted" event is notified
 * the data payload contains a |score| field which holds the rating picked by the user.
 * Please note that input parameters are already validated by the caller.
 *
 * @param chromeWindow
 *        The chrome window that the heartbeat notification is displayed in.
 * @param {Object} options Options object.
 * @param {String} options.message
 *        The message, or question, to display on the notification.
 * @param {String} options.thankyouMessage
 *        The thank you message to display after user votes.
 * @param {String} options.flowId
 *        An identifier for this rating flow. Please note that this is only used to
 *        identify the notification box.
 * @param {String} [options.engagementButtonLabel=null]
 *        The text of the engagement button to use instad of stars. If this is null
 *        or invalid, rating stars are used.
 * @param {String} [options.engagementURL=null]
 *        The engagement URL to open in a new tab once user has engaged. If this is null
 *        or invalid, no new tab is opened.
 * @param {String} [options.learnMoreMessage=null]
 *        The label of the learn more link. No link will be shown if this is null.
 * @param {String} [options.learnMoreURL=null]
 *        The learn more URL to open when clicking on the learn more link. No learn more
 *        will be shown if this is an invalid URL.
 * @param {boolean} [options.privateWindowsOnly=false]
 *        Whether the heartbeat UI should only be targeted at a private window (if one exists).
 *        No notifications should be fired when this is true.
 * @param {String} [options.surveyId]
 *        An ID for the survey, reflected in the Telemetry ping.
 * @param {Number} [options.surveyVersion]
 *        Survey's version number, reflected in the Telemetry ping.
 * @param {boolean} [options.testing]
 *        Whether this is a test survey, reflected in the Telemetry ping.
 */
exports.Heartbeat = class {
  constructor(chromeWindow, options) {
    Log.debug('Showing new Heartbeat', options);
    this.chromeWindow = chromeWindow;
    this.options = options;

    this.events = new EventEmitter(this.options.sandbox);
    this.surveyResults = {};

    this.buttons = null;

    if (this.options.engagementButtonLabel) {
      this.buttons = [{
        label: this.options.engagementButtonLabel,
        callback: () => {
          // Let the consumer know user engaged.
          this.maybeNotifyHeartbeat('Engaged');

          this.userEngaged(new Map([
            ['type', 'button'],
            ['flowid', this.options.flowId]
          ]));

          // Return true so that the notification bar doesn't close itself since
          // we have a thank you message to show.
          return true;
        },
      }];
    }

    this.notificationBox = this.chromeWindow.document.querySelector('#high-priority-global-notificationbox');
    this.notice = this.notificationBox.appendNotification(
      this.options.message,
      'heartbeat-' + this.options.flowId,
      'chrome://browser/skin/heartbeat-icon.svg',
      this.notificationBox.PRIORITY_INFO_HIGH,
      this.buttons,
      eventType => {
        if (eventType !== 'removed') {
          return;
        }
        this.maybeNotifyHeartbeat('NotificationClosed');
      }
    );

    // Holds the rating UI
    let frag = this.chromeWindow.document.createDocumentFragment();

    // Build the heartbeat stars
    const numStars = this.options.engagementButtonLabel ? 0 : 5;
    let ratingContainer = this.chromeWindow.document.createElement('hbox');
    ratingContainer.id = 'star-rating-container';

    for (let i = 0; i < numStars; i++) {
      // create a star rating element
      let ratingElement = this.chromeWindow.document.createElement('toolbarbutton');

      // style it
      let starIndex = numStars - i;
      ratingElement.className = 'plain star-x';
      ratingElement.id = 'star' + starIndex;
      ratingElement.setAttribute('data-score', starIndex);

      // Add the click handler
      ratingElement.addEventListener('click', ev => {
        let rating = parseInt(ev.target.getAttribute('data-score'));
        this.maybeNotifyHeartbeat('Voted', {score: rating});
        this.userEngaged({type: 'stars', score: rating, flowId: this.options.flowId});
      });

      ratingContainer.appendChild(ratingElement);
    }

    frag.appendChild(ratingContainer);

    if (this.options.learnMoreMessage && this.options.learnMoreURL) {
      let learnMore = this.chromeWindow.document.createElement('label');
      learnMore.className = 'text-link';
      learnMore.href = this.options.learnMoreURL;
      learnMore.setAttribute('value', this.options.learnMoreMessage);
      learnMore.addEventListener('click', () => this.maybeNotifyHeartbeat('LearnMore'));
      frag.appendChild(learnMore);
    }

    this.messageImage = this.chromeWindow.document.getAnonymousElementByAttribute(this.notice, 'anonid', 'messageImage');
    this.messageImage.classList.add('heartbeat', 'pulse-onshow');

    this.messageText = this.chromeWindow.document.getAnonymousElementByAttribute(this.notice, 'anonid', 'messageText');
    this.messageText.classList.add('heartbeat');

    // Make sure the stars are not pushed to the right by the spacer.
    let rightSpacer = this.chromeWindow.document.createElement('spacer');
    rightSpacer.flex = 20;
    frag.appendChild(rightSpacer);

    // collapse the space before the stars
    this.messageText.flex = 0;
    let leftSpacer = this.messageText.nextSibling;
    leftSpacer.flex = 0;

    // Append the fragment and apply the styling
    this.notice.appendChild(frag);
    this.notice.classList.add('heartbeat');

    // Let the consumer know the notification was shown.
    this.maybeNotifyHeartbeat('NotificationOffered');

    let handleWindowClosed = () => {
      this.maybeNotifyHeartbeat('WindowClosed');
      this.chromeWindow.removeEventListener('SSWindowClosing', handleWindowClosed);
    };
    this.chromeWindow.addEventListener('SSWindowClosing', handleWindowClosed);

    let surveyDuration = Services.prefs.getIntPref(PREF_SURVEY_DURATION) * 1000;
    this.surveyEndTimer = setTimeout(() => {
      this.maybeNotifyHeartbeat('SurveyExpired');
      this.notificationBox.removeNotification(this.notice);
    }, surveyDuration);
  }

  maybeNotifyHeartbeat(name, data={}) {
    Log.log('heartbeat event', name, data);

    if (this.pingSent) {
      Log.warn('Heartbeat event recieved after Telemetry ping sent. name:', name, 'data:', data);
    }

    let timestamp = Date.now();
    let sendPing = false;

    switch (name) {
      case 'NotificationOffered':
        this.surveyResults.flowId = this.options.flowId;
        this.surveyResults.offeredTS = timestamp;
        break;
      case 'LearnMore':
        if (!this.surveyResults.learnMoreTS) {
          this.surveyResults.learnMoreTS = timestamp;
        }
        break;
      case 'Engaged':
        this.surveyResults.engagedTS = timestamp;
        break;
      case 'Voted':
        this.surveyResults.votedTS = timestamp;
        this.surveyResults.score = data.score;
        break;
      case 'SurveyExpired':
        this.surveyResults.expiredTS = timestamp;
        break;
      case 'NotificationClosed':
        this.surveyResults.closedTS = timestamp;
        sendPing = true;
        break;
      case 'WindowClosed':
        this.surveyResults.windowClosedTS = timestamp;
        sendPing = true;
        break;
      default:
        Log.error('Unrecognized Heartbeat event:', name);
        break;
    }

    data.timestamp = timestamp;
    data.flowId = this.options.flowId;
    this.events.emit(name, data);

    if (!sendPing) {
      return;
    }

    // Send the ping to Telemetry
    let payload = Object.assign({version: 1}, this.surveyResults);
    for (let meta of ['surveyId', 'surveyVersion', 'testing']) {
      if (this.options.hasOwnProperty(meta)) {
        payload[meta] = this.options[meta];
      }
    }

    Log.debug('Sending payload to Telemetry: name:', name, 'payload:', payload);

    TelemetryController.submitExternalPing('heartbeat', payload, {
      addClientId: true,
      addEnvironment: true,
    });

    // only for testing
    this.events.emit('TelemetrySent', payload);

    // Survey is complete, clear out the expiry timer & survey configuration
    if (this.surveyEndTimer) {
      clearTimeout(this.surveyEndTimer);
      this.surveyEndTimer = null;
    }

    this.pingSent = true;
    this.surveyResults = null;
  }

  userEngaged(/* engagementParams */) {
    // Make the heartbeat icon pulse twice
    this.notice.label = this.options.thankyouMessage;
    this.messageImage.classList.remove('pulse-onshow');
    this.messageImage.classList.add('pulse-twice');

    // Remove all the children of the notice (rating container, and the flex)
    while (this.notice.firstChild) {
      this.notice.removeChild(this.notice.firstChild);
    }

    setTimeout(() => {
      this.notificationBox.removeNotification(this.notice);
    }, 3000);
  }
};
<commit_msg>Re-order appending elements to doc frag<commit_after>const {Cu} = require('chrome');

Cu.import('resource://gre/modules/Services.jsm'); /* globals Services */
Cu.import('resource://gre/modules/TelemetryController.jsm'); /* globals TelemetryController */
Cu.import('resource://gre/modules/Timer.jsm'); /* globals setTimeout, clearTimeout */

Cu.importGlobalProperties(['URL']); /* globals URL */

const {Log} = require('./Log.js');
const {EventEmitter} = require('./EventEmitter.js');

const PREF_SURVEY_DURATION = 'browser.uitour.surveyDuration';

/**
 * Show the Heartbeat UI to request user feedback. This function reports back to the
 * caller using |notify|. The notification event name reflects the current status the UI
 * is in (either "Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed",
 * "Heartbeat:LearnMore", "Heartbeat:Engaged", "Heartbeat:Voted",
 * "Heartbeat:SurveyExpired" or "Heartbeat:WindowClosed").
 * When a "Heartbeat:Voted" event is notified
 * the data payload contains a |score| field which holds the rating picked by the user.
 * Please note that input parameters are already validated by the caller.
 *
 * @param chromeWindow
 *        The chrome window that the heartbeat notification is displayed in.
 * @param {Object} options Options object.
 * @param {String} options.message
 *        The message, or question, to display on the notification.
 * @param {String} options.thankyouMessage
 *        The thank you message to display after user votes.
 * @param {String} options.flowId
 *        An identifier for this rating flow. Please note that this is only used to
 *        identify the notification box.
 * @param {String} [options.engagementButtonLabel=null]
 *        The text of the engagement button to use instad of stars. If this is null
 *        or invalid, rating stars are used.
 * @param {String} [options.engagementURL=null]
 *        The engagement URL to open in a new tab once user has engaged. If this is null
 *        or invalid, no new tab is opened.
 * @param {String} [options.learnMoreMessage=null]
 *        The label of the learn more link. No link will be shown if this is null.
 * @param {String} [options.learnMoreURL=null]
 *        The learn more URL to open when clicking on the learn more link. No learn more
 *        will be shown if this is an invalid URL.
 * @param {boolean} [options.privateWindowsOnly=false]
 *        Whether the heartbeat UI should only be targeted at a private window (if one exists).
 *        No notifications should be fired when this is true.
 * @param {String} [options.surveyId]
 *        An ID for the survey, reflected in the Telemetry ping.
 * @param {Number} [options.surveyVersion]
 *        Survey's version number, reflected in the Telemetry ping.
 * @param {boolean} [options.testing]
 *        Whether this is a test survey, reflected in the Telemetry ping.
 */
exports.Heartbeat = class {
  constructor(chromeWindow, options) {
    Log.debug('Showing new Heartbeat', options);
    this.chromeWindow = chromeWindow;
    this.options = options;

    this.events = new EventEmitter(this.options.sandbox);
    this.surveyResults = {};

    this.buttons = null;

    if (this.options.engagementButtonLabel) {
      this.buttons = [{
        label: this.options.engagementButtonLabel,
        callback: () => {
          // Let the consumer know user engaged.
          this.maybeNotifyHeartbeat('Engaged');

          this.userEngaged(new Map([
            ['type', 'button'],
            ['flowid', this.options.flowId]
          ]));

          // Return true so that the notification bar doesn't close itself since
          // we have a thank you message to show.
          return true;
        },
      }];
    }

    this.notificationBox = this.chromeWindow.document.querySelector('#high-priority-global-notificationbox');
    this.notice = this.notificationBox.appendNotification(
      this.options.message,
      'heartbeat-' + this.options.flowId,
      'chrome://browser/skin/heartbeat-icon.svg',
      this.notificationBox.PRIORITY_INFO_HIGH,
      this.buttons,
      eventType => {
        if (eventType !== 'removed') {
          return;
        }
        this.maybeNotifyHeartbeat('NotificationClosed');
      }
    );

    // Holds the rating UI
    let frag = this.chromeWindow.document.createDocumentFragment();

    // Build the heartbeat stars
    const numStars = this.options.engagementButtonLabel ? 0 : 5;
    let ratingContainer = this.chromeWindow.document.createElement('hbox');
    ratingContainer.id = 'star-rating-container';

    for (let i = 0; i < numStars; i++) {
      // create a star rating element
      let ratingElement = this.chromeWindow.document.createElement('toolbarbutton');

      // style it
      let starIndex = numStars - i;
      ratingElement.className = 'plain star-x';
      ratingElement.id = 'star' + starIndex;
      ratingElement.setAttribute('data-score', starIndex);

      // Add the click handler
      ratingElement.addEventListener('click', ev => {
        let rating = parseInt(ev.target.getAttribute('data-score'));
        this.maybeNotifyHeartbeat('Voted', {score: rating});
        this.userEngaged({type: 'stars', score: rating, flowId: this.options.flowId});
      });

      ratingContainer.appendChild(ratingElement);
    }

    frag.appendChild(ratingContainer);

    this.messageImage = this.chromeWindow.document.getAnonymousElementByAttribute(this.notice, 'anonid', 'messageImage');
    this.messageImage.classList.add('heartbeat', 'pulse-onshow');

    this.messageText = this.chromeWindow.document.getAnonymousElementByAttribute(this.notice, 'anonid', 'messageText');
    this.messageText.classList.add('heartbeat');

    // Make sure the stars are not pushed to the right by the spacer.
    let rightSpacer = this.chromeWindow.document.createElement('spacer');
    rightSpacer.flex = 20;
    frag.appendChild(rightSpacer);

    // collapse the space before the stars
    this.messageText.flex = 0;
    let leftSpacer = this.messageText.nextSibling;
    leftSpacer.flex = 0;

    // Add Learn More Link
    if (this.options.learnMoreMessage && this.options.learnMoreURL) {
      let learnMore = this.chromeWindow.document.createElement('label');
      learnMore.className = 'text-link';
      learnMore.href = this.options.learnMoreURL;
      learnMore.setAttribute('value', this.options.learnMoreMessage);
      learnMore.addEventListener('click', () => this.maybeNotifyHeartbeat('LearnMore'));
      frag.appendChild(learnMore);
    }

    // Append the fragment and apply the styling
    this.notice.appendChild(frag);
    this.notice.classList.add('heartbeat');

    // Let the consumer know the notification was shown.
    this.maybeNotifyHeartbeat('NotificationOffered');

    let handleWindowClosed = () => {
      this.maybeNotifyHeartbeat('WindowClosed');
      this.chromeWindow.removeEventListener('SSWindowClosing', handleWindowClosed);
    };
    this.chromeWindow.addEventListener('SSWindowClosing', handleWindowClosed);

    let surveyDuration = Services.prefs.getIntPref(PREF_SURVEY_DURATION) * 1000;
    this.surveyEndTimer = setTimeout(() => {
      this.maybeNotifyHeartbeat('SurveyExpired');
      this.notificationBox.removeNotification(this.notice);
    }, surveyDuration);
  }

  maybeNotifyHeartbeat(name, data={}) {
    Log.log('heartbeat event', name, data);

    if (this.pingSent) {
      Log.warn('Heartbeat event recieved after Telemetry ping sent. name:', name, 'data:', data);
    }

    let timestamp = Date.now();
    let sendPing = false;

    switch (name) {
      case 'NotificationOffered':
        this.surveyResults.flowId = this.options.flowId;
        this.surveyResults.offeredTS = timestamp;
        break;
      case 'LearnMore':
        if (!this.surveyResults.learnMoreTS) {
          this.surveyResults.learnMoreTS = timestamp;
        }
        break;
      case 'Engaged':
        this.surveyResults.engagedTS = timestamp;
        break;
      case 'Voted':
        this.surveyResults.votedTS = timestamp;
        this.surveyResults.score = data.score;
        break;
      case 'SurveyExpired':
        this.surveyResults.expiredTS = timestamp;
        break;
      case 'NotificationClosed':
        this.surveyResults.closedTS = timestamp;
        sendPing = true;
        break;
      case 'WindowClosed':
        this.surveyResults.windowClosedTS = timestamp;
        sendPing = true;
        break;
      default:
        Log.error('Unrecognized Heartbeat event:', name);
        break;
    }

    data.timestamp = timestamp;
    data.flowId = this.options.flowId;
    this.events.emit(name, data);

    if (!sendPing) {
      return;
    }

    // Send the ping to Telemetry
    let payload = Object.assign({version: 1}, this.surveyResults);
    for (let meta of ['surveyId', 'surveyVersion', 'testing']) {
      if (this.options.hasOwnProperty(meta)) {
        payload[meta] = this.options[meta];
      }
    }

    Log.debug('Sending payload to Telemetry: name:', name, 'payload:', payload);

    TelemetryController.submitExternalPing('heartbeat', payload, {
      addClientId: true,
      addEnvironment: true,
    });

    // only for testing
    this.events.emit('TelemetrySent', payload);

    // Survey is complete, clear out the expiry timer & survey configuration
    if (this.surveyEndTimer) {
      clearTimeout(this.surveyEndTimer);
      this.surveyEndTimer = null;
    }

    this.pingSent = true;
    this.surveyResults = null;
  }

  userEngaged(/* engagementParams */) {
    // Make the heartbeat icon pulse twice
    this.notice.label = this.options.thankyouMessage;
    this.messageImage.classList.remove('pulse-onshow');
    this.messageImage.classList.add('pulse-twice');

    // Remove all the children of the notice (rating container, and the flex)
    while (this.notice.firstChild) {
      this.notice.removeChild(this.notice.firstChild);
    }

    setTimeout(() => {
      this.notificationBox.removeNotification(this.notice);
    }, 3000);
  }
};
<|endoftext|>