<commit_before>define(function () {

    function RunQuery (bezl, queryName) {

        switch (queryName) {
            case "Invoices":
                bezl.vars.loading = true;

                // Pull in the accounts list for the logged in user
                bezl.dataService.add('Invoices','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "InvoiceInquiry",
                    "Parameters": [
                        { "Key": "StartDate", "Value": bezl.vars.startDate },
                        { "Key": "EndDate", "Value": bezl.vars.endDate }
                    ] },0);
                break;
            default:
                break;
        }
    }

    function Select(bezl, Invoice) {
        // Mark the selected customer as selected
        for (var i = 0; i < bezl.vars.Invoices.length; i++) {
            if (bezl.vars.Invoices[i].InvoiceNum == Invoice.InvoiceNum) {
                bezl.vars.Invoices[i].Selected = !bezl.vars.Invoices[i].Selected;

                if (bezl.vars.Invoices[i].Selected) {
                    localStorage.setItem('selectedInvoice', JSON.stringify(bezl.vars.Invoices[i]));
                    $('.panel').trigger('selectedInvoice', [bezl.vars.Invoices[i]]);
                } else {
                    localStorage.setItem('selectedInvoice', '');
                    $('.panel').trigger('selectedInvoice', [{}]);
                }

            } else {
                bezl.vars.Invoices[i].Selected = false;
            }
        };
    }

    function Sort(bezl, sortColumn) {

        // If the previous sort column was picked, make it the opposite sort
        if (bezl.vars.sortCol == sortColumn) {
            if (bezl.vars.sort == "desc") {
                bezl.vars.sort = "asc";
            } else {
                bezl.vars.sort = "desc";
            }
        } else {
            bezl.vars.sort = "asc";
        }

        // Store the sort column so the UI can reflect it
        bezl.vars.sortCol = sortColumn;

        // Test for numeric sort columns, otherwise sort alphabetic
        if (sortColumn == "InvoiceNum" || sortColumn == "PoNum" || sortColumn == "InvoiceAmt" || sortColumn == "InvoiceBal") {
            if (bezl.vars.sort == "asc") {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return A - B;
                });
            } else {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return B - A;
                });
            }
        } else if (sortColumn == "InvoiceDate" || sortColumn == "OrderDate") {
            if (bezl.vars.sort == "asc") {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = Date.parse(a[sortColumn]) || Number.MAX_SAFE_INTEGER;
                    var B = Date.parse(b[sortColumn]) || Number.MAX_SAFE_INTEGER;
                    return A - B;
                });
            } else {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = Date.parse(a[sortColumn]) || Number.MAX_SAFE_INTEGER * -1;
                    var B = Date.parse(b[sortColumn]) || Number.MAX_SAFE_INTEGER * -1;
                    return B - A;
                });
            }
        } else {
            if (bezl.vars.sort == "asc") {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A < B) {
                        return -1;
                    }
                    if (A > B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            } else {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A > B) {
                        return -1;
                    }
                    if (A < B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            }
        }
    }

    function InnerSort(bezl, sortColumn) {

        // If the previous sort column was picked, make it the opposite sort
        if (bezl.vars.sortCol == sortColumn) {
            if (bezl.vars.sortInner == "desc") {
                bezl.vars.sortInner = "asc";
            } else {
                bezl.vars.sortInner = "desc";
            }
        } else {
            bezl.vars.sortInner = "asc";
        }

        // Store the sort column so the UI can reflect it
        bezl.vars.sortCol = sortColumn;

        // Test for numeric sort columns, otherwise sort alphabetic
        if (sortColumn == "InvoiceLine" || sortColumn == "PartNum" || sortColumn == "UnitPrice" || sortColumn == "ExtPrice" || sortColumn == "Qty") {
            if (bezl.vars.sortInner == "asc") {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return A - B;
                });
            } else {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return B - A;
                });
            }
        } else {
            if (bezl.vars.sortInner == "asc") {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A < B) {
                        return -1;
                    }
                    if (A > B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            } else {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A > B) {
                        return -1;
                    }
                    if (A < B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            }
        }
    }

    return {
        runQuery: RunQuery,
        select: Select,
        sort: Sort,
        innerSort: InnerSort
    }
});<commit_msg>Sets baseline dates if none are entered<commit_after>define(function () {

    function RunQuery (bezl, queryName) {

        switch (queryName) {
            case "Invoices":
                bezl.vars.loading = true;

                // Pull in the accounts list for the logged in user
                bezl.dataService.add('Invoices','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "InvoiceInquiry",
                    "Parameters": [
                        { "Key": "StartDate", "Value": bezl.vars.startDate || '01/01/1900'},
                        { "Key": "EndDate", "Value": bezl.vars.endDate || '01/01/2100'}
                    ] },0);
                break;
            default:
                break;
        }
    }

    function Select(bezl, Invoice) {
        // Mark the selected customer as selected
        for (var i = 0; i < bezl.vars.Invoices.length; i++) {
            if (bezl.vars.Invoices[i].InvoiceNum == Invoice.InvoiceNum) {
                bezl.vars.Invoices[i].Selected = !bezl.vars.Invoices[i].Selected;

                if (bezl.vars.Invoices[i].Selected) {
                    localStorage.setItem('selectedInvoice', JSON.stringify(bezl.vars.Invoices[i]));
                    $('.panel').trigger('selectedInvoice', [bezl.vars.Invoices[i]]);
                } else {
                    localStorage.setItem('selectedInvoice', '');
                    $('.panel').trigger('selectedInvoice', [{}]);
                }

            } else {
                bezl.vars.Invoices[i].Selected = false;
            }
        };
    }

    function Sort(bezl, sortColumn) {

        // If the previous sort column was picked, make it the opposite sort
        if (bezl.vars.sortCol == sortColumn) {
            if (bezl.vars.sort == "desc") {
                bezl.vars.sort = "asc";
            } else {
                bezl.vars.sort = "desc";
            }
        } else {
            bezl.vars.sort = "asc";
        }

        // Store the sort column so the UI can reflect it
        bezl.vars.sortCol = sortColumn;

        // Test for numeric sort columns, otherwise sort alphabetic
        if (sortColumn == "InvoiceNum" || sortColumn == "PoNum" || sortColumn == "InvoiceAmt" || sortColumn == "InvoiceBal") {
            if (bezl.vars.sort == "asc") {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return A - B;
                });
            } else {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return B - A;
                });
            }
        } else if (sortColumn == "InvoiceDate" || sortColumn == "OrderDate") {
            if (bezl.vars.sort == "asc") {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = Date.parse(a[sortColumn]) || Number.MAX_SAFE_INTEGER;
                    var B = Date.parse(b[sortColumn]) || Number.MAX_SAFE_INTEGER;
                    return A - B;
                });
            } else {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = Date.parse(a[sortColumn]) || Number.MAX_SAFE_INTEGER * -1;
                    var B = Date.parse(b[sortColumn]) || Number.MAX_SAFE_INTEGER * -1;
                    return B - A;
                });
            }
        } else {
            if (bezl.vars.sort == "asc") {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A < B) {
                        return -1;
                    }
                    if (A > B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            } else {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A > B) {
                        return -1;
                    }
                    if (A < B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            }
        }
    }

    function InnerSort(bezl, sortColumn) {

        // If the previous sort column was picked, make it the opposite sort
        if (bezl.vars.sortCol == sortColumn) {
            if (bezl.vars.sortInner == "desc") {
                bezl.vars.sortInner = "asc";
            } else {
                bezl.vars.sortInner = "desc";
            }
        } else {
            bezl.vars.sortInner = "asc";
        }

        // Store the sort column so the UI can reflect it
        bezl.vars.sortCol = sortColumn;

        // Test for numeric sort columns, otherwise sort alphabetic
        if (sortColumn == "InvoiceLine" || sortColumn == "PartNum" || sortColumn == "UnitPrice" || sortColumn == "ExtPrice" || sortColumn == "Qty") {
            if (bezl.vars.sortInner == "asc") {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return A - B;
                });
            } else {
                bezl.vars.Invoices.sort(function (a, b) {
                    var A = a[sortColumn] || Number.MAX_SAFE_INTEGER;
                    var B = b[sortColumn] || Number.MAX_SAFE_INTEGER;
                    return B - A;
                });
            }
        } else {
            if (bezl.vars.sortInner == "asc") {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A < B) {
                        return -1;
                    }
                    if (A > B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            } else {
                bezl.vars.Invoices.sort(function(a, b) {
                    var A = a[sortColumn].toUpperCase(); // ignore upper and lowercase
                    var B = b[sortColumn].toUpperCase(); // ignore upper and lowercase
                    if (A > B) {
                        return -1;
                    }
                    if (A < B) {
                        return 1;
                    }

                    // names must be equal
                    return 0;
                });
            }
        }
    }

    return {
        runQuery: RunQuery,
        select: Select,
        sort: Sort,
        innerSort: InnerSort
    }
});<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
 /* eslint-env node */
'use strict';

var SDPUtils = require('sdp');
var utils = require('./utils');

