<commit_before>/*
 * raygun4js
 * https://github.com/MindscapeHQ/raygun4js
 *
 * Copyright (c) 2013 MindscapeHQ
 * Licensed under the MIT license.
 */

(function (window, $, undefined) {
  // pull local copy of TraceKit to handle stack trace collection
  var _traceKit = TraceKit.noConflict(),
      _raygun = window.Raygun,
      _raygunApiKey,
      _debugMode = false,
      _allowInsecureSubmissions = false,
      _ignoreAjaxAbort = false,
      _enableOfflineSave = false,
      _customData = {},
      _tags = [],
      _user,
      _version,
      _filteredKeys,
      _raygunApiUrl = 'https://api.raygun.io',
      $document;

  if ($) {
    $document = $(document);
  }

  var Raygun =
  {
    noConflict: function () {
      window.Raygun = _raygun;
      return Raygun;
    },

    init: function(key, options, customdata) {
      _raygunApiKey = key;
      _traceKit.remoteFetching = false;
      _customData = customdata;

      if (options)
      {
        _allowInsecureSubmissions = options.allowInsecureSubmissions || false;
        _ignoreAjaxAbort = options.ignoreAjaxAbort || false;

        if (options.debugMode)
        {
          _debugMode = options.debugMode;
        }
      }

      sendSavedErrors();

      return Raygun;
    },

    withCustomData: function (customdata) {
      _customData = customdata;
      return Raygun;
    },

    withTags: function (tags) {
      _tags = tags;
    },

    attach: function () {
      if (!isApiKeyConfigured()) {
        return;
      }
      _traceKit.report.subscribe(processUnhandledException);
      if ($document) {
        $document.ajaxError(processJQueryAjaxError);
      }
      return Raygun;
    },

    detach: function () {
      _traceKit.report.unsubscribe(processUnhandledException);
      if ($document) {
        $document.unbind('ajaxError', processJQueryAjaxError);
      }
      return Raygun;
    },

    send: function (ex, customData, tags) {
      try {
        processUnhandledException(_traceKit.computeStackTrace(ex), {
          customData: typeof _customData === 'function' ?
            merge(_customData(), customData) :
            merge(_customData, customData),
          tags: mergeArray(_tags, tags)
        });
      }
      catch (traceKitException) {
        if (ex !== traceKitException) {
          throw traceKitException;
        }
      }
      return Raygun;
    },

    setUser: function (user, isAnonymous, email, fullName, firstName, uuid) {
      _user = {
        'Identifier': user
      };
      if(isAnonymous) {
        _user['IsAnonymous'] = isAnonymous;
      }
      if(email) {
        _user['Email'] = email;
      }
      if(fullName) {
        _user['FullName'] = fullName;
      }
      if(firstName) {
        _user['FirstName'] = firstName;
      }
      if(uuid) {
        _user['UUID'] = uuid;
      }

      return Raygun;
    },

    setVersion: function (version) {
      _version = version;
      return Raygun;
    },

    saveIfOffline: function (enableOffline) {
      if (typeof enableOffline !== 'undefined' && typeof enableOffline === 'boolean') {
        _enableOfflineSave = enableOffline;
      }

      return Raygun;
    },
    filterSensitiveData: function (filteredKeys) {
      _filteredKeys = filteredKeys;
      return Raygun;
    }
  };

  /* internals */

  function truncateURL(url){
      // truncate after fourth /, or 24 characters, whichever is shorter
      // /api/1/diagrams/xyz/server becomes
      // /api/1/diagrams/...
      var truncated = url;
      var path = url.split('//')[1];

      if (path) {
        var queryStart = path.indexOf('?');
        var sanitizedPath = path.toString().substring(0, queryStart);
        var truncated_parts = sanitizedPath.split('/').slice(0, 4).join('/');
        var truncated_length = sanitizedPath.substring(0, 48);
        truncated = truncated_parts.length < truncated_length.length?
                        truncated_parts : truncated_length;
        if (truncated !== sanitizedPath) {
            truncated += '..';
        }
      }

      return truncated;
  }

  function processJQueryAjaxError(event, jqXHR, ajaxSettings, thrownError) {
    var message = 'AJAX Error: ' +
        (jqXHR.statusText || 'unknown') +' '+
        (ajaxSettings.type || 'unknown') + ' '+
        (truncateURL(ajaxSettings.url) || 'unknown');

    // ignore ajax abort if set in the options
    if (_ignoreAjaxAbort) {
      if (!jqXHR.getAllResponseHeaders()) {
         return;
       }
    }

    Raygun.send(thrownError || event.type, {
      status: jqXHR.status,
      statusText: jqXHR.statusText,
      type: ajaxSettings.type,
      url: ajaxSettings.url,
      ajaxErrorMessage: message,
      contentType: ajaxSettings.contentType,
      data: ajaxSettings.data ? ajaxSettings.data.slice(0, 10240) : undefined });
  }

  function log(message, data) {
    if (window.console && window.console.log && _debugMode) {
      window.console.log(message);

      if (data) {
        window.console.log(data);
      }
    }
  }

  function isApiKeyConfigured() {
    if (_raygunApiKey && _raygunApiKey !== '') {
      return true;
    }
    log("Raygun API key has not been configured, make sure you call Raygun.init(yourApiKey)");
    return false;
  }

  function merge(o1, o2) {
    var a, o3 = {};
    for (a in o1) { o3[a] = o1[a]; }
    for (a in o2) { o3[a] = o2[a]; }
    return o3;
  }

  function mergeArray(t0, t1) {
    if (t1 != null) {
      return t0.concat(t1);
    }
    return t0;
  }

  function forEach(set, func) {
    for (var i = 0; i < set.length; i++) {
      func.call(null, i, set[i]);
    }
  }

  function isEmpty(o) {
    for (var p in o) {
      if (o.hasOwnProperty(p)) {
        return false;
      }
    }
    return true;
  }

  function getRandomInt() {
    return Math.floor(Math.random() * 9007199254740993);
  }

  function getViewPort() {
    var e = document.documentElement,
    g = document.getElementsByTagName('body')[0],
    x = window.innerWidth || e.clientWidth || g.clientWidth,
    y = window.innerHeight || e.clientHeight || g.clientHeight;
    return { width: x, height: y };
  }

  function offlineSave (data) {
    var dateTime = new Date().toJSON();

    try {
      var key = 'raygunjs=' + dateTime + '=' + getRandomInt();

      if (typeof localStorage[key] === 'undefined') {
        localStorage[key] = data;
      }
    } catch (e) {
      log('Raygun4JS: LocalStorage full, cannot save exception');
    }
  }

  function localStorageAvailable(){
    try {
      return ('localStorage' in window) && window['localStorage'] !== null;
    } catch(e){
      return false;
    }
  }

  function sendSavedErrors() {
    if (localStorageAvailable() && localStorage.length > 0) {
        for (var key in localStorage) {
        if (key.substring(0, 9) === 'raygunjs=') {
          sendToRaygun(JSON.parse(localStorage[key]));

          localStorage.removeItem(key);
        }
      }
    }
  }

  function processUnhandledException(stackTrace, options) {
    var stack = [],
        qs = {};

    if (stackTrace.stack && stackTrace.stack.length) {
      forEach(stackTrace.stack, function (i, frame) {
        stack.push({
          'LineNumber': frame.line,
          'ColumnNumber': frame.column,
          'ClassName': 'line ' + frame.line + ', column ' + frame.column,
          'FileName': frame.url,
          'MethodName': frame.func || '[anonymous]'
        });
      });
    }

    if (window.location.search && window.location.search.length > 1) {
      forEach(window.location.search.substring(1).split('&'), function (i, segment) {
        var parts = segment.split('=');
        if (parts && parts.length === 2) {
          var key = decodeURIComponent(parts[0]);
          var value = parts[1];

          if (_filteredKeys) {
            if (Array.prototype.indexOf && _filteredKeys.indexOf === Array.prototype.indexOf) {
              if (_filteredKeys.indexOf(key) === -1) {
                 qs[key] = value;
              }
            } else {
              for (i = 0; i < _filteredKeys.length; i++) {
                if (_filteredKeys[i] === key) {
                   qs[key] = value;
                }
              }
            }
          } else {
            qs[key] = value;
          }

        }
      });
    }

    if (options === undefined) {
      options = {};
    }

    if (isEmpty(options.customData)) {
      if (typeof _customData === 'function') {
        options.customData = _customData();
      } else {
        options.customData = _customData;
      }
    }

    if (isEmpty(options.tags)) {
      options.tags = _tags;
    }

    var screen = window.screen || { width: getViewPort().width, height: getViewPort().height, colorDepth: 8 };
    var custom_message = options.customData && options.customData.ajaxErrorMessage;
    var finalCustomData = options.customData;

    try {
      JSON.stringify(finalCustomData);
    } catch (e) {
      var msg = 'Cannot add custom data; may contain circular reference';
      finalCustomData = { error: msg };
      log('Raygun4JS: ' + msg);
    }

    var payload = {
      'OccurredOn': new Date(),
      'Details': {
        'Error': {
          'ClassName': stackTrace.name,
          'Message': custom_message || stackTrace.message || options.status || 'Script error',
          'StackTrace': stack
        },
        'Environment': {
          'UtcOffset': new Date().getTimezoneOffset() / -60.0,
          'User-Language': navigator.userLanguage,
          'Document-Mode': document.documentMode,
          'Browser-Width': getViewPort().width,
          'Browser-Height': getViewPort().height,
          'Screen-Width': screen.width,
          'Screen-Height': screen.height,
          'Color-Depth': screen.colorDepth,
          'Browser': navigator.appCodeName,
          'Browser-Name': navigator.appName,
          'Browser-Version': navigator.appVersion,
          'Platform': navigator.platform
        },
        'Client': {
          'Name': 'raygun-js',
          'Version': '1.10.0'
        },
        'UserCustomData': finalCustomData,
        'Tags': options.tags,
        'Request': {
          'Url': document.location.href,
          'QueryString': qs,
          'Headers': {
            'User-Agent': navigator.userAgent,
            'Referer': document.referrer,
            'Host': document.domain
          }
        },
        'Version': _version || 'Not supplied'
      }
    };

    if (_user) {
      payload.Details.User = _user;
    }

    sendToRaygun(payload);
  }

  function sendToRaygun(data) {
    if (!isApiKeyConfigured()) {
      return;
    }

    log('Sending exception data to Raygun:', data);
    var url = _raygunApiUrl + '/entries?apikey=' + encodeURIComponent(_raygunApiKey);
    makePostCorsRequest(url, JSON.stringify(data));
  }

  // Create the XHR object.
  function createCORSRequest(method, url) {
    var xhr;

    xhr = new window.XMLHttpRequest();
    if ("withCredentials" in xhr) {
      // XHR for Chrome/Firefox/Opera/Safari.
      xhr.open(method, url, true);

    } else if (window.XDomainRequest) {
      // XDomainRequest for IE.
      if (_allowInsecureSubmissions) {
        // remove 'https:' and use relative protocol
        // this allows IE8 to post messages when running
        // on http
        url = url.slice(6);
      }

      xhr = new window.XDomainRequest();
      xhr.open(method, url);
    }

    xhr.timeout = 10000;

    return xhr;
  }

  // Make the actual CORS request.
  function makePostCorsRequest(url, data) {
    var xhr = createCORSRequest('POST', url, data);

    if ('withCredentials' in xhr) {

      xhr.onreadystatechange = function() {
        if (xhr.readyState !== 4) {
          return;
        }

        if (xhr.status === 202) {
          sendSavedErrors();
        } else if (_enableOfflineSave && xhr.status !== 403 && xhr.status !== 400) {
          offlineSave(data);
        }
      };

      xhr.onload = function () {
        log('logged error to Raygun');
      };

    } else if (window.XDomainRequest) {
      xhr.ontimeout = function () {
        if (_enableOfflineSave) {
          log('Raygun: saved error locally');
          offlineSave(data);
        }
      };

      xhr.onload = function () {
        log('logged error to Raygun');
        sendSavedErrors();
      };
    }

    xhr.onerror = function () {
      log('failed to log error to Raygun');
    };

    if (!xhr) {
      log('CORS not supported');
      return;
    }

    xhr.send(data);
  }

  window.Raygun = Raygun;
})(window, window.jQuery);

