<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var errors = require('./errors.js');

var TOMBSTONE_TTL_OFFSET = 500;

module.exports = Operations;

function Operations(opts) {
    var self = this;

    self.timers = opts.timers;
    self.logger = opts.logger;
    self.random = opts.random;
    self.connectionStalePeriod = opts.connectionStalePeriod;

    self.connection = opts.connection;
    self.destroyed = false; // TODO need this?

    self.requests = {
        in: Object.create(null),
        out: Object.create(null)
    };
    self.pending = {
        in: 0,
        out: 0
    };
    self.lastTimeoutTime = 0;
}

function OperationTombstone(operations, id, time, timeout) {
    var self = this;

    self.isTombstone = true;
    self.operations = operations;
    self.id = id;
    self.time = time;
    self.timeout = timeout;
}

OperationTombstone.prototype.onTimeout = function onTimeout(now) {
    var self = this;

    if (self.operations.requests.out[self.id] === self) {
        delete self.operations.requests.out[self.id];
    }
};

Operations.prototype.checkLastTimeoutTime = function checkLastTimeoutTime(now) {
    var self = this;

    if (self.lastTimeoutTime &&
        now > self.lastTimeoutTime + self.connectionStalePeriod) {
        var err = errors.ConnectionStaleTimeoutError({
            lastTimeoutTime: self.lastTimeoutTime
        });
        process.nextTick(function opCheckLastTimedout() {
            self.connection.timedOutEvent.emit(self.connection, err);
        });
    } else if (!self.lastTimeoutTime) {
        self.lastTimeoutTime = now;
    }
};

Operations.prototype.getRequests = function getRequests() {
    var self = this;

    return self.requests;
};

Operations.prototype.getPending = function getPending() {
    var self = this;

    return self.pending;
};

Operations.prototype.getOutReq = function getOutReq(id) {
    var self = this;

    var req = self.requests.out[id] || null;
    if (req && req.isTombstone) {
        return null;
    } else {
        return req;
    }
};

Operations.prototype.getInReq = function getInReq(id) {
    var self = this;

    return self.requests.in[id];
};