module.exports = {
  shimRTCIceCandidate: function(window) {
    // foundation is arbitrarily chosen as an indicator for full support for
    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
    if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in
        window.RTCIceCandidate.prototype)) {
      return;
    }

    var NativeRTCIceCandidate = window.RTCIceCandidate;
    window.RTCIceCandidate = function(args) {
      // Remove the a= which shouldn't be part of the candidate string.
      if (typeof args === 'object' && args.candidate &&
          args.candidate.indexOf('a=') === 0) {
        args = JSON.parse(JSON.stringify(args));
        args.candidate = args.candidate.substr(2);
      }

      if (args.candidate && args.candidate.length) {
        // Augment the native candidate with the parsed fields.
        var nativeCandidate = new NativeRTCIceCandidate(args);
        var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
        var augmentedCandidate = Object.assign(nativeCandidate,
            parsedCandidate);

        // Add a serializer that does not serialize the extra attributes.
        augmentedCandidate.toJSON = function() {
          return {
            candidate: augmentedCandidate.candidate,
            sdpMid: augmentedCandidate.sdpMid,
            sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
            usernameFragment: augmentedCandidate.usernameFragment,
          };
        };
        return augmentedCandidate;
      }
      return new NativeRTCIceCandidate(args);
    };
    window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;

    // Hook up the augmented candidate in onicecandidate and
    // addEventListener('icecandidate', ...)
    utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
      if (e.candidate) {
        Object.defineProperty(e, 'candidate', {
          value: new window.RTCIceCandidate(e.candidate),
          writable: 'false'
        });
      }
      return e;
    });
  },

  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.

  shimCreateObjectURL: function(window) {
    var URL = window && window.URL;

    if (!(typeof window === 'object' && window.HTMLMediaElement &&
          'srcObject' in window.HTMLMediaElement.prototype &&
        URL.createObjectURL && URL.revokeObjectURL)) {
      // Only shim CreateObjectURL using srcObject if srcObject exists.
      return undefined;
    }

    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
    var streams = new Map(), newId = 0;

    URL.createObjectURL = function(stream) {
      if ('getTracks' in stream) {
        var url = 'polyblob:' + (++newId);
        streams.set(url, stream);
        utils.deprecated('URL.createObjectURL(stream)',
            'elem.srcObject = stream');
        return url;
      }
      return nativeCreateObjectURL(stream);
    };
    URL.revokeObjectURL = function(url) {
      nativeRevokeObjectURL(url);
      streams.delete(url);
    };

    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
                                              'src');
    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
      get: function() {
        return dsc.get.apply(this);
      },
      set: function(url) {
        this.srcObject = streams.get(url) || null;
        return dsc.set.apply(this, [url]);
      }
    });

    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
    window.HTMLMediaElement.prototype.setAttribute = function() {
      if (arguments.length === 2 &&
          ('' + arguments[0]).toLowerCase() === 'src') {
        this.srcObject = streams.get(arguments[1]) || null;
      }
      return nativeSetAttribute.apply(this, arguments);
    };
  },

  shimMaxMessageSize: function(window) {
    if (window.RTCSctpTransport || !window.RTCPeerConnection) {
      return;
    }
    var browserDetails = utils.detectBrowser(window);

    if (!('sctp' in window.RTCPeerConnection.prototype)) {
      Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
        get: function() {
          return typeof this._sctp === 'undefined' ? null : this._sctp;
        }
      });
    }

    var sctpInDescription = function(description) {
      var sections = SDPUtils.splitSections(description.sdp);
      sections.shift();
      return sections.some(function(mediaSection) {
        var mLine = SDPUtils.parseMLine(mediaSection);
        return mLine && mLine.kind === 'application'
            && mLine.protocol.indexOf('SCTP') !== -1;
      });
    };

    var getRemoteFirefoxVersion = function(description) {
      // TODO: Is there a better solution for detecting Firefox?
      var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
      if (match === null || match.length < 2) {
        return -1;
      }
      var version = parseInt(match[1], 10);
      // Test for NaN (yes, this is ugly)
      return version !== version ? -1 : version;
    };

    var getCanSendMaxMessageSize = function(remoteIsFirefox) {
      // Every implementation we know can send at least 64 KiB.
      // Note: Although Chrome is technically able to send up to 256 KiB, the
      //       data does not reach the other peer reliably.
      //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
      var canSendMaxMessageSize = 65536;
      if (browserDetails.browser === 'firefox') {
        if (browserDetails.version < 57) {
          if (remoteIsFirefox === -1) {
            // FF < 57 will send in 16 KiB chunks using the deprecated PPID
            // fragmentation.
            canSendMaxMessageSize = 16384;
          } else {
            // However, other FF (and RAWRTC) can reassemble PPID-fragmented
            // messages. Thus, supporting ~2 GiB when sending.
            canSendMaxMessageSize = 2147483637;
          }
        } else if (browserDetails.version < 60) {
          // Currently, all FF >= 57 will reset the remote maximum message size
          // to the default value when a data channel is created at a later
          // stage. :(
          // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
          canSendMaxMessageSize =
            browserDetails.version === 57 ? 65535 : 65536;
        } else {
          // FF >= 60 supports sending ~2 GiB
          canSendMaxMessageSize = 2147483637;
        }
      }
      return canSendMaxMessageSize;
    };

    var getMaxMessageSize = function(description, remoteIsFirefox) {
      // Note: 65536 bytes is the default value from the SDP spec. Also,
      //       every implementation we know supports receiving 65536 bytes.
      var maxMessageSize = 65536;

      // FF 57 has a slightly incorrect default remote max message size, so
      // we need to adjust it here to avoid a failure when sending.
      // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
      if (browserDetails.browser === 'firefox'
           && browserDetails.version === 57) {
        maxMessageSize = 65535;
      }

      var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:');
      if (match.length > 0) {
        maxMessageSize = parseInt(match[0].substr(19), 10);
      } else if (browserDetails.browser === 'firefox' &&
                  remoteIsFirefox !== -1) {
        // If the maximum message size is not present in the remote SDP and
        // both local and remote are Firefox, the remote peer can receive
        // ~2 GiB.
        maxMessageSize = 2147483637;
      }
      return maxMessageSize;
    };

    var origSetRemoteDescription =
        window.RTCPeerConnection.prototype.setRemoteDescription;
    window.RTCPeerConnection.prototype.setRemoteDescription = function() {
      var pc = this;
      pc._sctp = null;

      if (sctpInDescription(arguments[0])) {
        // Check if the remote is FF.
        var isFirefox = getRemoteFirefoxVersion(arguments[0]);

        // Get the maximum message size the local peer is capable of sending
        var canSendMMS = getCanSendMaxMessageSize(isFirefox);

        // Get the maximum message size of the remote peer.
        var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);

        // Determine final maximum message size
        var maxMessageSize;
        if (canSendMMS === 0 && remoteMMS === 0) {
          maxMessageSize = Number.POSITIVE_INFINITY;
        } else if (canSendMMS === 0 || remoteMMS === 0) {
          maxMessageSize = Math.max(canSendMMS, remoteMMS);
        } else {
          maxMessageSize = Math.min(canSendMMS, remoteMMS);
        }

        // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
        // attribute.
        var sctp = {};
        Object.defineProperty(sctp, 'maxMessageSize', {
          get: function() {
            return maxMessageSize;
          }
        });
        pc._sctp = sctp;
      }

      return origSetRemoteDescription.apply(pc, arguments);
    };
  },

  shimSendThrowTypeError: function(window) {
    if (!(window.RTCPeerConnection &&
        'createDataChannel' in window.RTCPeerConnection.prototype)) {
      return;
    }

    // Note: Although Firefox >= 57 has a native implementation, the maximum
    //       message size can be reset for all data channels at a later stage.
    //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831

    function wrapDcSend(dc, pc) {
      var origDataChannelSend = dc.send;
      dc.send = function() {
        var data = arguments[0];
        var length = data.length || data.size || data.byteLength;
        if (length > pc.sctp.maxMessageSize) {
          throw new DOMException('Message too large (can send a maximum of ' +
            pc.sctp.maxMessageSize + ' bytes)', 'TypeError');
        }
        return origDataChannelSend.apply(dc, arguments);
      };
    }
    var origCreateDataChannel =
      window.RTCPeerConnection.prototype.createDataChannel;
    window.RTCPeerConnection.prototype.createDataChannel = function() {
      var pc = this;
      var dataChannel = origCreateDataChannel.apply(pc, arguments);
      wrapDcSend(dataChannel, pc);
      return dataChannel;
    };
    utils.wrapPeerConnectionEvent(window, 'datachannel', function(e) {
      wrapDcSend(e.channel, e.target);
      return e;
    });
  }
};
<commit_msg>improve checks, change to typeerror<commit_after>/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
 /* eslint-env node */
'use strict';

var SDPUtils = require('sdp');
var utils = require('./utils');