<commit_msg>Updated processing of _filteredKeys to fix bug<commit_after>/*
 * raygun4js
 * https://github.com/MindscapeHQ/raygun4js
 *
 * Copyright (c) 2013 MindscapeHQ
 * Licensed under the MIT license.
 */

(function (window, $, undefined) {
  // pull local copy of TraceKit to handle stack trace collection
  var _traceKit = TraceKit.noConflict(),
      _raygun = window.Raygun,
      _raygunApiKey,
      _debugMode = false,
      _allowInsecureSubmissions = false,
      _ignoreAjaxAbort = false,
      _enableOfflineSave = false,
      _customData = {},
      _tags = [],
      _user,
      _version,
      _filteredKeys,
      _raygunApiUrl = 'https://api.raygun.io',
      $document;

  if ($) {
    $document = $(document);
  }

  var Raygun =
  {
    noConflict: function () {
      window.Raygun = _raygun;
      return Raygun;
    },

    init: function(key, options, customdata) {
      _raygunApiKey = key;
      _traceKit.remoteFetching = false;
      _customData = customdata;

      if (options)
      {
        _allowInsecureSubmissions = options.allowInsecureSubmissions || false;
        _ignoreAjaxAbort = options.ignoreAjaxAbort || false;

        if (options.debugMode)
        {
          _debugMode = options.debugMode;
        }
      }

      sendSavedErrors();

      return Raygun;
    },

    withCustomData: function (customdata) {
      _customData = customdata;
      return Raygun;
    },

    withTags: function (tags) {
      _tags = tags;
    },

    attach: function () {
      if (!isApiKeyConfigured()) {
        return;
      }
      _traceKit.report.subscribe(processUnhandledException);
      if ($document) {
        $document.ajaxError(processJQueryAjaxError);
      }
      return Raygun;
    },

    detach: function () {
      _traceKit.report.unsubscribe(processUnhandledException);
      if ($document) {
        $document.unbind('ajaxError', processJQueryAjaxError);
      }
      return Raygun;
    },

    send: function (ex, customData, tags) {
      try {
        processUnhandledException(_traceKit.computeStackTrace(ex), {
          customData: typeof _customData === 'function' ?
            merge(_customData(), customData) :
            merge(_customData, customData),
          tags: mergeArray(_tags, tags)
        });
      }
      catch (traceKitException) {
        if (ex !== traceKitException) {
          throw traceKitException;
        }
      }
      return Raygun;
    },

    setUser: function (user, isAnonymous, email, fullName, firstName, uuid) {
      _user = {
        'Identifier': user
      };
      if(isAnonymous) {
        _user['IsAnonymous'] = isAnonymous;
      }
      if(email) {
        _user['Email'] = email;
      }
      if(fullName) {
        _user['FullName'] = fullName;
      }
      if(firstName) {
        _user['FirstName'] = firstName;
      }
      if(uuid) {
        _user['UUID'] = uuid;
      }

      return Raygun;
    },

    setVersion: function (version) {
      _version = version;
      return Raygun;
    },

    saveIfOffline: function (enableOffline) {
      if (typeof enableOffline !== 'undefined' && typeof enableOffline === 'boolean') {
        _enableOfflineSave = enableOffline;
      }

      return Raygun;
    },
    filterSensitiveData: function (filteredKeys) {
      _filteredKeys = filteredKeys;
      return Raygun;
    }
  };

  /* internals */

  function truncateURL(url){
      // truncate after fourth /, or 24 characters, whichever is shorter
      // /api/1/diagrams/xyz/server becomes
      // /api/1/diagrams/...
      var truncated = url;
      var path = url.split('//')[1];

      if (path) {
        var queryStart = path.indexOf('?');
        var sanitizedPath = path.toString().substring(0, queryStart);
        var truncated_parts = sanitizedPath.split('/').slice(0, 4).join('/');
        var truncated_length = sanitizedPath.substring(0, 48);
        truncated = truncated_parts.length < truncated_length.length?
                        truncated_parts : truncated_length;
        if (truncated !== sanitizedPath) {
            truncated += '..';
        }
      }

      return truncated;
  }

  function processJQueryAjaxError(event, jqXHR, ajaxSettings, thrownError) {
    var message = 'AJAX Error: ' +
        (jqXHR.statusText || 'unknown') +' '+
        (ajaxSettings.type || 'unknown') + ' '+
        (truncateURL(ajaxSettings.url) || 'unknown');

    // ignore ajax abort if set in the options
    if (_ignoreAjaxAbort) {
      if (!jqXHR.getAllResponseHeaders()) {
         return;
       }
    }

    Raygun.send(thrownError || event.type, {
      status: jqXHR.status,
      statusText: jqXHR.statusText,
      type: ajaxSettings.type,
      url: ajaxSettings.url,
      ajaxErrorMessage: message,
      contentType: ajaxSettings.contentType,
      data: ajaxSettings.data ? ajaxSettings.data.slice(0, 10240) : undefined });
  }

  function log(message, data) {
    if (window.console && window.console.log && _debugMode) {
      window.console.log(message);

      if (data) {
        window.console.log(data);
      }
    }
  }

  function isApiKeyConfigured() {
    if (_raygunApiKey && _raygunApiKey !== '') {
      return true;
    }
    log("Raygun API key has not been configured, make sure you call Raygun.init(yourApiKey)");
    return false;
  }

  function merge(o1, o2) {
    var a, o3 = {};
    for (a in o1) { o3[a] = o1[a]; }
    for (a in o2) { o3[a] = o2[a]; }
    return o3;
  }

  function mergeArray(t0, t1) {
    if (t1 != null) {
      return t0.concat(t1);
    }
    return t0;
  }

  function forEach(set, func) {
    for (var i = 0; i < set.length; i++) {
      func.call(null, i, set[i]);
    }
  }

  function isEmpty(o) {
    for (var p in o) {
      if (o.hasOwnProperty(p)) {
        return false;
      }
    }
    return true;
  }

  function getRandomInt() {
    return Math.floor(Math.random() * 9007199254740993);
  }

  function getViewPort() {
    var e = document.documentElement,
    g = document.getElementsByTagName('body')[0],
    x = window.innerWidth || e.clientWidth || g.clientWidth,
    y = window.innerHeight || e.clientHeight || g.clientHeight;
    return { width: x, height: y };
  }

  function offlineSave (data) {
    var dateTime = new Date().toJSON();

    try {
      var key = 'raygunjs=' + dateTime + '=' + getRandomInt();

      if (typeof localStorage[key] === 'undefined') {
        localStorage[key] = data;
      }
    } catch (e) {
      log('Raygun4JS: LocalStorage full, cannot save exception');
    }
  }

  function localStorageAvailable(){
    try {
      return ('localStorage' in window) && window['localStorage'] !== null;
    } catch(e){
      return false;
    }
  }

  function sendSavedErrors() {
    if (localStorageAvailable() && localStorage.length > 0) {
        for (var key in localStorage) {
        if (key.substring(0, 9) === 'raygunjs=') {
          sendToRaygun(JSON.parse(localStorage[key]));

          localStorage.removeItem(key);
        }
      }
    }
  }

  function processUnhandledException(stackTrace, options) {
    var stack = [],
        qs = {};

    if (stackTrace.stack && stackTrace.stack.length) {
      forEach(stackTrace.stack, function (i, frame) {
        stack.push({
          'LineNumber': frame.line,
          'ColumnNumber': frame.column,
          'ClassName': 'line ' + frame.line + ', column ' + frame.column,
          'FileName': frame.url,
          'MethodName': frame.func || '[anonymous]'
        });
      });
    }

    if (window.location.search && window.location.search.length > 1) {
      forEach(window.location.search.substring(1).split('&'), function (i, segment) {
        var parts = segment.split('=');
        if (parts && parts.length === 2) {
          var key = decodeURIComponent(parts[0]);
          var value = parts[1];

          if (_filteredKeys) {
            if (Array.prototype.indexOf && _filteredKeys.indexOf === Array.prototype.indexOf) {
              if (_filteredKeys.indexOf(key) === -1) {
                 qs[key] = value;
              }
            } else {
              var included = true;
              for (i = 0; i < _filteredKeys.length; i++) {
                if (_filteredKeys[i] === key) {
                   included = false;
                   break;
                }
              }
              if (included) {
                   qs[key] = value;
              }
            }
          } else {
            qs[key] = value;
          }

        }
      });
    }

    if (options === undefined) {
      options = {};
    }

    if (isEmpty(options.customData)) {
      if (typeof _customData === 'function') {
        options.customData = _customData();
      } else {
        options.customData = _customData;
      }
    }

    if (isEmpty(options.tags)) {
      options.tags = _tags;
    }

    var screen = window.screen || { width: getViewPort().width, height: getViewPort().height, colorDepth: 8 };
    var custom_message = options.customData && options.customData.ajaxErrorMessage;
    var finalCustomData = options.customData;

    try {
      JSON.stringify(finalCustomData);
    } catch (e) {
      var msg = 'Cannot add custom data; may contain circular reference';
      finalCustomData = { error: msg };
      log('Raygun4JS: ' + msg);
    }

    var payload = {
      'OccurredOn': new Date(),
      'Details': {
        'Error': {
          'ClassName': stackTrace.name,
          'Message': custom_message || stackTrace.message || options.status || 'Script error',
          'StackTrace': stack
        },
        'Environment': {
          'UtcOffset': new Date().getTimezoneOffset() / -60.0,
          'User-Language': navigator.userLanguage,
          'Document-Mode': document.documentMode,
          'Browser-Width': getViewPort().width,
          'Browser-Height': getViewPort().height,
          'Screen-Width': screen.width,
          'Screen-Height': screen.height,
          'Color-Depth': screen.colorDepth,
          'Browser': navigator.appCodeName,
          'Browser-Name': navigator.appName,
          'Browser-Version': navigator.appVersion,
          'Platform': navigator.platform
        },
        'Client': {
          'Name': 'raygun-js',
          'Version': '1.10.0'
        },
        'UserCustomData': finalCustomData,
        'Tags': options.tags,
        'Request': {
          'Url': document.location.href,
          'QueryString': qs,
          'Headers': {
            'User-Agent': navigator.userAgent,
            'Referer': document.referrer,
            'Host': document.domain
          }
        },
        'Version': _version || 'Not supplied'
      }
    };

    if (_user) {
      payload.Details.User = _user;
    }

    sendToRaygun(payload);
  }

  function sendToRaygun(data) {
    if (!isApiKeyConfigured()) {
      return;
    }

    log('Sending exception data to Raygun:', data);
    var url = _raygunApiUrl + '/entries?apikey=' + encodeURIComponent(_raygunApiKey);
    makePostCorsRequest(url, JSON.stringify(data));
  }

  // Create the XHR object.
  function createCORSRequest(method, url) {
    var xhr;

    xhr = new window.XMLHttpRequest();
    if ("withCredentials" in xhr) {
      // XHR for Chrome/Firefox/Opera/Safari.
      xhr.open(method, url, true);

    } else if (window.XDomainRequest) {
      // XDomainRequest for IE.
      if (_allowInsecureSubmissions) {
        // remove 'https:' and use relative protocol
        // this allows IE8 to post messages when running
        // on http
        url = url.slice(6);
      }

      xhr = new window.XDomainRequest();
      xhr.open(method, url);
    }

    xhr.timeout = 10000;

    return xhr;
  }

  // Make the actual CORS request.
  function makePostCorsRequest(url, data) {
    var xhr = createCORSRequest('POST', url, data);

    if ('withCredentials' in xhr) {

      xhr.onreadystatechange = function() {
        if (xhr.readyState !== 4) {
          return;
        }

        if (xhr.status === 202) {
          sendSavedErrors();
        } else if (_enableOfflineSave && xhr.status !== 403 && xhr.status !== 400) {
          offlineSave(data);
        }
      };

      xhr.onload = function () {
        log('logged error to Raygun');
      };

    } else if (window.XDomainRequest) {
      xhr.ontimeout = function () {
        if (_enableOfflineSave) {
          log('Raygun: saved error locally');
          offlineSave(data);
        }
      };

      xhr.onload = function () {
        log('logged error to Raygun');
        sendSavedErrors();
      };
    }

    xhr.onerror = function () {
      log('failed to log error to Raygun');
    };

    if (!xhr) {
      log('CORS not supported');
      return;
    }

    xhr.send(data);
  }

  window.Raygun = Raygun;
})(window, window.jQuery);