Operations.prototype.addOutReq = function addOutReq(req) {
    var self = this;

    req.operations = self;
    self.requests.out[req.id] = req;
    self.pending.out++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.addInReq = function addInReq(req) {
    var self = this;

    req.operations = self;
    self.requests.in[req.id] = req;
    self.pending.in++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.popOutReq = function popOutReq(id, context) {
    var self = this;

    var req = self.requests.out[id];
    if (!req) {
        self.logMissingOutRequest(id, context);
        return null;
    } else if (req.isTombstone) {
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    }

    var now = self.timers.now();
    var timeout = TOMBSTONE_TTL_OFFSET + req.timeout;
    var tombstone = new OperationTombstone(self, id, now, timeout);
    req.operations = null;
    self.requests.out[id] = tombstone;
    self.pending.out--;

    self.connection.channel.timeHeap.update(tombstone, now);

    return req;
};

Operations.prototype.logMissingOutRequest =
function logMissingOutRequest(id, context) {
    var self = this;

    // context is err or res
    if (context && context.originalId) {
        context = {
            error: context,
            id: context.originalId,
            info: 'got error frame for unknown id'
        };
    } else if (context && context.id) {
        context = {
            responseId: context.id,
            code: context.code,
            arg1: Buffer.isBuffer(context.arg1) ?
                String(context.arg1) : 'streamed-arg1',
            info: 'got call response for unknown id'
        };
    }

    // This could be because of a confused / corrupted server.
    self.logger.info('popOutReq received for unknown or lost id', {
        context: context,
        socketRemoteAddr: self.connection.socketRemoteAddr,
        direction: self.connection.direction
    });
};

Operations.prototype.popInReq = function popInReq(id) {
    var self = this;

    var req = self.requests.in[id];
    if (!req) {
        // TODO warn ?
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    }

    delete self.requests.in[id];
    self.pending.in--;

    return req;
};

Operations.prototype.clear = function clear() {
    var self = this;

    self.pending.in = 0;
    self.pending.out = 0;
};

Operations.prototype.destroy = function destroy() {
    var self = this;

    self.destroyed = true;
};

Operations.prototype.sanitySweep = function sanitySweep() {
    var self = this;

    self._sweepOps(self.requests.in, 'in');
    self._sweepOps(self.requests.out, 'out');
};

Operations.prototype._sweepOps = function _sweepOps(ops, direction) {
    var self = this;

    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var op = ops[id];
        if (op === undefined) {
            self.logger.warn('unexpected undefined operation', {
                direction: direction,
                id: id
            });
        } else if (op.timedOut) {
            self.logger.warn('lingering timed-out operation', {
                direction: direction,
                id: id
            });

            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        }
    }
};
<commit_msg>OperationTombstone: carry heap handle<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var errors = require('./errors.js');

var TOMBSTONE_TTL_OFFSET = 500;

module.exports = Operations;

function Operations(opts) {
    var self = this;

    self.timers = opts.timers;
    self.logger = opts.logger;
    self.random = opts.random;
    self.connectionStalePeriod = opts.connectionStalePeriod;

    self.connection = opts.connection;
    self.destroyed = false; // TODO need this?

    self.requests = {
        in: Object.create(null),
        out: Object.create(null)
    };
    self.pending = {
        in: 0,
        out: 0
    };
    self.lastTimeoutTime = 0;
}

function OperationTombstone(operations, id, time, timeout) {
    var self = this;

    self.isTombstone = true;
    self.operations = operations;
    self.id = id;
    self.time = time;
    self.timeout = timeout;
    self.timeHeapHandle = null;
}

OperationTombstone.prototype.onTimeout = function onTimeout(now) {
    var self = this;

    if (self.operations.requests.out[self.id] === self) {
        delete self.operations.requests.out[self.id];
    }

    self.timeHeapHandle = null;
};

Operations.prototype.checkLastTimeoutTime = function checkLastTimeoutTime(now) {
    var self = this;

    if (self.lastTimeoutTime &&
        now > self.lastTimeoutTime + self.connectionStalePeriod) {
        var err = errors.ConnectionStaleTimeoutError({
            lastTimeoutTime: self.lastTimeoutTime
        });
        process.nextTick(function opCheckLastTimedout() {
            self.connection.timedOutEvent.emit(self.connection, err);
        });
    } else if (!self.lastTimeoutTime) {
        self.lastTimeoutTime = now;
    }
};

Operations.prototype.getRequests = function getRequests() {
    var self = this;

    return self.requests;
};

Operations.prototype.getPending = function getPending() {
    var self = this;

    return self.pending;
};

Operations.prototype.getOutReq = function getOutReq(id) {
    var self = this;

    var req = self.requests.out[id] || null;
    if (req && req.isTombstone) {
        return null;
    } else {
        return req;
    }
};

Operations.prototype.getInReq = function getInReq(id) {
    var self = this;

    return self.requests.in[id];
};

Operations.prototype.addOutReq = function addOutReq(req) {
    var self = this;

    req.operations = self;
    self.requests.out[req.id] = req;
    self.pending.out++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.addInReq = function addInReq(req) {
    var self = this;

    req.operations = self;
    self.requests.in[req.id] = req;
    self.pending.in++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.popOutReq = function popOutReq(id, context) {
    var self = this;

    var req = self.requests.out[id];
    if (!req) {
        self.logMissingOutRequest(id, context);
        return null;
    } else if (req.isTombstone) {
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    }

    var now = self.timers.now();
    var timeout = TOMBSTONE_TTL_OFFSET + req.timeout;
    var tombstone = new OperationTombstone(self, id, now, timeout);
    req.operations = null;
    self.requests.out[id] = tombstone;
    self.pending.out--;

    tombstone.timeHeapHandle = self.connection.channel.timeHeap.update(tombstone, now);

    return req;
};

Operations.prototype.logMissingOutRequest =
function logMissingOutRequest(id, context) {
    var self = this;

    // context is err or res
    if (context && context.originalId) {
        context = {
            error: context,
            id: context.originalId,
            info: 'got error frame for unknown id'
        };
    } else if (context && context.id) {
        context = {
            responseId: context.id,
            code: context.code,
            arg1: Buffer.isBuffer(context.arg1) ?
                String(context.arg1) : 'streamed-arg1',
            info: 'got call response for unknown id'
        };
    }

    // This could be because of a confused / corrupted server.
    self.logger.info('popOutReq received for unknown or lost id', {
        context: context,
        socketRemoteAddr: self.connection.socketRemoteAddr,
        direction: self.connection.direction
    });
};

Operations.prototype.popInReq = function popInReq(id) {
    var self = this;

    var req = self.requests.in[id];
    if (!req) {
        // TODO warn ?
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    }

    delete self.requests.in[id];
    self.pending.in--;

    return req;
};

Operations.prototype.clear = function clear() {
    var self = this;

    self.pending.in = 0;
    self.pending.out = 0;
};

Operations.prototype.destroy = function destroy() {
    var self = this;

    self.destroyed = true;
};

Operations.prototype.sanitySweep = function sanitySweep() {
    var self = this;

    self._sweepOps(self.requests.in, 'in');
    self._sweepOps(self.requests.out, 'out');
};

Operations.prototype._sweepOps = function _sweepOps(ops, direction) {
    var self = this;

    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var op = ops[id];
        if (op === undefined) {
            self.logger.warn('unexpected undefined operation', {
                direction: direction,
                id: id
            });
        } else if (op.timedOut) {
            self.logger.warn('lingering timed-out operation', {
                direction: direction,
                id: id
            });

            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        }
    }
};
<|endoftext|>


<commit_before>/*********************** SPOTIFY API AUTHENTICATION ************************/

var stateKey = 'spotify_auth_state';
/**
 * Obtains parameters from the hash of the URL
 * @return Object
 */
function getHashParams() {
  var hashParams = {};
  var e, r = /([^&;=]+)=?([^&;]*)/g,
      q = window.location.hash.substring(1);
  while ( e = r.exec(q)) {
     hashParams[e[1]] = decodeURIComponent(e[2]);
  }
  return hashParams;
}
/**
 * Generates a random string containing numbers and letters
 * @param  {number} length The length of the string
 * @return {string} The generated string
 */
function generateRandomString(length) {
  var text = '';
  var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
};
//var userProfileSource = document.getElementById('user-profile-template').innerHTML,
//    userProfileTemplate = Handlebars.compile(userProfileSource),
//    userProfilePlaceholder = document.getElementById('user-profile'),
//
//    oauthSource = document.getElementById('oauth-template').innerHTML,
//    oauthTemplate = Handlebars.compile(oauthSource),
//    oauthPlaceholder = document.getElementById('oauth'),
//
//    playlistsSource = document.getElementById('playlists-template').innerHTML,
//    playlistsTemplate = Handlebars.compile(playlistsSource),
//    playlistsPlaceholder = document.getElementById('playlists');

//
//      playlist_1_Source = document.getElementById('playlist-open-1-template').innerHTML,
//      playlist_1_Template = Handlebars.compile(playlist_1_Source),
//      playlist_1_Placeholder = document.getElementById('playlist-open-1'),
//
//      playlist_2_Source = document.getElementById('playlist-open-2-template').innerHTML,
//      playlist_2_Template = Handlebars.compile(playlist_2_Source),
//      playlist_2_Placeholder = document.getElementById('playlist-open-2');

var params = getHashParams();
var access_token = params.access_token,
    state = params.state,
    storedState = localStorage.getItem(stateKey);
if (access_token && (state == null || state !== storedState)) {
  window.location = '/index.html';
  console.log('Access token reset');
} else {
  localStorage.removeItem(stateKey);
  if (access_token) {
    $('#login').hide();
    $('#loggedin').show();

    var user_id,
        ajaxHeaders = { 'Authorization': 'Bearer ' + access_token };

    var myPlayGraph,          // PlayGraph object...graph of tracks and playlists
        myVennSets,           // VennSets object...generates venn diagram data
        chart = venn.VennDiagram()
                      .duration(500)
                      .width(window.innerWidth - 250)
                      .height(window.innerHeight),
//        playSet,              // the queried set of tracks to play
//        activeSets = {},      // Hashes playlistID to playlists as d3 sets of trackIDs
//        playlistScore = {},   // Hashes playlistIDs to priority score
        maxActive = 8;        // maximum number of activePlaylists

    var div = d3.select("#venn"),
        plusbtn = d3.select("#plus-btn"),
        menu = d3.select("#menu"),
        sidebar = d3.select("#sidebar ul"),
        iframe = d3.select("#playbutton");

    // AJAX methods using spotify authorization
    function ajaxGet(url) {
      return $.ajax({
        url: url,
        method: 'GET',
        headers: ajaxHeaders
      });
    };

    function ajaxPost(url) {
      return $.ajax({
        url: url,
        method: 'POST',
        headers: ajaxHeaders
      });
    }

    function ajaxDelete(url) {
      return $.ajax({
        url: url,
        method: 'DELETE',
        headers: ajaxHeaders
      });
    }

    // Spotify API methods
    function getPlaylists() {
      return ajaxGet('https://api.spotify.com/v1/users/' + user_id
                     + '/playlists');
    }

    function getPlaylist(ownerID, playlistID) {
      return ajaxGet('https://api.spotify.com/v1/users/' + ownerID
                     + '/playlists/' + playlistID);
    }

    function addTrack(trackID, playlistID) {
      var owner = myPlayGraph.playlists[playlistID].ownerID;
      var uri = myPlayGraph.tracks[trackID].uri;
      ajaxPost('https://api.spotify.com/vi/users/' + owner
               + '/playlists/' + playlistID + '/tracks/uris=' + uri)
      .done( function(snapshot) {
        // update priority scores
        // update PlayGraph
        // update VennSets object
      }).fail( function(errorResponse) {
        // Error code 403 for forbidden access if user is not authorized
        // to add to playlist
        window.alert(errorResponse);
      });
    }

    function updateSidebar() {
      sidebar.selectAll("li").remove();
      var tracks = (myVennSets.getPlaySet()).values();
      for (var i = 0, l = tracks.length; i < l; i++) {
        sidebar
          .datum(tracks[i])
          .append("li")
          .html(function(d, i) {
            var markup = "";
            markup += myPlayGraph.tracks[d].name;
            markup += "
 "
            markup += myPlayGraph.tracks[d].artists.join(",");
            markup += ""
            return markup;
          })
          .on("mouseover", function(d, i) {
            var ids = Object.keys(myPlayGraph.tracks[d].containingPlaylists);
            ids.forEach(function(element) {
              d3.select("#g" + myVennSets.playlistIndex(element)).select("path")
                .transition().duration(250)
                .style("stroke-width", 2)
                .style("stroke-opacity", 1)
                .style("fill-opacity", 0.4);
            });
          })
          .on("mouseout", function(d, i) {
            var ids = Object.keys(myPlayGraph.tracks[d].containingPlaylists);
            ids.forEach(function(element) {
              d3.select("#g" + myVennSets.playlistIndex(element)).select("path")
                .transition().duration(250)
                .style("stroke-width", 0)
                .style("stroke-opacity", 0)
                .style("fill-opacity", 0.25);
            });
          });
      }
//       PLAYBUTTON IFRAME UPDATE
      if (tracks.length > 0) {
        iframe
          .attr("src", "https://embed.spotify.com/?uri=spotify:trackset:PREFEREDTITLE:" + tracks.join(","));
      } else {
        iframe
          .attr("src", "");
      }
    }

    // Update Venn Diagram
    function updateChart() {
      var data = myVennSets.getVennData();
      for (var j = 0; j < data.length; j++) {
        data[j].sets = data[j].ids.map(function (id) {
          return myPlayGraph.playlists[id].name;
        });
      }
      div.datum(data).call(chart);

      var tooltip = d3.select("body").append("div").attr("class", "venntooltip");

      div.selectAll("path")
        .style("stroke-opacity", 0)
        .style("stroke", "#fff")
        .style("stroke-width", 0)
        .style("fill", function(d, i) {
          return d.selected ? "white" : "gray";
        })
        .style("fill-opacity", function(d, i) {
          return 0.125 + d.ids.length/maxActive*0.5;
        });

      div.selectAll("g")
        .attr("id", function(d) { return "g" + d.index; })
        .on("mouseover", function (d, i) {
          // sort all the areas relative to the current item
          venn.sortAreas(div, d);

          // Display a tooltip with the current size
          tooltip.transition().duration(250).style("opacity", .8);
          tooltip.text(d.size + " track" + (d.size == 1 ? "" : "s"));

          // highlight the current path
          var selection = d3.select(this).transition("tooltip").duration(250);
          selection.select("path")
            .style("stroke-width", 2)
            .style("fill-opacity", d.sets.length == 1 ? .4 : .4)
            .style("stroke-opacity", 1);
        }).on("mousemove", function () {
          tooltip
            .style("left", (d3.event.pageX) + "px")
            .style("top", (d3.event.pageY - 28) + "px");
        }).on("mouseout", function (d, i) {
          tooltip.transition().duration(250).style("opacity", 0);
          var selection = d3.select(this).transition("tooltip").duration(250);
          selection.select("path")
            .style("stroke-width", 0)
            .style("fill-opacity", 0.125 + d.ids.length/maxActive*0.5)
//            .style("fill-opacity", d.sets.length == 1 ? .25 : .0)
            .style("stroke-opacity", 0);
        }).on("click", function(d, i) {
          if (d.selected) {
            myVennSets.deselectSet(d.ids);

            var indices = myVennSets.getAllIndicesContaining(d.ids);
            indices.forEach(function(element) {
              div.selectAll("#g" + element)
                .datum(function(data2, i) {
                  data2.selected = false;
                  return data2;
                })
                .select("path").style("fill", "gray")
                .style("fill-opacity", 0.125 + d.ids.length/maxActive*0.5);
            });
          } else {
            myVennSets.selectSet(d.ids);
            var indices = myVennSets.getAllIndicesContaining(d.ids);
            indices.forEach(function(element) {
              div.selectAll("#g" + element)
                .datum(function(data2, i) {
                  data2.selected = true;
                  return data2;
                })
                .select("path")
                .style("fill", "white")
                .style("fill-opacity", 0.125 + d.ids.length/maxActive*0.5);
            });
          }
          updateSidebar();
        })
        .selectAll("text").style("fill", "white");
    }

    function resizeChart() {
      chart = venn.VennDiagram()
                     .duration(500)
                     .width(window.innerWidth - 250)
                     .height(window.innerHeight);
      updateChart();
    }

    // Show playlist on venn diagram. Update data with new API call.
    function showPlaylist(playlistID) {
      myVennSets.addPlaylist(
        playlistID,
        Object.keys(myPlayGraph.playlists[playlistID].containedTracks)
      );

      // UI-animations
      plusbtn.classed("hidden", true);
      setTimeout(function () {
        plusbtn.classed("hidden", false);
      }, 500);

      updateChart();
    }

    // Hide playlist on venn diagram.
    function hidePlaylist(playlistID) {
      var action = myVennSets.removePlaylist(playlistID);
      if (!action)
        return false; // can't remove last playlist
      updateChart();
      if (!sidebar.selectAll("ul li").empty()) {
        updateSidebar();
      }
      return true;
    }

    // Toggle playlist on venn diagram.
    function togglePlaylist(playlistID) {
      if (myVennSets.containsPlaylist(playlistID)) {
        return hidePlaylist(playlistID);
      } else {
        showPlaylist(playlistID);
        return true;
      }
    }

    /***************** INITIALIZE APP *****************/

    // GET User Profile Information to test API.
    ajaxGet('https://api.spotify.com/v1/me')
    .done( function(response) {
//      userProfilePlaceholder.innerHTML = userProfileTemplate(response);
      console.log(response);
      user_id = response.id;
      console.log("User ID = " + user_id);

      // Initialize PlayGraph
      getPlaylists().done( function(spotifyPlaylistsObject) {
//        console.log(spotifyPlaylistsObject);

        myPlayGraph = new PlayGraph();
        var promises = [];
        for (var i = 0, l = spotifyPlaylistsObject.items.length; i < l; i++) {
          var simplePL = spotifyPlaylistsObject.items[i];
          promises.push(
            getPlaylist(simplePL.owner.id, simplePL.id)
            .done( function(spotifyPlaylistObject) {
              myPlayGraph.addPlaylist(spotifyPlaylistObject);
            })
            .fail( function(errorResponse) {
              window.alert(errorResponse);
            })
          );
        }

        // When all getPlaylist AJAX responses are received:
        $.when.apply(null, promises).done( function() {

          // Initialize VennSets and visualization
          var slice = Object.keys(myPlayGraph.playlists).slice(0, maxActive);
          var vsInput = {};
          for (var j = 0; j < slice.length; j++) {
            var id = slice[j];
            vsInput[id] = Object.keys(myPlayGraph.playlists[id].containedTracks);
          }
          myVennSets = new VennSets(vsInput);
          updateChart();

          // Initialize menu
          var ul = d3.select("#menu ul");
          var playlistIDs = Object.keys(myPlayGraph.playlists);
          for (var i = 0, l = playlistIDs.length; i < l; i++) {
            ul.datum(playlistIDs[i])
              .append("li")
              .classed("selected", true)
              .html(function (d, i) { return myPlayGraph.playlists[d].name; })
              .on("click", function (d, i) {
                if (togglePlaylist(d))
                d3.select(this).classed("selected", !d3.select(this).classed("selected"));
              });
          }

          updateSidebar();

          $(window).resize(_.debounce(resizeChart, 400));

          d3.select("#plus-btn").on("click", function(d, i) {
            menu.classed("closed", !menu.classed("closed"));
            plusbtn.classed("rotated", !plusbtn.classed("rotated"));
          });

          // Initialize priority score data structures
//          for (playlistID in Object.keys(myPlayGraph.playlists)) {
//            playlistScore[playlistID] = 1;
//          }
        });
      }).fail( function(errorResponse) {
        window.alert(errorResponse);
      });
    });

  } else { // login failed
      $('#login').show();
      $('#loggedin').hide();
  }
  document.getElementById('login-button').addEventListener('click', function() {
    var client_id = '173e56dc6f4f4f7bac61397e362bd814'; // Your client id
    var redirect_uri = 'taichiaritomo.github.io/PlaySet/'; // Your redirect uri
    var state = generateRandomString(16);
    localStorage.setItem(stateKey, state);
    var scope = 'user-read-private user-library-read playlist-modify-public playlist-read-collaborative playlist-modify-private';
    var url = 'https://accounts.spotify.com/authorize';
    url += '?response_type=token';
    url += '&client_id=' + encodeURIComponent(client_id);
    url += '&scope=' + encodeURIComponent(scope);
    url += '&redirect_uri=' + encodeURIComponent(redirect_uri);
    url += '&state=' + encodeURIComponent(state);
    window.location = url;
  }, false);
}<commit_msg>Fix URL auth<commit_after>/*********************** SPOTIFY API AUTHENTICATION ************************/

var stateKey = 'spotify_auth_state';
/**
 * Obtains parameters from the hash of the URL
 * @return Object
 */
function getHashParams() {
  var hashParams = {};
  var e, r = /([^&;=]+)=?([^&;]*)/g,
      q = window.location.hash.substring(1);
  while ( e = r.exec(q)) {
     hashParams[e[1]] = decodeURIComponent(e[2]);
  }
  return hashParams;
}
/**
 * Generates a random string containing numbers and letters
 * @param  {number} length The length of the string
 * @return {string} The generated string
 */
function generateRandomString(length) {
  var text = '';
  var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
};
//var userProfileSource = document.getElementById('user-profile-template').innerHTML,
//    userProfileTemplate = Handlebars.compile(userProfileSource),
//    userProfilePlaceholder = document.getElementById('user-profile'),
//
//    oauthSource = document.getElementById('oauth-template').innerHTML,
//    oauthTemplate = Handlebars.compile(oauthSource),
//    oauthPlaceholder = document.getElementById('oauth'),
//
//    playlistsSource = document.getElementById('playlists-template').innerHTML,
//    playlistsTemplate = Handlebars.compile(playlistsSource),
//    playlistsPlaceholder = document.getElementById('playlists');

//
//      playlist_1_Source = document.getElementById('playlist-open-1-template').innerHTML,
//      playlist_1_Template = Handlebars.compile(playlist_1_Source),
//      playlist_1_Placeholder = document.getElementById('playlist-open-1'),
//
//      playlist_2_Source = document.getElementById('playlist-open-2-template').innerHTML,
//      playlist_2_Template = Handlebars.compile(playlist_2_Source),
//      playlist_2_Placeholder = document.getElementById('playlist-open-2');

var params = getHashParams();
var access_token = params.access_token,
    state = params.state,
    storedState = localStorage.getItem(stateKey);
if (access_token && (state == null || state !== storedState)) {
  window.location = '/index.html';
  console.log('Access token reset');
} else {
  localStorage.removeItem(stateKey);
  if (access_token) {
    $('#login').hide();
    $('#loggedin').show();

    var user_id,
        ajaxHeaders = { 'Authorization': 'Bearer ' + access_token };

    var myPlayGraph,          // PlayGraph object...graph of tracks and playlists
        myVennSets,           // VennSets object...generates venn diagram data
        chart = venn.VennDiagram()
                      .duration(500)
                      .width(window.innerWidth - 250)
                      .height(window.innerHeight),
//        playSet,              // the queried set of tracks to play
//        activeSets = {},      // Hashes playlistID to playlists as d3 sets of trackIDs
//        playlistScore = {},   // Hashes playlistIDs to priority score
        maxActive = 8;        // maximum number of activePlaylists

    var div = d3.select("#venn"),
        plusbtn = d3.select("#plus-btn"),
        menu = d3.select("#menu"),
        sidebar = d3.select("#sidebar ul"),
        iframe = d3.select("#playbutton");

    // AJAX methods using spotify authorization
    function ajaxGet(url) {
      return $.ajax({
        url: url,
        method: 'GET',
        headers: ajaxHeaders
      });
    };

    function ajaxPost(url) {
      return $.ajax({
        url: url,
        method: 'POST',
        headers: ajaxHeaders
      });
    }

    function ajaxDelete(url) {
      return $.ajax({
        url: url,
        method: 'DELETE',
        headers: ajaxHeaders
      });
    }

    // Spotify API methods
    function getPlaylists() {
      return ajaxGet('https://api.spotify.com/v1/users/' + user_id
                     + '/playlists');
    }

    function getPlaylist(ownerID, playlistID) {
      return ajaxGet('https://api.spotify.com/v1/users/' + ownerID
                     + '/playlists/' + playlistID);
    }

    function addTrack(trackID, playlistID) {
      var owner = myPlayGraph.playlists[playlistID].ownerID;
      var uri = myPlayGraph.tracks[trackID].uri;
      ajaxPost('https://api.spotify.com/vi/users/' + owner
               + '/playlists/' + playlistID + '/tracks/uris=' + uri)
      .done( function(snapshot) {
        // update priority scores
        // update PlayGraph
        // update VennSets object
      }).fail( function(errorResponse) {
        // Error code 403 for forbidden access if user is not authorized
        // to add to playlist
        window.alert(errorResponse);
      });
    }

    function updateSidebar() {
      sidebar.selectAll("li").remove();
      var tracks = (myVennSets.getPlaySet()).values();
      for (var i = 0, l = tracks.length; i < l; i++) {
        sidebar
          .datum(tracks[i])
          .append("li")
          .html(function(d, i) {
            var markup = "";
            markup += myPlayGraph.tracks[d].name;
            markup += "
 "
            markup += myPlayGraph.tracks[d].artists.join(",");
            markup += ""
            return markup;
          })
          .on("mouseover", function(d, i) {
            var ids = Object.keys(myPlayGraph.tracks[d].containingPlaylists);
            ids.forEach(function(element) {
              d3.select("#g" + myVennSets.playlistIndex(element)).select("path")
                .transition().duration(250)
                .style("stroke-width", 2)
                .style("stroke-opacity", 1)
                .style("fill-opacity", 0.4);
            });
          })
          .on("mouseout", function(d, i) {
            var ids = Object.keys(myPlayGraph.tracks[d].containingPlaylists);
            ids.forEach(function(element) {
              d3.select("#g" + myVennSets.playlistIndex(element)).select("path")
                .transition().duration(250)
                .style("stroke-width", 0)
                .style("stroke-opacity", 0)
                .style("fill-opacity", 0.25);
            });
          });
      }
//       PLAYBUTTON IFRAME UPDATE
      if (tracks.length > 0) {
        iframe
          .attr("src", "https://embed.spotify.com/?uri=spotify:trackset:PREFEREDTITLE:" + tracks.join(","));
      } else {
        iframe
          .attr("src", "");
      }
    }

    // Update Venn Diagram
    function updateChart() {
      var data = myVennSets.getVennData();
      for (var j = 0; j < data.length; j++) {
        data[j].sets = data[j].ids.map(function (id) {
          return myPlayGraph.playlists[id].name;
        });
      }
      div.datum(data).call(chart);

      var tooltip = d3.select("body").append("div").attr("class", "venntooltip");

      div.selectAll("path")
        .style("stroke-opacity", 0)
        .style("stroke", "#fff")
        .style("stroke-width", 0)
        .style("fill", function(d, i) {
          return d.selected ? "white" : "gray";
        })
        .style("fill-opacity", function(d, i) {
          return 0.125 + d.ids.length/maxActive*0.5;
        });

      div.selectAll("g")
        .attr("id", function(d) { return "g" + d.index; })
        .on("mouseover", function (d, i) {
          // sort all the areas relative to the current item
          venn.sortAreas(div, d);

          // Display a tooltip with the current size
          tooltip.transition().duration(250).style("opacity", .8);
          tooltip.text(d.size + " track" + (d.size == 1 ? "" : "s"));

          // highlight the current path
          var selection = d3.select(this).transition("tooltip").duration(250);
          selection.select("path")
            .style("stroke-width", 2)
            .style("fill-opacity", d.sets.length == 1 ? .4 : .4)
            .style("stroke-opacity", 1);
        }).on("mousemove", function () {
          tooltip
            .style("left", (d3.event.pageX) + "px")
            .style("top", (d3.event.pageY - 28) + "px");
        }).on("mouseout", function (d, i) {
          tooltip.transition().duration(250).style("opacity", 0);
          var selection = d3.select(this).transition("tooltip").duration(250);
          selection.select("path")
            .style("stroke-width", 0)
            .style("fill-opacity", 0.125 + d.ids.length/maxActive*0.5)
//            .style("fill-opacity", d.sets.length == 1 ? .25 : .0)
            .style("stroke-opacity", 0);
        }).on("click", function(d, i) {
          if (d.selected) {
            myVennSets.deselectSet(d.ids);

            var indices = myVennSets.getAllIndicesContaining(d.ids);
            indices.forEach(function(element) {
              div.selectAll("#g" + element)
                .datum(function(data2, i) {
                  data2.selected = false;
                  return data2;
                })
                .select("path").style("fill", "gray")
                .style("fill-opacity", 0.125 + d.ids.length/maxActive*0.5);
            });
          } else {
            myVennSets.selectSet(d.ids);
            var indices = myVennSets.getAllIndicesContaining(d.ids);
            indices.forEach(function(element) {
              div.selectAll("#g" + element)
                .datum(function(data2, i) {
                  data2.selected = true;
                  return data2;
                })
                .select("path")
                .style("fill", "white")
                .style("fill-opacity", 0.125 + d.ids.length/maxActive*0.5);
            });
          }
          updateSidebar();
        })
        .selectAll("text").style("fill", "white");
    }

    function resizeChart() {
      chart = venn.VennDiagram()
                     .duration(500)
                     .width(window.innerWidth - 250)
                     .height(window.innerHeight);
      updateChart();
    }

    // Show playlist on venn diagram. Update data with new API call.
    function showPlaylist(playlistID) {
      myVennSets.addPlaylist(
        playlistID,
        Object.keys(myPlayGraph.playlists[playlistID].containedTracks)
      );

      // UI-animations
      plusbtn.classed("hidden", true);
      setTimeout(function () {
        plusbtn.classed("hidden", false);
      }, 500);

      updateChart();
    }

    // Hide playlist on venn diagram.
    function hidePlaylist(playlistID) {
      var action = myVennSets.removePlaylist(playlistID);
      if (!action)
        return false; // can't remove last playlist
      updateChart();
      if (!sidebar.selectAll("ul li").empty()) {
        updateSidebar();
      }
      return true;
    }

    // Toggle playlist on venn diagram.
    function togglePlaylist(playlistID) {
      if (myVennSets.containsPlaylist(playlistID)) {
        return hidePlaylist(playlistID);
      } else {
        showPlaylist(playlistID);
        return true;
      }
    }

    /***************** INITIALIZE APP *****************/

    // GET User Profile Information to test API.
    ajaxGet('https://api.spotify.com/v1/me')
    .done( function(response) {
//      userProfilePlaceholder.innerHTML = userProfileTemplate(response);
      console.log(response);
      user_id = response.id;
      console.log("User ID = " + user_id);

      // Initialize PlayGraph
      getPlaylists().done( function(spotifyPlaylistsObject) {
//        console.log(spotifyPlaylistsObject);

        myPlayGraph = new PlayGraph();
        var promises = [];
        for (var i = 0, l = spotifyPlaylistsObject.items.length; i < l; i++) {
          var simplePL = spotifyPlaylistsObject.items[i];
          promises.push(
            getPlaylist(simplePL.owner.id, simplePL.id)
            .done( function(spotifyPlaylistObject) {
              myPlayGraph.addPlaylist(spotifyPlaylistObject);
            })
            .fail( function(errorResponse) {
              window.alert(errorResponse);
            })
          );
        }

        // When all getPlaylist AJAX responses are received:
        $.when.apply(null, promises).done( function() {

          // Initialize VennSets and visualization
          var slice = Object.keys(myPlayGraph.playlists).slice(0, maxActive);
          var vsInput = {};
          for (var j = 0; j < slice.length; j++) {
            var id = slice[j];
            vsInput[id] = Object.keys(myPlayGraph.playlists[id].containedTracks);
          }
          myVennSets = new VennSets(vsInput);
          updateChart();

          // Initialize menu
          var ul = d3.select("#menu ul");
          var playlistIDs = Object.keys(myPlayGraph.playlists);
          for (var i = 0, l = playlistIDs.length; i < l; i++) {
            ul.datum(playlistIDs[i])
              .append("li")
              .classed("selected", true)
              .html(function (d, i) { return myPlayGraph.playlists[d].name; })
              .on("click", function (d, i) {
                if (togglePlaylist(d))
                d3.select(this).classed("selected", !d3.select(this).classed("selected"));
              });
          }

          updateSidebar();

          $(window).resize(_.debounce(resizeChart, 400));

          d3.select("#plus-btn").on("click", function(d, i) {
            menu.classed("closed", !menu.classed("closed"));
            plusbtn.classed("rotated", !plusbtn.classed("rotated"));
          });

          // Initialize priority score data structures
//          for (playlistID in Object.keys(myPlayGraph.playlists)) {
//            playlistScore[playlistID] = 1;
//          }
        });
      }).fail( function(errorResponse) {
        window.alert(errorResponse);
      });
    });

  } else { // login failed
      $('#login').show();
      $('#loggedin').hide();
  }
  document.getElementById('login-button').addEventListener('click', function() {
    var client_id = '173e56dc6f4f4f7bac61397e362bd814'; // Your client id
    var redirect_uri = 'http://taichiaritomo.github.io/PlaySet/'; // Your redirect uri
    var state = generateRandomString(16);
    localStorage.setItem(stateKey, state);
    var scope = 'user-read-private user-library-read playlist-modify-public playlist-read-collaborative playlist-modify-private';
    var url = 'https://accounts.spotify.com/authorize';
    url += '?response_type=token';
    url += '&client_id=' + encodeURIComponent(client_id);
    url += '&scope=' + encodeURIComponent(scope);
    url += '&redirect_uri=' + encodeURIComponent(redirect_uri);
    url += '&state=' + encodeURIComponent(state);
    window.location = url;
  }, false);
}<|endoftext|>


<commit_before>// very basic stuff, not really transducers but less code
import { seq } from "./seq";

export function isIterable(obj) {
	// FIXME is this acceptable?
	return !!obj && typeof obj != "string" && typeof obj[Symbol.iterator] === 'function';
}

function Singleton(val) {
	this.val = val;
}

Singleton.prototype.next = function(){
	if(this.val !== undefined) {
		var val = this.val;
		this.val = undefined;
		return { value:val };
	}
	return { done: true };
};

function _getIter(iterable) {
	return iterable === undefined ? new Singleton() :
		isIterable(iterable) ? iterable[Symbol.iterator]() :
			iterable.constructor == Object ? Object.entries(iterable)[Symbol.iterator]() :
				typeof iterable.next === "function" ? iterable :
					new Singleton(iterable);
}

export function compose(...funcs) {
	const l = funcs.length;
	return (v, i, iterable, z) => {
		let reset = false, c = _append;
		for (var j = 0; j < l; j++) {
			let ret = reset ? z : funcs[j].call(null, v, i, iterable, z);
			if (ret === undefined) {
				reset = true;
				continue;
			}
			// if it's a step, continue processing
			if (ret["@@step"]) {
				v = ret.v;
				z = ret.z;
				c = ret.f;
				if(ret.t == i) return !reset ? step(z, v, c) : z;
			} else {
				// stop processing current iteration
				reset = true;
				z = ret;
			}
		}
		// append at the end
		return !reset ? step(z, v, c) : z;
	};
}

// TODO pass control function to the point where a value would be yielded
// use that to control a custom iterator
function _iterate(iterable, f, z) {
	if (z === undefined) z = _new(iterable);
	var i = 0;
	// iterate anything
	var iter = _getIter(iterable);
	let next;
	while (next = iter.next(), !next.done) {
		let v = next.value;
		let ret = f(v, i, iterable, z);
		if(ret !== undefined){
			if (ret["@@step"]) {
				z = ret.f(ret.z, ret.v);
				if(ret.t == i) return z;
			} else {
				z = ret;
			}
		}
		i++;
	}
	return z;
}

function _new(iterable) {
	return iterable["@@empty"] ? iterable["@@empty"]() : new iterable.constructor();
}

// checkiecheckie
function _append(iterable, appendee) {
	if(iterable["@@append"]) {
		return iterable["@@append"](appendee);
	} else if(iterable.push){
		let appended = iterable.push(appendee);
		// stateful stuff
		if (appended !== iterable) {
			return iterable;
		}
		return appended;
	} else if(iterable.set) {
		let appended = iterable.set(appendee[0], appendee[1]);
		// stateful stuff
		if (appended !== iterable) {
			return iterable;
		}
		return appended;
	} else {
		return seq(appendee);
	}
	// badeet badeet bathatsallfolks!
}

// introduce a step so we can reuse _iterate for foldLeft
function step(z, v, f, t, d) {
	// we're going to process this further
	return {
		z: z,
		v: v,
		f: f,
		t:t,
		"@@step": true
	};
}

function _contains(iterable, value, comp){
	// FIXME how to prevent iteration?
	let iter = _getIter(iterable);
	let next;
	while (next = iter.next(), !next.done) {
		if(next.value === value) return true;
	}
	return false;
}

export function distinctCat$1(f) {
	// FIXME how to optimize?
	return function transDistinctCat(v, i, iterable, z) {
		return step(z, v, function (z, v) {
			return foldLeft(v, z, function(z,v){
				if (f(z, v)) return _append(z,v);
				return z;
			});
		});
	};
}

export function cat(v, i, iterable, z) {
	return step(z, v, function(z,v){
		return foldLeft(v,z,_append);
	});
}

function forEach$1(f) {
	return function transForEach(v, i, iterable, z) {
		return step(z, f(v, i, iterable), _append);
	};
}

function filter$1(f) {
	return function transFilter(v, i, iterable, z) {
		if (f(v, i, iterable)) {
			return step(z, v, _append);
		}
		return z;
	};
}

function foldLeft$1(f, z) {
	return function transFoldLeft(v, i, iterable, z) {
		return f(z, v, i, iterable);
	};
}

function take$1(idx){
	return function transTake(v, i, iterable, z) {
		if (i < idx) {
			return step(z, v, _append, idx);
		}
		return z;
	};
}

function drop$1(idx){
	return function transDrop(v, i, iterable, z) {
		if (i >= idx) {
			return step(z, v, _append, -1);
		}
		return z;
	};
}

export function drop(iterable, i){
	if (arguments.length == 1) return drop$1(iterable);
	return _iterate(iterable, drop$1(i), _new(iterable));
}

export function take(iterable, i){
	if (arguments.length == 1) return take$1(iterable);
	return _iterate(iterable, take$1(i), _new(iterable));
}

export function forEach(iterable, f) {
	if (arguments.length == 1) return forEach$1(iterable);
	return _iterate(iterable, forEach$1(f), _new(iterable));
}

export function filter(iterable, f) {
	if (arguments.length == 1) return filter$1(iterable);
	return _iterate(iterable, filter$1(f), _new(iterable));
}

export function distinctCat(iterable, f) {
	if (arguments.length < 2) return distinctCat$1(iterable || _contains);
	return _iterate(iterable, distinctCat$1(f), _new(iterable));
}

// non-composables!
export function foldLeft(iterable, z, f) {
	return _iterate(iterable, foldLeft$1(f), z);
}

// FIXME always return a collection, iterate by overriding _append to just return the value
export function transform(iterable, f) {
	return _iterate(iterable, f);
}

export function into(iterable, f, z) {
	return _iterate(iterable, f, z);
}

export function range(n,s=0) {
	var arr = [];
	for(var i=s; i<n; i++) {
		arr.push(i);
	}
	return seq(arr);
}

// TODO:
// rewindable/fastforwardable iterators
<commit_msg>moved transducers to standard library<commit_after>import { seq, isSeq } from "./seq";
import * as t from "transducers.js";
import { RangeObservable } from "rxjs/observable/RangeObservable";

export function compose() {
	var l = arguments.length;
	var funcs = new Array(l);
	for(var i = 0; i < l; ++i) {
		funcs[i] = arguments[i];
	}
	return function(r) {
		var value = r;
		for(var i = l - 1; i >= 0; i--) {
			value = funcs[i](value);
		}
		return value;
	};
}

export function forEach(iterable, f) {
	if (arguments.length == 1) return t.map(iterable);
	return transform(iterable, t.map(f));
}
/*
export function distinctCat(iterable, f) {
	if (arguments.length < 2) return distinctCat$1(iterable || _contains);
	return _iterate(iterable, distinctCat$1(f), _new(iterable));
}
*/
// non-composable
export function foldLeft(iterable, z, f) {
	return iterable.reduce(f,z);
}

// FIXME always return a collection, iterate by overriding _append to just return the value
export function transform(iterable, f) {
	return isSeq(iterable) ? iterable.transform(f) : t.seq(iterable, f);
}

export function into(iterable, f, z) {
	return t.into(z, f, iterable);
}

export function range(n,s=0) {
	return seq(new RangeObservable(s,n));
}

export { MergeMapOperator } from "rxjs/operator/mergeMap";

export { cat, filter, erase, keep, take, drop, takeWhile, dropWhile, dedupe } from "transducers.js";
<|endoftext|>


<commit_before>import "net";

/**
 * Helper for secure SolarNetwork API access. Configure a token and secret
 * and then use the json function to make secure SolarNetwork API requests. The returned
 * object is also a function that can be called directly, instead of using the json property.
 *
 * @class
 * @param {String} [apiToken] - An initial token to use.
 * @param {String} [apiTokenSecret] - An initial token secret to use.
 * @returns {sn.net.securityHelper}
 * @require uri-js https://github.com/garycourt/uri-js
 * @preserve
 */
sn.net.securityHelper = function(apiToken, apiTokenSecret) {
	'use strict';
	var that = json;

	var kFormUrlEncodedContentTypeRegex = /^application\/x-www-form-urlencoded/i;

	// our in-memory credentials
	var cred = {token: apiToken, secret: apiTokenSecret, signingKey: null, signingKeyExpiry: null};

	// setup core properties
	Object.defineProperties(that, {
		version								: { value : '1.3.0' },
		hasTokenCredentials					: { value : hasTokenCredentials },
		token								: { value : token },
		secret								: { value : secret },
		hasSecret 							: { value : hasSecret },
		clearSecret 						: { value : clearSecret },
		generateAuthorizationHeaderValue 	: { value : generateAuthorizationHeaderValue },
		parseURLQueryTerms 					: { value : parseURLQueryTerms },
		json 								: { value : json }
	});

	/**
	 * Return true if both a token and a secret have been set, false otherwise.
	 *
	 * @return {Boolean} true if a token and secret have been set.
	 * @preserve
	 */
	function hasTokenCredentials() {
		return (cred.token && cred.token.length > 0 && cred.secret && cred.secret.length > 0);
	}

	/**
	 * Get or set the in-memory security token to use.
	 *
	 * @param {String} [value] The value to set, or null to clear.
	 * @returs When used as a getter, the current token value, otherwise this object.
	 * @preserve
	 */
	function token(value) {
		if ( !arguments.length ) return cred.token;
		cred.token = (value && value.length > 0 ? value : undefined);
		return that;
	}

	/**
	 * Set the in-memory security token secret to use.
	 *
	 * @param {String} [value] The value to set.
	 * @returns This object.
	 * @preserve
	 */
	function secret(value) {
		if ( arguments.length ) {
			cred.secret = value;
		}
		return that;
	}

	/**
	 * Return true if a secret has been set, false otherwise.
	 *
	 * @return {Boolean} true if a secret has been set.
	 * @preserve
	 */
	function hasSecret() {
		return (cred.secret && cred.secret.length > 0);
	}

	/**
	 * Clear the in-memory secret.
	 *
	 * @returns This object.
	 * @preserve
	 */
	function clearSecret() {
		cred.secret = undefined;
		return that;
	}

	/**
	 * Test if a Digest header should be included in the request, based on the
	 * request content type.
	 *
	 * @param {String} contentType the content type
	 * @returns {Boolean} true if including the Digest hash is appropriate
	 */
	function shouldIncludeContentDigest(contentType) {
		// we don't send Digest for form data, because server treats this as URL parameters
		return (contentType && contentType.match(kFormUrlEncodedContentTypeRegex));
	}

	/**
	 * Generate the canonical request message for a set of request parameters.
	 *
	 * @param {Object} params the request parameters
	 * @param {String} params.method the HTTP request method
	 * @param {Object} params.uri a parsed URI object for the request
	 * @param {String} params.queryParams the canonical query parameters string
	 * @param {Object} params.headers the canonical headers object
	 * @param {Object} params.bodyDigest the CryptoJS body content digest
	 * @return {String} the message
	 * @preserve
	 */
	function generateCanonicalRequestMessage(params) {
		var msg =
			(params.method === undefined ? 'GET' : params.method.toUpperCase()) + '\n'
			+params.uri.path +'\n'
			+params.queryParams +'\n';
		params.headers.headerNames.forEach(function(name) {
			msg += name + ':' +params.headers.headers[name] + '\n';
		});
		msg += params.headers.headerNames.join(';') +'\n';
		msg += CryptoJS.enc.Hex.stringify(params.bodyDigest);
		return msg;
	}

	function generateSigningMessage(date, canonRequestMsg) {
		var msg = 'SNWS2-HMAC-SHA256\n'
			+iso8601Date(date, true) +'\n'
			+CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(canonRequestMsg));
		return msg;
	}

	/**
	 * Generate the V2 HTTP Authorization header.
	 *
	 * @param {String} token the SN token ID
	 * @param {Array} signedHeaders the sorted array of signed header names
	 * @param {Object} signKey the key to encrypt the signature with
	 * @param {String} signingMsg the message to sign
	 * @return {String} the HTTP header value
	 * @preserve
	 */
	function generateAuthorizationHeaderValue(signedHeaders, signKey, signingMsg) {
		var signature = CryptoJS.HmacSHA256(signingMsg, signKey);
		var authHeader = 'SNWS2 Credential=' +cred.token
			+',SignedHeaders=' +signedHeaders.join(';')
			+',Signature=' +CryptoJS.enc.Hex.stringify(signature);
		return authHeader;
	}

	/**
	 * Parse the query portion of a URL string, and return a parameter object for the
	 * parsed key/value pairs.
	 *
	 *
Multiple parameters of the same name are not supported.

	 *
	 * @param {String} search the query portion of the URL, which may optionally include
	 *                        the leading '?' character
	 * @return {Object} the parsed query parameters, as a parameter object
	 * @preserve
	 */
	function parseURLQueryTerms(search) {
		var params = {};
		var pairs;
		var pair;
		var i, len;
		if ( search !== undefined && search.length > 0 ) {
			// remove any leading ? character
			if ( search.match(/^\?/) ) {
				search = search.substring(1);
			}
			pairs = search.split('&');
			for ( i = 0, len = pairs.length; i < len; i += 1 ) {
				pair = pairs[i].split('=', 2);
				if ( pair.length === 2 ) {
					params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
				}
			}
		}
		return params;
	}

	function _encodeURIComponent(str) {
	  return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
		return '%' + c.charCodeAt(0).toString(16);
	  });
	}

	function canonicalQueryParameters(uri, data, contentType) {
		var params = parseURLQueryTerms(data && contentType.match(kFormUrlEncodedContentTypeRegex)
			? data : uri.query);
		var sortedKeys = [],
			key,
			i,
			len,
			first = true,
			result;

		for ( key in params ) {
			sortedKeys.push(key);
		}
		sortedKeys.sort();
		for ( i = 0, len = sortedKeys.length; i < len; i += 1 ) {
			if ( first ) {
				first = false;
				result = '';
			} else {
				result += '&';
			}
			key = sortedKeys[i];
			result +=  _encodeURIComponent(key);
			result += '=';
			result += _encodeURIComponent(params[key]);
		}
		return result;
	}

	/**
	 * Create an object with the canonical header names and their associated values.
	 *
	 * @param {Object} uri           a parsed URI object for the request
	 * @param {String} contentType   the request content type
	 * @param {Date} date            the request date
	 * @param {Object} contentSHA256 the CryptoJS digest object of the request body content
	 * @return {Object} An object with a sorted headerNames property array of strings, and
	 *                  a headers object of the header name values for keys and
	 *                  associated header values as values.
	 */
	function canonicalHeaders(uri, contentType, date, contentSHA256) {
		var result = {
			headerNames : ['host', 'x-sn-date'],
			headers : {
				'host' : uri.host + (uri.port && uri.port !== 80 && uri.port !== 443 ? ':'+uri.port : ''),
				'x-sn-date' : date.toUTCString()
			}
		};
		if ( shouldIncludeContentDigest(contentType) ) {
			result.headerNames.push('content-type');
			result.headers['content-type'] = contentType;
			result.headerNames.push('digest');
			result.headers['digest'] = 'sha-256='+CryptoJS.enc.Base64.stringify(contentSHA256);
		}
		result.headerNames.sort();
		return result;
	}

	function bodyContentSHA256(data, contentType) {
		return CryptoJS.SHA256(data && !contentType.match(kFormUrlEncodedContentTypeRegex)
			? data
			: '');
	}

	function iso8601Date(date, includeTime) {
		return ''+date.getUTCFullYear()
				+(date.getUTCMonth() < 9 ? '0' : '') +(date.getUTCMonth()+1)
				+(date.getUTCDate() < 10 ? '0' : '') + date.getUTCDate()
				+(includeTime ?
					'T'
					+(date.getUTCHours() < 10 ? '0' : '') + date.getUTCHours()
					+(date.getUTCMinutes() < 10 ? '0' : '') + date.getUTCMinutes()
					+(date.getUTCSeconds() < 10 ? '0' : '') +date.getUTCSeconds()
					+'Z'
					: '');
	}

	function signingKey(date) {
		var dateString,
			key = cred.signingKey,
			expireDate;
		date = date || new Date();
		if ( !key || date.getTime() > cred.signingKeyExpiry ) {
			dateString = iso8601Date(date);
			key = CryptoJS.HmacSHA256('snws2_request', CryptoJS.HmacSHA256(dateString, 'SNWS2' + cred.secret));
			cred.signingKey = key;

			expireDate = new Date(date);
			expireDate.setUTCHours(0);
			expireDate.setUTCMinutes(0);
			expireDate.setUTCSeconds(0);
			expireDate.setUTCMilliseconds(0);
			cred.signingKeyExpiry = expireDate.getTime() + (7 * 24 * 60 * 60 * 1000);
		}
		return key;
	}

	/**
	 * Invoke the web service URL, adding the required SolarNetworkWS authorization
	 * headers to the request.
	 *
	 *
This method will construct the X-SN-Date and Authorization
	 * header values needed to invoke the web service. It returns a d3 XHR object,
	 * so you can call .on() on that to handle the response, unless a callback
	 * parameter is specified, then the request is issued immediately, passing the
	 * method, data, and callback parameters
	 * to xhr.send().

	 *
	 * @param {String} url the web service URL to invoke
	 * @param {String} method the HTTP method to use; e.g. GET or POST
	 * @param {String} [data] the data to upload
	 * @param {String} [contentType] the content type of the data
	 * @param {Function} [callback] if defined, a d3 callback function to handle the response JSON with
	 * @return {Object} d3 XHR object
	 * @preserve
	 */
	function json(url, method, data, contentType, callback) {
		var requestUrl = url;
		// We might be passed to queue, and then our callback will be the last argument (but possibly not #5
		// if the original call to queue didn't pass all arguments) so we check for that at the start and
		// adjust what we consider the method, data, and contentType parameter values.
		if ( arguments.length > 0 ) {
			if ( arguments.length < 5 && typeof arguments[arguments.length - 1] === 'function' ) {
				callback = arguments[arguments.length - 1];
			}
			if ( typeof method !== 'string' ) {
				method = undefined;
			}
			if ( typeof data !== 'string' ) {
				data = undefined;
			}
			if ( typeof contentType !== 'string' ) {
				contentType = undefined;
			}
		}
		method = (method === undefined ? 'GET' : method.toUpperCase());
		if ( method === 'POST' || method === 'PUT' ) {
			// extract any URL request parameters and put into POST body
			if ( !data ) {
				(function() {
					var queryIndex = url.indexOf('?');
					if ( queryIndex !== -1 ) {
						if ( queryIndex + 1 < url.length - 1 ) {
							data = url.substring(queryIndex + 1);
						}
						requestUrl = url.substring(0, queryIndex);
						contentType = 'application/x-www-form-urlencoded; charset=UTF-8';
					}
				}());
			}
		}
		var xhr = d3.json(requestUrl);
		if ( contentType !== undefined ) {
			xhr.header('Content-Type', contentType);
		}
		xhr.on('beforesend', function(request) {
			var date = new Date();

			var uri = URI.parse(url);

			var canonQueryParams = canonicalQueryParameters(uri, data, contentType);

			var bodyContentDigest = bodyContentSHA256(data, contentType);

			var canonHeaders = canonicalHeaders(uri, contentType, date, bodyContentDigest);

			var canonRequestMsg = generateCanonicalRequestMessage({
				method: method,
				uri: uri,
				queryParams : canonQueryParams,
				headers : canonHeaders,
				bodyDigest: bodyContentDigest
			});

			var signingMsg = generateSigningMessage(date, canonRequestMsg);

			var signKey = signingKey(date);

			var auth = generateAuthorizationHeaderValue(canonHeaders.headerNames, signKey, signingMsg);

			// set the headers on our request
			request.setRequestHeader('Authorization', auth);
			if ( bodyContentDigest && shouldIncludeContentDigest(contentType) ) {
				request.setRequestHeader('Digest', canonHeaders.headers['digest']);
			}
			request.setRequestHeader('X-SN-Date', canonHeaders.headers['x-sn-date']);
		});

		// register a load handler always, just so one is present
		xhr.on('load.internal', function() {
			//sn.log('URL {0} response received.', url);
		});

		if ( callback !== undefined ) {
			xhr.send(method, data, callback);
		}
		return xhr;
	}

	return that;
};