module.exports = {
  shimRTCIceCandidate: function(window) {
    // foundation is arbitrarily chosen as an indicator for full support for
    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
    if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in
        window.RTCIceCandidate.prototype)) {
      return;
    }

    var NativeRTCIceCandidate = window.RTCIceCandidate;
    window.RTCIceCandidate = function(args) {
      // Remove the a= which shouldn't be part of the candidate string.
      if (typeof args === 'object' && args.candidate &&
          args.candidate.indexOf('a=') === 0) {
        args = JSON.parse(JSON.stringify(args));
        args.candidate = args.candidate.substr(2);
      }

      if (args.candidate && args.candidate.length) {
        // Augment the native candidate with the parsed fields.
        var nativeCandidate = new NativeRTCIceCandidate(args);
        var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
        var augmentedCandidate = Object.assign(nativeCandidate,
            parsedCandidate);

        // Add a serializer that does not serialize the extra attributes.
        augmentedCandidate.toJSON = function() {
          return {
            candidate: augmentedCandidate.candidate,
            sdpMid: augmentedCandidate.sdpMid,
            sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
            usernameFragment: augmentedCandidate.usernameFragment,
          };
        };
        return augmentedCandidate;
      }
      return new NativeRTCIceCandidate(args);
    };
    window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;

    // Hook up the augmented candidate in onicecandidate and
    // addEventListener('icecandidate', ...)
    utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
      if (e.candidate) {
        Object.defineProperty(e, 'candidate', {
          value: new window.RTCIceCandidate(e.candidate),
          writable: 'false'
        });
      }
      return e;
    });
  },

  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.

  shimCreateObjectURL: function(window) {
    var URL = window && window.URL;

    if (!(typeof window === 'object' && window.HTMLMediaElement &&
          'srcObject' in window.HTMLMediaElement.prototype &&
        URL.createObjectURL && URL.revokeObjectURL)) {
      // Only shim CreateObjectURL using srcObject if srcObject exists.
      return undefined;
    }

    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
    var streams = new Map(), newId = 0;

    URL.createObjectURL = function(stream) {
      if ('getTracks' in stream) {
        var url = 'polyblob:' + (++newId);
        streams.set(url, stream);
        utils.deprecated('URL.createObjectURL(stream)',
            'elem.srcObject = stream');
        return url;
      }
      return nativeCreateObjectURL(stream);
    };
    URL.revokeObjectURL = function(url) {
      nativeRevokeObjectURL(url);
      streams.delete(url);
    };

    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
                                              'src');
    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
      get: function() {
        return dsc.get.apply(this);
      },
      set: function(url) {
        this.srcObject = streams.get(url) || null;
        return dsc.set.apply(this, [url]);
      }
    });

    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
    window.HTMLMediaElement.prototype.setAttribute = function() {
      if (arguments.length === 2 &&
          ('' + arguments[0]).toLowerCase() === 'src') {
        this.srcObject = streams.get(arguments[1]) || null;
      }
      return nativeSetAttribute.apply(this, arguments);
    };
  },

  shimMaxMessageSize: function(window) {
    if (window.RTCSctpTransport || !window.RTCPeerConnection) {
      return;
    }
    var browserDetails = utils.detectBrowser(window);

    if (!('sctp' in window.RTCPeerConnection.prototype)) {
      Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
        get: function() {
          return typeof this._sctp === 'undefined' ? null : this._sctp;
        }
      });
    }

    var sctpInDescription = function(description) {
      var sections = SDPUtils.splitSections(description.sdp);
      sections.shift();
      return sections.some(function(mediaSection) {
        var mLine = SDPUtils.parseMLine(mediaSection);
        return mLine && mLine.kind === 'application'
            && mLine.protocol.indexOf('SCTP') !== -1;
      });
    };

    var getRemoteFirefoxVersion = function(description) {
      // TODO: Is there a better solution for detecting Firefox?
      var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
      if (match === null || match.length < 2) {
        return -1;
      }
      var version = parseInt(match[1], 10);
      // Test for NaN (yes, this is ugly)
      return version !== version ? -1 : version;
    };

    var getCanSendMaxMessageSize = function(remoteIsFirefox) {
      // Every implementation we know can send at least 64 KiB.
      // Note: Although Chrome is technically able to send up to 256 KiB, the
      //       data does not reach the other peer reliably.
      //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
      var canSendMaxMessageSize = 65536;
      if (browserDetails.browser === 'firefox') {
        if (browserDetails.version < 57) {
          if (remoteIsFirefox === -1) {
            // FF < 57 will send in 16 KiB chunks using the deprecated PPID
            // fragmentation.
            canSendMaxMessageSize = 16384;
          } else {
            // However, other FF (and RAWRTC) can reassemble PPID-fragmented
            // messages. Thus, supporting ~2 GiB when sending.
            canSendMaxMessageSize = 2147483637;
          }
        } else if (browserDetails.version < 60) {
          // Currently, all FF >= 57 will reset the remote maximum message size
          // to the default value when a data channel is created at a later
          // stage. :(
          // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
          canSendMaxMessageSize =
            browserDetails.version === 57 ? 65535 : 65536;
        } else {
          // FF >= 60 supports sending ~2 GiB
          canSendMaxMessageSize = 2147483637;
        }
      }
      return canSendMaxMessageSize;
    };

    var getMaxMessageSize = function(description, remoteIsFirefox) {
      // Note: 65536 bytes is the default value from the SDP spec. Also,
      //       every implementation we know supports receiving 65536 bytes.
      var maxMessageSize = 65536;

      // FF 57 has a slightly incorrect default remote max message size, so
      // we need to adjust it here to avoid a failure when sending.
      // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
      if (browserDetails.browser === 'firefox'
           && browserDetails.version === 57) {
        maxMessageSize = 65535;
      }

      var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:');
      if (match.length > 0) {
        maxMessageSize = parseInt(match[0].substr(19), 10);
      } else if (browserDetails.browser === 'firefox' &&
                  remoteIsFirefox !== -1) {
        // If the maximum message size is not present in the remote SDP and
        // both local and remote are Firefox, the remote peer can receive
        // ~2 GiB.
        maxMessageSize = 2147483637;
      }
      return maxMessageSize;
    };

    var origSetRemoteDescription =
        window.RTCPeerConnection.prototype.setRemoteDescription;
    window.RTCPeerConnection.prototype.setRemoteDescription = function() {
      var pc = this;
      pc._sctp = null;

      if (sctpInDescription(arguments[0])) {
        // Check if the remote is FF.
        var isFirefox = getRemoteFirefoxVersion(arguments[0]);

        // Get the maximum message size the local peer is capable of sending
        var canSendMMS = getCanSendMaxMessageSize(isFirefox);

        // Get the maximum message size of the remote peer.
        var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);

        // Determine final maximum message size
        var maxMessageSize;
        if (canSendMMS === 0 && remoteMMS === 0) {
          maxMessageSize = Number.POSITIVE_INFINITY;
        } else if (canSendMMS === 0 || remoteMMS === 0) {
          maxMessageSize = Math.max(canSendMMS, remoteMMS);
        } else {
          maxMessageSize = Math.min(canSendMMS, remoteMMS);
        }

        // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
        // attribute.
        var sctp = {};
        Object.defineProperty(sctp, 'maxMessageSize', {
          get: function() {
            return maxMessageSize;
          }
        });
        pc._sctp = sctp;
      }

      return origSetRemoteDescription.apply(pc, arguments);
    };
  },

  shimSendThrowTypeError: function(window) {
    if (!(window.RTCPeerConnection &&
        'createDataChannel' in window.RTCPeerConnection.prototype)) {
      return;
    }

    // Note: Although Firefox >= 57 has a native implementation, the maximum
    //       message size can be reset for all data channels at a later stage.
    //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831

    function wrapDcSend(dc, pc) {
      var origDataChannelSend = dc.send;
      dc.send = function() {
        var data = arguments[0];
        var length = data.length || data.size || data.byteLength;
        if (dc.readyState === 'open' &&
            pc.sctp && length > pc.sctp.maxMessageSize) {
          throw new TypeError('Message too large (can send a maximum of ' +
            pc.sctp.maxMessageSize + ' bytes)');
        }
        return origDataChannelSend.apply(dc, arguments);
      };
    }
    var origCreateDataChannel =
      window.RTCPeerConnection.prototype.createDataChannel;
    window.RTCPeerConnection.prototype.createDataChannel = function() {
      var pc = this;
      var dataChannel = origCreateDataChannel.apply(pc, arguments);
      wrapDcSend(dataChannel, pc);
      return dataChannel;
    };
    utils.wrapPeerConnectionEvent(window, 'datachannel', function(e) {
      wrapDcSend(e.channel, e.target);
      return e;
    });
  }
};
<|endoftext|>


<commit_before>'use strict';

const Lf = require('lovefield');
const Qs = require('qs');

const Track = require('./track');
const Trackcache = require('./trackcache');
const Playlist = require('./playlist');
const Splaylist = require('./splaylist');

const Reporting = require('./reporting');

const GM_BASE_URL = 'https://play.google.com/music/';
const GM_SERVICE_URL = `${GM_BASE_URL}services/`;