<|endoftext|>


<commit_before>import _ from 'lodash'
import filter from './odata/filter'
import * as error from './error'

export const MANY_TO_ONE = Symbol('many-to-one')
export const ONE_TO_MANY = Symbol('one-to-many')

export default function resource(spec) {
  const {collection} = spec
  const {relationshipField} = spec
  const {relationshipCollection} = spec
  let {relationship} = spec
  let {map} = spec
  let {idParam} = spec
  let {idField} = spec

  idParam = idParam || 'id'
  idField = idField || '_id'
  relationship = relationship || ONE_TO_MANY

  /**
   * Normalize the request query. The resource routes do access certain query
   * parameters, neither they are set or not.
   * @param {Object} query The original query object
   * @returns {Object} The normalized query object
   * @private
   */
  function normalize(query) {
    // Clone query object
    query = _.clone(query)

    // Filter
    if (!query.filter) {
      query.filter = ''
    }

    // Property expansion
    if (query.expand) {
      query.expand = query.expand.split(',')
    } else {
      query.expand = []
    }

    // Order
    if (!query.sort) {
      query.sort = idField
    }

    // Pagination
    if (!query.offset) {
      query.offset = 0
    } else {
      query.offset = Number(query.offset)
    }
    if (!query.limit) {
      query.limit = 10
    } else {
      query.limit = Number(query.limit)
    }

    return query
  }

  /**
   * Maps request parameters to a properties hash object which can be
   * used to create a filter expression for database queries. This method
   * is also used to constraint requests to subresources.
   *
   * The following example tries to fetch messages from a specific user.
   * In order to reduce the messages within the database, there must be some
   * kind of 1:n relationship between a user and its messages.
   *
   * The mapping allows you to define a dynamic relationship between a parameter
   * and a specific document property. Internal this mapping is used to reduce
   * the resultset of documents by adding a $where statement to the database
   * query.
   *
   * Example:
   * // Request
   * GET /users/:user/messages
   *
   * // Mapping
   * {
   *  'owner': ':user'
   * }
   *
   * @param {Object} parameters
   * @returns {Object}
   * @private
   */
  function params(parameters) {
    map = _.clone(map)

    return _.forOwn(_.clone(map), function(val, key, obj) {
      // handle route params
      if (val.charAt(0) === ':') {
        obj[key] = parameters[val.substring(1)]
        return
      }

      // Static value
      obj[key] = val
    })
  }

  function queryExtension(q, query) {
    // populate
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    // sort
    q = q.sort(query.sort)

    // pagination
    q = q.skip(query.offset)
    if (query.limit && query.limit !== -1) {
      q = q.limit(query.limit)
    }

    return q
  }

  /**
   * Fetches database for a list of documents. As all public resource methods,
   * this one provides several predefined parameters someone can use to
   * manipulate the resultset.
   *
   * Example:
   * GET /users?filter=email EQ
   * 'box@mail.tld'&sort=email-&limit=10&offset=1&expand=messages
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function list(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize(req.query)

      // filter
      let f = filter(query.filter)

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec(function(err, doc) {
            if (err) {
              return reject(err)
            }

            if (!doc) {
              return resolve([])
            }

            // query
            let q = relationshipCollection.find(f.toObject())

            // select only related
            q = q.where('_id').in(doc[relationshipField])

            // extensions
            q = queryExtension(q, query)

            // execute
            q.exec(function(err, documents) {
              if (err) {
                return reject(error.internalServerError(err.message))
              }

              if (!_.isArray(documents)) {
                return resolve([])
              }

              resolve(documents)
            })
          })
      }

      if (relationship === ONE_TO_MANY) {
        f = f.where(params(req.params))

        // query
        let q = collection.find(f.toObject())

        // extensions
        q = queryExtension(q, query)

        // execute
        q.exec(function(err, documents) {
          if (err) {
            return reject(error.internalServerError(err.message))
          }

          if (!_.isArray(documents)) {
            return resolve([])
          }

          resolve(documents)
        })
      }

    })
  }

  /**
   * Fetches a single document from database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function read(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize(req.query)

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec(function(err, doc) {
            if (err) {
              return reject(err)
            }

            if (!doc) {
              return reject(error.notFound('Document not found'))
            }

            // query options
            let qo = {[idField]: req.params[idParam]}

            // query
            let q = relationshipCollection.findOne(qo)

            // populate
            if (query.expand.length > 0) {
              q = q.populate(query.expand.join(' '))
            }

            // execute
            q.exec(function(err, document) {
              if (err) {
                return reject(error.internalServerError(err.message))
              }
              if (!document) {
                return reject(error.notFound('Document not found'))
              }

              resolve(document)
            })
          })
      }

      if (relationship === ONE_TO_MANY) {
        // query options
        let qo = {[idField]: req.params[idParam]}

        // query
        let q = collection.findOne(qo)

        // populate
        if (query.expand.length > 0) {
          q = q.populate(query.expand.join(' '))
        }

        // execute
        q.exec(function(err, document) {
          if (err) {
            return reject(error.internalServerError(err.message))
          }
          if (!document) {
            return reject(error.notFound('Document not found'))
          }

          resolve(document)
        })
      }

    })
  }

  /**
   * Creates a new document and save it to the database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function create(req) {
    return new Promise((resolve, reject) => {
      // normalize
      let query = normalize(req.query)

      // handle relationship
      if (map) {
        let key = Object.keys(map)[0]
        if (relationship === ONE_TO_MANY && map) {
          req.body[key] = req.params[map[key].substr(1)]
        }

        if (relationship === MANY_TO_ONE && map) {
          reject(new Error('MANY_TO_ONE relationships are not supported'))
        }

      }

      // create model
      collection.create(req.body, function(err, documents) {
        if (err) {
          return reject(error.internalServerError(err.message))
        }
        if (!documents) {
          return reject(error.internalServerError('Document could not be created'))
        }

        // populate
        if (query.expand.length > 0) {
          documents.populate(query.expand.join(' '), function() {
            resolve(documents)
          })
        } else {
          resolve(documents)
        }
      })
    })
  }

  /**
   * Modifies a single and existing document
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function update(req) {
    return new Promise((resolve, reject) => {
      // normalize
      let query = normalize(req.query)

      // query
      let qo = {}
      qo[idField] = req.params[idParam]

      // Build query
      let q = collection.findOne(qo)

      // Execute query
      q.exec(function(err, document) {
        if (err) {
          return reject(error.internalServerError(err.message))
        }
        if (!document) {
          return reject(error.notFound('Document not found'))
        }

        document
          .merge(req.body)
          .save(function(err) {
            if (err) {
              return reject(error.internalServerError(err.message))
            }

            // Populate
            if (req.query.expand) {
              document.populate(query.expand.join(' '), function() {
                resolve(document)
              })
            } else {
              resolve(document)
            }
          })
      })
    })
  }

  /**
   * Deletes an existing document from database
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function remove(req) {
    return new Promise((resolve, reject) => {
      let qo = {}

      // Query Options
      qo[idField] = req.params[idParam]
      collection.findOneAndRemove(qo, function(err, documents) {
        if (err) {
          return reject(error.internalServerError(err.message))
        }
        if (!documents) {
          return reject(error.notFound('Document not found'))
        }

        resolve()
      })

    })
  }

  return Object.freeze({
    list,
    read,
    create,
    update,
    remove
  })
}
<commit_msg>refactor: list method got some extraction<commit_after>import _ from 'lodash'
import filter from './odata/filter'
import * as error from './error'

export const MANY_TO_ONE = Symbol('many-to-one')
export const ONE_TO_MANY = Symbol('one-to-many')

export default function resource(spec) {
  const {collection} = spec
  const {relationshipField} = spec
  const {relationshipCollection} = spec
  let {relationship} = spec
  let {map} = spec
  let {idParam} = spec
  let {idField} = spec

  idParam = idParam || 'id'
  idField = idField || '_id'
  relationship = relationship || ONE_TO_MANY

  /**
   * Normalize the request query. The resource routes do access certain query
   * parameters, neither they are set or not.
   * @param {Object} query The original query object
   * @returns {Object} The normalized query object
   * @private
   */
  function normalize(query) {
    // Clone query object
    query = _.clone(query)

    // Filter
    if (!query.filter) {
      query.filter = ''
    }

    // Property expansion
    if (query.expand) {
      query.expand = query.expand.split(',')
    } else {
      query.expand = []
    }

    // Order
    if (!query.sort) {
      query.sort = idField
    }

    // Pagination
    if (!query.offset) {
      query.offset = 0
    } else {
      query.offset = Number(query.offset)
    }
    if (!query.limit) {
      query.limit = 10
    } else {
      query.limit = Number(query.limit)
    }

    return query
  }

  /**
   * Maps request parameters to a properties hash object which can be
   * used to create a filter expression for database queries. This method
   * is also used to constraint requests to subresources.
   *
   * The following example tries to fetch messages from a specific user.
   * In order to reduce the messages within the database, there must be some
   * kind of 1:n relationship between a user and its messages.
   *
   * The mapping allows you to define a dynamic relationship between a parameter
   * and a specific document property. Internal this mapping is used to reduce
   * the resultset of documents by adding a $where statement to the database
   * query.
   *
   * Example:
   * // Request
   * GET /users/:user/messages
   *
   * // Mapping
   * {
   *  'owner': ':user'
   * }
   *
   * @param {Object} parameters
   * @returns {Object}
   * @private
   */
  function params(parameters) {
    map = _.clone(map)

    return _.forOwn(_.clone(map), function(val, key, obj) {
      // handle route params
      if (val.charAt(0) === ':') {
        obj[key] = parameters[val.substring(1)]
        return
      }

      // Static value
      obj[key] = val
    })
  }

  function queryExtension(q, query) {
    // populate
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    // sort
    q = q.sort(query.sort)

    // pagination
    q = q.skip(query.offset)
    if (query.limit && query.limit !== -1) {
      q = q.limit(query.limit)
    }

    return q
  }

  function listHandle(q, query, resolve, reject) {
    // populate
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    // sort
    q = q.sort(query.sort)

    // pagination
    q = q.skip(query.offset)
    if (query.limit && query.limit !== -1) {
      q = q.limit(query.limit)
    }

    // execute
    q.exec(function(err, documents) {
      if (err) {
        return reject(error.internalServerError(err.message))
      }

      if (!_.isArray(documents)) {
        return resolve([])
      }

      resolve(documents)
    })
  }

  /**
   * Fetches database for a list of documents. As all public resource methods,
   * this one provides several predefined parameters someone can use to
   * manipulate the resultset.
   *
   * Example:
   * GET /users?filter=email EQ
   * 'box@mail.tld'&sort=email-&limit=10&offset=1&expand=messages
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function list(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize(req.query)

      // filter
      let f = filter(query.filter)

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec(function(err, doc) {
            if (err) {
              return reject(err)
            }

            if (!doc) {
              return resolve([])
            }

            // query
            let q = relationshipCollection.find(f.toObject())

            // select only related
            q = q.where('_id').in(doc[relationshipField])

            // handle
            listHandle(q, query, resolve, reject)
          })

        return
      }

      if (relationship === ONE_TO_MANY) {
        f = f.where(params(req.params))

        // query
        let q = collection.find(f.toObject())

        // handle
        listHandle(q, query, resolve, reject)

        return
      }

      reject(error.internalServerError(
        'Cannot handle relationships, is "map" options set?'
      ))
    })
  }

  /**
   * Fetches a single document from database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function read(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize(req.query)

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec(function(err, doc) {
            if (err) {
              return reject(err)
            }

            if (!doc) {
              return reject(error.notFound('Document not found'))
            }

            // query options
            let qo = {[idField]: req.params[idParam]}

            // query
            let q = relationshipCollection.findOne(qo)

            // populate
            if (query.expand.length > 0) {
              q = q.populate(query.expand.join(' '))
            }

            // execute
            q.exec(function(err, document) {
              if (err) {
                return reject(error.internalServerError(err.message))
              }
              if (!document) {
                return reject(error.notFound('Document not found'))
              }

              resolve(document)
            })
          })
      }

      if (relationship === ONE_TO_MANY) {
        // query options
        let qo = {[idField]: req.params[idParam]}

        // query
        let q = collection.findOne(qo)

        // populate
        if (query.expand.length > 0) {
          q = q.populate(query.expand.join(' '))
        }

        // execute
        q.exec(function(err, document) {
          if (err) {
            return reject(error.internalServerError(err.message))
          }
          if (!document) {
            return reject(error.notFound('Document not found'))
          }

          resolve(document)
        })
      }

    })
  }

  /**
   * Creates a new document and save it to the database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function create(req) {
    return new Promise((resolve, reject) => {
      // normalize
      let query = normalize(req.query)

      // handle relationship
      if (map) {
        let key = Object.keys(map)[0]
        if (relationship === ONE_TO_MANY && map) {
          req.body[key] = req.params[map[key].substr(1)]
        }

        if (relationship === MANY_TO_ONE && map) {
          reject(new Error('MANY_TO_ONE relationships are not supported'))
        }

      }

      // create model
      collection.create(req.body, function(err, documents) {
        if (err) {
          return reject(error.internalServerError(err.message))
        }
        if (!documents) {
          return reject(error.internalServerError('Document could not be created'))
        }

        // populate
        if (query.expand.length > 0) {
          documents.populate(query.expand.join(' '), function() {
            resolve(documents)
          })
        } else {
          resolve(documents)
        }
      })
    })
  }

  /**
   * Modifies a single and existing document
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function update(req) {
    return new Promise((resolve, reject) => {
      // normalize
      let query = normalize(req.query)

      // query
      let qo = {[idField]: req.params[idParam]}

      // Build query
      let q = collection.findOne(qo)

      // Execute query
      q.exec(function(err, document) {
        if (err) {
          return reject(error.internalServerError(err.message))
        }
        if (!document) {
          return reject(error.notFound('Document not found'))
        }

        document
          .merge(req.body)
          .save(function(err) {
            if (err) {
              return reject(error.internalServerError(err.message))
            }

            // Populate
            if (req.query.expand) {
              document.populate(query.expand.join(' '), function() {
                resolve(document)
              })
            } else {
              resolve(document)
            }
          })
      })
    })
  }

  /**
   * Deletes an existing document from database
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function remove(req) {
    return new Promise((resolve, reject) => {
      let qo = {}

      // Query Options
      qo[idField] = req.params[idParam]

      collection.findOneAndRemove(qo, function(err, documents) {
        if (err) {
          return reject(error.internalServerError(err.message))
        }
        if (!documents) {
          return reject(error.notFound('Document not found'))
        }

        resolve()
      })

    })
  }

  return Object.freeze({
    list,
    read,
    create,
    update,
    remove
  })
}
<|endoftext|>


<commit_before>(function() {
  this.SpinalTap.Record = function(model, attributes, opts) {
    if (!model) return; // we get called with no arguments as part of cloning

    this.model = model;
    this.eventSink = SpinalTap.$(this);
    this.opts = opts === void 0 ? {} : opts;

    this.registerEvents(this.events);
    this.setAttributes(attributes, {reset: true, persisted: this.opts.persisted});
    this.eventSink.trigger("afterInitialize");
  };

  _.extend(SpinalTap.Record.prototype, {
    events: {},

    getID: function() {
      return this.attributes.id;
    },

    getURL: function() {
      return this.isNew() ?
        this.model.url :
        this.model.url + "/" + encodeURIComponent(this.getID());
    },

    isNew: function() {
      return !this.getID();
    },

    getChangedAttributes: function() {
      var changed   = {},
          persisted = this.persistedAttributes;

      _.each(this.attributes,
             function(value, key) { if (value !== persisted[key]) changed[key] = value; });

      return changed;
    },

    getSaveableAttributes: function() {
      return this.getChangedAttributes();
    },

    reload: function(opts) {
      opts = _.extend({url: this.getURL()}, opts);

      var handleResults = function(newRecord) {
        return this.setAttributes(newRecord.attributes, {reset: true, persisted: true});
      };

      return this.model.one(opts).then(_.bind(handleResults, this));
    },

    validate: function(deferred) {
      deferred.resolve();
    },

    save: function(opts) {
      var validator = SpinalTap.$.Deferred();
      this.validate(validator);

      return validator.then(_.bind(this.saveWithoutValidation, this, opts));
    },

    saveWithoutValidation: function(opts) {
      return SpinalTap.Persistence.save(this, opts).then(_.bind(this.processSaveResults, this, opts));
    },

    processSaveResults: function(opts, data) {
      var attribs = (opts && opts.resultProcessor || this.model.wireToAttributes)(data);
      this.setAttributes(attribs, {persisted: true, reset: true});
      this.eventSink.trigger("afterSave", data);
      return this;
    },

    setAttributes: function(attributes, opts) {
      var initialAttributes = this.attributes || {};

      if (opts && opts.reset) {
        this.attributes = this.a = {};
        this.persistedAttributes = {};
      }

      if (attributes !== void 0) {
        for (var property in attributes) {
          if (initialAttributes[property] !== attributes[property]) {
            this.eventSink.trigger("attributeChange", {attribute: property, oldValue: initialAttributes[property]});
          }
          this.attributes[property] = attributes[property];
        }

        if (opts && opts.persisted) {
          _.extend(this.persistedAttributes, attributes);
        }
      }

      return this;
    },

    updateAttributes: function(attributes, opts) {
      return this.setAttributes(attributes, opts).save();
    },

    /* Converting between attributes hashes and what we send/receive on the wire */

    attributesToWire: function(attributes) {
      return attributes;
    },

    registerEvents: function(events) {
      _.each(events,
             function(object, eventName) { this.eventSink.on(eventName, object); },
             this);
    },

    // TODO : destroy
  });

  SpinalTap.Record.prototype.set    = SpinalTap.Record.prototype.setAttributes;
  SpinalTap.Record.prototype.update = SpinalTap.Record.prototype.updateAttributes;
}).call(this);
<commit_msg>Fire attributeChange event after the attribute has been changed(!)<commit_after>(function() {
  this.SpinalTap.Record = function(model, attributes, opts) {
    if (!model) return; // we get called with no arguments as part of cloning

    this.model = model;
    this.eventSink = SpinalTap.$(this);
    this.opts = opts === void 0 ? {} : opts;

    this.registerEvents(this.events);
    this.setAttributes(attributes, {reset: true, persisted: this.opts.persisted});
    this.eventSink.trigger("afterInitialize");
  };

  _.extend(SpinalTap.Record.prototype, {
    events: {},

    getID: function() {
      return this.attributes.id;
    },

    getURL: function() {
      return this.isNew() ?
        this.model.url :
        this.model.url + "/" + encodeURIComponent(this.getID());
    },

    isNew: function() {
      return !this.getID();
    },

    getChangedAttributes: function() {
      var changed   = {},
          persisted = this.persistedAttributes;

      _.each(this.attributes,
             function(value, key) { if (value !== persisted[key]) changed[key] = value; });

      return changed;
    },

    getSaveableAttributes: function() {
      return this.getChangedAttributes();
    },

    reload: function(opts) {
      opts = _.extend({url: this.getURL()}, opts);

      var handleResults = function(newRecord) {
        return this.setAttributes(newRecord.attributes, {reset: true, persisted: true});
      };

      return this.model.one(opts).then(_.bind(handleResults, this));
    },

    validate: function(deferred) {
      deferred.resolve();
    },

    save: function(opts) {
      var validator = SpinalTap.$.Deferred();
      this.validate(validator);

      return validator.then(_.bind(this.saveWithoutValidation, this, opts));
    },

    saveWithoutValidation: function(opts) {
      return SpinalTap.Persistence.save(this, opts).then(_.bind(this.processSaveResults, this, opts));
    },

    processSaveResults: function(opts, data) {
      var attribs = (opts && opts.resultProcessor || this.model.wireToAttributes)(data);
      this.setAttributes(attribs, {persisted: true, reset: true});
      this.eventSink.trigger("afterSave", data);
      return this;
    },

    setAttributes: function(attributes, opts) {
      var initialAttributes = this.attributes || {};

      if (opts && opts.reset) {
        this.attributes = this.a = {};
        this.persistedAttributes = {};
      }

      if (attributes !== void 0) {
        for (var property in attributes) {
          var oldValue = initialAttributes[property];

          this.attributes[property] = attributes[property];

          if (oldValue !== attributes[property]) {
            this.eventSink.trigger("attributeChange", {attribute: property, oldValue: oldValue});
          }
        }

        if (opts && opts.persisted) {
          _.extend(this.persistedAttributes, attributes);
        }
      }

      return this;
    },

    updateAttributes: function(attributes, opts) {
      return this.setAttributes(attributes, opts).save();
    },

    /* Converting between attributes hashes and what we send/receive on the wire */

    attributesToWire: function(attributes) {
      return attributes;
    },

    registerEvents: function(events) {
      _.each(events,
             function(object, eventName) { this.eventSink.on(eventName, object); },
             this);
    },

    // TODO : destroy
  });

  SpinalTap.Record.prototype.set    = SpinalTap.Record.prototype.setAttributes;
  SpinalTap.Record.prototype.update = SpinalTap.Record.prototype.updateAttributes;
}).call(this);
<|endoftext|>


