<commit_before>
renderer = {
  add_build: function(build) {
    var self = this;
    row = self.create_build_row( build );

    row[row.length-1].addClass( 'divide' );

    $.each( row, function(i,r) {
      $( '#builds' ).append( row[i] );
    } );

    if ( self.lastSuccessfulBuild && ( self.lastSuccessfulBuild.number == build.number ) ) {
      $( '#latest-stable' ).append( row[0].clone() );
    }

    row = $( '.build-' + build.number );
    row.removeClass( 'result-unknown' );

    column = $( '.build-summary.build-' + build.number ).find( 'td.result' );

    if ( build.result == 'SUCCESS' ) {
      row.addClass( 'build-success' );
      column.append(
        $( 'Passed' )
      );
    } else if ( build.result == 'FAILURE' ) {
      row.addClass( 'build-failure' );
      column.append(
        $( 'Failed' )
      );
    }  else if ( build.result == 'ABORTED' ) {
      row.addClass( 'build-aborted' );
      row.find( 'td *' ).hide();
      row.find( 'td .number' ).show();
      row.find( 'td .number a' ).show();
      $( '.build-' + build.number + '.build-details td *' ).hide();
      $( '.build-' + build.number + '.build-details' ).addClass( 'hidden' );
    }  else if ( build.building ) {
      row.addClass( 'build-building' );
      column.append(
        $( 'Building' )
      );
    }

    var duration;

    if ( build.building ) {
      duration = new Date() - new Date( build.timestamp );
    } else {
      duration = build.duration;
    }

    duration = Math.floor( duration / ( 60 * 1000 ) );

    column.append( ': ' + duration + ' min' );

    // if ( build.building ) {
    //   column.append( $( '
' ).append(
    //       $( '
boot.log
'  ),
    //       $( '
output.log
'  ),
    //       $( '
server.log
'  )
    //     )
    //   );
    // }

    self.populate_artifacts( build );
    //self.update_artifacts( build );

  },

  populate_artifacts: function(build) {
    var self = this;

    if ( build.result != 'SUCCESS' ) {
      return;
    }

    //Binary

    binary_column = $( '.build-summary.build-' + build.number ).find( 'td.binary' );
    ul = $( '
' );
    ul.append( $( '
Binary ZIP
' ) );
    binary_column.append( ul );

    // Docs

    // docs_column = $( '.build-summary.build-' + build.number ).find( 'td.docs' );
    // ul = $( '
' );
    // ul.append( $( '
Browse HTML
' ) );
    // ul.append( $( '
PDF
' ) );
    // ul.append( $( '
ePub
' ) );
    // ul.append( $( '
Java API Docs
' ) );
    // ul.append( $( '
Gem RDocs
' ) );

    // docs_column.append( ul );

  },

  update_artifacts: function(build) {
    if ( build.result != 'SUCCESS' ) {
      return;
    }

    $( '.build-summary.build-' + build.number ).find( 'li.artifact.newdocs' ).hide();

    $.get( '/builds/' + build.number + '/published-artifacts.json',
           function(data) {
               $( '.build-summary.build-' + build.number ).find( 'li.artifact' ).each( function(idx, li) {
                   href = $( li ).find( 'a' ).attr( 'href' );
                   artifact_name = href.match( new RegExp( "/([^/]*)/?$" ) )[1];
                   for(i=0; i < data.length; i++) {
                       url = data[i]
                       if (url.match( new RegExp( artifact_name ) )) {
                           $( li ).show();
                           return;
                       }
                   }
                   $( li ).hide();
               } );
           },
         'json' );
  },

  create_build_row: function(build) {
    var self = this;
    row = $( '
' ).append(
            $( '' ).append(
              $( '' + build.number + '' ),
              $( '' + self.build_date( build ) + '' ),
              $( '' + self.build_time( build ) + '' )
            ),
            $( '' ),
            //$( '' ),
            $( '' ),
            $( '' )
          );

    if ( self.build_sha1( build ) ) {
      row.find( '.build-info' ).append(
        $( '' ).append(
          $('' + self.build_sha1_short( build ) + '' )
        )
      );
      row.find( '.git').append(
        $( '
' ).append(
          $( '
').append(
            $( 'Commits' )
          ),
          $( '
').append(
            $( 'Tree' )
          )
        )
      );
    }

    details_row = $( '
' ).append(
      $( '' )
    );

    if ( build.result == 'FAILURE' ) {
      if ( build.culprits.length > 0 ) {
        names = $.map( build.culprits, function(each,i) {
          return each.fullName;
        } );
        details_row.find( 'td' ).append( "Possible culprits: " + names.join( ', ' ) );
      }
    }

    if ( build.building ) {
      row.addClass( 'build-building' );
      details_row.addClass( 'build-building' );
      $('#build-currently-building').append( $( 'A build is currently in progress.' ) );
    }

    return [ row, details_row ];
  },

  build_date: function(build) {
     date = new Date( build.timestamp );
     return date.format( "dd mmmm yyyy" );
  },

  build_time: function(build) {
     date = new Date( build.timestamp );
     return date.format( "HH:MM" ) + ' US Eastern';
  },

  build_sha1: function(build) {
    if ( build.actions && build.actions.length >= 2 && build.actions[1].lastBuiltRevision ) {
      return build.actions[1].lastBuiltRevision.SHA1;
    }
    return null;
  },

  build_sha1_short: function(build) {
    if ( build.actions && build.actions.length >= 2 ) {
      return build.actions[1].lastBuiltRevision.SHA1.substring(0,8);
    }
    return null;
  },

  locate_artifact: function(build, filename) {
    result = null;
    var filename_regexp = new RegExp( filename );
    $.each( build.artifacts, function(i, artifact) {
      if ( artifact.relativePath.match( filename_regexp )  ) {
        result = artifact;
        return false;
      }
    } );
    return result;
  }

};

j = new Jenkins( renderer, 'http://projectodd.ci.cloudbees.com', 'immutant-incremental', [] );

<commit_msg>Fix aborted builds display.<commit_after>
renderer = {
  add_build: function(build) {
    var self = this;
    row = self.create_build_row( build );

    row[row.length-1].addClass( 'divide' );

    $.each( row, function(i,r) {
      $( '#builds' ).append( row[i] );
    } );

    if ( self.lastSuccessfulBuild && ( self.lastSuccessfulBuild.number == build.number ) ) {
      $( '#latest-stable' ).append( row[0].clone() );
    }

    row = $( '.build-' + build.number );
    row.removeClass( 'result-unknown' );

    column = $( '.build-summary.build-' + build.number ).find( 'td.result' );

    if ( build.result == 'SUCCESS' ) {
      row.addClass( 'build-success' );
      column.append(
        $( 'Passed' )
      );
    } else if ( build.result == 'FAILURE' ) {
      row.addClass( 'build-failure' );
      column.append(
        $( 'Failed' )
      );
    }  else if ( build.result == 'ABORTED' ) {
      row.addClass( 'build-aborted' );
      row.find( 'td *' ).hide();
      row.find( 'td .number' ).show();
      row.find( 'td .number a' ).show();
      column.append(
        $( 'Aborted' )
      );
    }  else if ( build.building ) {
      row.addClass( 'build-building' );
      column.append(
        $( 'Building' )
      );
    }

    var duration;

    if ( build.building ) {
      duration = new Date() - new Date( build.timestamp );
    } else {
      duration = build.duration;
    }

    duration = Math.floor( duration / ( 60 * 1000 ) );

    column.append( ': ' + duration + ' min' );

    // if ( build.building ) {
    //   column.append( $( '
' ).append(
    //       $( '
boot.log
'  ),
    //       $( '
output.log
'  ),
    //       $( '
server.log
'  )
    //     )
    //   );
    // }

    self.populate_artifacts( build );
    //self.update_artifacts( build );

  },

  populate_artifacts: function(build) {
    var self = this;

    if ( build.result != 'SUCCESS' ) {
      return;
    }

    //Binary

    binary_column = $( '.build-summary.build-' + build.number ).find( 'td.binary' );
    ul = $( '
' );
    ul.append( $( '
Binary ZIP
' ) );
    binary_column.append( ul );

    // Docs

    // docs_column = $( '.build-summary.build-' + build.number ).find( 'td.docs' );
    // ul = $( '
' );
    // ul.append( $( '
Browse HTML
' ) );
    // ul.append( $( '
PDF
' ) );
    // ul.append( $( '
ePub
' ) );
    // ul.append( $( '
Java API Docs
' ) );
    // ul.append( $( '
Gem RDocs
' ) );

    // docs_column.append( ul );

  },

  update_artifacts: function(build) {
    if ( build.result != 'SUCCESS' ) {
      return;
    }

    $( '.build-summary.build-' + build.number ).find( 'li.artifact.newdocs' ).hide();

    $.get( '/builds/' + build.number + '/published-artifacts.json',
           function(data) {
               $( '.build-summary.build-' + build.number ).find( 'li.artifact' ).each( function(idx, li) {
                   href = $( li ).find( 'a' ).attr( 'href' );
                   artifact_name = href.match( new RegExp( "/([^/]*)/?$" ) )[1];
                   for(i=0; i < data.length; i++) {
                       url = data[i]
                       if (url.match( new RegExp( artifact_name ) )) {
                           $( li ).show();
                           return;
                       }
                   }
                   $( li ).hide();
               } );
           },
         'json' );
  },

  create_build_row: function(build) {
    var self = this;
    row = $( '
' ).append(
            $( '' ).append(
              $( '' + build.number + '' ),
              $( '' + self.build_date( build ) + '' ),
              $( '' + self.build_time( build ) + '' )
            ),
            $( '' ),
            //$( '' ),
            $( '' ),
            $( '' )
          );

    if ( self.build_sha1( build ) ) {
      row.find( '.build-info' ).append(
        $( '' ).append(
          $('' + self.build_sha1_short( build ) + '' )
        )
      );
      row.find( '.git').append(
        $( '
' ).append(
          $( '
').append(
            $( 'Commits' )
          ),
          $( '
').append(
            $( 'Tree' )
          )
        )
      );
    }

    details_row = $( '
' ).append(
      $( '' )
    );

    if ( build.result == 'FAILURE' ) {
      if ( build.culprits.length > 0 ) {
        names = $.map( build.culprits, function(each,i) {
          return each.fullName;
        } );
        details_row.find( 'td' ).append( "Possible culprits: " + names.join( ', ' ) );
      }
    }

    if ( build.building ) {
      row.addClass( 'build-building' );
      details_row.addClass( 'build-building' );
      $('#build-currently-building').append( $( 'A build is currently in progress.' ) );
    }

    return [ row, details_row ];
  },

  build_date: function(build) {
     date = new Date( build.timestamp );
     return date.format( "dd mmmm yyyy" );
  },

  build_time: function(build) {
     date = new Date( build.timestamp );
     return date.format( "HH:MM" ) + ' US Eastern';
  },

  build_sha1: function(build) {
    if ( build.actions && build.actions.length >= 2 && build.actions[1].lastBuiltRevision ) {
      return build.actions[1].lastBuiltRevision.SHA1;
    }
    return null;
  },

  build_sha1_short: function(build) {
    if ( build.actions && build.actions.length >= 2 ) {
      return build.actions[1].lastBuiltRevision.SHA1.substring(0,8);
    }
    return null;
  },

  locate_artifact: function(build, filename) {
    result = null;
    var filename_regexp = new RegExp( filename );
    $.each( build.artifacts, function(i, artifact) {
      if ( artifact.relativePath.match( filename_regexp )  ) {
        result = artifact;
        return false;
      }
    } );
    return result;
  }

};

j = new Jenkins( renderer, 'http://projectodd.ci.cloudbees.com', 'immutant-incremental', [] );

<|endoftext|>


<commit_before>/* load and initialize using this closure (add var before "db" on line 6, to privatize scope)
  (function(){
    var url = 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.js';
    document.head.appendChild(document.createElement('script')).src=url;
    function initialize(){
      if (
          typeof(Dashboard) !== 'undefined' &&
          typeof(bootbox) !== 'undefined' &&
          typeof(ko) !== 'undefined' &&
          ko.dataFor(app) !== undefined
          ) {
          db = new Dashboard(ko.dataFor(app), ko, bootbox);
      } else {
          setTimeout(function(){initialize()}, 100);
      }
    }
    initialize();
  })();
*/

function Dashboard(application, ko, bootbox) {
  var self = this;
  this.app = application;
  console.log(this.app);
  this.Tools = {
    bobParse: function(raw){
      var clients = [], data = {};
      // exit on null entry
      if ( raw === null ) return;
      for (var cells = raw.replace(/Basic[\r\n\s\ta-z]*Partner ID[\t\s]+/i, '').split(/\t/),
        client = {}, cols = ['Account ID',  'Account Start Date',  'Account Bucket',  'Account Spend',
        'Date of Next Ticket',  'Engaged Status',  'Timezone',  'Welcome Call',  'Keyword Research',
        'Local Profile Tab',  'Obtain or Create Google Logins',  'Create and Verify Google Profile',
        'Obtain Site Access',  'Implement Onsite Edits',  'Create Yahoo Account',  'Create and Verify Yahoo Profile',
        'Create and Verify Bing Profile',  'Install Analytics',  'Company Name',  'Contact Name',  'Website URL',
        'Email Address',  'Phone Number',  'Partner ID',];
        cells.length>0;){
        var keys = Object.keys(client).length;
        if (keys<cols.length-1){
          var param = cols[keys];
          client[param] = cells[0];
          cells.shift();
        } else {
          client.count = keys;
          clients.push(client);
          client = {};
        }
      }
      for (var i=0;i<clients.length;i++){
        var date = clients[i]['Date of Next Ticket'];
        if (data[date] === undefined) data[date] = [];
        data[date].push(clients[i]);
      }
      return data;
    },
    bobCal: function(){},
    todoistExport: function (){
      var view, data,
      taskDate = function(day){
        var days = [new Date(),new Date(),new Date()];
        if (days[0].getDay() == 5) {
          days[1].setDate(days[0].getDate()+3);
          days[2].setDate(days[0].getDate()+4);
        } else if (days[0].getDay() == 4) {
          days[1].setDate(days[0].getDate()+1);
          days[2].setDate(days[0].getDate()+4);
        } else {
          days[1].setDate(days[0].getDate()+1);
          days[2].setDate(days[0].getDate()+2);
        }
        return 'date '+days[day].toDateString().substring(4);
      },
      textarea = document.createElement('textarea'), text = '',
      modal = bootbox.alert({
          size: 'large',
          message: 'placeholder',
          callback: function(){},
      })[0].children[0].children[0].children[0].children[1];
      if (ko.dataFor(window.app) !== undefined){
        view = ko.dataFor(window.app).contentViewModel();
      } else if (ko.dataFor(self.app) !== undefined){
        view = ko.dataFor(self.app).contentViewModel();
      }
      if (! view.hasOwnProperty('customersDataTable')){
        modal.remove();
        bootbox.alert('No client data available in active view.
Try running this tool after searching for a client.');
        return 1;
      } else {
        if (typeof(view.customersDataTable) === undefined) {
          console.log('No customersDataTable');
          return;
        } else {
          data = view.customersDataTable.dataSource()[0];
        }
        text += data.CustomerId+' - '+data.Name;
        text += '[[NOTE]]: https://launchpad.boostability.com/#customerservice/customersearch/'+data.CustomerId;
        text += '\n...'+data.CustomerId+' Welcome Call [['+taskDate(2)+']]';
        text += '\n......'+data.CustomerId+' 1st Welcome Call [['+taskDate(0)+']]';
        text += '\n......'+data.CustomerId+' 1st Welcome Email [['+taskDate(0)+']]';
        text += '\n......'+data.CustomerId+' 2nd Welcome Call [['+taskDate(1)+']]';
        text += '\n......'+data.CustomerId+' 2nd Welcome Email [['+taskDate(1)+']]';
        text += '\n......'+data.CustomerId+' 3rd Welcome Call [['+taskDate(2)+']]';
        text += '\n......'+data.CustomerId+' 3rd Welcome Email [['+taskDate(2)+']]';
        text += '\n...'+data.CustomerId+' Keyword Research';
        text += '\n...'+data.CustomerId+' Local Profile';
        textarea.value = text;
        modal.parentNode.parentNode.parentNode.style.width = "65em";
        modal.innerHTML = '
'+text+'
';
      }
    },
    openClient: function(client) {
      self.app.showManageCustomer(client);
    },
    closeClient: function() {
      var buttons = document.getElementsByClassName('close'),
      i = buttons.length - 1;
      if (i >= 1) buttons[i - 1].click();
    },
    createElement: function(parent, tag, attributes) {
      var element = parent.appendChild(document.createElement(tag));
      for (var attribute in attributes) {
        if (attributes.hasOwnProperty(attribute)) {
          element[attribute] = attributes[attribute];
        }
      }
      return element;
    },
  };
  this.Tickets = {
    calendar: function(month, year, data) {
      if ( typeof(month) === Object ) {
        data = month;
        month = null;
      }
      console.log(data);
      this.current_date = new Date();
      this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
      this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
      this.month = (isNaN(month) || month === null) ? this.current_date.getMonth() : month;
      this.year = (isNaN(year) || year === null) ? this.current_date.getFullYear() : year;
      console.log((this.month+1)+'/'+day+'/'+this.year);
      this.draw = function(ele){
          if (this.month > 11) {
            this.month -= 12;
            this.year += 1;
          } else if (month < 0) {
            this.month += 12;
            this.year -= 1;
          }
          // get dates
          var startingDay = new Date(this.year, this.month, 1).getDay(),
          endingDate = new Date(this.year, this.month + 1, 0),
          monthLength = endingDate.getDate(),
          inLastRow = 6 - endingDate.getDay();

          // generate header
          this.table = document.createElement('table');
          this.head = document.createElement('thead');
          this.prev = document.createElement('th');
          this.next = document.createElement('th');
          this.title = document.createElement('th');
          this.days = document.createElement('tr');
          this.body = document.createElement('tbody');
          this.cells = '';
          this.table.setAttribute('class', 'calendar');
          this.table.appendChild(this.head);
          this.head.appendChild(this.prev);
          this.prev.innerHTML = "«";
          this.prev.id = 'prev';
          this.head.appendChild(this.title);
          this.head.appendChild(this.next);
          this.next.innerHTML = "»";
          this.next.id = 'next';
          this.table.appendChild(this.days);
          this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
          this.title.setAttribute('colspan', '5');
          this.days.setAttribute('class', 'header');
          for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
          this.days.innerHTML = this.cells;

          //generate calendar
          var html = '
';
          if ( startingDay ) html += '	';
          for (var day = 1; day <= monthLength; day++) {
          html += '
' + day + '
';
          //if data argument present, append key value for current day
          if (data) {
            var today = (this.month+1)+'/'+day+'/'+this.year;
            if (data[today]) html += '
'+Object.keys(data[today]).length+' Clients
';
          }
          html += '	';
          if ( (day + startingDay) % 7 === 0 && day != monthLength ) html += '

';
          }
          if ( inLastRow ) html += '	';
          html += '
';
          this.table.innerHTML += html;
          if (ele) {
            ele.innerHTML = this.table.outerHTML;
          } else {
            return this.table.outerHTML;
          }
          // save reference to dom node in object
          this.node = ele;
          // save reference to calendar object for enclosure scope
          var cal = this;
              (function(){
            // save calendar object reference for listeners
            var self = this;
                self.prev.addEventListener('click', function(){
              cal.month--;
                    cal.draw(cal.node);
                });
            self.next.addEventListener('click', function(){
                    cal.month++;
              cal.draw(cal.node);
                });
                document.onkeydown = function(e) {
                  e = e || window.event;
                    if (e.keyCode == '37') {
                    // left arrow
                    cal.month--;
                    cal.draw(cal.node);
                    } else if (e.keyCode == '39') {
                        // right arrow
                        cal.month++;
                    cal.draw(cal.node);
                    }
                };
              })();
        };
    },
    getTickets: function() {
      //get tickets
      if (self.app.contentViewModel() !== undefined){
        if (typeof(self.app.contentViewModel().myTickets) === "function"){
          self.Tickets.ticketArray = self.app.contentViewModel().myTickets();
        }
      } else if (localStorage.getItem('ticketCache') !== null){
        self.Tickets.ticketArray = JSON.parse(localStorage.getItem('ticketCache'));
      }
      //return results
      if (self.Tickets.ticketArray) {
        localStorage.setItem('ticketCache', JSON.stringify(self.Tickets.ticketArray));
        return self.Tickets.ticketArray;
      } else {
        alert('Unable to access tickets at this time!');
        return [];
      }
    },
    getCritical: function() {
      var tickets = this.getTickets(),
      critical = [];
      for (var i = 0; i < tickets.length; i++) {
        if (tickets[i].ScheduledEndDate < new Date()) {
          critical.push(tickets[i]);
        }
      }
      if (tickets.length > 0){
        return critical;
      } else {
        return false;
      }
    },
    countCritical: function() {
      var criticals = self.Tickets.getCritical();
      if (criticals) {
        return 'Critical Tickets: ' + criticals.length + '';
      } else {
        return 'Error loading tickets!';
      }
    },
    sortTickets: function(tickets){
      if (! tickets){
        tickets = this.getTickets();
      }
      var data = {};
      for (var i=0;i<tickets.length;i++){
        var ticket = tickets[i],
        date = new Date(ticket.ScheduledEndDate),
        day = date.getDate()+'/'+(date.getMonth()+1)+'/'+date.getFullYear();
        if (! data[day]){ data[day] = [];}
        data[day].push(ticket);
      }
      return data;
    },
    listTickets: function(tickets){
      var list = document.createElement('ul');
      for (var i=0;i<tickets.length;i++){
        var ticket = tickets[i],
        listItem = document.createElement('li'),
        text = ticket.CompanyName+': '+ticket.TicketTypeName;
        text += ' (Due: '+(ticket.ScheduledEndDate.getMonth()+1);
        text += '/'+ticket.ScheduledEndDate.getDate()+'/';
        text += ticket.ScheduledEndDate.getFullYear()+')';
        listItem.innerHTML = text;
        list.appendChild(listItem);
      }
      return list;
    },
    tryCache: function(){
      try {
        if (self.Tickets.ticketArray) {
          localStorage.setItem('ticketCache', JSON.stringify(self.Tickets.ticketArray));
        }
      } catch(err) {
        setTimeout(function(){self.Tickets.tryCache();}, 1000);
      }
    },
  };
  this.HTML = {};
  this.HTML.dashboard = this.Tools.createElement(document.body, 'div', {
        className: 'navbar navbar-inverse navbar-fixed-bottom',
        id: 'dashboard',
  });
  this.HTML.style = this.Tools.createElement(this.HTML.dashboard, 'link', {
    rel: 'stylesheet',
    type: 'text/css',
    href: 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.css',
  }),
  this.HTML.nav = this.Tools.createElement(this.HTML.dashboard, 'ul', {
    className: 'nav navbar-nav',
  }),
  this.HTML.tools = this.Tools.createElement(this.HTML.nav, 'li', {
    innerHTML: 'Tools',
    className: 'dropup',
  }),
  this.HTML.toolsMenu = this.Tools.createElement(this.HTML.tools, 'ul', {
    className: 'dropdown-menu',
  }),
  this.HTML.todoistExport = this.Tools.createElement(this.HTML.toolsMenu, 'li', {
    innerHTML: 'Todoist Export',
    onclick: function(){
      self.Tools.todoistExport();
    },
  }),
  this.HTML.tickets = this.Tools.createElement(this.HTML.nav, 'li', {
    innerHTML: 'Tickets',
    className: 'dropup',
  }),
  this.HTML.ticketsMenu = this.Tools.createElement(this.HTML.tickets, 'ul', {
    className: 'dropdown-menu',
  }),
  this.HTML.ticketCalendar = this.Tools.createElement(this.HTML.ticketsMenu, 'li', {
    innerHTML: 'Calendar',
    onclick: function(){
      var node = bootbox.alert('none')[0].children[0].children[0].children[0],
      data = self.Tickets.sortTickets(),
      calendar = new self.Tickets.calendar(data);
      console.log(data);
      calendar.draw(node);
    }
  }),
  this.HTML.critical = this.Tools.createElement(this.HTML.nav, 'li', {
    innerHTML: self.Tickets.countCritical(),
    id: 'critical',
    onclick: function(){
      self.HTML.critical.innerHTML = self.Tickets.countCritical();
    },
  });

  window.beforeunload = function(){
    self.Tickets.tryCache();
  };
  window.onhashchange = function(){
    if (window.location.href == 'https://launchpad.boostability.com/#/customerservice/activetickets'){
      self.Tickets.tryCache();
    }
  };
  if (localStorage.getItem('ticketCache') !== null) self.Tickets.ticketArray = JSON.parse(localStorage.getItem('ticketCache'));
}
<commit_msg>Update dashboard.js<commit_after>/* load and initialize using this closure (add var before "db" on line 6, to privatize scope)
  (function(){
    var url = 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.js';
    document.head.appendChild(document.createElement('script')).src=url;
    function initialize(){
      if (
          typeof(Dashboard) !== 'undefined' &&
          typeof(bootbox) !== 'undefined' &&
          typeof(ko) !== 'undefined' &&
          ko.dataFor(app) !== undefined
          ) {
          db = new Dashboard(ko.dataFor(app), ko, bootbox);
      } else {
          setTimeout(function(){initialize()}, 100);
      }
    }
    initialize();
  })();
*/

function Dashboard(application, ko, bootbox) {
  var self = this;
  this.app = application;
  console.log(this.app);
  this.Tools = {
    bobParse: function(raw){
      var clients = [], data = {};
      // exit on null entry
      if ( raw === null ) return;
      for (var cells = raw.replace(/Basic[\r\n\s\ta-z]*Partner ID[\t\s]+/i, '').split(/\t/),
        client = {}, cols = ['Account ID',  'Account Start Date',  'Account Bucket',  'Account Spend',
        'Date of Next Ticket',  'Engaged Status',  'Timezone',  'Welcome Call',  'Keyword Research',
        'Local Profile Tab',  'Obtain or Create Google Logins',  'Create and Verify Google Profile',
        'Obtain Site Access',  'Implement Onsite Edits',  'Create Yahoo Account',  'Create and Verify Yahoo Profile',
        'Create and Verify Bing Profile',  'Install Analytics',  'Company Name',  'Contact Name',  'Website URL',
        'Email Address',  'Phone Number',  'Partner ID',];
        cells.length>0;){
        var keys = Object.keys(client).length;
        if (keys<cols.length-1){
          var param = cols[keys];
          client[param] = cells[0];
          cells.shift();
        } else {
          client.count = keys;
          clients.push(client);
          client = {};
        }
      }
      for (var i=0;i<clients.length;i++){
        var date = clients[i]['Date of Next Ticket'];
        if (data[date] === undefined) data[date] = [];
        data[date].push(clients[i]);
      }
      return data;
    },
    bobCal: function(){},
    todoistExport: function (){
      var view, data,
      taskDate = function(day){
        var days = [new Date(),new Date(),new Date()];
        if (days[0].getDay() == 5) {
          days[1].setDate(days[0].getDate()+3);
          days[2].setDate(days[0].getDate()+4);
        } else if (days[0].getDay() == 4) {
          days[1].setDate(days[0].getDate()+1);
          days[2].setDate(days[0].getDate()+4);
        } else {
          days[1].setDate(days[0].getDate()+1);
          days[2].setDate(days[0].getDate()+2);
        }
        return 'date '+days[day].toDateString().substring(4);
      },
      textarea = document.createElement('textarea'), text = '',
      modal = bootbox.alert({
          size: 'large',
          message: 'placeholder',
          callback: function(){},
      })[0].children[0].children[0].children[0].children[1];
      if (ko.dataFor(window.app) !== undefined){
        view = ko.dataFor(window.app).contentViewModel();
      } else if (ko.dataFor(self.app) !== undefined){
        view = ko.dataFor(self.app).contentViewModel();
      }
      if (! view.hasOwnProperty('customersDataTable')){
        modal.remove();
        bootbox.alert('No client data available in active view.
Try running this tool after searching for a client.');
        return 1;
      } else {
        if (typeof(view.customersDataTable) === undefined) {
          console.log('No customersDataTable');
          return;
        } else {
          data = view.customersDataTable.dataSource()[0];
        }
        text += data.CustomerId+' - '+data.Name;
        text += '[[NOTE]]: https://launchpad.boostability.com/#customerservice/customersearch/'+data.CustomerId;
        text += '\n...'+data.CustomerId+' Welcome Call [['+taskDate(2)+']]';
        text += '\n......'+data.CustomerId+' 1st Welcome Call [['+taskDate(0)+']]';
        text += '\n......'+data.CustomerId+' 1st Welcome Email [['+taskDate(0)+']]';
        text += '\n......'+data.CustomerId+' 2nd Welcome Call [['+taskDate(1)+']]';
        text += '\n......'+data.CustomerId+' 2nd Welcome Email [['+taskDate(1)+']]';
        text += '\n......'+data.CustomerId+' 3rd Welcome Call [['+taskDate(2)+']]';
        text += '\n......'+data.CustomerId+' 3rd Welcome Email [['+taskDate(2)+']]';
        text += '\n...'+data.CustomerId+' Keyword Research';
        text += '\n...'+data.CustomerId+' Local Profile';
        textarea.value = text;
        modal.parentNode.parentNode.parentNode.style.width = "65em";
        modal.innerHTML = '
'+text+'
';
      }
    },
    openClient: function(client) {
      self.app.showManageCustomer(client);
    },
    closeClient: function() {
      var buttons = document.getElementsByClassName('close'),
      i = buttons.length - 1;
      if (i >= 1) buttons[i - 1].click();
    },
    createElement: function(parent, tag, attributes) {
      var element = parent.appendChild(document.createElement(tag));
      for (var attribute in attributes) {
        if (attributes.hasOwnProperty(attribute)) {
          element[attribute] = attributes[attribute];
        }
      }
      return element;
    },
  };
  this.Tickets = {
    calendar: function(month, year, data) {
      if ( typeof(month) === 'object' ) {
        data = month;
        month = null;
      }
      console.log(data);
      this.current_date = new Date();
      this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
      this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
      this.month = (isNaN(month) || month === null) ? this.current_date.getMonth() : month;
      this.year = (isNaN(year) || year === null) ? this.current_date.getFullYear() : year;
      console.log((this.month+1)+'/'+day+'/'+this.year);
      this.draw = function(ele){
          if (this.month > 11) {
            this.month -= 12;
            this.year += 1;
          } else if (month < 0) {
            this.month += 12;
            this.year -= 1;
          }
          // get dates
          var startingDay = new Date(this.year, this.month, 1).getDay(),
          endingDate = new Date(this.year, this.month + 1, 0),
          monthLength = endingDate.getDate(),
          inLastRow = 6 - endingDate.getDay();

          // generate header
          this.table = document.createElement('table');
          this.head = document.createElement('thead');
          this.prev = document.createElement('th');
          this.next = document.createElement('th');
          this.title = document.createElement('th');
          this.days = document.createElement('tr');
          this.body = document.createElement('tbody');
          this.cells = '';
          this.table.setAttribute('class', 'calendar');
          this.table.appendChild(this.head);
          this.head.appendChild(this.prev);
          this.prev.innerHTML = "«";
          this.prev.id = 'prev';
          this.head.appendChild(this.title);
          this.head.appendChild(this.next);
          this.next.innerHTML = "»";
          this.next.id = 'next';
          this.table.appendChild(this.days);
          this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
          this.title.setAttribute('colspan', '5');
          this.days.setAttribute('class', 'header');
          for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
          this.days.innerHTML = this.cells;

          //generate calendar
          var html = '
';
          if ( startingDay ) html += '	';
          for (var day = 1; day <= monthLength; day++) {
          html += '
' + day + '
';
          //if data argument present, append key value for current day
          if (data) {
            var today = (this.month+1)+'/'+day+'/'+this.year;
            if (data[today]) html += '
'+Object.keys(data[today]).length+' Clients
';
          }
          html += '	';
          if ( (day + startingDay) % 7 === 0 && day != monthLength ) html += '

';
          }
          if ( inLastRow ) html += '	';
          html += '
';
          this.table.innerHTML += html;
          if (ele) {
            ele.innerHTML = this.table.outerHTML;
          } else {
            return this.table.outerHTML;
          }
          // save reference to dom node in object
          this.node = ele;
          // save reference to calendar object for enclosure scope
          var cal = this;
              (function(){
            // save calendar object reference for listeners
            var self = this;
                self.prev.addEventListener('click', function(){
              cal.month--;
                    cal.draw(cal.node);
                });
            self.next.addEventListener('click', function(){
                    cal.month++;
              cal.draw(cal.node);
                });
                document.onkeydown = function(e) {
                  e = e || window.event;
                    if (e.keyCode == '37') {
                    // left arrow
                    cal.month--;
                    cal.draw(cal.node);
                    } else if (e.keyCode == '39') {
                        // right arrow
                        cal.month++;
                    cal.draw(cal.node);
                    }
                };
              })();
        };
    },
    getTickets: function() {
      //get tickets
      if (self.app.contentViewModel() !== undefined){
        if (typeof(self.app.contentViewModel().myTickets) === "function"){
          self.Tickets.ticketArray = self.app.contentViewModel().myTickets();
        }
      } else if (localStorage.getItem('ticketCache') !== null){
        self.Tickets.ticketArray = JSON.parse(localStorage.getItem('ticketCache'));
      }
      //return results
      if (self.Tickets.ticketArray) {
        localStorage.setItem('ticketCache', JSON.stringify(self.Tickets.ticketArray));
        return self.Tickets.ticketArray;
      } else {
        alert('Unable to access tickets at this time!');
        return [];
      }
    },
    getCritical: function() {
      var tickets = this.getTickets(),
      critical = [];
      for (var i = 0; i < tickets.length; i++) {
        if (tickets[i].ScheduledEndDate < new Date()) {
          critical.push(tickets[i]);
        }
      }
      if (tickets.length > 0){
        return critical;
      } else {
        return false;
      }
    },
    countCritical: function() {
      var criticals = self.Tickets.getCritical();
      if (criticals) {
        return 'Critical Tickets: ' + criticals.length + '';
      } else {
        return 'Error loading tickets!';
      }
    },
    sortTickets: function(tickets){
      if (! tickets){
        tickets = this.getTickets();
      }
      var data = {};
      for (var i=0;i<tickets.length;i++){
        var ticket = tickets[i],
        date = new Date(ticket.ScheduledEndDate),
        day = (date.getMonth()+1)+'/'+date.getDate()+'/'+date.getFullYear();
        if (! data[day]){ data[day] = [];}
        data[day].push(ticket);
      }
      return data;
    },
    listTickets: function(tickets){
      var list = document.createElement('ul');
      for (var i=0;i<tickets.length;i++){
        var ticket = tickets[i],
        listItem = document.createElement('li'),
        text = ticket.CompanyName+': '+ticket.TicketTypeName;
        text += ' (Due: '+(ticket.ScheduledEndDate.getMonth()+1);
        text += '/'+ticket.ScheduledEndDate.getDate()+'/';
        text += ticket.ScheduledEndDate.getFullYear()+')';
        listItem.innerHTML = text;
        list.appendChild(listItem);
      }
      return list;
    },
    tryCache: function(){
      try {
        if (self.Tickets.ticketArray) {
          localStorage.setItem('ticketCache', JSON.stringify(self.Tickets.ticketArray));
        }
      } catch(err) {
        setTimeout(function(){self.Tickets.tryCache();}, 1000);
      }
    },
  };
  this.HTML = {};
  this.HTML.dashboard = this.Tools.createElement(document.body, 'div', {
        className: 'navbar navbar-inverse navbar-fixed-bottom',
        id: 'dashboard',
  });
  this.HTML.style = this.Tools.createElement(this.HTML.dashboard, 'link', {
    rel: 'stylesheet',
    type: 'text/css',
    href: 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.css',
  }),
  this.HTML.nav = this.Tools.createElement(this.HTML.dashboard, 'ul', {
    className: 'nav navbar-nav',
  }),
  this.HTML.tools = this.Tools.createElement(this.HTML.nav, 'li', {
    innerHTML: 'Tools',
    className: 'dropup',
  }),
  this.HTML.toolsMenu = this.Tools.createElement(this.HTML.tools, 'ul', {
    className: 'dropdown-menu',
  }),
  this.HTML.todoistExport = this.Tools.createElement(this.HTML.toolsMenu, 'li', {
    innerHTML: 'Todoist Export',
    onclick: function(){
      self.Tools.todoistExport();
    },
  }),
  this.HTML.tickets = this.Tools.createElement(this.HTML.nav, 'li', {
    innerHTML: 'Tickets',
    className: 'dropup',
  }),
  this.HTML.ticketsMenu = this.Tools.createElement(this.HTML.tickets, 'ul', {
    className: 'dropdown-menu',
  }),
  this.HTML.ticketCalendar = this.Tools.createElement(this.HTML.ticketsMenu, 'li', {
    innerHTML: 'Calendar',
    onclick: function(){
      var node = bootbox.alert('none')[0].children[0].children[0].children[0],
      data = self.Tickets.sortTickets(),
      calendar = new self.Tickets.calendar(data);
      console.log(data);
      calendar.draw(node);
    }
  }),
  this.HTML.critical = this.Tools.createElement(this.HTML.nav, 'li', {
    innerHTML: self.Tickets.countCritical(),
    id: 'critical',
    onclick: function(){
      self.HTML.critical.innerHTML = self.Tickets.countCritical();
    },
  });

  window.beforeunload = function(){
    self.Tickets.tryCache();
  };
  window.onhashchange = function(){
    if (window.location.href == 'https://launchpad.boostability.com/#/customerservice/activetickets'){
      self.Tickets.tryCache();
    }
  };
  if (localStorage.getItem('ticketCache') !== null) self.Tickets.ticketArray = JSON.parse(localStorage.getItem('ticketCache'));
}
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const bodyParser = require('body-parser');
const express = require('express');
const fs = require('fs');
const mongodb = require('mongodb');
const path = require('path');
const sendPostRequest = require('request').post;
const colors = require('colors/safe');

const app = express();
const MongoClient = mongodb.MongoClient;
const port = 5000;
const mongoCreds = require('./auth.json');
const mongoURL = `mongodb://${mongoCreds.user}:${mongoCreds.password}@localhost:27017/`;
const handlers = {};

function makeMessage(text) {
  return `${colors.blue('[store]')} ${text}`;
}

function log(text) {
  console.log(makeMessage(text));
}

function error(text) {
  console.error(makeMessage(text));
}

function failure(response, text) {
  const message = makeMessage(text);
  console.error(message);
  return response.status(500).send(message);
}

function success(response, text) {
  const message = makeMessage(text);
  console.log(message);
  return response.send(message);
}

function mongoConnectWithRetry(delayInMilliseconds, callback) {
  MongoClient.connect(mongoURL, (err, connection) => {
    if (err) {
      console.error(`Error connecting to MongoDB: ${err}`);
      setTimeout(() => mongoConnectWithRetry(delayInMilliseconds, callback), delayInMilliseconds);
    } else {
      log('connected succesfully to mongodb');
      callback(connection);
    }
  });
}

// Keep track of which games have used each stim
function recordStimUse(stimdb, gameid, idList) {
  _.forEach(idList, id => {
    stimdb.update({_id: id}, {
      $push : {games : gameid},
      $inc  : {numGames : 1}
    }, {multi: true}, function(err, items) {
      // do something when done?
    });
  });
}

function serve() {

  mongoConnectWithRetry(2000, (connection) => {

    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true}));

    app.post('/db/insert', (request, response) => {
      if (!request.body) {
        return failure(response, '/db/insert needs post request body');
      }
      log(`got request to insert into ${request.body.colname}`);

      const databaseName = request.body.dbname;
      const collectionName = request.body.colname;
      if (!collectionName) {
        return failure(response, '/db/insert needs collection');
      }
      if (!databaseName) {
        return failure(response, '/db/insert needs database');
      }

      const database = connection.db(databaseName);

      // Add collection if it doesn't already exist
      if (!database.collection(collectionName)) {
        console.log('creating collection ' + collectionName);
        database.createCollection(collectionName);
      }

      const collection = database.collection(collectionName);

      const data = _.omit(request.body, ['colname', 'dbname']);
      // log(`inserting data: ${JSON.stringify(data)}`);
      collection.insert(data, (err, result) => {
        if (err) {
          return failure(response, `error inserting data: ${err}`);
        } else {
          return success(response, `successfully inserted data. result: ${JSON.stringify(result)}`);
        }
      });
    });

    app.post('/db/getstims', (request, response) => {
      if (!request.body) {
        return failure(response, '/db/getstims needs post request body');
      }
      log(`got request to get stims from ${request.body.dbname}/${request.body.colname}`);

      const databaseName = request.body.dbname;
      const collectionName = request.body.colname;
      if (!collectionName) {
        return failure(response, '/db/getstims needs collection');
      }
      if (!databaseName) {
        return failure(response, '/db/getstims needs database');
      }

      const database = connection.db(databaseName);
      const collection = database.collection(collectionName);

      // collection.aggregate([
      // { $group : {_id : "$numGames", count: { $sum: 1 }}}
      //     ], (err, results) => {console.log('counts...'); });

      // get a random sample of stims that haven't appeared more than k times
      collection.aggregate([
        { $addFields : { numGames: { $size: '$games'} } },
        { $sort : { numGames : 1} },
        { $limit : request.body.numTrials }
      ]).toArray( (err, results) => {
        if(err) {
          console.log(err);
        } else {
          console.log('getting results')
          console.log(results);
          recordStimUse(collection, request.body.gameid, _.map(results, '_id'));
          response.send(results);
        }
      });
    });

    app.listen(port, () => {
      log(`running at http://localhost:${port}`);
    });

  });

}

serve();

<commit_msg>remove excess print commands<commit_after>'use strict';

const _ = require('lodash');
const bodyParser = require('body-parser');
const express = require('express');
const fs = require('fs');
const mongodb = require('mongodb');
const path = require('path');
const sendPostRequest = require('request').post;
const colors = require('colors/safe');

const app = express();
const MongoClient = mongodb.MongoClient;
const port = 5000;
const mongoCreds = require('./auth.json');
const mongoURL = `mongodb://${mongoCreds.user}:${mongoCreds.password}@localhost:27017/`;
const handlers = {};

function makeMessage(text) {
  return `${colors.blue('[store]')} ${text}`;
}

function log(text) {
  console.log(makeMessage(text));
}

function error(text) {
  console.error(makeMessage(text));
}

function failure(response, text) {
  const message = makeMessage(text);
  console.error(message);
  return response.status(500).send(message);
}

function success(response, text) {
  const message = makeMessage(text);
  console.log(message);
  return response.send(message);
}

function mongoConnectWithRetry(delayInMilliseconds, callback) {
  MongoClient.connect(mongoURL, (err, connection) => {
    if (err) {
      console.error(`Error connecting to MongoDB: ${err}`);
      setTimeout(() => mongoConnectWithRetry(delayInMilliseconds, callback), delayInMilliseconds);
    } else {
      log('connected succesfully to mongodb');
      callback(connection);
    }
  });
}

// Keep track of which games have used each stim
function recordStimUse(stimdb, gameid, idList) {
  _.forEach(idList, id => {
    stimdb.update({_id: id}, {
      $push : {games : gameid},
      $inc  : {numGames : 1}
    }, {multi: true}, function(err, items) {
      // do something when done?
    });
  });
}

function serve() {

  mongoConnectWithRetry(2000, (connection) => {

    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true}));

    app.post('/db/insert', (request, response) => {
      if (!request.body) {
        return failure(response, '/db/insert needs post request body');
      }
      log(`got request to insert into ${request.body.colname}`);

      const databaseName = request.body.dbname;
      const collectionName = request.body.colname;
      if (!collectionName) {
        return failure(response, '/db/insert needs collection');
      }
      if (!databaseName) {
        return failure(response, '/db/insert needs database');
      }

      const database = connection.db(databaseName);

      // Add collection if it doesn't already exist
      if (!database.collection(collectionName)) {
        console.log('creating collection ' + collectionName);
        database.createCollection(collectionName);
      }

      const collection = database.collection(collectionName);

      const data = _.omit(request.body, ['colname', 'dbname']);
      // log(`inserting data: ${JSON.stringify(data)}`);
      collection.insert(data, (err, result) => {
        if (err) {
          return failure(response, `error inserting data: ${err}`);
        } else {
          return success(response, `successfully inserted data. result: ${JSON.stringify(result)}`);
        }
      });
    });

    app.post('/db/getstims', (request, response) => {
      if (!request.body) {
        return failure(response, '/db/getstims needs post request body');
      }
      log(`got request to get stims from ${request.body.dbname}/${request.body.colname}`);

      const databaseName = request.body.dbname;
      const collectionName = request.body.colname;
      if (!collectionName) {
        return failure(response, '/db/getstims needs collection');
      }
      if (!databaseName) {
        return failure(response, '/db/getstims needs database');
      }

      const database = connection.db(databaseName);
      const collection = database.collection(collectionName);

      // collection.aggregate([
      // { $group : {_id : "$numGames", count: { $sum: 1 }}}
      //     ], (err, results) => {console.log('counts...'); });

      // get a random sample of stims that haven't appeared more than k times
      collection.aggregate([
        { $addFields : { numGames: { $size: '$games'} } },
        { $sort : { numGames : 1} },
        { $limit : request.body.numTrials }
      ]).toArray( (err, results) => {
        if(err) {
          console.log(err);
        } else {
          recordStimUse(collection, request.body.gameid, _.map(results, '_id'));
          response.send(results);
        }
      });
    });

    app.listen(port, () => {
      log(`running at http://localhost:${port}`);
    });

  });

}

serve();

<|endoftext|>


<commit_before>// I don't know why these are here
// https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();

function MultiAudio(src, instances = 5) {
  const output = [];
  const Audio = window.Audio || function Audio() {};

  for (let i = 0; i < instances; i += 1) {
    output.push(new Audio(src));
  }

  let current = 0;

  this.play = function play() {
    const audio = output[(current += 1) % instances];
    audio.currentTime = 0;
    audio.play();
  };
}

export default MultiAudio;
<commit_msg>remove what I don't understand<commit_after>function MultiAudio(src, instances = 5) {
  const output = [];
  const Audio = window.Audio || function Audio() {};

  for (let i = 0; i < instances; i += 1) {
    output.push(new Audio(src));
  }

  let current = 0;

  this.play = function play() {
    const audio = output[(current += 1) % instances];
    audio.currentTime = 0;
    audio.play();
  };
}

export default MultiAudio;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('GoogleSmartCard.ConnectorApp.BackgroundMain');

goog.require('GoogleSmartCard.ConnectorApp.Background.MainWindowManaging');
goog.require('GoogleSmartCard.EmscriptenModule');
goog.require('GoogleSmartCard.ExecutableModule');
goog.require('GoogleSmartCard.LibusbLoginStateHook');
goog.require('GoogleSmartCard.LibusbProxyReceiver');
goog.require('GoogleSmartCard.LogBufferForwarder');
goog.require('GoogleSmartCard.Logging');
goog.require('GoogleSmartCard.MessageChannelPair');
goog.require('GoogleSmartCard.MessageChannelPool');
goog.require('GoogleSmartCard.MessagingCommon');
goog.require('GoogleSmartCard.MessagingOrigin');
goog.require('GoogleSmartCard.NaclModule');
goog.require('GoogleSmartCard.Packaging');
goog.require('GoogleSmartCard.PcscLiteServer.ReaderTracker');
goog.require('GoogleSmartCard.PcscLiteServerClientsManagement.ClientHandler');
goog.require('GoogleSmartCard.PcscLiteServerClientsManagement.ReadinessTracker');
goog.require('GoogleSmartCard.PortMessageChannel');
goog.require('GoogleSmartCard.SingleMessageBasedChannel');
goog.require('goog.asserts');
goog.require('goog.log');
goog.require('goog.log.Logger');
goog.require('goog.messaging.AbstractChannel');

goog.scope(function() {

const GSC = GoogleSmartCard;

const JS_LOGS_HANDLER_MESSAGE_TYPE = 'js_logs_handler';

/** @type {!goog.log.Logger} */
const logger = GSC.Logging.getScopedLogger('ConnectorApp.BackgroundMain');

/**
 * Returns a forwarder for the JS logs, or null if there's none.
 * @return {GSC.LogBufferForwarder?}
 */
function maybeCreateLogBufferForwarder() {
  if (GSC.ExecutableModule.TOOLCHAIN !== GSC.ExecutableModule.Toolchain.PNACL)
    return null;
  // Used to forward logs collected on the JS side to the NaCl module's stdout,
  // in order to simplify accessing them in some configurations.
  return new GSC.LogBufferForwarder(
      GSC.Logging.getLogBuffer(), JS_LOGS_HANDLER_MESSAGE_TYPE);
}

// Note that this object needs to be created as early as possible, in order to
// not miss any important log.
const logBufferForwarderToNaclModule = maybeCreateLogBufferForwarder();

const extensionManifest = chrome.runtime.getManifest();
goog.log.info(
    logger,
    `The Smart Card Connector app (id "${chrome.runtime.id}", version ` +
        `${extensionManifest.version}) background script started. Browser ` +
        `version: "${window.navigator.appVersion}".`);

/**
 * Loads the binary executable module depending on the toolchain configuration.
 * @return {!GSC.ExecutableModule}
 */
function createExecutableModule() {
  switch (GSC.ExecutableModule.TOOLCHAIN) {
    case GSC.ExecutableModule.Toolchain.PNACL:
      return new GSC.NaclModule(
          'executable_module.nmf', GSC.NaclModule.Type.PNACL);
    case GSC.ExecutableModule.Toolchain.EMSCRIPTEN:
      return new GSC.EmscriptenModule('executable_module');
  }
  GSC.Logging.fail(
      `Cannot load executable module: unknown toolchain ` +
      `${GSC.ExecutableModule.TOOLCHAIN}`);
  goog.asserts.fail();
}

/**
 * The binary executable module that contains the actual smart card client code.
 * @type {!GSC.ExecutableModule}
 */
const executableModule = createExecutableModule();
executableModule.addOnDisposeCallback(executableModuleDisposedListener);

if (logBufferForwarderToNaclModule) {
  executableModule.getLoadPromise().then(() => {
    // Skip forwarding logs that were received from the NaCl module or generated
    // while sending messages to it, in order to avoid duplication and/or
    // infinite recursion.
    logBufferForwarderToNaclModule.ignoreLogger(
        executableModule.logMessagesReceiver.logger.getName());
    // Start forwarding all future log messages collected on the JS side, but
    // also immediately post the messages that have been accumulated so far.
    logBufferForwarderToNaclModule.startForwarding(
        executableModule.getMessageChannel());
  }, () => {});
}

const libusbProxyReceiver = new GSC.LibusbProxyReceiver(
    executableModule.getMessageChannel(), libusbChromeUsbBackend);
libusbProxyReceiver.addHook(new GSC.LibusbLoginStateHook());

const pcscLiteReadinessTracker =
    new GSC.PcscLiteServerClientsManagement.ReadinessTracker(
        executableModule.getMessageChannel());
const messageChannelPool = new GSC.MessageChannelPool();

const readerTrackerMessageChannelPair = new GSC.MessageChannelPair();
createClientHandler(readerTrackerMessageChannelPair.getFirst(), undefined);
const readerTracker = new GSC.PcscLiteServer.ReaderTracker(
    executableModule.getMessageChannel(),
    readerTrackerMessageChannelPair.getSecond(), executableModule.getLogger());

executableModule.startLoading();

// Launching is only possible for an app; in case of the extension packaging
// mode, the user should click on the browser action icon next to the address
// bar.
if (GSC.Packaging.MODE === GSC.Packaging.Mode.APP)
  chrome.app.runtime.onLaunched.addListener(launchedListener);

chrome.runtime.onConnect.addListener(connectionListener);
chrome.runtime.onConnectExternal.addListener(externalConnectionListener);
chrome.runtime.onMessageExternal.addListener(externalMessageListener);

/**
 * Called when the executable module is disposed of.
 */
function executableModuleDisposedListener() {
  if (!goog.DEBUG) {
    // Trigger the fatal error in the Release mode, that will emit an error
    // message and trigger the app reload.
    GSC.Logging.failWithLogger(logger, 'Executable module was disposed of');
  }
}

/**
 * Called when the onLaunched event is received (that is, when the user clicks
 * on the app in the Chrome OS app launcher).
 */
function launchedListener() {
  goog.log.fine(logger, 'Received onLaunched event, opening window...');
  GSC.ConnectorApp.Background.MainWindowManaging.openWindowDueToUserRequest();
}

/**
 * Called when the onConnect event is received.
 * @param {!Port} port
 */
function connectionListener(port) {
  goog.log.fine(logger, 'Received onConnect event');
  const portMessageChannel = new GSC.PortMessageChannel(port);
  createClientHandler(portMessageChannel, undefined);
}

/**
 * Called when the onConnectExternal event is received.
 * @param {!Port} port
 */
function externalConnectionListener(port) {
  goog.log.fine(logger, 'Received onConnectExternal event');
  const portMessageChannel = new GSC.PortMessageChannel(port);
  if (portMessageChannel.messagingOrigin === null) {
    goog.log.warning(
        logger,
        'Ignoring the external connection as there is no sender specified');
    return;
  }
  messageChannelPool.addChannel(
      portMessageChannel.messagingOrigin, portMessageChannel);
  GSC.MessagingCommon.setNonFatalDefaultServiceCallback(portMessageChannel);
  createClientHandler(portMessageChannel, portMessageChannel.messagingOrigin);
}

/**
 * Called when the onMessageExternal event is received.
 * @param {*} message
 * @param {!MessageSender} sender
 */
function externalMessageListener(message, sender) {
  goog.log.fine(logger, 'Received onMessageExternal event');
  if (sender.id === undefined) {
    // Note: The single-message-based communication protocol doesn't support
    // talking to web pages, since there's no way to send messages to web pages
    // other than in response to messages from them.
    goog.log.warning(
        logger,
        'Ignoring the external message as there is no sender ' +
            'extension id specified');
    return;
  }
  let channel = getOrCreateSingleMessageBasedChannel(sender.id);
  if (!channel)
    return;
  channel.deliverMessage(message);
  if (channel.isDisposed()) {
    // The channel could get disposed of due to the detected client reload. So
    // here an attempt is made to deliver the same message again to a freshly
    // created message channel, so that the message from the reloaded client has
    // a chance to be handled.
    channel = getOrCreateSingleMessageBasedChannel(sender.id);
    if (!channel)
      return;
    channel.deliverMessage(message);
  }
}

/**
 * Returns a single message based channel instance that talks to the specified
 * extension - either returns an existing instance if there's one, or creates a
 * new one otherwise. May return null if the channel becomes immediately
 * disposed of due to some error.
 * @param {string} senderExtensionId
 * @return {GSC.SingleMessageBasedChannel?}
 */
function getOrCreateSingleMessageBasedChannel(senderExtensionId) {
  const messagingOrigin =
      GSC.MessagingOrigin.getFromExtensionId(senderExtensionId);
  const existingChannel =
      messageChannelPool.getChannels(messagingOrigin).find(function(channel) {
        return channel instanceof GSC.SingleMessageBasedChannel;
      });
  if (existingChannel)
    return /** @type {!GSC.SingleMessageBasedChannel} */ (existingChannel);
  const newChannel =
      new GSC.SingleMessageBasedChannel(senderExtensionId, undefined, true);
  messageChannelPool.addChannel(messagingOrigin, newChannel);
  GSC.MessagingCommon.setNonFatalDefaultServiceCallback(newChannel);
  createClientHandler(newChannel, messagingOrigin);
  if (newChannel.isDisposed())
    return null;
  return newChannel;
}

/**
 * Creates a PC/SC-Lite client handler for the specified message channel and the
 * client extension.
 * The clientOrigin argument equal to undefined denotes the case when the sender
 * is our own extension/app.
 * @param {!goog.messaging.AbstractChannel} clientMessageChannel
 * @param {string|undefined} clientOrigin
 */
function createClientHandler(clientMessageChannel, clientOrigin) {
  GSC.Logging.checkWithLogger(logger, !clientMessageChannel.isDisposed());
  const clientOriginForLog =
      clientOrigin !== undefined ? clientOrigin : 'ourselves';

  if (executableModule.isDisposed() ||
      executableModule.getMessageChannel().isDisposed()) {
    goog.log.warning(
        logger,
        'Could not create PC/SC-Lite client handler for ' + clientOriginForLog +
            ' as the server is disposed. Disposing of the client message ' +
            'channel...');
    clientMessageChannel.dispose();
    return;
  }

  // Note: the reference to the created client handler is not stored anywhere,
  // because it manages its lifetime itself, based on the lifetimes of the
  // passed message channels.
  const clientHandler = new GSC.PcscLiteServerClientsManagement.ClientHandler(
      executableModule.getMessageChannel(), pcscLiteReadinessTracker,
      clientMessageChannel, clientOrigin);

  const logMessage = 'Created a new PC/SC-Lite client handler for ' +
      clientOriginForLog + ' (handler id ' + clientHandler.id + ')';
  if (clientOrigin !== undefined)
    goog.log.info(logger, logMessage);
  else
    goog.log.fine(logger, logMessage);
}

/**
 * Sets global variables to be used by the main window (once it's opened).
 */
function exposeGlobalsForMainWindow() {
  goog.global['googleSmartCard_clientAppListUpdateSubscriber'] =
      messageChannelPool.addOnUpdateListener.bind(messageChannelPool);
  goog.global['googleSmartCard_readerTrackerSubscriber'] =
      readerTracker.addOnUpdateListener.bind(readerTracker);
  goog.global['googleSmartCard_readerTrackerUnsubscriber'] =
      readerTracker.removeOnUpdateListener.bind(readerTracker);
}

exposeGlobalsForMainWindow();
});  // goog.scope
<commit_msg>[usb] Fix bad merge from #470<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('GoogleSmartCard.ConnectorApp.BackgroundMain');

goog.require('GoogleSmartCard.ConnectorApp.Background.MainWindowManaging');
goog.require('GoogleSmartCard.EmscriptenModule');
goog.require('GoogleSmartCard.ExecutableModule');
goog.require('GoogleSmartCard.LibusbLoginStateHook');
goog.require('GoogleSmartCard.LibusbProxyReceiver');
goog.require('GoogleSmartCard.LogBufferForwarder');
goog.require('GoogleSmartCard.Logging');
goog.require('GoogleSmartCard.MessageChannelPair');
goog.require('GoogleSmartCard.MessageChannelPool');
goog.require('GoogleSmartCard.MessagingCommon');
goog.require('GoogleSmartCard.MessagingOrigin');
goog.require('GoogleSmartCard.NaclModule');
goog.require('GoogleSmartCard.Packaging');
goog.require('GoogleSmartCard.PcscLiteServer.ReaderTracker');
goog.require('GoogleSmartCard.PcscLiteServerClientsManagement.ClientHandler');
goog.require('GoogleSmartCard.PcscLiteServerClientsManagement.ReadinessTracker');
goog.require('GoogleSmartCard.PortMessageChannel');
goog.require('GoogleSmartCard.SingleMessageBasedChannel');
goog.require('goog.asserts');
goog.require('goog.log');
goog.require('goog.log.Logger');
goog.require('goog.messaging.AbstractChannel');

goog.scope(function() {

const GSC = GoogleSmartCard;

const JS_LOGS_HANDLER_MESSAGE_TYPE = 'js_logs_handler';

/** @type {!goog.log.Logger} */
const logger = GSC.Logging.getScopedLogger('ConnectorApp.BackgroundMain');

/**
 * Returns a forwarder for the JS logs, or null if there's none.
 * @return {GSC.LogBufferForwarder?}
 */
function maybeCreateLogBufferForwarder() {
  if (GSC.ExecutableModule.TOOLCHAIN !== GSC.ExecutableModule.Toolchain.PNACL)
    return null;
  // Used to forward logs collected on the JS side to the NaCl module's stdout,
  // in order to simplify accessing them in some configurations.
  return new GSC.LogBufferForwarder(
      GSC.Logging.getLogBuffer(), JS_LOGS_HANDLER_MESSAGE_TYPE);
}

// Note that this object needs to be created as early as possible, in order to
// not miss any important log.
const logBufferForwarderToNaclModule = maybeCreateLogBufferForwarder();

const extensionManifest = chrome.runtime.getManifest();
goog.log.info(
    logger,
    `The Smart Card Connector app (id "${chrome.runtime.id}", version ` +
        `${extensionManifest.version}) background script started. Browser ` +
        `version: "${window.navigator.appVersion}".`);

/**
 * Loads the binary executable module depending on the toolchain configuration.
 * @return {!GSC.ExecutableModule}
 */
function createExecutableModule() {
  switch (GSC.ExecutableModule.TOOLCHAIN) {
    case GSC.ExecutableModule.Toolchain.PNACL:
      return new GSC.NaclModule(
          'executable_module.nmf', GSC.NaclModule.Type.PNACL);
    case GSC.ExecutableModule.Toolchain.EMSCRIPTEN:
      return new GSC.EmscriptenModule('executable_module');
  }
  GSC.Logging.fail(
      `Cannot load executable module: unknown toolchain ` +
      `${GSC.ExecutableModule.TOOLCHAIN}`);
  goog.asserts.fail();
}

/**
 * The binary executable module that contains the actual smart card client code.
 * @type {!GSC.ExecutableModule}
 */
const executableModule = createExecutableModule();
executableModule.addOnDisposeCallback(executableModuleDisposedListener);

if (logBufferForwarderToNaclModule) {
  executableModule.getLoadPromise().then(() => {
    // Skip forwarding logs that were received from the NaCl module or generated
    // while sending messages to it, in order to avoid duplication and/or
    // infinite recursion.
    logBufferForwarderToNaclModule.ignoreLogger(
        executableModule.logMessagesReceiver.logger.getName());
    // Start forwarding all future log messages collected on the JS side, but
    // also immediately post the messages that have been accumulated so far.
    logBufferForwarderToNaclModule.startForwarding(
        executableModule.getMessageChannel());
  }, () => {});
}

const libusbProxyReceiver = new GSC.LibusbProxyReceiver(
    executableModule.getMessageChannel());
libusbProxyReceiver.addHook(new GSC.LibusbLoginStateHook());

const pcscLiteReadinessTracker =
    new GSC.PcscLiteServerClientsManagement.ReadinessTracker(
        executableModule.getMessageChannel());
const messageChannelPool = new GSC.MessageChannelPool();

const readerTrackerMessageChannelPair = new GSC.MessageChannelPair();
createClientHandler(readerTrackerMessageChannelPair.getFirst(), undefined);
const readerTracker = new GSC.PcscLiteServer.ReaderTracker(
    executableModule.getMessageChannel(),
    readerTrackerMessageChannelPair.getSecond(), executableModule.getLogger());

executableModule.startLoading();

// Launching is only possible for an app; in case of the extension packaging
// mode, the user should click on the browser action icon next to the address
// bar.
if (GSC.Packaging.MODE === GSC.Packaging.Mode.APP)
  chrome.app.runtime.onLaunched.addListener(launchedListener);

chrome.runtime.onConnect.addListener(connectionListener);
chrome.runtime.onConnectExternal.addListener(externalConnectionListener);
chrome.runtime.onMessageExternal.addListener(externalMessageListener);

/**
 * Called when the executable module is disposed of.
 */
function executableModuleDisposedListener() {
  if (!goog.DEBUG) {
    // Trigger the fatal error in the Release mode, that will emit an error
    // message and trigger the app reload.
    GSC.Logging.failWithLogger(logger, 'Executable module was disposed of');
  }
}

/**
 * Called when the onLaunched event is received (that is, when the user clicks
 * on the app in the Chrome OS app launcher).
 */
function launchedListener() {
  goog.log.fine(logger, 'Received onLaunched event, opening window...');
  GSC.ConnectorApp.Background.MainWindowManaging.openWindowDueToUserRequest();
}

/**
 * Called when the onConnect event is received.
 * @param {!Port} port
 */
function connectionListener(port) {
  goog.log.fine(logger, 'Received onConnect event');
  const portMessageChannel = new GSC.PortMessageChannel(port);
  createClientHandler(portMessageChannel, undefined);
}

/**
 * Called when the onConnectExternal event is received.
 * @param {!Port} port
 */
function externalConnectionListener(port) {
  goog.log.fine(logger, 'Received onConnectExternal event');
  const portMessageChannel = new GSC.PortMessageChannel(port);
  if (portMessageChannel.messagingOrigin === null) {
    goog.log.warning(
        logger,
        'Ignoring the external connection as there is no sender specified');
    return;
  }
  messageChannelPool.addChannel(
      portMessageChannel.messagingOrigin, portMessageChannel);
  GSC.MessagingCommon.setNonFatalDefaultServiceCallback(portMessageChannel);
  createClientHandler(portMessageChannel, portMessageChannel.messagingOrigin);
}

/**
 * Called when the onMessageExternal event is received.
 * @param {*} message
 * @param {!MessageSender} sender
 */
function externalMessageListener(message, sender) {
  goog.log.fine(logger, 'Received onMessageExternal event');
  if (sender.id === undefined) {
    // Note: The single-message-based communication protocol doesn't support
    // talking to web pages, since there's no way to send messages to web pages
    // other than in response to messages from them.
    goog.log.warning(
        logger,
        'Ignoring the external message as there is no sender ' +
            'extension id specified');
    return;
  }
  let channel = getOrCreateSingleMessageBasedChannel(sender.id);
  if (!channel)
    return;
  channel.deliverMessage(message);
  if (channel.isDisposed()) {
    // The channel could get disposed of due to the detected client reload. So
    // here an attempt is made to deliver the same message again to a freshly
    // created message channel, so that the message from the reloaded client has
    // a chance to be handled.
    channel = getOrCreateSingleMessageBasedChannel(sender.id);
    if (!channel)
      return;
    channel.deliverMessage(message);
  }
}

/**
 * Returns a single message based channel instance that talks to the specified
 * extension - either returns an existing instance if there's one, or creates a
 * new one otherwise. May return null if the channel becomes immediately
 * disposed of due to some error.
 * @param {string} senderExtensionId
 * @return {GSC.SingleMessageBasedChannel?}
 */
function getOrCreateSingleMessageBasedChannel(senderExtensionId) {
  const messagingOrigin =
      GSC.MessagingOrigin.getFromExtensionId(senderExtensionId);
  const existingChannel =
      messageChannelPool.getChannels(messagingOrigin).find(function(channel) {
        return channel instanceof GSC.SingleMessageBasedChannel;
      });
  if (existingChannel)
    return /** @type {!GSC.SingleMessageBasedChannel} */ (existingChannel);
  const newChannel =
      new GSC.SingleMessageBasedChannel(senderExtensionId, undefined, true);
  messageChannelPool.addChannel(messagingOrigin, newChannel);
  GSC.MessagingCommon.setNonFatalDefaultServiceCallback(newChannel);
  createClientHandler(newChannel, messagingOrigin);
  if (newChannel.isDisposed())
    return null;
  return newChannel;
}

/**
 * Creates a PC/SC-Lite client handler for the specified message channel and the
 * client extension.
 * The clientOrigin argument equal to undefined denotes the case when the sender
 * is our own extension/app.
 * @param {!goog.messaging.AbstractChannel} clientMessageChannel
 * @param {string|undefined} clientOrigin
 */
function createClientHandler(clientMessageChannel, clientOrigin) {
  GSC.Logging.checkWithLogger(logger, !clientMessageChannel.isDisposed());
  const clientOriginForLog =
      clientOrigin !== undefined ? clientOrigin : 'ourselves';

  if (executableModule.isDisposed() ||
      executableModule.getMessageChannel().isDisposed()) {
    goog.log.warning(
        logger,
        'Could not create PC/SC-Lite client handler for ' + clientOriginForLog +
            ' as the server is disposed. Disposing of the client message ' +
            'channel...');
    clientMessageChannel.dispose();
    return;
  }

  // Note: the reference to the created client handler is not stored anywhere,
  // because it manages its lifetime itself, based on the lifetimes of the
  // passed message channels.
  const clientHandler = new GSC.PcscLiteServerClientsManagement.ClientHandler(
      executableModule.getMessageChannel(), pcscLiteReadinessTracker,
      clientMessageChannel, clientOrigin);

  const logMessage = 'Created a new PC/SC-Lite client handler for ' +
      clientOriginForLog + ' (handler id ' + clientHandler.id + ')';
  if (clientOrigin !== undefined)
    goog.log.info(logger, logMessage);
  else
    goog.log.fine(logger, logMessage);
}

/**
 * Sets global variables to be used by the main window (once it's opened).
 */
function exposeGlobalsForMainWindow() {
  goog.global['googleSmartCard_clientAppListUpdateSubscriber'] =
      messageChannelPool.addOnUpdateListener.bind(messageChannelPool);
  goog.global['googleSmartCard_readerTrackerSubscriber'] =
      readerTracker.addOnUpdateListener.bind(readerTracker);
  goog.global['googleSmartCard_readerTrackerUnsubscriber'] =
      readerTracker.removeOnUpdateListener.bind(readerTracker);
}

exposeGlobalsForMainWindow();
});  // goog.scope
<|endoftext|>


<commit_before>var $ = require('jquery');
var _ = require('underscore');
var misc = require('../misc');
var S3Ajax = require('S3Ajax');

var AUTH_NAME = 'AmazonS3MultiUser';

// AmazonS3MultiUserPublisher app config, partially host-based
// TODO: Make this user-configurable - in localstorage?
var config = _.extend({
  S3_BASE_URL: 'https://s3.amazonaws.com',
  TOKEN_DURATION: 900,
  CLIENT_ID: 'amzn1.application-oa2-client.c64da1621c67449ab764c4cdf2f99761',
  ROLE_ARN: 'arn:aws:iam::197006402464:role/tootr-dev-users',
  BUCKET: 'toots-dev.lmorchard.com',
  REGISTER_URL: 'https://localhost:9443/amazon/register',
  PRESIGNER_URL: 'https://localhost:9443/amazon/presigned'
}, {
  "lmorchard.github.io": {
    CLIENT_ID: 'amzn1.application-oa2-client.d3ce7b272419457abf84b88a9d7d6bd3',
    ROLE_ARN: 'arn:aws:iam::197006402464:role/tootsr-amazon-user-buckets',
    BUCKET: 'toots.lmorchard.com',
    REGISTER_URL: 'https://tootr.herokuapp.com/amazon/register',
    PRESIGNER_URL: 'https://tootr.herokuapp.com/amazon/presigned'
  }
}[location.hostname]);

module.exports = function (publishers, baseModule) {
  var AmazonS3MultiUserPublisher = baseModule();

  setupAmazonLoginButton();

  AmazonS3MultiUserPublisher.startLogin = function () {
    var options = { scope : 'profile' };
    var redir = location.protocol + '//' + location.hostname +
      (location.port ? ':' + location.port : '') +
      location.pathname + '?loginType=' + AUTH_NAME;
    amazon.Login.authorize(options, redir);
  };

  AmazonS3MultiUserPublisher.checkAuth = function (cb) {
    var auth = publishers.getProfile();

    // If we don't have an auth profile, it's possible that we've just received
    // an access token on the redirect side of login.
    if (!auth) {
      var qparams = misc.getQueryParameters();
      if (qparams.loginType === AUTH_NAME) {
        var qparams = misc.getQueryParameters();
        if (qparams.access_token) {
          AmazonS3MultiUserPublisher.refreshAuth(qparams.access_token);
          // Clean out the auth redirect parameters from location
          history.replaceState({}, '', location.protocol + '//' +
              location.hostname + (location.port ? ':' + location.port : '') +
              location.pathname);
        }
      }
      return cb();
    }

    // We have an auth profile, but it's not ours.
    if (auth.type !== AUTH_NAME) { return cb(); }

    // We have an auth profile, but it could have expired. Refresh, if so.
    var now = new Date();
    var expiration = new Date(auth.credentials.Expiration);
    if (now >= expiration) {
      AmazonS3MultiUserPublisher.refreshAuth(auth.access_token);
      return cb();
    }

    // Looks like we have a fresh auth profile, so just go ahead and use it.
    publishers.setCurrent(auth, new AmazonS3MultiUserPublisher(auth));
    return cb();
  };

  AmazonS3MultiUserPublisher.refreshAuth = function (access_token) {
    var auth = {
      type: AUTH_NAME,
      access_token: access_token
    };
    $.ajax({
      url: 'https://api.amazon.com/user/profile',
      headers: { 'Authorization': 'bearer ' + access_token }
    }).fail(function (xhr, status, err) {
      publishers.clearCurrent();
    }).then(function (data, status, xhr) {
      return $.ajax({
        url: config.S3_BASE_URL + '/' + config.BUCKET +
          '/users/amazon/' + data.user_id + '.json',
        cache: false
      });
    }).fail(function (xhr, status, err) {
      AmazonS3MultiUserPublisher.startRegistration(access_token);
    }).then(function (profile, status, xhr) {
      _.extend(auth, profile);
      return $.ajax('https://sts.amazonaws.com/?' + $.param({
        'Action': 'AssumeRoleWithWebIdentity',
        'Version': '2011-06-15',
        'RoleSessionName': 'web-identity-federation',
        'ProviderId': 'www.amazon.com',
        'DurationSeconds': config.TOKEN_DURATION,
        'RoleArn': config.ROLE_ARN,
        'WebIdentityToken': access_token
      }));
    }).fail(function (xhr, status, err) {
      publishers.clearCurrent();
    }).then(function (dataXML, status, xhr) {
      auth.credentials = misc.xmlToObj(dataXML)
        .AssumeRoleWithWebIdentityResponse
        .AssumeRoleWithWebIdentityResult
        .Credentials;
      publishers.setCurrent(auth, new AmazonS3MultiUserPublisher(auth));
    });
  };

  AmazonS3MultiUserPublisher.startRegistration = function (access_token) {
    // Get a nickname from the user.
    // TODO: Rework this to not use a browser dialog.
    var nickname = window.prompt(
        "Login successful, but profile not found.\n" +
        "Enter a nickname to create a new one?");

    // Bail, if no nickname provided.
    if (!nickname) { return; }

    // Attempt to register the account with given nickname
    $.ajax({
      url: config.REGISTER_URL,
      type: 'POST',
      dataType: 'json',
      contentType: "application/json; charset=utf-8",
      data: JSON.stringify({
        AccessToken: access_token,
        nickname: nickname
      })
    }).fail(function (xhr, status, err) {
      var again = window.confirm(
        "Problem registering: " + xhr.responseText + "\n" +
        "Try again?");
      if (again) {
        AmazonS3MultiUserPublisher.startRegistration(access_token);
      }
    }).done(function (data, status, xhr) {
      AmazonS3MultiUserPublisher.refreshAuth(access_token);
    });
  };

  AmazonS3MultiUserPublisher.prototype.init = function (options) {
    AmazonS3MultiUserPublisher.__base__.init.apply(this, arguments);

    var credentials = this.options.credentials;
    this.prefix = this.options.prefix;
    this.client = new S3Ajax({
      base_url: config.S3_BASE_URL,
      key_id: credentials.AccessKeyId,
      secret_key: credentials.SecretAccessKey,
      security_token: credentials.SessionToken,
      defeat_cache: true
    });
  };

  AmazonS3MultiUserPublisher.prototype.startLogout = function () {
    amazon.Login.logout();
    publishers.clearCurrent();
  };

  AmazonS3MultiUserPublisher.prototype.list = function (path, cb) {
    var prefix = this.prefix + path;
    this.client.listKeys(
      config.BUCKET,
      {prefix: prefix},
      function (req, obj) {
        var out = {};
        if (obj.ListBucketResult && obj.ListBucketResult.Contents) {
          obj.ListBucketResult.Contents.forEach(function (item) {
            var path = item.Key.replace(prefix, '');
            out[path] = item;
          });
        }
        cb(null, out);
      },
      function (req, obj) { cb(obj.Error, obj); }
    );
  };

  AmazonS3MultiUserPublisher.prototype.get = function (path, cb) {
    this.client.get(
      config.BUCKET, this.prefix + path,
      function (req, obj) { cb(null, req.responseText); },
      function (req, obj) { cb(obj.Error, null); }
    );
  };

  AmazonS3MultiUserPublisher.prototype.rm = function (path, cb) {
    this.client.deleteKey(
      config.BUCKET, this.prefix + path,
      function (req, obj) { cb(null, req.responseText); },
      function (req, obj) { cb(obj.Error, null); }
    );
  };

  AmazonS3MultiUserPublisher.prototype.put = function (path, content, cb) {
    var ext = path.substr(path.lastIndexOf('.')+1);
    var types = {
      'html': 'text/html; charset=UTF-8',
      'css': 'text/css; charset=UTF-8',
      'js': 'text/javascript; charset=UTF-8'
    };
    var access_token = this.options.access_token;

    /*
     * To keep some control over uploads, I use a presigner service that
     * imposes a policy. Then, I need to rework this as a form POST instead of
     * an S3 REST API request.
     */
    $.ajax({
      url: config.PRESIGNER_URL,
      type: 'POST',
      dataType: 'json',
      contentType: "application/json; charset=utf-8",
      data: JSON.stringify({
        AccessToken: access_token,
        Path: path,
        ContentType: types[ext]
      })
    }).then(function (data, status, xhr) {

      var formdata = new FormData();
      for (var k in data) {
        formdata.append(k, data[k]);
      }
      formdata.append('file', content);

      return $.ajax({
        url: config.S3_BASE_URL +  '/' + config.BUCKET + '/',
        type: 'POST',
        data: formdata,
        processData: false,
        contentType: false,
        cache: false
      });

    }).then(function (data, status, xhr) {
      cb(null, true);
    }, function (xhr, status, err) {
      cb(err, null);
    });

  };

  function setupAmazonLoginButton () {
    // Set up the Login with Amazon button
    // TODO: Maybe do this conditionally / on-demand only when an Amazon login is desired?
    window.onAmazonLoginReady = function() {
      amazon.Login.setClientId(config.CLIENT_ID);
      $('#LoginWithAmazonMultiUser').click(function () {
        AmazonS3MultiUserPublisher.startLogin();
        return false;
      });
    };
    (function(d) {
      var r = d.createElement('div');
      r.id = 'amazon-root';
      d.body.appendChild(r);
      var a = d.createElement('script');
      a.type = 'text/javascript';
      a.async = true;
      a.id = 'amazon-login-sdk';
      a.src = 'https://api-cdn.amazon.com/sdk/login1.js';
      r.appendChild(a);
    })(document);
  }

  return AmazonS3MultiUserPublisher;
};
<commit_msg>jQuery deferreds do not work the way I thought they did<commit_after>var $ = require('jquery');
var _ = require('underscore');
var misc = require('../misc');
var S3Ajax = require('S3Ajax');

var AUTH_NAME = 'AmazonS3MultiUser';

// AmazonS3MultiUserPublisher app config, partially host-based
// TODO: Make this user-configurable - in localstorage?
var config = _.extend({
  S3_BASE_URL: 'https://s3.amazonaws.com',
  TOKEN_DURATION: 900,
  CLIENT_ID: 'amzn1.application-oa2-client.c64da1621c67449ab764c4cdf2f99761',
  ROLE_ARN: 'arn:aws:iam::197006402464:role/tootr-dev-users',
  BUCKET: 'toots-dev.lmorchard.com',
  REGISTER_URL: 'https://localhost:9443/amazon/register',
  PRESIGNER_URL: 'https://localhost:9443/amazon/presigned'
}, {
  "lmorchard.github.io": {
    CLIENT_ID: 'amzn1.application-oa2-client.d3ce7b272419457abf84b88a9d7d6bd3',
    ROLE_ARN: 'arn:aws:iam::197006402464:role/tootsr-amazon-user-buckets',
    BUCKET: 'toots.lmorchard.com',
    REGISTER_URL: 'https://tootr.herokuapp.com/amazon/register',
    PRESIGNER_URL: 'https://tootr.herokuapp.com/amazon/presigned'
  }
}[location.hostname]);

module.exports = function (publishers, baseModule) {
  var AmazonS3MultiUserPublisher = baseModule();

  setupAmazonLoginButton();

  AmazonS3MultiUserPublisher.startLogin = function () {
    var options = { scope : 'profile' };
    var redir = location.protocol + '//' + location.hostname +
      (location.port ? ':' + location.port : '') +
      location.pathname + '?loginType=' + AUTH_NAME;
    amazon.Login.authorize(options, redir);
  };

  AmazonS3MultiUserPublisher.checkAuth = function (cb) {
    var auth = publishers.getProfile();

    // If we don't have an auth profile, it's possible that we've just received
    // an access token on the redirect side of login.
    if (!auth) {
      var qparams = misc.getQueryParameters();
      if (qparams.loginType === AUTH_NAME) {
        var qparams = misc.getQueryParameters();
        if (qparams.access_token) {
          AmazonS3MultiUserPublisher.refreshAuth(qparams.access_token);
          // Clean out the auth redirect parameters from location
          history.replaceState({}, '', location.protocol + '//' +
              location.hostname + (location.port ? ':' + location.port : '') +
              location.pathname);
        }
      }
      return cb();
    }

    // We have an auth profile, but it's not ours.
    if (auth.type !== AUTH_NAME) { return cb(); }

    // We have an auth profile, but it could have expired. Refresh, if so.
    var now = new Date();
    var expiration = new Date(auth.credentials.Expiration);
    if (now >= expiration) {
      AmazonS3MultiUserPublisher.refreshAuth(auth.access_token);
      return cb();
    }

    // Looks like we have a fresh auth profile, so just go ahead and use it.
    publishers.setCurrent(auth, new AmazonS3MultiUserPublisher(auth));
    return cb();
  };

  AmazonS3MultiUserPublisher.refreshAuth = function (access_token) {
    var auth = {
      type: AUTH_NAME,
      access_token: access_token
    };

    var user_id = null;
    var profile = null;

    $.ajax({
      url: 'https://api.amazon.com/user/profile',
      headers: { 'Authorization': 'bearer ' + access_token }
    }).then(function (data, status, xhr) {

      user_id = data.user_id;

      return $.ajax({
        url: config.S3_BASE_URL + '/' + config.BUCKET +
          '/users/amazon/' + user_id + '.json',
        cache: false
      });

    }).then(function (data, status, xhr) {

      profile = data;
      _.extend(auth, profile);

      return $.ajax('https://sts.amazonaws.com/?' + $.param({
        'Action': 'AssumeRoleWithWebIdentity',
        'Version': '2011-06-15',
        'RoleSessionName': 'web-identity-federation',
        'ProviderId': 'www.amazon.com',
        'DurationSeconds': config.TOKEN_DURATION,
        'RoleArn': config.ROLE_ARN,
        'WebIdentityToken': access_token
      }));

    }).then(function (dataXML, status, xhr) {

      auth.credentials = misc.xmlToObj(dataXML)
        .AssumeRoleWithWebIdentityResponse
        .AssumeRoleWithWebIdentityResult
        .Credentials;
      publishers.setCurrent(auth, new AmazonS3MultiUserPublisher(auth));

    }).fail(function (xhr, status, err) {

      if (user_id && !profile) {
        // If we have a user_id, then Amazon login is okay. But, if we're
        // missing a profile then we need to start registration.
        AmazonS3MultiUserPublisher.startRegistration(access_token);
      } else {
        // All other failures in auth refresh lead to logout.
        publishers.clearCurrent();
      }

    });
  };

  AmazonS3MultiUserPublisher.startRegistration = function (access_token) {
    // Get a nickname from the user.
    // TODO: Rework this to not use a browser dialog.
    var nickname = window.prompt(
        "Login successful, but profile not found.\n" +
        "Enter a nickname to create a new one?");

    // Bail, if no nickname provided.
    if (!nickname) { return; }

    // Attempt to register the account with given nickname
    $.ajax({
      url: config.REGISTER_URL,
      type: 'POST',
      dataType: 'json',
      contentType: "application/json; charset=utf-8",
      data: JSON.stringify({
        AccessToken: access_token,
        nickname: nickname
      })
    }).fail(function (xhr, status, err) {
      var again = window.confirm(
        "Problem registering: " + xhr.responseText + "\n" +
        "Try again?");
      if (again) {
        AmazonS3MultiUserPublisher.startRegistration(access_token);
      }
    }).done(function (data, status, xhr) {
      AmazonS3MultiUserPublisher.refreshAuth(access_token);
    });
  };

  AmazonS3MultiUserPublisher.prototype.init = function (options) {
    AmazonS3MultiUserPublisher.__base__.init.apply(this, arguments);

    var credentials = this.options.credentials;
    this.prefix = this.options.prefix;
    this.client = new S3Ajax({
      base_url: config.S3_BASE_URL,
      key_id: credentials.AccessKeyId,
      secret_key: credentials.SecretAccessKey,
      security_token: credentials.SessionToken,
      defeat_cache: true
    });
  };

  AmazonS3MultiUserPublisher.prototype.startLogout = function () {
    amazon.Login.logout();
    publishers.clearCurrent();
  };

  AmazonS3MultiUserPublisher.prototype.list = function (path, cb) {
    var prefix = this.prefix + path;
    this.client.listKeys(
      config.BUCKET,
      {prefix: prefix},
      function (req, obj) {
        var out = {};
        if (obj.ListBucketResult && obj.ListBucketResult.Contents) {
          obj.ListBucketResult.Contents.forEach(function (item) {
            var path = item.Key.replace(prefix, '');
            out[path] = item;
          });
        }
        cb(null, out);
      },
      function (req, obj) { cb(obj.Error, obj); }
    );
  };

  AmazonS3MultiUserPublisher.prototype.get = function (path, cb) {
    this.client.get(
      config.BUCKET, this.prefix + path,
      function (req, obj) { cb(null, req.responseText); },
      function (req, obj) { cb(obj.Error, null); }
    );
  };

  AmazonS3MultiUserPublisher.prototype.rm = function (path, cb) {
    this.client.deleteKey(
      config.BUCKET, this.prefix + path,
      function (req, obj) { cb(null, req.responseText); },
      function (req, obj) { cb(obj.Error, null); }
    );
  };

  AmazonS3MultiUserPublisher.prototype.put = function (path, content, cb) {
    var ext = path.substr(path.lastIndexOf('.')+1);
    var types = {
      'html': 'text/html; charset=UTF-8',
      'css': 'text/css; charset=UTF-8',
      'js': 'text/javascript; charset=UTF-8'
    };
    var access_token = this.options.access_token;

    /*
     * To keep some control over uploads, I use a presigner service that
     * imposes a policy. Then, I need to rework this as a form POST instead of
     * an S3 REST API request.
     */
    $.ajax({
      url: config.PRESIGNER_URL,
      type: 'POST',
      dataType: 'json',
      contentType: "application/json; charset=utf-8",
      data: JSON.stringify({
        AccessToken: access_token,
        Path: path,
        ContentType: types[ext]
      })
    }).then(function (data, status, xhr) {

      var formdata = new FormData();
      for (var k in data) {
        formdata.append(k, data[k]);
      }
      formdata.append('file', content);

      return $.ajax({
        url: config.S3_BASE_URL +  '/' + config.BUCKET + '/',
        type: 'POST',
        data: formdata,
        processData: false,
        contentType: false,
        cache: false
      });

    }).then(function (data, status, xhr) {
      cb(null, true);
    }, function (xhr, status, err) {
      cb(err, null);
    });

  };

  function setupAmazonLoginButton () {
    // Set up the Login with Amazon button
    // TODO: Maybe do this conditionally / on-demand only when an Amazon login is desired?
    window.onAmazonLoginReady = function() {
      amazon.Login.setClientId(config.CLIENT_ID);
      $('#LoginWithAmazonMultiUser').click(function () {
        AmazonS3MultiUserPublisher.startLogin();
        return false;
      });
    };
    (function(d) {
      var r = d.createElement('div');
      r.id = 'amazon-root';
      d.body.appendChild(r);
      var a = d.createElement('script');
      a.type = 'text/javascript';
      a.async = true;
      a.id = 'amazon-login-sdk';
      a.src = 'https://api-cdn.amazon.com/sdk/login1.js';
      r.appendChild(a);
    })(document);
  }

  return AmazonS3MultiUserPublisher;
};
<|endoftext|>