function authedGMRequest(endpoint, data, user, method, callback, onError) {
  // Call an endpoint and callback with it's parsed response.

  let format = '';
  let payload = {json: JSON.stringify(data)};
  if (data.constructor === Array || method === 'get') {
    format = 'format=jsarray&';
    if (method !== 'get') {
      payload = JSON.stringify(data);
    }
  }

  const url = `${GM_SERVICE_URL}${endpoint}?${format}${Qs.stringify({u: user.userIndex, xt: user.xt})}`;

  // TODO this is stupid
  let dataType = 'json';
  if (method === 'get') {
    dataType = 'html';
  }

  let ajaxOnError = onError;
  if (typeof onError === 'undefined') {
    ajaxOnError = res => {
      console.error('request failed:', url, data, res);
      Reporting.Raven.captureMessage(`request to ${endpoint} failed`, {
        extra: {url, data, res},
      });
    };
  }

  // TODO jquery should be injected with browserify?
  $[method](
    url,
    payload,
    res => {
      callback(res);
    },

    dataType
  )
  .fail(ajaxOnError);
}

exports.getTrackChanges = function getTrackChanges(user, sinceTimestamp, callback) {
  // Callback {success: true, newTimestamp: 1234, upsertedTracks: [{}], deletedIds: ['']}, or {success: false, ...} on failure.
  // timestamps are in microseconds.
  const payload = {
    lastUpdated: sinceTimestamp,
    tier: 1, // TODO need proper tier?
    requestCause: 1,
    sessionId: '',
  };

  console.debug('getTrackChanges', sinceTimestamp);

  authedGMRequest('streamingloadalltracks', payload, user, 'get', response => {
    // Try to parse a json response first, which is sent for errors.
    try {
      const jsonResponse = JSON.parse(response);

      console.warn('received json response from streamingloadalltracks:', response);

      if (!(jsonResponse.success === false && jsonResponse.reloadXsrf)) {
        Reporting.Raven.captureMessage('unexpected json response from streamingloadalltracks', {
          extra: {jsonResponse},
        });
      }

      return callback(jsonResponse);
    } catch (SyntaxError) {
      // eslint-disable-line no-empty
    }

    // Otherwise, parse the javascript from the html response.
    const result = {newTimestamp: null, upsertedTracks: [], deletedIds: []};

    const parser = new DOMParser();
    const doc = parser.parseFromString(response, 'application/xml');

    const scripts = doc.getElementsByTagName('script');

    // want arg to window.parent['slat_process']
    const startMark = '\nwindow.parent[\'slat_process\'](';
    const endMark = '000]';
    for (let i = 0; i < scripts.length; i++) {
      let code = scripts[i].innerHTML;
      let start = code.indexOf(startMark);
      if (start === -1) {
        continue;
      }

      let end = code.lastIndexOf(endMark);

      start += startMark.length;
      end += endMark.length;
      code = code.slice(start, end);

      const parsed = eval(code);

      // we just want the last chunk's timestamp
      result.newTimestamp = parsed[1];

      for (let j = 0; j < parsed[0].length; j++) {
        const gtrack = parsed[0][j];
        if (gtrack[19] === 1) {
          result.deletedIds.push(gtrack[0]);
        } else {
          result.upsertedTracks.push(Track.fromJsproto(gtrack));
        }
      }

      console.debug(parsed[0].length, 'tracks');
    }

    result.success = true;
    const summary = {
      success: result.success,
      newTimestamp: result.newTimestamp,
      numUpsertedTracks: result.upsertedTracks.length,
      numDeletedIds: result.deletedIds.length,
    };
    console.debug('getTrackChanges success:', JSON.stringify(summary));
    callback(result);
  }, ajaxError => {
    const result = {success: false};
    if (ajaxError.status === 403) {
      result.unauthed = true;
    } else {
      console.error('unexpected streamingload response', ajaxError);
      Reporting.Raven.captureMessage('unexpected streamingload response', {
        extra: {ajaxError, payload, user},
      });
    }
    console.debug('getTrackChanges failure:', JSON.stringify(result));
    callback(result);
  });
};

exports.updatePlaylist = function updatePlaylist(user, id, title, playlist, playlists, splaylistcache, callback) {
  // Callback no args after updating an existing playlist.
  const lastSync = new Date().toLocaleString();

  const description = Playlist.toString(playlist, playlists, splaylistcache);
  const syncMsg = `Synced ${lastSync} by Autoplaylists for Google Music™ to contain: ${description}.`;

  const payload = [['', 1], [id, null, title, syncMsg]];
  console.debug('updatePlaylist', playlist);

  authedGMRequest('editplaylist', payload, user, 'post', response => {
    console.debug('editPlaylist response:', JSON.stringify(response));
    callback();
  });
};

exports.createRemotePlaylist = function createRemotePlaylist(user, title, callback) {
  // Callback a playlist id for a new, empty playlist.
  const payload = [['', 1], [false, title, null, []]];

  console.debug('createRemotePlaylist', title);

  // response:
  // [[0,2,0] ,["id","some long base64 string",null,timestamp]]
  authedGMRequest('createplaylist', payload, user, 'post', response => {
    console.debug('createplaylist response:', JSON.stringify(response));
    callback(response[1][0]);
  });
};

exports.deleteRemotePlaylist = function deleteRemotePlaylist(user, remoteId, callback) {
  // Callback no args after deleting a playlist.

  const payload = {
    id: remoteId,
    requestCause: 1,
    requestType: 1,
    sessionId: '',
  };

  console.debug('deleteRemotePlaylist', remoteId);

  authedGMRequest('deleteplaylist', payload, user, 'post', response => {
    console.debug('delete playlist response', response);
    callback();
  });
};

function addTracks(user, playlistId, tracks, callback, onError) {
  // Append these tracks and callback the api response, or null if adding 0 tracks.

  if (tracks.length === 0) {
    console.debug('skipping add of 0 tracks');
    return callback(null);
  }

  console.debug('adding', tracks.length, 'tracks. first 5 are', JSON.stringify(tracks.slice(0, 5), null, 2));

  // [["<sessionid>",1],["<listid>",[["<store id or songid>",tracktype]]]]
  const payload = [['', 1],
    [
      // Google always sends [id, type] pairs, but that's caused problems for me around AA and store ids and types.
      // Just sending an id seems to work, so maybe that'll fix everything?
      playlistId, tracks.map(t => [t.id]),
    ],
  ];
  authedGMRequest('addtrackstoplaylist', payload, user, 'post', response => {
    console.debug('add response', JSON.stringify(response, null, 2));
    if (response.length <= 1 || response[1].length <= 0 || response[1][0] === 0) {
      // I used to think a [0] response array of 0, 2, 0 signaled errors,
      // but I've seen some successful responses with that recently.
      // 0 instead of the update timestamp seems a better indicator of errors.
      let responseArray = null;
      if (response.length > 0) {
        responseArray = JSON.stringify(response[0]);
      }

      // These events are really common and filling up the sentry quota.
      // Only sending 1 in 1k ought to bring them under the quota.
      if (Math.random() < 0.001) {
        Reporting.Raven.captureMessage('probable error from addTracks', {
          tags: {playlistId, responseArray},
          extra: {response, playlistId, tracks},
        });
      }
    }
    callback(response);
  }, onError);
}

function deleteEntries(user, playlistId, entries, callback, onError) {
  // Delete entries with id and entryId keys; callback the api response.
  console.debug('deleting', entries.length, 'entries. first 5 are', JSON.stringify(entries.slice(0, 5), null, 2));
  const payload = [
    ['', 1],
    [playlistId, entries.map(entry => entry.id), entries.map(entry => entry.entryId)],
  ];
  authedGMRequest('deleteplaylisttrack', payload, user, 'post', response => {
    console.debug('delete response', JSON.stringify(response, null, 2));
    callback(response);
  }, onError);
}

exports.getPlaylistContents = function getPlaylistContents(user, playlistId, callback, onError) {
  // Callback a list of objects with entryId and track keys.

  const payload = [['', 1], [playlistId]];
  authedGMRequest('loaduserplaylist', payload, user, 'post', response => {
    if (response.length < 2) {
      return onError(`unexpected getPlaylistContents response: ${JSON.stringify(response, null, 2)}`);
    }

    const contents = [];

    if (response[1].length !== 0) {
      const gentries = response[1][0];
      console.debug('playlist', playlistId, 'has', gentries.length, 'entries. first 3:',
                    JSON.stringify(gentries.slice(0, 3), null, 2));

      for (let i = 0; i < gentries.length; i++) {
        const gentry = gentries[i];
        const entryId = gentry[43];
        const track = Track.fromJsproto(gentry);
        contents.push({entryId, track});
      }
    }

    callback(contents);
  }, onError);
};

exports.getPlaylists = function getPlaylists(user, callback, onError) {
  // Callback a list of splaylists.

  const payload = [['', 1], []];
  authedGMRequest('loadplaylists', payload, user, 'post', response => {
    if (response.length < 2 || !Array.isArray(response[1][0])) {
      return onError(`unexpected loadplaylists response: ${JSON.stringify(response, null, 2)}`);
    }

    const splaylists = [];
    const gplaylists = response[1][0];

    for (let i = 0; i < gplaylists.length; i++) {
      splaylists.push(Splaylist.fromJsproto(gplaylists[i]));
    }

    callback(splaylists);
  }, error => {
    onError(error);
  });
};