<commit_before>import wd from 'wd';
import promisifyEvent from 'promisify-event'
import SaucelabsRequestAdapter from './request';
import wait from '../utils/wait';

const CHECK_TEST_RESULT_DELAY  = 10 * 1000;
const MAX_JOB_RESTART_COUNT    = 3;
const BROWSER_INIT_RETRY_DELAY = 30 * 1000;
const BROWSER_INIT_RETRIES     = 3;
const BROWSER_INIT_TIMEOUT     = 9 * 60 * 1000;

wd.configureHttp({
    retryDelay: BROWSER_INIT_RETRY_DELAY,
    retries:    BROWSER_INIT_RETRIES,
    timeout:    BROWSER_INIT_TIMEOUT
});

//Job
export default class Job {
    constructor (options, platform) {
        this.options = {
            username:         options.username,
            accessKey:        options.accessKey,
            build:            options.build,
            testName:         options.testName,
            tags:             options.tags,
            urls:             options.urls,
            tunnelIdentifier: options.tunnelIdentifier,
            testsTimeout:     options.timeout * 1000
        };

        this.requestAdapter = new SaucelabsRequestAdapter(this.options.username, this.options.accessKey);
        this.platform       = platform;
        this.browser        = wd.promiseRemote('ondemand.saucelabs.com', 80, options.username, options.accessKey);

        this.status         = Job.STATUSES.INITIALIZED;
        this.restartCount   = 0;
        this.startTestsTime = null;
    }