// provide a global singleton helper on sn.net.sec
sn.net.sec = sn.net.securityHelper();

<commit_msg>Fix hash computation when no query parameters are used.<commit_after>import "net";

/**
 * Helper for secure SolarNetwork API access. Configure a token and secret
 * and then use the json function to make secure SolarNetwork API requests. The returned
 * object is also a function that can be called directly, instead of using the json property.
 *
 * @class
 * @param {String} [apiToken] - An initial token to use.
 * @param {String} [apiTokenSecret] - An initial token secret to use.
 * @returns {sn.net.securityHelper}
 * @require uri-js https://github.com/garycourt/uri-js
 * @preserve
 */
sn.net.securityHelper = function(apiToken, apiTokenSecret) {
	'use strict';
	var that = json;

	var kFormUrlEncodedContentTypeRegex = /^application\/x-www-form-urlencoded/i;

	// our in-memory credentials
	var cred = {token: apiToken, secret: apiTokenSecret, signingKey: null, signingKeyExpiry: null};

	// setup core properties
	Object.defineProperties(that, {
		version								: { value : '1.3.0' },
		hasTokenCredentials					: { value : hasTokenCredentials },
		token								: { value : token },
		secret								: { value : secret },
		hasSecret 							: { value : hasSecret },
		clearSecret 						: { value : clearSecret },
		generateAuthorizationHeaderValue 	: { value : generateAuthorizationHeaderValue },
		parseURLQueryTerms 					: { value : parseURLQueryTerms },
		json 								: { value : json }
	});

	/**
	 * Return true if both a token and a secret have been set, false otherwise.
	 *
	 * @return {Boolean} true if a token and secret have been set.
	 * @preserve
	 */
	function hasTokenCredentials() {
		return (cred.token && cred.token.length > 0 && cred.secret && cred.secret.length > 0);
	}

	/**
	 * Get or set the in-memory security token to use.
	 *
	 * @param {String} [value] The value to set, or null to clear.
	 * @returs When used as a getter, the current token value, otherwise this object.
	 * @preserve
	 */
	function token(value) {
		if ( !arguments.length ) return cred.token;
		cred.token = (value && value.length > 0 ? value : undefined);
		return that;
	}

	/**
	 * Set the in-memory security token secret to use.
	 *
	 * @param {String} [value] The value to set.
	 * @returns This object.
	 * @preserve
	 */
	function secret(value) {
		if ( arguments.length ) {
			cred.secret = value;
		}
		return that;
	}

	/**
	 * Return true if a secret has been set, false otherwise.
	 *
	 * @return {Boolean} true if a secret has been set.
	 * @preserve
	 */
	function hasSecret() {
		return (cred.secret && cred.secret.length > 0);
	}

	/**
	 * Clear the in-memory secret.
	 *
	 * @returns This object.
	 * @preserve
	 */
	function clearSecret() {
		cred.secret = undefined;
		return that;
	}

	/**
	 * Test if a Digest header should be included in the request, based on the
	 * request content type.
	 *
	 * @param {String} contentType the content type
	 * @returns {Boolean} true if including the Digest hash is appropriate
	 */
	function shouldIncludeContentDigest(contentType) {
		// we don't send Digest for form data, because server treats this as URL parameters
		return (contentType && contentType.match(kFormUrlEncodedContentTypeRegex));
	}

	/**
	 * Generate the canonical request message for a set of request parameters.
	 *
	 * @param {Object} params the request parameters
	 * @param {String} params.method the HTTP request method
	 * @param {Object} params.uri a parsed URI object for the request
	 * @param {String} params.queryParams the canonical query parameters string
	 * @param {Object} params.headers the canonical headers object
	 * @param {Object} params.bodyDigest the CryptoJS body content digest
	 * @return {String} the message
	 * @preserve
	 */
	function generateCanonicalRequestMessage(params) {
		var msg =
			(params.method === undefined ? 'GET' : params.method.toUpperCase()) + '\n'
			+params.uri.path +'\n'
			+(params.queryParams ? params.queryParams : '') +'\n';
		params.headers.headerNames.forEach(function(name) {
			msg += name + ':' +params.headers.headers[name] + '\n';
		});
		msg += params.headers.headerNames.join(';') +'\n';
		msg += CryptoJS.enc.Hex.stringify(params.bodyDigest);
		return msg;
	}

	function generateSigningMessage(date, canonRequestMsg) {
		var msg = 'SNWS2-HMAC-SHA256\n'
			+iso8601Date(date, true) +'\n'
			+CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(canonRequestMsg));
		return msg;
	}

	/**
	 * Generate the V2 HTTP Authorization header.
	 *
	 * @param {String} token the SN token ID
	 * @param {Array} signedHeaders the sorted array of signed header names
	 * @param {Object} signKey the key to encrypt the signature with
	 * @param {String} signingMsg the message to sign
	 * @return {String} the HTTP header value
	 * @preserve
	 */
	function generateAuthorizationHeaderValue(signedHeaders, signKey, signingMsg) {
		var signature = CryptoJS.HmacSHA256(signingMsg, signKey);
		var authHeader = 'SNWS2 Credential=' +cred.token
			+',SignedHeaders=' +signedHeaders.join(';')
			+',Signature=' +CryptoJS.enc.Hex.stringify(signature);
		return authHeader;
	}

	/**
	 * Parse the query portion of a URL string, and return a parameter object for the
	 * parsed key/value pairs.
	 *
	 *
Multiple parameters of the same name are not supported.

	 *
	 * @param {String} search the query portion of the URL, which may optionally include
	 *                        the leading '?' character
	 * @return {Object} the parsed query parameters, as a parameter object
	 * @preserve
	 */
	function parseURLQueryTerms(search) {
		var params = {};
		var pairs;
		var pair;
		var i, len;
		if ( search !== undefined && search.length > 0 ) {
			// remove any leading ? character
			if ( search.match(/^\?/) ) {
				search = search.substring(1);
			}
			pairs = search.split('&');
			for ( i = 0, len = pairs.length; i < len; i += 1 ) {
				pair = pairs[i].split('=', 2);
				if ( pair.length === 2 ) {
					params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
				}
			}
		}
		return params;
	}

	function _encodeURIComponent(str) {
	  return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
		return '%' + c.charCodeAt(0).toString(16);
	  });
	}

	function canonicalQueryParameters(uri, data, contentType) {
		var params = parseURLQueryTerms(data && contentType.match(kFormUrlEncodedContentTypeRegex)
			? data : uri.query);
		var sortedKeys = [],
			key,
			i,
			len,
			first = true,
			result;

		for ( key in params ) {
			sortedKeys.push(key);
		}
		sortedKeys.sort();
		for ( i = 0, len = sortedKeys.length; i < len; i += 1 ) {
			if ( first ) {
				first = false;
				result = '';
			} else {
				result += '&';
			}
			key = sortedKeys[i];
			result +=  _encodeURIComponent(key);
			result += '=';
			result += _encodeURIComponent(params[key]);
		}
		return result;
	}

	/**
	 * Create an object with the canonical header names and their associated values.
	 *
	 * @param {Object} uri           a parsed URI object for the request
	 * @param {String} contentType   the request content type
	 * @param {Date} date            the request date
	 * @param {Object} contentSHA256 the CryptoJS digest object of the request body content
	 * @return {Object} An object with a sorted headerNames property array of strings, and
	 *                  a headers object of the header name values for keys and
	 *                  associated header values as values.
	 */
	function canonicalHeaders(uri, contentType, date, contentSHA256) {
		var result = {
			headerNames : ['host', 'x-sn-date'],
			headers : {
				'host' : uri.host + (uri.port && uri.port !== 80 && uri.port !== 443 ? ':'+uri.port : ''),
				'x-sn-date' : date.toUTCString()
			}
		};
		if ( shouldIncludeContentDigest(contentType) ) {
			result.headerNames.push('content-type');
			result.headers['content-type'] = contentType;
			result.headerNames.push('digest');
			result.headers['digest'] = 'sha-256='+CryptoJS.enc.Base64.stringify(contentSHA256);
		}
		result.headerNames.sort();
		return result;
	}

	function bodyContentSHA256(data, contentType) {
		return CryptoJS.SHA256(data && !contentType.match(kFormUrlEncodedContentTypeRegex)
			? data
			: '');
	}

	function iso8601Date(date, includeTime) {
		return ''+date.getUTCFullYear()
				+(date.getUTCMonth() < 9 ? '0' : '') +(date.getUTCMonth()+1)
				+(date.getUTCDate() < 10 ? '0' : '') + date.getUTCDate()
				+(includeTime ?
					'T'
					+(date.getUTCHours() < 10 ? '0' : '') + date.getUTCHours()
					+(date.getUTCMinutes() < 10 ? '0' : '') + date.getUTCMinutes()
					+(date.getUTCSeconds() < 10 ? '0' : '') +date.getUTCSeconds()
					+'Z'
					: '');
	}

	function signingKey(date) {
		var dateString,
			key = cred.signingKey,
			expireDate;
		date = date || new Date();
		if ( !key || date.getTime() > cred.signingKeyExpiry ) {
			dateString = iso8601Date(date);
			key = CryptoJS.HmacSHA256('snws2_request', CryptoJS.HmacSHA256(dateString, 'SNWS2' + cred.secret));
			cred.signingKey = key;

			expireDate = new Date(date);
			expireDate.setUTCHours(0);
			expireDate.setUTCMinutes(0);
			expireDate.setUTCSeconds(0);
			expireDate.setUTCMilliseconds(0);
			cred.signingKeyExpiry = expireDate.getTime() + (7 * 24 * 60 * 60 * 1000);
		}
		return key;
	}

	/**
	 * Invoke the web service URL, adding the required SolarNetworkWS authorization
	 * headers to the request.
	 *
	 *
This method will construct the X-SN-Date and Authorization
	 * header values needed to invoke the web service. It returns a d3 XHR object,
	 * so you can call .on() on that to handle the response, unless a callback
	 * parameter is specified, then the request is issued immediately, passing the
	 * method, data, and callback parameters
	 * to xhr.send().

	 *
	 * @param {String} url the web service URL to invoke
	 * @param {String} method the HTTP method to use; e.g. GET or POST
	 * @param {String} [data] the data to upload
	 * @param {String} [contentType] the content type of the data
	 * @param {Function} [callback] if defined, a d3 callback function to handle the response JSON with
	 * @return {Object} d3 XHR object
	 * @preserve
	 */
	function json(url, method, data, contentType, callback) {
		var requestUrl = url;
		// We might be passed to queue, and then our callback will be the last argument (but possibly not #5
		// if the original call to queue didn't pass all arguments) so we check for that at the start and
		// adjust what we consider the method, data, and contentType parameter values.
		if ( arguments.length > 0 ) {
			if ( arguments.length < 5 && typeof arguments[arguments.length - 1] === 'function' ) {
				callback = arguments[arguments.length - 1];
			}
			if ( typeof method !== 'string' ) {
				method = undefined;
			}
			if ( typeof data !== 'string' ) {
				data = undefined;
			}
			if ( typeof contentType !== 'string' ) {
				contentType = undefined;
			}
		}
		method = (method === undefined ? 'GET' : method.toUpperCase());
		if ( method === 'POST' || method === 'PUT' ) {
			// extract any URL request parameters and put into POST body
			if ( !data ) {
				(function() {
					var queryIndex = url.indexOf('?');
					if ( queryIndex !== -1 ) {
						if ( queryIndex + 1 < url.length - 1 ) {
							data = url.substring(queryIndex + 1);
						}
						requestUrl = url.substring(0, queryIndex);
						contentType = 'application/x-www-form-urlencoded; charset=UTF-8';
					}
				}());
			}
		}
		var xhr = d3.json(requestUrl);
		if ( contentType !== undefined ) {
			xhr.header('Content-Type', contentType);
		}
		xhr.on('beforesend', function(request) {
			var date = new Date();

			var uri = URI.parse(url);

			var canonQueryParams = canonicalQueryParameters(uri, data, contentType);

			var bodyContentDigest = bodyContentSHA256(data, contentType);

			var canonHeaders = canonicalHeaders(uri, contentType, date, bodyContentDigest);

			var canonRequestMsg = generateCanonicalRequestMessage({
				method: method,
				uri: uri,
				queryParams : canonQueryParams,
				headers : canonHeaders,
				bodyDigest: bodyContentDigest
			});

			var signingMsg = generateSigningMessage(date, canonRequestMsg);

			var signKey = signingKey(date);

			var auth = generateAuthorizationHeaderValue(canonHeaders.headerNames, signKey, signingMsg);

			// set the headers on our request
			request.setRequestHeader('Authorization', auth);
			if ( bodyContentDigest && shouldIncludeContentDigest(contentType) ) {
				request.setRequestHeader('Digest', canonHeaders.headers['digest']);
			}
			request.setRequestHeader('X-SN-Date', canonHeaders.headers['x-sn-date']);
		});

		// register a load handler always, just so one is present
		xhr.on('load.internal', function() {
			//sn.log('URL {0} response received.', url);
		});

		if ( callback !== undefined ) {
			xhr.send(method, data, callback);
		}
		return xhr;
	}

	return that;
};