exports.setPlaylistContents = function setPlaylistContents(db, user, playlistId, tracks, callback, onError) {
  // Update a remote playlist to contain only the given tracks, in any order.

  // This requires multiple requests:
  // 1) get playlist tracks
  // 2) delete current - desired
  // 3) add desired - current

  exports.getPlaylistContents(user, playlistId, contents => {
    if (contents.length !== 0) {
      const idsToAdd = {};
      for (let i = 0; i < tracks.length; i++) {
        const track = tracks[i];
        idsToAdd[track.id] = track;
      }

      const deleteCandidates = {};
      for (let i = 0; i < contents.length; i++) {
        const remoteTrack = contents[i].track;
        const entryId = contents[i].entryId;

        if (!(remoteTrack.id in idsToAdd)) {
          deleteCandidates[remoteTrack.id] = entryId;
        } else {
          delete idsToAdd[remoteTrack.id];
        }
      }

      const tracksToAdd = [];
      for (const id in idsToAdd) {
        tracksToAdd.push(idsToAdd[id]);
      }

      const entriesToDelete = [];
      const deleteCandidateIds = Object.keys(deleteCandidates);
      if (deleteCandidateIds.length > 0) {
        // Don't delete tracks that may be currently playing.
        // This requires a query against the track cache, since the lastPlayed
        // for remote tracks is set to 0 for any recent plays!?
        // FIXME pull this out to Trackcache.
        const track = db.getSchema().table('Track');
        db.select().from(track).where(
          // We don't know if these entries name a library or store id.
          Lf.op.or(track.id.in(deleteCandidateIds),
                   track.storeId.in(deleteCandidateIds))
        ).exec().then(rows => {
          const nowMillis = new Date().getTime();
          const delayMillis = 0;

          rows.forEach(row => {
            if (delayMillis + (row.lastPlayed / 1000) > nowMillis - row.durationMillis) {
              console.info('not deleting', row, 'since it may be playing.',
                           delayMillis + (row.lastPlayed / 1000), nowMillis - row.durationMillis
                          );
              if (row.id in deleteCandidates) {
                delete deleteCandidates[row.id];
              } else {
                delete deleteCandidates[row.storeId];
              }
            }
          });

          for (const deleteId in deleteCandidates) {
            entriesToDelete.push({id: deleteId, entryId: deleteCandidates[deleteId]});
          }

          if (entriesToDelete.length > 0) {
            deleteEntries(user, playlistId, entriesToDelete, deleteResponse => { // eslint-disable-line no-unused-vars
              // We log the delete response inside deleteEntries and have no other need for it here.
              addTracks(user, playlistId, tracksToAdd, callback);
            }, onError);
          } else {
            console.debug('no need to delete post-filter; adding');
            addTracks(user, playlistId, tracksToAdd, callback, onError);
          }
        }).catch(e => {
          console.error(e);
          Reporting.Raven.captureMessage('setplaylistcontents.select', {
            tags: {playlistId},
            extra: {e, deleteCandidates, deleteCandidateIds, tracksToAdd},
          });
        });
      } else {
        console.debug('no need to delete pre-filter; adding');
        addTracks(user, playlistId, tracksToAdd, callback, onError);
      }
    } else {
      console.debug('adding to empty');
      addTracks(user, playlistId, tracks, callback, onError);
    }
  }, onError);
};

exports.setPlaylistOrder = function setPlaylistOrder(db, user, playlist, callback, onError) {
  // Set the remote ordering of a playlist according to playlist's sort order.
  // This trusts that the remote contents are already correct.

  // This approach handles the maybe-playing tracks that wouldn't be in our tracks
  // if we queried them locally.

  exports.getPlaylistContents(user, playlist.remoteId, contents => {
    if (contents.length !== 0) {
      // Reordering calls deal in entry ids, not track ids.
      const currentOrdering = [];
      const desiredOrdering = [];
      const idToEntryId = {};

      for (let i = 0; i < contents.length; i++) {
        idToEntryId[contents[i].track.id] = contents[i].entryId;
        currentOrdering.push(contents[i].entryId);
      }

      const remoteTracks = contents.map(c => c.track);

      Trackcache.orderTracks(db, playlist, remoteTracks, orderedTracks => {
        for (let i = 0; i < orderedTracks.length; i++) {
          const track = orderedTracks[i];
          desiredOrdering.push(idToEntryId[track.id]);
        }

        // It's ridiculous that javascript doesn't have a builtin for this.
        // Thankfully we have simple items and can get away with this hack.
        if (JSON.stringify(currentOrdering) !== JSON.stringify(desiredOrdering)) {
          // The two empty strings are sentinels for "first track" and "last track".
          // This lets us send our entire reordering at once without calculating the relative movements.
          // I'm not sure if the interface was intended to be used this way, but it seems to work.
          const payload = [['', 1], [desiredOrdering, '', '']];
          authedGMRequest('changeplaylisttrackorder', payload, user, 'post', response => {
            // TODO These should all be checked for errors.
            // It looks like responses will have [[0, 1, 1], [call-specific response]] on success.
            callback(response);
          }, onError);
        } else {
          // Avoid triggering a ui refresh on noop reorderings.
          console.debug('no need to reorder playlist', playlist.title);
          callback(null);
        }
      }, e => {
        console.error(e);
        onError(e);
      });
    } else {
      console.debug('no need to reorder empty playlist', playlist.title);
      callback(null);
    }
  }, onError);
};
<commit_msg>template description date<commit_after>'use strict';

const Lf = require('lovefield');
const Qs = require('qs');

const Track = require('./track');
const Trackcache = require('./trackcache');
const Playlist = require('./playlist');
const Splaylist = require('./splaylist');

const Reporting = require('./reporting');

const GM_BASE_URL = 'https://play.google.com/music/';
const GM_SERVICE_URL = `${GM_BASE_URL}services/`;

function authedGMRequest(endpoint, data, user, method, callback, onError) {
  // Call an endpoint and callback with it's parsed response.

  let format = '';
  let payload = {json: JSON.stringify(data)};
  if (data.constructor === Array || method === 'get') {
    format = 'format=jsarray&';
    if (method !== 'get') {
      payload = JSON.stringify(data);
    }
  }

  const url = `${GM_SERVICE_URL}${endpoint}?${format}${Qs.stringify({u: user.userIndex, xt: user.xt})}`;

  // TODO this is stupid
  let dataType = 'json';
  if (method === 'get') {
    dataType = 'html';
  }

  let ajaxOnError = onError;
  if (typeof onError === 'undefined') {
    ajaxOnError = res => {
      console.error('request failed:', url, data, res);
      Reporting.Raven.captureMessage(`request to ${endpoint} failed`, {
        extra: {url, data, res},
      });
    };
  }

  // TODO jquery should be injected with browserify?
  $[method](
    url,
    payload,
    res => {
      callback(res);
    },

    dataType
  )
  .fail(ajaxOnError);
}

exports.getTrackChanges = function getTrackChanges(user, sinceTimestamp, callback) {
  // Callback {success: true, newTimestamp: 1234, upsertedTracks: [{}], deletedIds: ['']}, or {success: false, ...} on failure.
  // timestamps are in microseconds.
  const payload = {
    lastUpdated: sinceTimestamp,
    tier: 1, // TODO need proper tier?
    requestCause: 1,
    sessionId: '',
  };

  console.debug('getTrackChanges', sinceTimestamp);

  authedGMRequest('streamingloadalltracks', payload, user, 'get', response => {
    // Try to parse a json response first, which is sent for errors.
    try {
      const jsonResponse = JSON.parse(response);

      console.warn('received json response from streamingloadalltracks:', response);

      if (!(jsonResponse.success === false && jsonResponse.reloadXsrf)) {
        Reporting.Raven.captureMessage('unexpected json response from streamingloadalltracks', {
          extra: {jsonResponse},
        });
      }

      return callback(jsonResponse);
    } catch (SyntaxError) {
      // eslint-disable-line no-empty
    }

    // Otherwise, parse the javascript from the html response.
    const result = {newTimestamp: null, upsertedTracks: [], deletedIds: []};

    const parser = new DOMParser();
    const doc = parser.parseFromString(response, 'application/xml');

    const scripts = doc.getElementsByTagName('script');

    // want arg to window.parent['slat_process']
    const startMark = '\nwindow.parent[\'slat_process\'](';
    const endMark = '000]';
    for (let i = 0; i < scripts.length; i++) {
      let code = scripts[i].innerHTML;
      let start = code.indexOf(startMark);
      if (start === -1) {
        continue;
      }

      let end = code.lastIndexOf(endMark);

      start += startMark.length;
      end += endMark.length;
      code = code.slice(start, end);

      const parsed = eval(code);

      // we just want the last chunk's timestamp
      result.newTimestamp = parsed[1];

      for (let j = 0; j < parsed[0].length; j++) {
        const gtrack = parsed[0][j];
        if (gtrack[19] === 1) {
          result.deletedIds.push(gtrack[0]);
        } else {
          result.upsertedTracks.push(Track.fromJsproto(gtrack));
        }
      }

      console.debug(parsed[0].length, 'tracks');
    }

    result.success = true;
    const summary = {
      success: result.success,
      newTimestamp: result.newTimestamp,
      numUpsertedTracks: result.upsertedTracks.length,
      numDeletedIds: result.deletedIds.length,
    };
    console.debug('getTrackChanges success:', JSON.stringify(summary));
    callback(result);
  }, ajaxError => {
    const result = {success: false};
    if (ajaxError.status === 403) {
      result.unauthed = true;
    } else {
      console.error('unexpected streamingload response', ajaxError);
      Reporting.Raven.captureMessage('unexpected streamingload response', {
        extra: {ajaxError, payload, user},
      });
    }
    console.debug('getTrackChanges failure:', JSON.stringify(result));
    callback(result);
  });
};