    static STATUSES = {
        INIT_BROWSER: 'init browser',
        INITIALIZED:  'initialized',
        IN_PROGRESS:  'in progress',
        COMPLETED:    'completed',
        FAILED:       'failed'
    };

    async _getTestResult () {
        var testResult = null;

        while (!testResult) {
            if (new Date() - this.startTestsTime > this.options.testsTimeout)
                throw new Error('Test exceeded maximum duration');

            await(wait(CHECK_TEST_RESULT_DELAY));

            testResult = await this.browser.eval('window.global_test_results');
        }

        return testResult;
    }

    async _getJobResult () {
        this.status = Job.STATUSES.IN_PROGRESS;

        this.startTestsTime = new Date();

        var testResult = await this._getTestResult();

        this.status = Job.STATUSES.COMPLETED;

        try {
            await this._publishTestResult(testResult);
        }
        catch (error) {
            this._reportError(`An error occured while the test result was being published: ${error}`);
        }

        return {
            url:      `https://saucelabs.com/jobs/${this.browser.sessionID}`,
            platform: this.platform,
            result:   testResult,
            job_id:   this.browser.sessionID
        };
    }

    _reportError (error) {
        console.log(`The task (${this.platform}) failed: ${error}`);
    }

    async _publishTestResult (testResult) {
        var testSuccess = (testResult.errors.length === 0 || Object.keys(testResult.errors).length === 0)
                          && testResult.failed === 0;

        var data = {
            public:        'public',
            passed:        testSuccess,
            'custom-data': {
                'qunit': testResult
            }
        };

        await this.requestAdapter.put(`/v1/${this.options.username}/jobs/${this.browser.sessionID}`, data);
    }

    async run () {
        var jobResult = null;
        var jobFailed = false;

        var initBrowserParams = {
            name:             this.options.testName,
            tags:             this.options.tags,
            build:            this.options.build,
            platform:         this.platform[0],
            browserName:      this.platform[1],
            version:          this.platform[2],
            tunnelIdentifier: this.options.tunnelIdentifier
        };

        this.status = Job.STATUSES.INIT_BROWSER;

        try {
            var initBrowserPromise = promisifyEvent(this.browser, 'status');

            this.browser.init(initBrowserParams);

            await initBrowserPromise;
            await this.browser.get(this.options.urls[0]);
        }
        catch (error) {
            this._reportError(`An error occured while the browser was being initialized: ${error}`);
            jobFailed = true;
        }

        if (!jobFailed) {
            try {
                jobResult = await this._getJobResult();
            }
            catch (error) {
                this._reportError(error);
                jobFailed = true;
            }

            try {
                await this.browser.quit();
            }
            catch (error) {
                this._reportError(`An error occured while the browser was being closed: ${error}`);
            }
        }

        if (jobFailed) {
            if (++this.restartCount < MAX_JOB_RESTART_COUNT) {
                console.log(`Attempt ${this.restartCount} to restart the task (${this.platform})`);

                jobResult = await this.run();
            }
            else {
                this.status = Job.STATUSES.FAILED;

                jobResult = {
                    platform: this.platform,
                    job_id:   this.browser.sessionID
                };

                if (this.status === Job.STATUSES.IN_PROGRESS)
                    jobResult.url = `https://saucelabs.com/jobs/${this.browser.sessionID}`;

                this.status = Job.STATUSES.FAILED;
            }
        }

        return jobResult;
    }