// provide a global singleton helper on sn.net.sec
sn.net.sec = sn.net.securityHelper();

<|endoftext|>


<commit_before>// This is for things that don't belong in webModel,
// or things that need to be "picked up" from weModel,
// and acted on before being tagged on the robot

module.exports = {
  debug: false,
  // Some timestamps for use to help know when to run stuff
  bootTime: new Date(), // Time the node script was initialized (Can I do this?)
  startROSTime: undefined, // Time that ROS start was completed.
  mapLoadTime: undefined, // Time that map load was complete
  makeMap: undefined,
  master: {
    isClose: false, // Eddy Beacon === 1
    isInRoom: false, // Eddy Beacon < 1
    isAsleep: false,
  },
  whereamiTextSent: false,
  unplugMeTextSent: false,
  fullyCharged: false,
  webCamInUse: false,
  gettingQRcode: false,
  initialPoseSet: false,
  cmdTopicIdle: false,
  lastMovementTime: 0,
  active_cmd: 'idle', // Hold the active cmd that is currently driving the robot.
  wayPointNavigator: {
    destinaitonWaypoint: undefined, // This will be the place we want the robot to go to.
  },
  usbRelayControlBusy: false,
  masterRelayBusy: false,
  volumeHasBeenSet: false,
  semaphoreFilesWatcher: null,
};
<commit_msg>Remove some disused variables.<commit_after>// This is for things that don't belong in webModel,
// or things that need to be "picked up" from weModel,
// and acted on before being tagged on the robot

module.exports = {
  debug: false,
  // Some timestamps for use to help know when to run stuff
  bootTime: new Date(), // Time the node script was initialized (Can I do this?)
  startROSTime: undefined, // Time that ROS start was completed.
  mapLoadTime: undefined, // Time that map load was complete
  makeMap: undefined,
  master: {
    isAsleep: false,
  },
  whereamiTextSent: false,
  unplugMeTextSent: false,
  fullyCharged: false,
  webCamInUse: false,
  gettingQRcode: false,
  initialPoseSet: false,
  cmdTopicIdle: false,
  lastMovementTime: 0,
  active_cmd: 'idle', // Hold the active cmd that is currently driving the robot.
  wayPointNavigator: {
    destinaitonWaypoint: undefined, // This will be the place we want the robot to go to.
  },
  usbRelayControlBusy: false,
  masterRelayBusy: false,
  volumeHasBeenSet: false,
  semaphoreFilesWatcher: null,
};
<|endoftext|>