exports.updatePlaylist = function updatePlaylist(user, id, title, playlist, playlists, splaylistcache, callback) {
  // Callback no args after updating an existing playlist.
  const description = Playlist.toString(playlist, playlists, splaylistcache);
  const syncMsg = `Synced ${new Date().toLocaleString()} by Autoplaylists for Google Music™ to contain: ${description}.`;

  const payload = [['', 1], [id, null, title, syncMsg]];
  console.debug('updatePlaylist', playlist);

  authedGMRequest('editplaylist', payload, user, 'post', response => {
    console.debug('editPlaylist response:', JSON.stringify(response));
    callback();
  });
};

exports.createRemotePlaylist = function createRemotePlaylist(user, title, callback) {
  // Callback a playlist id for a new, empty playlist.
  const payload = [['', 1], [false, title, null, []]];

  console.debug('createRemotePlaylist', title);

  // response:
  // [[0,2,0] ,["id","some long base64 string",null,timestamp]]
  authedGMRequest('createplaylist', payload, user, 'post', response => {
    console.debug('createplaylist response:', JSON.stringify(response));
    callback(response[1][0]);
  });
};

exports.deleteRemotePlaylist = function deleteRemotePlaylist(user, remoteId, callback) {
  // Callback no args after deleting a playlist.

  const payload = {
    id: remoteId,
    requestCause: 1,
    requestType: 1,
    sessionId: '',
  };

  console.debug('deleteRemotePlaylist', remoteId);

  authedGMRequest('deleteplaylist', payload, user, 'post', response => {
    console.debug('delete playlist response', response);
    callback();
  });
};

function addTracks(user, playlistId, tracks, callback, onError) {
  // Append these tracks and callback the api response, or null if adding 0 tracks.

  if (tracks.length === 0) {
    console.debug('skipping add of 0 tracks');
    return callback(null);
  }

  console.debug('adding', tracks.length, 'tracks. first 5 are', JSON.stringify(tracks.slice(0, 5), null, 2));

  // [["<sessionid>",1],["<listid>",[["<store id or songid>",tracktype]]]]
  const payload = [['', 1],
    [
      // Google always sends [id, type] pairs, but that's caused problems for me around AA and store ids and types.
      // Just sending an id seems to work, so maybe that'll fix everything?
      playlistId, tracks.map(t => [t.id]),
    ],
  ];
  authedGMRequest('addtrackstoplaylist', payload, user, 'post', response => {
    console.debug('add response', JSON.stringify(response, null, 2));
    if (response.length <= 1 || response[1].length <= 0 || response[1][0] === 0) {
      // I used to think a [0] response array of 0, 2, 0 signaled errors,
      // but I've seen some successful responses with that recently.
      // 0 instead of the update timestamp seems a better indicator of errors.
      let responseArray = null;
      if (response.length > 0) {
        responseArray = JSON.stringify(response[0]);
      }

      // These events are really common and filling up the sentry quota.
      // Only sending 1 in 1k ought to bring them under the quota.
      if (Math.random() < 0.001) {
        Reporting.Raven.captureMessage('probable error from addTracks', {
          tags: {playlistId, responseArray},
          extra: {response, playlistId, tracks},
        });
      }
    }
    callback(response);
  }, onError);
}

function deleteEntries(user, playlistId, entries, callback, onError) {
  // Delete entries with id and entryId keys; callback the api response.
  console.debug('deleting', entries.length, 'entries. first 5 are', JSON.stringify(entries.slice(0, 5), null, 2));
  const payload = [
    ['', 1],
    [playlistId, entries.map(entry => entry.id), entries.map(entry => entry.entryId)],
  ];
  authedGMRequest('deleteplaylisttrack', payload, user, 'post', response => {
    console.debug('delete response', JSON.stringify(response, null, 2));
    callback(response);
  }, onError);
}

exports.getPlaylistContents = function getPlaylistContents(user, playlistId, callback, onError) {
  // Callback a list of objects with entryId and track keys.

  const payload = [['', 1], [playlistId]];
  authedGMRequest('loaduserplaylist', payload, user, 'post', response => {
    if (response.length < 2) {
      return onError(`unexpected getPlaylistContents response: ${JSON.stringify(response, null, 2)}`);
    }

    const contents = [];

    if (response[1].length !== 0) {
      const gentries = response[1][0];
      console.debug('playlist', playlistId, 'has', gentries.length, 'entries. first 3:',
                    JSON.stringify(gentries.slice(0, 3), null, 2));

      for (let i = 0; i < gentries.length; i++) {
        const gentry = gentries[i];
        const entryId = gentry[43];
        const track = Track.fromJsproto(gentry);
        contents.push({entryId, track});
      }
    }

    callback(contents);
  }, onError);
};

exports.getPlaylists = function getPlaylists(user, callback, onError) {
  // Callback a list of splaylists.

  const payload = [['', 1], []];
  authedGMRequest('loadplaylists', payload, user, 'post', response => {
    if (response.length < 2 || !Array.isArray(response[1][0])) {
      return onError(`unexpected loadplaylists response: ${JSON.stringify(response, null, 2)}`);
    }

    const splaylists = [];
    const gplaylists = response[1][0];

    for (let i = 0; i < gplaylists.length; i++) {
      splaylists.push(Splaylist.fromJsproto(gplaylists[i]));
    }

    callback(splaylists);
  }, error => {
    onError(error);
  });
};

exports.setPlaylistContents = function setPlaylistContents(db, user, playlistId, tracks, callback, onError) {
  // Update a remote playlist to contain only the given tracks, in any order.

  // This requires multiple requests:
  // 1) get playlist tracks
  // 2) delete current - desired
  // 3) add desired - current

  exports.getPlaylistContents(user, playlistId, contents => {
    if (contents.length !== 0) {
      const idsToAdd = {};
      for (let i = 0; i < tracks.length; i++) {
        const track = tracks[i];
        idsToAdd[track.id] = track;
      }

      const deleteCandidates = {};
      for (let i = 0; i < contents.length; i++) {
        const remoteTrack = contents[i].track;
        const entryId = contents[i].entryId;

        if (!(remoteTrack.id in idsToAdd)) {
          deleteCandidates[remoteTrack.id] = entryId;
        } else {
          delete idsToAdd[remoteTrack.id];
        }
      }

      const tracksToAdd = [];
      for (const id in idsToAdd) {
        tracksToAdd.push(idsToAdd[id]);
      }

      const entriesToDelete = [];
      const deleteCandidateIds = Object.keys(deleteCandidates);
      if (deleteCandidateIds.length > 0) {
        // Don't delete tracks that may be currently playing.
        // This requires a query against the track cache, since the lastPlayed
        // for remote tracks is set to 0 for any recent plays!?
        // FIXME pull this out to Trackcache.
        const track = db.getSchema().table('Track');
        db.select().from(track).where(
          // We don't know if these entries name a library or store id.
          Lf.op.or(track.id.in(deleteCandidateIds),
                   track.storeId.in(deleteCandidateIds))
        ).exec().then(rows => {
          const nowMillis = new Date().getTime();
          const delayMillis = 0;

          rows.forEach(row => {
            if (delayMillis + (row.lastPlayed / 1000) > nowMillis - row.durationMillis) {
              console.info('not deleting', row, 'since it may be playing.',
                           delayMillis + (row.lastPlayed / 1000), nowMillis - row.durationMillis
                          );
              if (row.id in deleteCandidates) {
                delete deleteCandidates[row.id];
              } else {
                delete deleteCandidates[row.storeId];
              }
            }
          });

          for (const deleteId in deleteCandidates) {
            entriesToDelete.push({id: deleteId, entryId: deleteCandidates[deleteId]});
          }

          if (entriesToDelete.length > 0) {
            deleteEntries(user, playlistId, entriesToDelete, deleteResponse => { // eslint-disable-line no-unused-vars
              // We log the delete response inside deleteEntries and have no other need for it here.
              addTracks(user, playlistId, tracksToAdd, callback);
            }, onError);
          } else {
            console.debug('no need to delete post-filter; adding');
            addTracks(user, playlistId, tracksToAdd, callback, onError);
          }
        }).catch(e => {
          console.error(e);
          Reporting.Raven.captureMessage('setplaylistcontents.select', {
            tags: {playlistId},
            extra: {e, deleteCandidates, deleteCandidateIds, tracksToAdd},
          });
        });
      } else {
        console.debug('no need to delete pre-filter; adding');
        addTracks(user, playlistId, tracksToAdd, callback, onError);
      }
    } else {
      console.debug('adding to empty');
      addTracks(user, playlistId, tracks, callback, onError);
    }
  }, onError);
};