    getStatus () {
        return this.status;
    }
}
<commit_msg>Errors thrown when fetching test results are handled (close #19)<commit_after>import wd from 'wd';
import promisifyEvent from 'promisify-event'
import SaucelabsRequestAdapter from './request';
import wait from '../utils/wait';

const CHECK_TEST_RESULT_DELAY  = 10 * 1000;
const MAX_JOB_RESTART_COUNT    = 3;
const BROWSER_INIT_RETRY_DELAY = 30 * 1000;
const BROWSER_INIT_RETRIES     = 3;
const BROWSER_INIT_TIMEOUT     = 9 * 60 * 1000;

wd.configureHttp({
    retryDelay: BROWSER_INIT_RETRY_DELAY,
    retries:    BROWSER_INIT_RETRIES,
    timeout:    BROWSER_INIT_TIMEOUT
});

//Job
export default class Job {
    constructor (options, platform) {
        this.options = {
            username:         options.username,
            accessKey:        options.accessKey,
            build:            options.build,
            testName:         options.testName,
            tags:             options.tags,
            urls:             options.urls,
            tunnelIdentifier: options.tunnelIdentifier,
            testsTimeout:     options.timeout * 1000
        };

        this.requestAdapter = new SaucelabsRequestAdapter(this.options.username, this.options.accessKey);
        this.platform       = platform;
        this.browser        = wd.promiseRemote('ondemand.saucelabs.com', 80, options.username, options.accessKey);

        this.status         = Job.STATUSES.INITIALIZED;
        this.restartCount   = 0;
        this.startTestsTime = null;
    }

    static STATUSES = {
        INIT_BROWSER: 'init browser',
        INITIALIZED:  'initialized',
        IN_PROGRESS:  'in progress',
        COMPLETED:    'completed',
        FAILED:       'failed'
    };

    async _getTestResult () {
        var testResult = null;

        while (!testResult) {
            if (new Date() - this.startTestsTime > this.options.testsTimeout)
                throw new Error('Test exceeded maximum duration');

            await(wait(CHECK_TEST_RESULT_DELAY));

            try {
                testResult = await this.browser.eval('window.global_test_results');
            }
            catch (error) {
                var windowErrorMessage = 'window has no properties';

                // NOTE: this error may occur while testing against internet explorer 11.
                // This may happen because the IE driver sometimes throws an unknown error
                // when executing an expression with the 'window' object.
                var ie11ErrorMessage = [
                    'Error response status: 13, , ',
                    'UnknownError - An unknown server-side error occurred while processing the command. ',
                    'Selenium error: JavaScript error (WARNING: The server did not provide any stacktrace information)'
                ].join('');

                if (error.message.indexOf(windowErrorMessage) < 0 && error.message.indexOf(ie11ErrorMessage) < 0)
                    throw error;
            }
        }

        return testResult;
    }

    async _getJobResult () {
        this.status = Job.STATUSES.IN_PROGRESS;

        this.startTestsTime = new Date();

        var testResult = await this._getTestResult();

        this.status = Job.STATUSES.COMPLETED;

        try {
            await this._publishTestResult(testResult);
        }
        catch (error) {
            this._reportError(`An error occured while the test result was being published: ${error}`);
        }

        return {
            url:      `https://saucelabs.com/jobs/${this.browser.sessionID}`,
            platform: this.platform,
            result:   testResult,
            job_id:   this.browser.sessionID
        };
    }

    _reportError (error) {
        console.log(`The task (${this.platform}) failed: ${error}`);
    }

    async _publishTestResult (testResult) {
        var testSuccess = (testResult.errors.length === 0 || Object.keys(testResult.errors).length === 0)
                          && testResult.failed === 0;

        var data = {
            public:        'public',
            passed:        testSuccess,
            'custom-data': {
                'qunit': testResult
            }
        };

        await this.requestAdapter.put(`/v1/${this.options.username}/jobs/${this.browser.sessionID}`, data);
    }

    async run () {
        var jobResult = null;
        var jobFailed = false;

        var initBrowserParams = {
            name:             this.options.testName,
            tags:             this.options.tags,
            build:            this.options.build,
            platform:         this.platform[0],
            browserName:      this.platform[1],
            version:          this.platform[2],
            tunnelIdentifier: this.options.tunnelIdentifier
        };

        this.status = Job.STATUSES.INIT_BROWSER;

        try {
            var initBrowserPromise = promisifyEvent(this.browser, 'status');

            this.browser.init(initBrowserParams);

            await initBrowserPromise;
            await this.browser.get(this.options.urls[0]);
        }
        catch (error) {
            this._reportError(`An error occured while the browser was being initialized: ${error}`);
            jobFailed = true;
        }

        if (!jobFailed) {
            try {
                jobResult = await this._getJobResult();
            }
            catch (error) {
                this._reportError(error);
                jobFailed = true;
            }

            try {
                await this.browser.quit();
            }
            catch (error) {
                this._reportError(`An error occured while the browser was being closed: ${error}`);
            }
        }

        if (jobFailed) {
            if (++this.restartCount < MAX_JOB_RESTART_COUNT) {
                console.log(`Attempt ${this.restartCount} to restart the task (${this.platform})`);

                jobResult = await this.run();
            }
            else {
                this.status = Job.STATUSES.FAILED;

                jobResult = {
                    platform: this.platform,
                    job_id:   this.browser.sessionID
                };

                if (this.status === Job.STATUSES.IN_PROGRESS)
                    jobResult.url = `https://saucelabs.com/jobs/${this.browser.sessionID}`;

                this.status = Job.STATUSES.FAILED;
            }
        }

        return jobResult;
    }

    getStatus () {
        return this.status;
    }
}
<|endoftext|>


<commit_before>(function($, undefined) {

var optimizedMapping;

//
$('html').addClass('render-loading');

// Options auto load from defaults and data attributes
if ($.Widget) {
  function camelize(str) {
    return (str || "").replace(/(\-|_)+(.)?/g, function(match, dash, chr) {
      return chr ? chr.toUpperCase() : '';
    });
  }

  function dataAttr(elem, key) {
    // Try to fetch any
    // data from the HTML5 data-* attribute
    if (elem.nodeType === 1 && $.inArray(key, $.render.exceptOptions) == -1) {
      var data = elem.getAttribute('data-'+key);
      try {
        data = data === "true" ? true :
        data === "false" ? false :
        data === "null" ? null :
        !jQuery.isNaN(data) ? parseFloat(data) : data;
      } catch( e ) {}
      return data;
    } else {
      return undefined;
    }
  }

  $.Widget.prototype._getCreateOptions = function() {
    var data = {}, result = {}, attrs = this.element[0].attributes, name, option;
    for (var i = 0, l = attrs.length; i < l; i++) {
      name = attrs[i].name;
      if (name.indexOf("data-") === 0) {
        name = name.substr(5);
        option = dataAttr(this.element[0], name);
        if (option) { data[name] = option; }
      }
    }
    var options = $.extend({}, $.render.defaultOptions[this.widgetName], data);
    $.each(options, function(key, value) {
      if (key == 'icon') {
        result.icons = result.icons || {};
        result.icons.primary = 'ui-icon-'+value;
      } else if (key == 'icon-primary' || key == 'icon-secondary') {
        result.icons = result.icons || {};
        result.icons[key.split('-')[1]] = 'ui-icon-'+value;
      } else {
        result[camelize(key)] = value;
      }
    });
    return result;
  };
}

//
$.fn.render = function() {
  var target = this, mapping = optimizedMapping || $.render.mapping;

  // Do actual rendering
  target.addClass('render-working');

  var beforeEvent = new $.Event('renderbefore');
  this.trigger(beforeEvent, {'fragment': target});
  if (beforeEvent.result !== false) {
    $.each(mapping, function(method, selector) {
      var fn = $.fn[method];
      if ($.isFunction(fn)) {
        fn.apply(target.filter(selector).add(target.find(selector)), []);
      }
    });
    $(document).trigger('renderafter', {'fragment': target});
  }
  return target.removeClass('render-working');
};

$.render = {

  //
  mapping: {},

  //
  defaultOptions: {},

  //
  exceptOptions: [],

  optimize: function() {
    optimizedMapping = {};
    $.each($.render.mapping, function(method, selector) {
      if ($.isFunction($.fn[method])) {
        optimizedMapping[method] = selector;
      }
    });
  },

  start: function() {
    $(function() {
      // Initialize relations
      if ($.actions) { $.actions.start(); }
      // Watch for dialog open events and render content
      if ($.ui && $.ui.dialog) {
        $(':ui-dialog').live('dialogopen', function() { $(this).render(); });
      }
      $.render.optimize();
      $('body').render();
    });
  }
};

$(document).trigger('renderinit').one('renderafter', function() {
  $('html').removeClass('render-loading');
});

// Default to render on load
if (!('auto' in $.render)) { $.render.auto = true; }
if ($.render.auto === true) {
  $.render.start();
}

})(jQuery);
<commit_msg>some refactoring<commit_after>(function($, undefined) {

var optimizedMapping;

//
$('html').addClass("render-loading");

// Options auto load from defaults and data attributes
if ($.Widget) {
  function camelize(str) {
    return (str || "").replace(/(\-|_)+(.)?/g, function(match, dash, chr) {
      return chr ? chr.toUpperCase() : '';
    });
  }

  function dataAttr(elem, key) {
    // Try to fetch any
    // data from the HTML5 data-* attribute
    if (elem.nodeType === 1 && $.inArray(key, $.render.exceptOptions) == -1) {
      var data = elem.getAttribute("data-"+key);
      try {
        data = data === "true" ? true :
        data === "false" ? false :
        data === "null" ? null :
        !jQuery.isNaN(data) ? parseFloat(data) : data;
      } catch( e ) {}
      return data;
    } else {
      return undefined;
    }
  }

  $.Widget.prototype._getCreateOptions = function() {
    var data = {}, result = {}, attrs = this.element[0].attributes, name, option;
    for (var i = 0, l = attrs.length; i < l; i++) {
      name = attrs[i].name;
      if (name.indexOf("data-") === 0) {
        name = name.substr(5);
        option = dataAttr(this.element[0], name);
        if (option) { data[name] = option; }
      }
    }
    var options = $.extend({}, $.render.defaultOptions[this.widgetName], data);
    $.each(options, function(key, value) {
      if (key == "icon") {
        result.icons = result.icons || {};
        result.icons.primary = 'ui-icon-'+value;
      } else if (key == 'icon-primary' || key == 'icon-secondary') {
        result.icons = result.icons || {};
        result.icons[key.split('-')[1]] = 'ui-icon-'+value;
      } else {
        result[camelize(key)] = value;
      }
    });
    return result;
  };
}

//
$.fn.render = function() {
  var target = this, mapping = optimizedMapping || $.render.mapping;

  // Do actual rendering
  target.addClass('render-working');

  var beforeEvent = new $.Event('renderbefore');
  this.trigger(beforeEvent, {'fragment': target});
  if (beforeEvent.result !== false) {
    $.each(mapping, function(method, selector) {
      var fn = $.fn[method];
      if ($.isFunction(fn)) {
        fn.apply(target.filter(selector).add(target.find(selector)), []);
      }
    });
    $(document).trigger('renderafter', {'fragment': target});
  }
  return target.removeClass('render-working');
};

$.render = {

  //
  mapping: {},

  //
  defaultOptions: {},

  //
  exceptOptions: [],

  optimize: function() {
    optimizedMapping = {};
    $.each($.render.mapping, function(method, selector) {
      if ($.isFunction($.fn[method])) {
        optimizedMapping[method] = selector;
      }
    });
  },

  start: function() {
    $(function() {
      // Initialize actions
      if ($.actions) { $.actions.start(); }
      // Initialize orientation
      if ($.orientation) { $.orientation.start(); }
      // Watch for dialog open events and render content
      if ($.ui && $.ui.dialog) {
        $(':ui-dialog').live('dialogopen', function() { $(this).render(); });
      }
      $.render.optimize();
      $('body').render();
    });
  }
};

$(document).trigger('renderinit').one('renderafter', function() {
  $('html').removeClass('render-loading');
});

// Default to render on load
if (!('auto' in $.render)) { $.render.auto = true; }
if ($.render.auto === true) {
  $.render.start();
}

})(jQuery);
<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'

import Debug from './components/Debug'
import Nav from './components/Nav'
import NotFound from './components/NotFound'
import TldrPage from './components/TldrPage'
import Welcome from './components/Welcome'

let navigate = (history: History) => (path: string) =>
  history.push({...history.getCurrentLocation(), pathname: `/${path}`})

const Oops = (error) => (

 Holy! Page not found!

 Something has gone terribly wrong.

)

const Loading = () => (

 Loading...

)

export default (props) => {
  let {found, page, state: {debug, command, history}} = props
  try {
    ReactDOM.render((

        { !command.name && <Welcome /> }
        { command.name && found === undefined && <Loading /> }
        { command.name && found === false     && <NotFound /> }
        { found && page && !page.error && <TldrPage {...page} /> }
        { found && page &&  page.error && <Oops /> }
        { debug && <Debug {...props} /> }

    ), document.getElementById('tldr'))
  } catch (e) {
    console.log(e)
  }
}
<commit_msg>Close #39<commit_after>import React from 'react'
import ReactDOM from 'react-dom'

import Debug from './components/Debug'
import Nav from './components/Nav'
import NotFound from './components/NotFound'
import TldrPage from './components/TldrPage'
import Welcome from './components/Welcome'

let navigate = (history: History) => (path: string) =>
  history.push({...history.getCurrentLocation(),
               pathname: `/${path.toLowerCase().replace(' ','-')}`})

const Oops = (error) => (

 Holy! Page not found!

 Something has gone terribly wrong.

)

const Loading = () => (

 Loading...

)

export default (props) => {
  let {found, page, state: {debug, command, history}} = props
  try {
    ReactDOM.render((

        { !command.name && <Welcome /> }
        { command.name && found === undefined && <Loading /> }
        { command.name && found === false     && <NotFound /> }
        { found && page && !page.error && <TldrPage {...page} /> }
        { found && page &&  page.error && <Oops /> }
        { debug && <Debug {...props} /> }

    ), document.getElementById('tldr'))
  } catch (e) {
    console.log(e)
  }
}
<|endoftext|>


<commit_before>/* semproelShare : A Pure Javascript Share Plugin. MIT - Copyright (c) 2016 Atok Fakhrudin */
(function(global, kitchen) {
   "use strict";

   global.semproelShare = kitchen(global);

}(window, function(WIN) {
   "use strict";

   var semproelShareText = "semproelShare(): ",
       DOC = document,
       platformKitchen = {},
       platformPreciseCount = {},
       totalPreciseCount, paltformAttributeIndicator, hostElementForCountInfo,

       fetchJsonp = (function(root, doc, htmlBody) {
          function createInjectedScript(url, callbackName) {
             var injectedScript = doc.createElement('script');

             injectedScript.async = true;
             injectedScript.src = url + callbackName;

             return injectedScript;
          }

          return function(url) {
             return new Promise(function(resolve, reject) {
                var callbackName = 'kolbek' + Math.round(100000 * Math.random()),
                    injectedScript = createInjectedScript(url, callbackName);

                root[callbackName] = function(data) {
                   resolve(data);
                };

                injectedScript.onerror = function() {
                   reject("Network error! Either you lost connection to the server before the data is received or error was occuring in the origin server of " + url);
                   // Here also need to be cleaned up
                   root[callbackName] = null;
                   delete root[callbackName];
                   htmlBody.removeChild(injectedScript);
                   injectedScript = null;
                };

                injectedScript.onload = function() {
                   root[callbackName] = null;
                   delete root[callbackName];
                   htmlBody.removeChild(injectedScript);
                   injectedScript = null;
                };

                htmlBody.appendChild(injectedScript);
             });
          };
       }(WIN, DOC, DOC.body)),

       executeClickShare = (function(root, doc) {
          // Credits: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html with trivial modification
          // param {url, title, popupWidth, popupHeight}
          function popCenteredWindow(param) {
             var dualScreenLeft = root.screenX || root.screenLeft,
                 dualScreenTop = root.screenY || root.screenTop,
                 currentBrowserWidth = root.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth,
                 currentBrowserHeight = root.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight,
                 left = ((currentBrowserWidth / 2) - (param.popupWidth / 2)) + dualScreenLeft,
                 top = ((currentBrowserHeight / 2) - (param.popupHeight / 2)) + dualScreenTop;

             var popWindow = root.open(param.url, param.title, "width=" + param.popupWidth + ",height=" + param.popupHeight + ",top=" + top + ",left=" + left + ",titlebar=yes,toolbar=no,menubar=no,location=yes,resizable=no,scrollbars=yes,status=yes");

             if (root.focus) {
                popWindow.focus();
             }

             return popWindow;
          }

          // pK = platformKitchen
          return function(clickedPlatform, pK) {
             var popupWindow = popCenteredWindow({
                                  url: pK[clickedPlatform].urlSharer, // sementara
                                  title: "Share on " + clickedPlatform,
                                  popupWidth: 750,
                                  popupHeight: 450
                               }),
                 isPopupWindowClosed,
                 currentClickedPlatformCount = platformPreciseCount[clickedPlatform];

             isPopupWindowClosed = root.setInterval(function() {
                if (popupWindow.closed === true) {
                   root.clearInterval(isPopupWindowClosed);
                   root.setTimeout(function() {
                      fetchJsonp(pK[clickedPlatform].urlCountGetter).then(pK[clickedPlatform].success.bind(platformPreciseCount)).then(
                         function(responseCount) {
                            console.log("Success fetching individual count exec");
                            totalPreciseCount = (totalPreciseCount - currentClickedPlatformCount) + responseCount;
                            determineShareCountAppearance(totalPreciseCount.toString(), hostElementForCountInfo);
                         },

                         function(error) {
                            throw new Error(semproelShareText + "\"Fetching " + v + " count error! Reason: " + error + "\"");
                         }
                      );
                   }, pK[clickedPlatform].wait || 0);
                }
             }, 250);
          };
       }(WIN, DOC));

   /*
    | the param just exactly as written below when called
    |
    | param: {
    |    platformKitchen: platformKitchen,
    |    sharePlatform: param.sharePlatform,
    |    perPlatformCount: platformPreciseCount
    | }
    */
   function fetchUnifiedCount(param) {
      var promiseOfShareCount = [],
          platform = param.platformKitchen,
          perPlatformCount = param.perPlatformCount;

      param.sharePlatform.forEach(function(v, i) {
         promiseOfShareCount[i] = fetchJsonp(platform[v].urlCountGetter).then(platform[v].success.bind(perPlatformCount), function(error) {
               console.log(Error(semproelShareText + "\"Fetching " + v + " count error! Reason: " + error + "\""));
               return 0;
         });
      });

      return promiseOfShareCount;
   }

   function determineShareCountAppearance(stringifiedTotalCount, hostElementForCount) {
      switch (stringifiedTotalCount.length) {
         case 4:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 1) + "." + stringifiedTotalCount.slice(1, 2) + "k";
            break;

         case 5:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 2) + "." + stringifiedTotalCount.slice(2, 3) + "k";
            break;

         case 6:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 3) + "k";
            break;

         case 7:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 1) + "." + stringifiedTotalCount.slice(1, 2) + "m";
            break;

         case 8:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 2) + "." + stringifiedTotalCount.slice(2, 3) + "m";
            break;

         case 9:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 3) + "m";
            break;

         case 10:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 1) + "." + stringifiedTotalCount.slice(1, 2) + "g";
            break;

         default:
            hostElementForCount.innerHTML = stringifiedTotalCount;
      }
   }

   function respondClickShare(e) {
      var clickTarget;

      e.preventDefault();
      clickTarget = e.target;

      if (clickTarget.hasAttribute(paltformAttributeIndicator)) {
         executeClickShare(clickTarget.getAttribute(paltformAttributeIndicator), platformKitchen);
      } else if (clickTarget.parentNode.hasAttribute(paltformAttributeIndicator)) {
         executeClickShare(clickTarget.parentNode.getAttribute(paltformAttributeIndicator), platformKitchen);
      }

      e.stopPropagation();
   }

   function semproelShare(param) {
      if (arguments.length < 1) {
         throw new Error(semproelShareText + "\"You are missing a parameter. Please check the documentation.\"");
      }

      var requiredParam = ["share", "shareCount", "sharePlatform", "shareButtonContainer"],
          i = requiredParam.length;

      for ( ; i--; ) {
         if (param[ requiredParam[i] ] === undefined || typeof param[ requiredParam[i] ]  !== "string") {
            throw new Error(semproelShareText + "\"You are either missing required one object key (among these: \"share\", \"shareCount\", \"sharePlatform\", \"shareButtonContainer\") in your parameter or giving it a wrong type (while it must be a string). Please refer to the documentation.\"");
         }
      }

      if (!(/^data-(?:[a-z]+-?)*(?:[a-z]+$)/.test(param.sharePlatform))) {
         throw new Error("Your 'data-*' attribute for sharePlatform object key must follow this pattern (using all lowercase letters with no numbers): data-younamethis-or-and-with-this");
      }

      var doc = DOC;

      param.pageUrl = param.pageUrl || doc.URL;
      param.pageTitle = param.pageTitle || doc.title;
      param.pageDescription = param.pageDescription || doc.head.querySelector("[name=description]").content || "";
      param.pageMedia = param.pageMedia || "";
      param.share = doc.querySelector(param.share);
      param.shareCount = param.share.querySelector(param.shareCount);
      paltformAttributeIndicator = param.sharePlatform;
      param.sharePlatform = (function(par) {
                              var chosenPlatform = par.share.querySelectorAll("[" + par.sharePlatform + "]"),
                                  i = chosenPlatform.length, platformList = [], root = WIN, pK = platformKitchen, currentEncodedUri;

                              for ( ; i--; ) {
                                 platformList[i] = chosenPlatform[i].getAttribute(par.sharePlatform);

                                 if (semproelShare.mediaPlatform[platformList[i]] !== undefined && typeof semproelShare.mediaPlatform[platformList[i]] === "function") {
                                    currentEncodedUri = root.encodeURI(par.pageUrl);
                                    pK[platformList[i]] = new semproelShare.mediaPlatform[platformList[i]]();
                                    pK[platformList[i]].urlSharer = pK[platformList[i]].urlSharer.replace("{{url}}", currentEncodedUri).replace("{{title}}", root.encodeURIComponent(par.pageTitle)).replace("{{description}}", root.encodeURIComponent(par.pageDescription)).replace("{{media}}", root.encodeURI(par.pageMedia));
                                    pK[platformList[i]].urlCountGetter += currentEncodedUri + "&callback=";
                                 } else {
                                    throw new Error(semproelShareText + "One of your hook at \"semproelShare.mediaPlatform.yourSocialMediaHook\" is not available among values of \"" + paltformAttributeIndicator + "\". They should not duplicate (only one at hook and only one becomes the value of " + paltformAttributeIndicator+ ") and should correlate equally (i.e. be the same). For instance if at semproelShare.mediaPlatform hook is \"semproelShare.mediaPlatform.facebook\", then at " + paltformAttributeIndicator + " should look like \"" + paltformAttributeIndicator + "='facebook'\".");
                                 }
                              }

                              return platformList;
                           }(param)),
      param.shareButtonContainer = param.share.querySelector(param.shareButtonContainer);

      if (param.share === null || param.shareCount === null || param.sharePlatform.length === 0 || param.shareButtonContainer === null) {
         throw new Error(semproelShareText + "\"Your CSS selector for one of these parameter {param.share, param.shareCount, param.sharePlatform, param.shareButtonContainer} does not match.\"");
      }

      function initiateUnifiedCountFetch() {
         Promise.all(fetchUnifiedCount({
            platformKitchen: platformKitchen,
            sharePlatform: param.sharePlatform,
            perPlatformCount: platformPreciseCount
         })).then(function(response) {
            totalPreciseCount = response.reduce(function(a, b) {
               return a + b;
            });
            determineShareCountAppearance(totalPreciseCount.toString(), param.shareCount);
            WIN.removeEventListener("load", initiateUnifiedCountFetch);
         })["catch"](function(error) {
            WIN.removeEventListener("load", initiateUnifiedCountFetch);
            throw new Error(semproelShareText + error);
         });
      }

      hostElementForCountInfo = param.shareCount;
      WIN.addEventListener("load", initiateUnifiedCountFetch);
      param.shareButtonContainer.addEventListener("click", respondClickShare, false);
   }

   semproelShare.mediaPlatform = {};

   return semproelShare;
}));
<commit_msg>Fix wrong comma, replace to semicolon<commit_after>/* semproelShare : A Pure Javascript Share Plugin. MIT - Copyright (c) 2016 Atok Fakhrudin */
(function(global, kitchen) {
   "use strict";

   global.semproelShare = kitchen(global);

}(window, function(WIN) {
   "use strict";

   var semproelShareText = "semproelShare(): ",
       DOC = document,
       platformKitchen = {},
       platformPreciseCount = {},
       totalPreciseCount, paltformAttributeIndicator, hostElementForCountInfo,

       fetchJsonp = (function(root, doc, htmlBody) {
          function createInjectedScript(url, callbackName) {
             var injectedScript = doc.createElement('script');

             injectedScript.async = true;
             injectedScript.src = url + callbackName;

             return injectedScript;
          }

          return function(url) {
             return new Promise(function(resolve, reject) {
                var callbackName = 'kolbek' + Math.round(100000 * Math.random()),
                    injectedScript = createInjectedScript(url, callbackName);

                root[callbackName] = function(data) {
                   resolve(data);
                };

                injectedScript.onerror = function() {
                   reject("Network error! Either you lost connection to the server before the data is received or error was occuring in the origin server of " + url);
                   // Here also need to be cleaned up
                   root[callbackName] = null;
                   delete root[callbackName];
                   htmlBody.removeChild(injectedScript);
                   injectedScript = null;
                };

                injectedScript.onload = function() {
                   root[callbackName] = null;
                   delete root[callbackName];
                   htmlBody.removeChild(injectedScript);
                   injectedScript = null;
                };

                htmlBody.appendChild(injectedScript);
             });
          };
       }(WIN, DOC, DOC.body)),

       executeClickShare = (function(root, doc) {
          // Credits: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html with trivial modification
          // param {url, title, popupWidth, popupHeight}
          function popCenteredWindow(param) {
             var dualScreenLeft = root.screenX || root.screenLeft,
                 dualScreenTop = root.screenY || root.screenTop,
                 currentBrowserWidth = root.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth,
                 currentBrowserHeight = root.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight,
                 left = ((currentBrowserWidth / 2) - (param.popupWidth / 2)) + dualScreenLeft,
                 top = ((currentBrowserHeight / 2) - (param.popupHeight / 2)) + dualScreenTop;

             var popWindow = root.open(param.url, param.title, "width=" + param.popupWidth + ",height=" + param.popupHeight + ",top=" + top + ",left=" + left + ",titlebar=yes,toolbar=no,menubar=no,location=yes,resizable=no,scrollbars=yes,status=yes");

             if (root.focus) {
                popWindow.focus();
             }

             return popWindow;
          }

          // pK = platformKitchen
          return function(clickedPlatform, pK) {
             var popupWindow = popCenteredWindow({
                                  url: pK[clickedPlatform].urlSharer, // sementara
                                  title: "Share on " + clickedPlatform,
                                  popupWidth: 750,
                                  popupHeight: 450
                               }),
                 isPopupWindowClosed,
                 currentClickedPlatformCount = platformPreciseCount[clickedPlatform];

             isPopupWindowClosed = root.setInterval(function() {
                if (popupWindow.closed === true) {
                   root.clearInterval(isPopupWindowClosed);
                   root.setTimeout(function() {
                      fetchJsonp(pK[clickedPlatform].urlCountGetter).then(pK[clickedPlatform].success.bind(platformPreciseCount)).then(
                         function(responseCount) {
                            console.log("Success fetching individual count exec");
                            totalPreciseCount = (totalPreciseCount - currentClickedPlatformCount) + responseCount;
                            determineShareCountAppearance(totalPreciseCount.toString(), hostElementForCountInfo);
                         },

                         function(error) {
                            throw new Error(semproelShareText + "\"Fetching " + v + " count error! Reason: " + error + "\"");
                         }
                      );
                   }, pK[clickedPlatform].wait || 0);
                }
             }, 250);
          };
       }(WIN, DOC));

   /*
    | the param just exactly as written below when called
    |
    | param: {
    |    platformKitchen: platformKitchen,
    |    sharePlatform: param.sharePlatform,
    |    perPlatformCount: platformPreciseCount
    | }
    */
   function fetchUnifiedCount(param) {
      var promiseOfShareCount = [],
          platform = param.platformKitchen,
          perPlatformCount = param.perPlatformCount;

      param.sharePlatform.forEach(function(v, i) {
         promiseOfShareCount[i] = fetchJsonp(platform[v].urlCountGetter).then(platform[v].success.bind(perPlatformCount), function(error) {
               console.log(Error(semproelShareText + "\"Fetching " + v + " count error! Reason: " + error + "\""));
               return 0;
         });
      });

      return promiseOfShareCount;
   }

   function determineShareCountAppearance(stringifiedTotalCount, hostElementForCount) {
      switch (stringifiedTotalCount.length) {
         case 4:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 1) + "." + stringifiedTotalCount.slice(1, 2) + "k";
            break;

         case 5:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 2) + "." + stringifiedTotalCount.slice(2, 3) + "k";
            break;

         case 6:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 3) + "k";
            break;

         case 7:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 1) + "." + stringifiedTotalCount.slice(1, 2) + "m";
            break;

         case 8:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 2) + "." + stringifiedTotalCount.slice(2, 3) + "m";
            break;

         case 9:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 3) + "m";
            break;

         case 10:
            hostElementForCount.innerHTML = stringifiedTotalCount.slice(0, 1) + "." + stringifiedTotalCount.slice(1, 2) + "g";
            break;

         default:
            hostElementForCount.innerHTML = stringifiedTotalCount;
      }
   }

   function respondClickShare(e) {
      var clickTarget;

      e.preventDefault();
      clickTarget = e.target;

      if (clickTarget.hasAttribute(paltformAttributeIndicator)) {
         executeClickShare(clickTarget.getAttribute(paltformAttributeIndicator), platformKitchen);
      } else if (clickTarget.parentNode.hasAttribute(paltformAttributeIndicator)) {
         executeClickShare(clickTarget.parentNode.getAttribute(paltformAttributeIndicator), platformKitchen);
      }

      e.stopPropagation();
   }

   function semproelShare(param) {
      if (arguments.length < 1) {
         throw new Error(semproelShareText + "\"You are missing a parameter. Please check the documentation.\"");
      }

      var requiredParam = ["share", "shareCount", "sharePlatform", "shareButtonContainer"],
          i = requiredParam.length;

      for ( ; i--; ) {
         if (param[ requiredParam[i] ] === undefined || typeof param[ requiredParam[i] ]  !== "string") {
            throw new Error(semproelShareText + "\"You are either missing required one object key (among these: \"share\", \"shareCount\", \"sharePlatform\", \"shareButtonContainer\") in your parameter or giving it a wrong type (while it must be a string). Please refer to the documentation.\"");
         }
      }

      if (!(/^data-(?:[a-z]+-?)*(?:[a-z]+$)/.test(param.sharePlatform))) {
         throw new Error("Your 'data-*' attribute for sharePlatform object key must follow this pattern (using all lowercase letters with no numbers): data-younamethis-or-and-with-this");
      }

      var doc = DOC;

      param.pageUrl = param.pageUrl || doc.URL;
      param.pageTitle = param.pageTitle || doc.title;
      param.pageDescription = param.pageDescription || doc.head.querySelector("[name=description]").content || "";
      param.pageMedia = param.pageMedia || "";
      param.share = doc.querySelector(param.share);
      param.shareCount = param.share.querySelector(param.shareCount);
      paltformAttributeIndicator = param.sharePlatform;
      param.sharePlatform = (function(par) {
                              var chosenPlatform = par.share.querySelectorAll("[" + par.sharePlatform + "]"),
                                  i = chosenPlatform.length, platformList = [], root = WIN, pK = platformKitchen, currentEncodedUri;

                              for ( ; i--; ) {
                                 platformList[i] = chosenPlatform[i].getAttribute(par.sharePlatform);

                                 if (semproelShare.mediaPlatform[platformList[i]] !== undefined && typeof semproelShare.mediaPlatform[platformList[i]] === "function") {
                                    currentEncodedUri = root.encodeURI(par.pageUrl);
                                    pK[platformList[i]] = new semproelShare.mediaPlatform[platformList[i]]();
                                    pK[platformList[i]].urlSharer = pK[platformList[i]].urlSharer.replace("{{url}}", currentEncodedUri).replace("{{title}}", root.encodeURIComponent(par.pageTitle)).replace("{{description}}", root.encodeURIComponent(par.pageDescription)).replace("{{media}}", root.encodeURI(par.pageMedia));
                                    pK[platformList[i]].urlCountGetter += currentEncodedUri + "&callback=";
                                 } else {
                                    throw new Error(semproelShareText + "One of your hook at \"semproelShare.mediaPlatform.yourSocialMediaHook\" is not available among values of \"" + paltformAttributeIndicator + "\". They should not duplicate (only one at hook and only one becomes the value of " + paltformAttributeIndicator+ ") and should correlate equally (i.e. be the same). For instance if at semproelShare.mediaPlatform hook is \"semproelShare.mediaPlatform.facebook\", then at " + paltformAttributeIndicator + " should look like \"" + paltformAttributeIndicator + "='facebook'\".");
                                 }
                              }

                              return platformList;
                           }(param));
      param.shareButtonContainer = param.share.querySelector(param.shareButtonContainer);

      if (param.share === null || param.shareCount === null || param.sharePlatform.length === 0 || param.shareButtonContainer === null) {
         throw new Error(semproelShareText + "\"Your CSS selector for one of these parameter {param.share, param.shareCount, param.sharePlatform, param.shareButtonContainer} does not match.\"");
      }

      function initiateUnifiedCountFetch() {
         Promise.all(fetchUnifiedCount({
            platformKitchen: platformKitchen,
            sharePlatform: param.sharePlatform,
            perPlatformCount: platformPreciseCount
         })).then(function(response) {
            totalPreciseCount = response.reduce(function(a, b) {
               return a + b;
            });
            determineShareCountAppearance(totalPreciseCount.toString(), param.shareCount);
            WIN.removeEventListener("load", initiateUnifiedCountFetch);
         })["catch"](function(error) {
            WIN.removeEventListener("load", initiateUnifiedCountFetch);
            throw new Error(semproelShareText + error);
         });
      }

      hostElementForCountInfo = param.shareCount;
      WIN.addEventListener("load", initiateUnifiedCountFetch);
      param.shareButtonContainer.addEventListener("click", respondClickShare, false);
   }

   semproelShare.mediaPlatform = {};

   return semproelShare;
}));
<|endoftext|>