exports.setPlaylistOrder = function setPlaylistOrder(db, user, playlist, callback, onError) {
  // Set the remote ordering of a playlist according to playlist's sort order.
  // This trusts that the remote contents are already correct.

  // This approach handles the maybe-playing tracks that wouldn't be in our tracks
  // if we queried them locally.

  exports.getPlaylistContents(user, playlist.remoteId, contents => {
    if (contents.length !== 0) {
      // Reordering calls deal in entry ids, not track ids.
      const currentOrdering = [];
      const desiredOrdering = [];
      const idToEntryId = {};

      for (let i = 0; i < contents.length; i++) {
        idToEntryId[contents[i].track.id] = contents[i].entryId;
        currentOrdering.push(contents[i].entryId);
      }

      const remoteTracks = contents.map(c => c.track);

      Trackcache.orderTracks(db, playlist, remoteTracks, orderedTracks => {
        for (let i = 0; i < orderedTracks.length; i++) {
          const track = orderedTracks[i];
          desiredOrdering.push(idToEntryId[track.id]);
        }

        // It's ridiculous that javascript doesn't have a builtin for this.
        // Thankfully we have simple items and can get away with this hack.
        if (JSON.stringify(currentOrdering) !== JSON.stringify(desiredOrdering)) {
          // The two empty strings are sentinels for "first track" and "last track".
          // This lets us send our entire reordering at once without calculating the relative movements.
          // I'm not sure if the interface was intended to be used this way, but it seems to work.
          const payload = [['', 1], [desiredOrdering, '', '']];
          authedGMRequest('changeplaylisttrackorder', payload, user, 'post', response => {
            // TODO These should all be checked for errors.
            // It looks like responses will have [[0, 1, 1], [call-specific response]] on success.
            callback(response);
          }, onError);
        } else {
          // Avoid triggering a ui refresh on noop reorderings.
          console.debug('no need to reorder playlist', playlist.title);
          callback(null);
        }
      }, e => {
        console.error(e);
        onError(e);
      });
    } else {
      console.debug('no need to reorder empty playlist', playlist.title);
      callback(null);
    }
  }, onError);
};
<|endoftext|>


<commit_before>import {
    $,
    addClass,
    append,
    apply,
    attr,
    css,
    endsWith,
    includes,
    isFocusable,
    last,
    noop,
    on,
    once,
    parent,
    pointerCancel,
    pointerDown,
    pointerUp,
    removeClass,
    scrollParents,
    toFloat,
    width,
    within,
} from 'uikit-util';
import Class from './class';
import Container from './container';
import Togglable from './togglable';

const active = [];

export default {
    mixins: [Class, Container, Togglable],

    props: {
        selPanel: String,
        selClose: String,
        escClose: Boolean,
        bgClose: Boolean,
        stack: Boolean,
    },

    data: {
        cls: 'uk-open',
        escClose: true,
        bgClose: true,
        overlay: true,
        stack: false,
    },

    computed: {
        panel({ selPanel }, $el) {
            return $(selPanel, $el);
        },

        transitionElement() {
            return this.panel;
        },

        bgClose({ bgClose }) {
            return bgClose && this.panel;
        },
    },

    beforeDisconnect() {
        if (includes(active, this)) {
            this.toggleElement(this.$el, false, false);
        }
    },

    events: [
        {
            name: 'click',

            delegate() {
                return this.selClose;
            },

            handler(e) {
                e.preventDefault();
                this.hide();
            },
        },

        {
            name: 'toggle',

            self: true,

            handler(e) {
                if (e.defaultPrevented) {
                    return;
                }

                e.preventDefault();

                if (this.isToggled() === includes(active, this)) {
                    this.toggle();
                }
            },
        },

        {
            name: 'beforeshow',

            self: true,

            handler(e) {
                if (includes(active, this)) {
                    return false;
                }

                if (!this.stack && active.length) {
                    Promise.all(active.map((modal) => modal.hide())).then(this.show);
                    e.preventDefault();
                } else {
                    active.push(this);
                }
            },
        },

        {
            name: 'show',

            self: true,

            handler() {
                once(
                    this.$el,
                    'hide',
                    on(document, 'focusin', (e) => {
                        if (last(active) === this && !within(e.target, this.$el)) {
                            this.$el.focus();
                        }
                    })
                );

                if (this.overlay) {
                    once(this.$el, 'hidden', preventOverscroll(this.$el), { self: true });
                    once(this.$el, 'hidden', preventBackgroundScroll(), { self: true });
                }

                if (this.stack) {
                    css(this.$el, 'zIndex', toFloat(css(this.$el, 'zIndex')) + active.length);
                }

                addClass(document.documentElement, this.clsPage);

                if (this.bgClose) {
                    once(
                        this.$el,
                        'hide',
                        on(document, pointerDown, ({ target }) => {
                            if (
                                last(active) !== this ||
                                (this.overlay && !within(target, this.$el)) ||
                                within(target, this.panel)
                            ) {
                                return;
                            }

                            once(
                                document,
                                `${pointerUp} ${pointerCancel} scroll`,
                                ({ defaultPrevented, type, target: newTarget }) => {
                                    if (
                                        !defaultPrevented &&
                                        type === pointerUp &&
                                        target === newTarget
                                    ) {
                                        this.hide();
                                    }
                                },
                                true
                            );
                        }),
                        { self: true }
                    );
                }

                if (this.escClose) {
                    once(
                        this.$el,
                        'hide',
                        on(document, 'keydown', (e) => {
                            if (e.keyCode === 27 && last(active) === this) {
                                this.hide();
                            }
                        }),
                        { self: true }
                    );
                }
            },
        },

        {
            name: 'shown',

            self: true,

            handler() {
                if (!isFocusable(this.$el)) {
                    attr(this.$el, 'tabindex', '-1');
                }

                if (!$(':focus', this.$el)) {
                    this.$el.focus();
                }
            },
        },

        {
            name: 'hidden',

            self: true,

            handler() {
                if (includes(active, this)) {
                    active.splice(active.indexOf(this), 1);
                }

                css(this.$el, 'zIndex', '');

                if (!active.some((modal) => modal.clsPage === this.clsPage)) {
                    removeClass(document.documentElement, this.clsPage);
                }
            },
        },
    ],

    methods: {
        toggle() {
            return this.isToggled() ? this.hide() : this.show();
        },

        show() {
            if (this.container && parent(this.$el) !== this.container) {
                append(this.container, this.$el);
                return new Promise((resolve) =>
                    requestAnimationFrame(() => this.show().then(resolve))
                );
            }

            return this.toggleElement(this.$el, true, animate(this));
        },

        hide() {
            return this.toggleElement(this.$el, false, animate(this));
        },
    },
};

function animate({ transitionElement, _toggle }) {
    return (el, show) =>
        new Promise((resolve, reject) =>
            once(el, 'show hide', () => {
                el._reject?.();
                el._reject = reject;

                _toggle(el, show);

                const off = once(
                    transitionElement,
                    'transitionstart',
                    () => {
                        once(transitionElement, 'transitionend transitioncancel', resolve, {
                            self: true,
                        });
                        clearTimeout(timer);
                    },
                    { self: true }
                );

                const timer = setTimeout(() => {
                    off();
                    resolve();
                }, toMs(css(transitionElement, 'transitionDuration')));
            })
        ).then(() => delete el._reject);
}

function toMs(time) {
    return time ? (endsWith(time, 'ms') ? toFloat(time) : toFloat(time) * 1000) : 0;
}

export function preventOverscroll(el) {
    if (CSS.supports('overscroll-behavior', 'contain')) {
        const elements = filterChildren(el, (child) => /auto|scroll/.test(css(child, 'overflow')));
        css(elements, 'overscrollBehavior', 'contain');
        return () => css(elements, 'overscrollBehavior', '');
    }

    let startClientY;

    const events = [
        on(
            el,
            'touchstart',
            ({ targetTouches }) => {
                if (targetTouches.length === 1) {
                    startClientY = targetTouches[0].clientY;
                }
            },
            { passive: true }
        ),

        on(
            el,
            'touchmove',
            (e) => {
                if (e.targetTouches.length !== 1) {
                    return;
                }

                let [scrollParent] = scrollParents(e.target, /auto|scroll/);
                if (!within(scrollParent, el)) {
                    scrollParent = el;
                }

                const clientY = e.targetTouches[0].clientY - startClientY;
                const { scrollTop, scrollHeight, clientHeight } = scrollParent;

                if (
                    clientHeight >= scrollHeight ||
                    (scrollTop === 0 && clientY > 0) ||
                    (scrollHeight - scrollTop <= clientHeight && clientY < 0)
                ) {
                    e.cancelable && e.preventDefault();
                }
            },
            { passive: false }
        ),
    ];

    return () => events.forEach((fn) => fn());
}

let prevented;
export function preventBackgroundScroll() {
    if (prevented) {
        return noop;
    }
    prevented = true;
    const { body, documentElement } = document;
    css(body, {
        overflowY: width(window) > documentElement.clientWidth ? 'scroll' : '',
        touchAction: 'none',
    });
    css(documentElement, 'overflowY', 'hidden');
    return () => {
        prevented = false;
        css(documentElement, 'overflowY', '');
        css(body, { overflowY: '', touchAction: '' });
    };
}

function filterChildren(el, fn) {
    const children = [];
    apply(el, (node) => {
        if (fn(node)) {
            children.push(node);
        }
    });
    return children;
}
<commit_msg>refactor: use paddingRight to prevent shifting<commit_after>import {
    $,
    addClass,
    append,
    apply,
    attr,
    css,
    endsWith,
    includes,
    isFocusable,
    last,
    noop,
    on,
    once,
    parent,
    pointerCancel,
    pointerDown,
    pointerUp,
    removeClass,
    scrollParents,
    toFloat,
    width,
    within,
} from 'uikit-util';
import Class from './class';
import Container from './container';
import Togglable from './togglable';

const active = [];

export default {
    mixins: [Class, Container, Togglable],

    props: {
        selPanel: String,
        selClose: String,
        escClose: Boolean,
        bgClose: Boolean,
        stack: Boolean,
    },

    data: {
        cls: 'uk-open',
        escClose: true,
        bgClose: true,
        overlay: true,
        stack: false,
    },

    computed: {
        panel({ selPanel }, $el) {
            return $(selPanel, $el);
        },

        transitionElement() {
            return this.panel;
        },

        bgClose({ bgClose }) {
            return bgClose && this.panel;
        },
    },

    beforeDisconnect() {
        if (includes(active, this)) {
            this.toggleElement(this.$el, false, false);
        }
    },

    events: [
        {
            name: 'click',

            delegate() {
                return this.selClose;
            },

            handler(e) {
                e.preventDefault();
                this.hide();
            },
        },

        {
            name: 'toggle',

            self: true,

            handler(e) {
                if (e.defaultPrevented) {
                    return;
                }

                e.preventDefault();

                if (this.isToggled() === includes(active, this)) {
                    this.toggle();
                }
            },
        },

        {
            name: 'beforeshow',

            self: true,

            handler(e) {
                if (includes(active, this)) {
                    return false;
                }

                if (!this.stack && active.length) {
                    Promise.all(active.map((modal) => modal.hide())).then(this.show);
                    e.preventDefault();
                } else {
                    active.push(this);
                }
            },
        },

        {
            name: 'show',

            self: true,

            handler() {
                once(
                    this.$el,
                    'hide',
                    on(document, 'focusin', (e) => {
                        if (last(active) === this && !within(e.target, this.$el)) {
                            this.$el.focus();
                        }
                    })
                );

                if (this.overlay) {
                    once(this.$el, 'hidden', preventOverscroll(this.$el), { self: true });
                    once(this.$el, 'hidden', preventBackgroundScroll(), { self: true });
                }

                if (this.stack) {
                    css(this.$el, 'zIndex', toFloat(css(this.$el, 'zIndex')) + active.length);
                }

                addClass(document.documentElement, this.clsPage);

                if (this.bgClose) {
                    once(
                        this.$el,
                        'hide',
                        on(document, pointerDown, ({ target }) => {
                            if (
                                last(active) !== this ||
                                (this.overlay && !within(target, this.$el)) ||
                                within(target, this.panel)
                            ) {
                                return;
                            }

                            once(
                                document,
                                `${pointerUp} ${pointerCancel} scroll`,
                                ({ defaultPrevented, type, target: newTarget }) => {
                                    if (
                                        !defaultPrevented &&
                                        type === pointerUp &&
                                        target === newTarget
                                    ) {
                                        this.hide();
                                    }
                                },
                                true
                            );
                        }),
                        { self: true }
                    );
                }

                if (this.escClose) {
                    once(
                        this.$el,
                        'hide',
                        on(document, 'keydown', (e) => {
                            if (e.keyCode === 27 && last(active) === this) {
                                this.hide();
                            }
                        }),
                        { self: true }
                    );
                }
            },
        },

        {
            name: 'shown',

            self: true,

            handler() {
                if (!isFocusable(this.$el)) {
                    attr(this.$el, 'tabindex', '-1');
                }

                if (!$(':focus', this.$el)) {
                    this.$el.focus();
                }
            },
        },

        {
            name: 'hidden',

            self: true,

            handler() {
                if (includes(active, this)) {
                    active.splice(active.indexOf(this), 1);
                }

                css(this.$el, 'zIndex', '');

                if (!active.some((modal) => modal.clsPage === this.clsPage)) {
                    removeClass(document.documentElement, this.clsPage);
                }
            },
        },
    ],

    methods: {
        toggle() {
            return this.isToggled() ? this.hide() : this.show();
        },

        show() {
            if (this.container && parent(this.$el) !== this.container) {
                append(this.container, this.$el);
                return new Promise((resolve) =>
                    requestAnimationFrame(() => this.show().then(resolve))
                );
            }

            return this.toggleElement(this.$el, true, animate(this));
        },

        hide() {
            return this.toggleElement(this.$el, false, animate(this));
        },
    },
};

function animate({ transitionElement, _toggle }) {
    return (el, show) =>
        new Promise((resolve, reject) =>
            once(el, 'show hide', () => {
                el._reject?.();
                el._reject = reject;

                _toggle(el, show);

                const off = once(
                    transitionElement,
                    'transitionstart',
                    () => {
                        once(transitionElement, 'transitionend transitioncancel', resolve, {
                            self: true,
                        });
                        clearTimeout(timer);
                    },
                    { self: true }
                );

                const timer = setTimeout(() => {
                    off();
                    resolve();
                }, toMs(css(transitionElement, 'transitionDuration')));
            })
        ).then(() => delete el._reject);
}

function toMs(time) {
    return time ? (endsWith(time, 'ms') ? toFloat(time) : toFloat(time) * 1000) : 0;
}

export function preventOverscroll(el) {
    if (CSS.supports('overscroll-behavior', 'contain')) {
        const elements = filterChildren(el, (child) => /auto|scroll/.test(css(child, 'overflow')));
        css(elements, 'overscrollBehavior', 'contain');
        return () => css(elements, 'overscrollBehavior', '');
    }

    let startClientY;

    const events = [
        on(
            el,
            'touchstart',
            ({ targetTouches }) => {
                if (targetTouches.length === 1) {
                    startClientY = targetTouches[0].clientY;
                }
            },
            { passive: true }
        ),

        on(
            el,
            'touchmove',
            (e) => {
                if (e.targetTouches.length !== 1) {
                    return;
                }

                let [scrollParent] = scrollParents(e.target, /auto|scroll/);
                if (!within(scrollParent, el)) {
                    scrollParent = el;
                }

                const clientY = e.targetTouches[0].clientY - startClientY;
                const { scrollTop, scrollHeight, clientHeight } = scrollParent;

                if (
                    clientHeight >= scrollHeight ||
                    (scrollTop === 0 && clientY > 0) ||
                    (scrollHeight - scrollTop <= clientHeight && clientY < 0)
                ) {
                    e.cancelable && e.preventDefault();
                }
            },
            { passive: false }
        ),
    ];

    return () => events.forEach((fn) => fn());
}

let prevented;
export function preventBackgroundScroll() {
    if (prevented) {
        return noop;
    }
    prevented = true;

    const { scrollingElement } = document;
    css(scrollingElement, {
        overflowY: 'hidden',
        touchAction: 'none',
        paddingRight: width(window) - scrollingElement.clientWidth,
    });
    return () => {
        prevented = false;
        css(scrollingElement, { overflowY: '', touchAction: '', paddingRight: '' });
    };
}

function filterChildren(el, fn) {
    const children = [];
    apply(el, (node) => {
        if (fn(node)) {
            children.push(node);
        }
    });
    return children;
}
<|endoftext|>


<commit_before>(function(){

'use strict';

   var app =  angular.module('app',['ngRoute','ui.bootstrap' ]);

   app.config(['$routeProvider',function($routeProvider){

       $routeProvider.when('/operations',{
           templateUrl: 'html/operations.html'
       }).when('/guidelines',{
           templateUrl: 'html/guidelines.html'
       }).when('/architecture',{
           templateUrl: 'html/architecture.html'
       }).when('/standards',{
           templateUrl: 'html/standards.html'
       }).when('/setup',{
           templateUrl: 'html/setup.html'
       }).when('/help',{
           templateUrl: 'html/help.html'
       }).when('/team',{
           templateUrl: 'html/team.html'
       }).when('/schedule',{
           templateUrl: 'html/schedule.html'
       }).when('/codepen',{
           templateUrl: 'html/codepen.html'
       }).otherwise({
            redirectTo:  'html/operations.html'
       });

   }]);

})();
<commit_msg>Initital Commit<commit_after>
   var app =  angular.module('app',['ngRoute','ui.bootstrap' ]);

   app.config(['$routeProvider',function($routeProvider){

       $routeProvider.when('/operations',{
           templateUrl: 'html/operations.html'
       }).when('/guidelines',{
           templateUrl: 'html/guidelines.html'
       }).when('/architecture',{
           templateUrl: 'html/architecture.html'
       }).when('/standards',{
           templateUrl: 'html/standards.html'
       }).when('/setup',{
           templateUrl: 'html/setup.html'
       }).when('/help',{
           templateUrl: 'html/help.html'
       }).when('/team',{
           templateUrl: 'html/team.html'
       }).when('/schedule',{
           templateUrl: 'html/schedule.html'
       }).when('/codepen',{
           templateUrl: 'html/codepen.html'
       }).otherwise({
            redirectTo:  'html/operations.html'
       });

   }]);

<|endoftext|>