<commit_before>/* CloudMine JavaScript Library v0.9.5 cloudmine.me | cloudmine.me/license */ 
(function() {
  var version = '0.9.5';

  /**
   * Construct a new WebService instance
   *
   * <p>Each method on the WebService instance will return an APICall object which may be used to
   * access the results of the method called. You can chain multiple events together with the
   * returned object (an APICall instance).
   *
   * <p>All events are at least guaranteed to have the callback signature: function(data, apicall).
   * supported events:
   * <p>   200, 201, 400, 401, 404, 409, ok, created, badrequest, unauthorized, notfound, conflict,
   *    success, error, complete, meta, result, abort
   * <p>Event order: success callbacks, meta callbacks, result callbacks, error callbacks, complete
   * callbacks 
   *
   * <p>Example:
   * <pre class='code'>
   * var ws = new cloudmine.WebService({appid: "abc", apikey: "abc", appname: 'SampleApp', appversion: '1.0'});
   * ws.get("MyKey").on("success", function(data, apicall) {
   *    console.log("MyKey value: %o", data["MyKey"]);
   * }).on("error", function(data, apicall) {
   *    console.log("Failed to get MyKey: %o", apicall.status);
   * }).on("unauthorized", function(data, apicall) {
   *    console.log("I'm not authorized to access 'appid'");
   * }).on(404, function(data, apicall) {
   *    console.log("Could not find 'MyKey'");
   * }).on("complete", function(data, apicall) {
   *    console.log("Finished get on 'MyKey':", data);
   * });
   * </pre>
   * <p>Refer to APICall's documentation for further information on events.
   *
   * @param {object} Default configuration for this WebService
   * @config {string} [appid] The application id for requests (Required)
   * @config {string} [apikey] The api key for requests (Required)
   * @config {string} [appname] An alphanumeric identifier for your app, used for stats purposes
   * @config {string} [appversion] A version identifier for you app, used for stats purposes
   * @config {boolean} [applevel] If true, always send requests to application.
   *                              If false, always send requests to user-level, trigger error if not logged in.
   *                              Otherwise, send requests to user-level if logged in.
   * @config {boolean} [savelogin] If true, session token and userid will be persisted between logins.
   * @config {integer} [limit] Set the default result limit for requests
   * @config {string} [sort] Set the field on which to sort results
   * @config {integer} [skip] Set the default number of results to skip for requests
   * @config {boolean} [count] Return the count of results for request.
   * @config {string} [snippet] Run the specified code snippet during the request.
   * @config {string|object} [params] Parameters to give the code snippet (applies only for code snippets)
   * @config {boolean} [dontwait] Don't wait for the result of the code snippet (applies only for code snippets)
   * @config {boolean} [resultsonly] Only return results from the code snippet (applies only for code snippets)
   * @name WebService
   * @constructor
   */
  function WebService(options) {
    this.options = opts(this, options);

    var src = this.options.appid;
    if (options.savelogin) {
      if (!this.options.userid) this.options.userid = retrieve('userid', src);
      if (!this.options.session_token) this.options.session_token = retrieve('session_token', src);
    }

    this.options.user_token = retrieve('ut', src) || store('ut', uuid(), src);
  }

  /** @namespace WebService.prototype */
  WebService.prototype = {
    /**
     * Get data from CloudMine.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|string[]|null} [keys] If set, return the specified keys, otherwise return all keys. 
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    get: function(keys, options) {
      if (isArray(keys)) keys = keys.join(',');
      else if (isObject(keys)) {
        options = keys;
        keys = null;
      }

      options = opts(this, options);
      var query = keys ? server_params(options, {keys: keys}) : null;
      return new APICall({
        action: 'text',
        type: 'GET',
        options: options,
        query: query
      });
    },

    /**
     * Create new data, and merge existing data.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} data An object hash where the top level properties are the keys.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name update
     * @memberOf WebService.prototype
     */
    /**
     * Create new data, and merge existing data.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|null} key The key to affect. If given null, a random key will be assigned.
     * @param {string|number|object} value The value of the object
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name update^2
     * @memberOf WebService.prototype
     */
    update: function(key, value, options) {
      if (isObject(key)) options = value;
      else {
        if (!key) key = uuid();
        var out = {};
        out[key] = value;
        key = out;
      }
      options = opts(this, options);

      return new APICall({
        action: 'text',
        type: 'POST',
        options: options,
        query: server_params(options),
        data: JSON.stringify(key)
      });
    },

    /**
     * Create or overwrite existing objects in CloudMine with the given key or keys.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} data An object hash where the top level properties are the keys.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name set
     * @memberOf WebService.prototype
     */
    /**
     * Create or overwrite existing objects in CloudMine with the given key or keys.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|null} key The key to affect. If given null, a random key will be assigned.
     * @param {string|number|object} value The object to store.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name set^2
     * @memberOf WebService.prototype
     */
    set: function(key, value, options) {
      if (isObject(key)) options = value;
      else {
        if (!key) key = uuid();
        var out = {};
        out[key] = value;
        key = out;
      }
      options = opts(this, options);

      return new APICall({
        action: 'text',
        type: 'PUT',
        options: options,
        query: server_params(options),
        data: JSON.stringify(key)
      });
    },

    /**
     * Destroy one or more keys on the server.
     * If given null and options.all is true, delete all objects on the server.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|string[]|null} keys The keys to delete on the server.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    destroy: function(keys, options) {
      options = opts(this, options);
      if (keys == null && options.all === true) keys = {all: true};
      else keys = {keys: (isArray(keys) ? keys.join(',') : keys)}

      return new APICall({
        action: 'data',
        type: 'DELETE',
        options: options,
        query: server_params(options, keys)
      });
    },

    
    /**
     * Run a code snippet directly.
     * Default http method is 'GET', to change the method set the method option for options.
     * @param {string} snippet The name of the code snippet to run.
     * @param {object} params Data to send to the code snippet (optional).
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    run: function(snippet, parameters, options) {
      options = opts(this, options);
      parameters = merge({}, options.params, parameters);
      options.params = null;
      options.snippet = null;

      return new APICall({
        action: 'run/' + snippet,
        type: options.method || 'GET',
        query: parameters,
        options: options
      });
    },

    /**
     * Search CloudMine for text objects.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string} query Query parameters to search for.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    search: function(query, options) {
      options = opts(this, options);
      query = {q: query != null ? convertQueryInput(query) : ''}
      return new APICall({
        action: 'search',
        type: 'GET',
        query: server_params(options, query),
        options: options
      });
    },

    /**
     * Search CloudMine explicitly querying for files.
     * Note: This does not search the contents of files.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string} query Additional query parameters to search for.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    searchFiles: function (query, options) {
      query = convertQueryInput(query);
      var newQuery = '[__type__ = "file"';

      if (!query || query.replace(/\s+/, '').length == 0) {
        newQuery += ']';
      } else if (query[0] != '[') {
        newQuery += '].' + query;
      } else {
        newQuery += (query[1] == ']' ? '' : ', ') + query.substring(1);
      }

      return this.search(newQuery, options);
    },

    /**
     * Search CloudMine user objects by custom attributes.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string} query Additional query parameters to search for in [key="value", key="value"] format.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name searchUsers
     * @memberOf WebService.prototype
     */
    /**
     * Search CloudMine user objects by custom attributes.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} query Additional query parameters to search for in {key: value, key: value} format.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name searchUsers^2
     * @memberOf WebService.prototype
     */
    searchUsers: function(query, options) {
      query = {p: query != null ? convertQueryInput(query) : ''}
      options = opts(this, options);
      return new APICall({
        action: 'account/search',
        type: 'GET',
        query: server_params(options, query),
        options: options
      });
    },
    
    /**
     * Get all user objects.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */

    allUsers: function(options) {
      options = opts(this, options);
      return new APICall({
        action: 'account',
        type: 'GET',
        query: server_params(options, ''),
        options: options
      }); 
    },

    /**
     * Get specific user by id.
     * @param {string} id User id being requested.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * Results may be affected by defaults and/or by the options parameter.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */

    getUser: function(id, options) {
      options = opts(this, options);
      return new APICall({
        action: 'account/' + id,
        type: 'GET',
        query: server_params(options, ''),
        options: options
      }); 
    },

    /**
     * Search using CloudMine's geoquery API.
     * @param {string} field Field to search on.
     * @param {number} longitude The longitude to search for objects at.
     * @param {number} latitude The latitude to search for objects at.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @param {string} [options.units = 'km'] The unit to use when not specified for. Can be 'km', 'mi', 'm', 'ft'.
     * @param {boolean} [options.distance = false] If true, include distance calculations in the meta result for objects.
     * @param {string|number} [options.radius] Distance around the target. If string, include units. If number, specify the unit in options.unit.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    /**
     * Search using CloudMine's geoquery API.
     * @param {string} field Field to search on.
     * @param {object} target A reference object that has geo-location data.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @param {string} [options.units = 'km'] The unit to use when not specified for. Can be 'km', 'mi', 'm', 'ft'.
     * @param {boolean} [options.distance = false] If true, include distance calculations in the meta result for objects.
     * @param {string|number} [options.radius] Distance around the target. If string, include units. If number, specify the unit in options.unit.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name searchGeo^2
     * @memberOf WebService.prototype
     */
    searchGeo: function(field, longitude, latitude, options) {
      var geo, options;

      // Source is 1 argument for object, 2 for lat/long
      if (isObject(longitude)) {
        options = latitude || {};
        geo = extractGeo(longitude, field);
      } else {
        if (!options) options = {};
        geo = extractGeo(longitude, latitude);
      }

      if (!geo) throw new TypeError('Parameters given do not provide geolocation data');
      
      // Handle radius formats
      var radius = options.radius;
      if (isNumber(radius)) radius = ', ' + radius + (options && options.units ? options.units : 'km');
      else if (isString(radius) && radius.length) {
        radius = ', ' + radius;
        if (!options.units) options.units = radius.match(/mi?|km|ft/)[0];
      }
      else radius = '';

      var locTerms = (field || 'location') + ' near (' + geo.longitude + ', ' + geo.latitude + ')' + radius;
      
      return this.search('[' + locTerms + ']', options);
    },
    
    /**
     * Upload a file stored in CloudMine.
     * @param {string} key The binary file's object key.
     * @param {file|string} file FileAPI: A HTML5 FileAPI File object, Node.js: The filename to upload.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    upload: function(key, file, options) {
      options = opts(this, options);
      if (!key) key = uuid();
      if (!options.filename) options.filename = key;

      // Warning: may not necessarily use ajax to perform upload.
      var apicall = new APICall({
        action: 'binary/' + key,
        type: 'post',
        later: true,
        encoding: 'binary',
        options: options,
        processResponse: APICall.basicResponse
      });

      function upload(data, type) {
        if (!options.contentType) options.contentType = type || defaultType;
        APICall.binaryUpload(apicall, data, options.filename, options.contentType).done();
      }

      if (isString(file) || (Buffer && file instanceof Buffer)) {
        // Upload by filename

        if (isNode) {
          if (isString(file)) file = fs.readFileSync(file);
          upload(file);
        }
        else NotSupported();
      } else if (file.toDataURL) {
        // Canvas will have a toDataURL function.
        upload(file, 'image/png');
      } else if (CanvasRenderingContext2D && file instanceof CanvasRenderingContext2D) {
        upload(file.canvas, 'image/png');
      } else if (isBinary(file)) {
        // Binary files are base64 encoded from a buffer.
        var reader = new FileReader();

        /** @private */
        reader.onabort = function() {
          apicall.setData("FileReader aborted").abort();
        }

        /** @private */
        reader.onerror = function(e) {
          apicall.setData(e.target.error).abort();
        }

        /** @private */
        reader.onload = function(e) {
          upload(e.target.result);
        }

        // Don't need to transform Files to Blobs.
        if (File && file instanceof File) {
          if (!options.contentType && file.type != "") options.contentType = file.type;
        } else {
          file = new Blob([ new Uint8Array(file) ], {type: options.contentType || defaultType});
        }

        reader.readAsDataURL(file);
      } else NotSupported();

      return apicall;
    },

    /**
     * Download a file stored in CloudMine.
     * @param {string} key The binary file's object key.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {string} [filename] If present, the file will be downloaded directly to the computer with the
     *                             filename given. This does not validate the filename given!
     * @config {string} [mode] If buffer, automatically move returning data to either an ArrayBuffer or Buffer
     *                         if supported. Otherwise the result will be a standard string.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    download: function(key, options) {
      options = opts(this, options);
      var response = {success: {}}, query;

      if (options.filename) {
        query = {
          force_download: true,
          apikey: options.apikey,
          session_token: options.session_token,
          filename: options.filename
        }
      }

      var apicall = new APICall({
        action: 'binary/' + key,
        type: 'GET',
        later: true,
        encoding: 'binary',
        options: options,
        query: query
      });

      // Download file directly to computer if given a filename.
      if (options.filename) {
        if (isNode) {
          apicall.setProcessor(function(data) {
            response.success[key] = fs.writeFileSync(options.filename, data, 'binary');
            return response;
          }).done();
        } else {
          function detach() {
            if (iframe.parentNode) document.body.removeChild(iframe);
          }
          var iframe = document.createElement('iframe');
          iframe.style.display = 'none';
          document.body.appendChild(iframe);
          setTimeout(function() { iframe.src = apicall.url; }, 25);
          iframe.onload = function() {
            clearTimeout(detach.timer);
            detach.timer = setTimeout(detach, 5000);
          };
          detach.timer = setTimeout(detach, 60000);
          response.success[key] = iframe;
        }

        apicall.done(response);
      } else if (options.mode === 'buffer' && (ArrayBuffer || Buffer)) {
        apicall.setProcessor(function(data) {
          var buffer;
          if (Buffer) {
            buffer = new Buffer(data, 'binary');
          } else {
            buffer = new ArrayBuffer(data.length);
            var charView = new Uint8Array(buffer);
            for (var i = 0; i < data.length; ++i) {
              charView[i] = data[i] & 0xFF;
            }
          }

          response.success[key] = buffer;
          return response;
        }).done();
      } else {
        // Raw data return. Do not attempt to process the result.
        apicall.setProcessor(function(data) {
          response.success[key] = data;
          return response;
        }).done();
      }

      return apicall;
    },


    /**
     * Create a new user.
     * @param {object} data An object with a userid and password field.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {object} [options.profile] Create a user with the given user profile.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name createUser
     * @memberOf WebService.prototype
     */
    /**
     * Create a new user.
     * @param {string} user The userid to login as.
     * @param {string} password The password to login as.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {object} [options.profile] Create a user with the given user profile.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name createUser^2
     * @memberOf WebService.prototype
     */
    createUser: function(user, password, options) {
      if (isObject(user)) options = password;
      else user = {userid: user, password: password};
      options = opts(this, options);
      options.applevel = true;

      var payload = JSON.stringify({
        credentials: {
          email: user.userid,
          password: <PASSWORD>
        },
        profile: options.profile
      });

      return new APICall({
        action: 'account/create',
        type: 'POST',
        options: options,
        processResponse: APICall.basicResponse,
        data: payload
      });
    },

    /**
     * Update user object of logged in user.
     * @param {object} data An object to merge into the logged in user object.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name updateUser
     * @memberOf WebService.prototype
     */
    /**
     * Update user object of logged in user.
     * @param {string} field The field to merge into the logged in user object.
     * @param {string} value The value to set the field to.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name updateUser^2
     * @memberOf WebService.prototype
     */

    updateUser: function(field, value, options) {
      if (isObject(field)) options = value;
      else {
        var out = {};
        out[field] = value;
        field = out;
      }
      options = opts(this, options);

      return new APICall({
        action: 'account',
        type: 'POST',
        options: options,
        query: server_params(options),
        data: JSON.stringify(field)
      });
    },


    /**
     * Change a user's password
     * @param {object} data An object with userid, password, and oldpassword fields.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name changePassword
     * @memberOf WebService.prototype
     */
    /**
     * Change a user's password
     * @param {string} user The userid to change the password.
     * @param {string} oldpassword The existing password for the user.
     * @param {string} password The new password for the user.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name changePassword^2
     * @memberOf WebService.prototype
     */
    changePassword: function(user, oldpassword, password, options) {
      if (isObject(user)) options = oldpassword;
      else user = {userid: user, oldpassword: <PASSWORD>, password: password};
      options = opts(this, options);
      options.applevel = true;

      var payload = JSON.stringify({
        password: <PASSWORD>
      });

      return new APICall({
        action: 'account/password/change',
        type: 'POST',
        data: payload,
        options: options,
        processResponse: APICall.basicResponse,
        username: user.userid,
        password: <PASSWORD>
      });
    },

    /**
     * Initiate a password reset request.
     * @param {string} userid The userid to send a reset password email to.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    resetPassword: function(userid, options) {
      options = opts(this, options);
      options.applevel = true;
      var payload = JSON.stringify({
        email: userid
      });

      return new APICall({ 
        action: 'account/password/reset',
        type: 'POST',
        options: options,
        processResponse: APICall.basicResponse,
        data: payload
      });
    },

    /**
     * Change the password for an account from the token received from password reset.
     * @param {string} token The token for password reset. Usually received by email.
     * @param {string} newPassword The password to assign to the user.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    confirmReset: function(token, newPassword, options) {
      options = opts(this, options);
      options.applevel = true;
      var payload = JSON.stringify({
        password: <PASSWORD>Password
      });

      return new APICall({
        action: "account/password/reset/" + token,
        type: 'POST',
        data: payload,
        processResponse: APICall.basicResponse,
        options: options
      });
    },

    /**
     * Login as a user to access user-level data.
     * @param {object} data An object hash with userid and password fields.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name login
     * @memberOf WebService.prototype
     */
    /**
     * Login as a user to access user-level data.
     * @param {string} user The user to login as
     * @param {string} password The <PASSWORD>
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name login^2
     * @memberOf WebService.prototype
     */
    login: function(user, password, options) {
      if (isObject(user)) options = password;
      else user = {userid: user, password: password};
      options = opts(this, options);
      options.applevel = true;

      // Wipe out existing login information.
      this.options.userid = null;
      this.options.session_token = null;

      if (options.savelogin) {
        store('userid', null, this.options.appid);
        store('session_token', null, this.options.appid);
      }

      var self = this;
      return new APICall({
        action: 'account/login',
        type: 'POST',
        options: options,
        username: user.userid,
        password: <PASSWORD>,
        processResponse: APICall.basicResponse
      }).on('success', function(data) {
        if (options.savelogin) {
          store('userid', user.userid, self.options.appid);
          store('session_token', data.session_token, self.options.appid);
        }
        self.options.userid = user.userid;
        self.options.session_token = data.session_token;
      });
    },

    /**
     * Login a user via a social network credentials.
     * This only works for browsers (i.e. not in a node.js environment) and requires user interaction (a browser window).
     * @param {string} network A network to authenticate against. @see WebService.SocialNetworks
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {string} [options.link] If false, do not link social network to currently logged in user.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    loginSocial: function(network, options) {
      // This does not work with Node.JS, or unrecognized services.
      if (isNode) NotSupported();
      options = opts(this, options);
      options.applevel = true;

      var challenge = uuid(), self = this;
      var apicall = new APICall({
        action: 'account/social/login/status/'+challenge,
        options: options,
        later: true,
        processResponse: function(data) {
          if (data.finished) {
            self.options.session_token = data.session_token;
            data = { success: data }
          }
          else {
            self.options.session_token = null;
            data = { errors: [ "Login unsuccessful." ] }
          }

          if (options.savelogin) {
            store('session_token', self.options.session_token, self.options.appid);
          }

          return data;
        }
      });

      var url = apiroot+"/v1/app/"+options.appid+"/account/social/login?service="+network+"&apikey="+options.apikey+"&challenge="+challenge;

      if (this.options.session_token && options.link !== false) {
        url += "&session_token=" + this.options.session_token;
      }

      var win = window.open(url, challenge, "width=600,height=400,menubar=0,location=0,toolbar=0,status=0");

      function checkOpen() {
        if (win.closed) {
          clearTimeout(checkOpen.interval);
          apicall.done();
        }
      }
      checkOpen.interval = setInterval(checkOpen, 50);

      return apicall;
    },

    /**
     * Query a social network.
     * Must be logged in as a user who has logged in to a social network.
     * @param {object} query An object with the parameters of the query.
     * @config {string} query.network A network to authenticate against. @see WebService.SocialNetworks
     * @config {string} query.endpoint The endpoint to hit, on the social network side. See the social network's documentation for more details.
     * @config {string} query.method HTTP verb to use when querying.
     * @config {object} query.headers Extra headers to pass in the HTTP request to the social network.
     * @config {object} query.params Extra parameters to pass in the HTTP request to the social network.
     * @config {string} query.data Data to pass in the body of the HTTP request.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    socialQuery: function(query, options) {
      options = opts(this, options);

      if(!options.session_token) throw new Error("Must be logged in to perform a social query");

      var url = "social/"+query.network+"/"+query.endpoint;

      if(query.headers) {
        if(url.indexOf('?') === -1) {
            url = url + "?";
        } else {
            url = url + "&";
        }

        url = url + "headers=" + JSON.stringify(query.headers);
      }

      if(query.params) {
        if(url.indexOf('?') === -1) {
            url = url + "?";
        } else {
            url = url + "&";
        }

        url = url + "params=" + JSON.stringify(query.params);
      }

      var apicall = new APICall({
        action: url,
        type: query.method,
        options: options,
        data: query.data
      });

      return apicall;
    },

    /**
     * Logout the current user.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    logout: function(options) {
      options = opts(this, options);
      options.applevel = true;

      var token = this.options.session_token;
      this.options.userid = null;
      this.options.session_token = null;

      if (options.savelogin) {
        store('userid', null, this.options.appid);
        store('session_token', this.options.appid);
      }

      return new APICall({
        action: 'account/logout',
        type: 'POST',
        processResponse: APICall.basicResponse,
        headers: {
          'X-CloudMine-SessionToken': token
        },
        options: options
      });
    },

    /**
     * Verify if the given userid and password is valid.
     * @param {object} data An object with userid and password fields.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name verify
     * @memberOf WebService.prototype
     */
    /**
     * Verify if the given userid and password is valid.
     * @param {string} user The userid to login
     * @param {string} password The password of the <PASSWORD>
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name verify^2
     * @memberOf WebService.prototype
     */
    verify: function(user, password, options) {
      if (isObject(user)) opts = password;
      else user = {userid: user, password: password};
      options = opts(this, options);
      options.applevel = true;

      return new APICall({
        action: 'account/login',
        type: 'POST',
        processResponse: APICall.basicResponse,
        username: user.userid,
        password: <PASSWORD>,
        options: options
      });
    },

    /**
     * Delete a user.
     * If you are using the master api key, omit the user password to delete the user.
     * If you are not using the master api key, provide the user name and password in the corresponding
     * userid and password fields.
     *
     * @param {object} data An object that may contain a userid field and optionally a password field.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name deleteUser
     * @memberOf WebService.prototype
     */
    /**
     * Delete a user.
     * If you are using the master api key, omit the user password to delete the user.
     * If you are not using the master api key, provide the user name and password in the corresponding
     * userid and password fields.
     *
     * @param {string} userid The username to delete. If using a master key use a user id.
     * @param {string} password The password for the account. Omit if using a master key.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name deleteUser^2
     * @memberOf WebService.prototype
     */
    deleteUser: function(userid, password, options) {
      if (isObject(userid)) options = password;
      else userid = {userid: userid, password: password}
      options = opts(this, options);
      options.applevel = true;

      var config = {
        action: 'account',
        type: 'DELETE',
        options: options,
        processResponse: APICall.basicResponse
      };

      // Drop session if we are referring to ourselves.
      if (userid.userid == this.options.userid) {
        this.options.session_token = null;
        this.options.userid = null;
        
        if (options.savelogin) {
          store('userid', null, this.options.appid);
          store('session_token', null, this.options.appid);
        }
      }

      if (userid.password) {
        // Non-master key access
        config.username = userid.userid;
        config.password = <PASSWORD>;
      } else {
        // Master key access
        config.action += '/' + userid.userid;
      }

      return new APICall(config);
    },

    /**
     * Check if the store has a logged in user.
     * @return {boolean} True if the user is logged in, false otherwise.
     */
    isLoggedIn: function() {
      return !!this.options.session_token;
    },

    /**
     * Get the current userid
     * @return {string} The logged in userid, if applicable.
     */
    getUserID: function() {
      return this.options.userid;
    },

    /**
     * Get the current session token.
     * @return {string} The current session token, if logged in.
     */
    getSessionToken: function() {
      return this.options.session_token;
    },

    /**
     * Get a default option that is sent to the server.
     * @param {string} option A default parameter to send to the server.
     * @return {*} The value of the default parameter.
     */
    getOption: function(option) {
      return (valid_params[option] ? this.options[option] : null);
    },

    /**
     * Set a default option that is sent to the server
     * @param {string} option A default parameter to send to the server.
     * @param {string} value The value of the option to set.
     * @return {boolean} true if the option was set, false for invalid options.
     */
    setOption: function(option, value) {
      if (valid_params[option]) {
        this.options[option] = value;
        return true;
      }
      return false;
    },

    /**
     * Set the application or user-level data mode for this store.
     * @param {boolean|undefined} state If true, this store will only operate in application data.
     *                            If false, this store will only operate in user-level data.
     *                            If null/undefined, this store will use user-level data if logged in,
     *                            application data otherwise.
     */
    useApplicationData: function(state) {
      this.options.applevel = (state === true || state === false) ? state : undefined;
    },

    /**
     * Determine if this store is using application data.
     * @return {boolean} true if this store is using application data, false if is using user-level data.
     */
    isApplicationData: function() {
      if (this.options.applevel === true || this.options.applevel === false) return this.options.applevel;
      return this.options.session_token == null;
    }
  };

  WebService.VERSION = version;

  // Supported Social Networks
  WebService.SocialNetworks = [
    'bodymedia',
    'dropbox',
    'facebook',
    'fitbit',
    'flickr',
    'foursquare',
    'gcontacts',
    'gdocs',
    'github',
    'gmail',
    'google',
    'gplus',
    'instagram',
    'linkedin',
    'meetup',
    'runkeeper',
    'tumblr',
    'twitter',
    'withings',
    'wordpress',
    'yammer',
    'zeo'
  ];


  /**
   * <p>WebService will return an instance of this class that should be used to interact with
   * the API. Upon completion of the AJAX call, this object will fire the event handlers based on
   * what were attached.
   *
   * <p>You may chain event creation.
   *
   * <p><b>Note:</b> It is recommended to avoid referring directly to the ajax implementation used by this
   *       function. Depending on environment, features on it may vary since this library only
   *       requires a small subset of jQuery-like AJAX functionality.
   *
   * <p>Event firing order:
   *    <div>Successes: HTTP Code String, HTTP Code Number, 'success'</div>
   *    <div>Meta: 'meta' - This is for operations that can write meta data.</div>
   *    <div>Result: 'result' - This is results from code snippets if used.</div>
   *    <div>Errors: HTTP Code String, HTTP Code Number, 'error'</div>
   *
   * <p>Event callback signatures:
   *    <div>HTTP Codes: function(keys, responseObject, statusCode)</div>
   *    <div>'error': function(keys, responesObject)</div>
   *    <div>'success': function(keys, responseObject)</div>
   *    <div>'meta': function(keys, responseObject)</div>
   *    <div>'result: function(keys, responseObject)</div>
   *    <div>'abort': function(keys, responseObject)</div>
   * @class
   * @name APICall
   */
  function APICall(config) {
    this.config = merge({}, defaultConfig, config);
    this._events = {};

    var agent = 'JS/' + version;
    var opts = this.config.options;
    if (opts.appname) {
      agent += ' ' + opts.appname.replace(agentInvalid, '_');
      if (opts.appversion) {
        agent += '/' + opts.appversion.replace(agentInvalid, '_');
      }
    }

    // Fields that are available at the completion of the api call.
    this.additionalData = this.config.callbackData;
    this.data = null;
    this.hasErrors = false;
    this.requestData = this.config.data;
    this.requestHeaders = {
      'X-CloudMine-ApiKey': opts.apikey,
      'X-CloudMine-Agent': agent,
      'X-CloudMine-UT': opts.user_token
    };

    this.requestHeaders = merge({}, this.requestHeaders, config.headers);

    this.responseHeaders = {};
    this.responseText = null;
    this.status = null;
    this.type = this.config.type || 'GET';

    // Build the URL and headers
    var query = stringify(server_params(opts, this.config.query));
    var root = '/', session = opts.session_token, applevel = opts.applevel;
    if (applevel === false || (applevel !== true && session != null)) {
      if (config.action.split('/')[0] !== 'account'){
        root = '/user/';
      }
      if (session != null) this.requestHeaders['X-CloudMine-SessionToken'] = session;
    }
    
    // Merge in headers in case-insensitive (if necessary) manner.
    for (var key in this.config.headers) {
      mapInsensitive(this.requestHeaders, key, this.config.headers[key]);
    }

    this.config.headers = this.requestHeaders;

    if (!isEmptyObject(perfComplete)) {
      this.config.headers['X-CloudMine-UT'] += ';' + stringify(perfComplete, ':', ',');
      perfComplete = {};
    }

    // CORS cripples the withCredentials flag to uselessness for no good reason.
    // W3C specs seriously needs a justifications section because what did they expect?
    if (config.username || config.password) {
      this.config.headers['Authorization'] = "Basic " + btoa(config.username + ':' + config.password);<commit_msg>didn't mean to leave this in<commit_after>/* CloudMine JavaScript Library v0.9.5 cloudmine.me | cloudmine.me/license */ 
(function() {
  var version = '0.9.5';

  /**
   * Construct a new WebService instance
   *
   * <p>Each method on the WebService instance will return an APICall object which may be used to
   * access the results of the method called. You can chain multiple events together with the
   * returned object (an APICall instance).
   *
   * <p>All events are at least guaranteed to have the callback signature: function(data, apicall).
   * supported events:
   * <p>   200, 201, 400, 401, 404, 409, ok, created, badrequest, unauthorized, notfound, conflict,
   *    success, error, complete, meta, result, abort
   * <p>Event order: success callbacks, meta callbacks, result callbacks, error callbacks, complete
   * callbacks 
   *
   * <p>Example:
   * <pre class='code'>
   * var ws = new cloudmine.WebService({appid: "abc", apikey: "abc", appname: 'SampleApp', appversion: '1.0'});
   * ws.get("MyKey").on("success", function(data, apicall) {
   *    console.log("MyKey value: %o", data["MyKey"]);
   * }).on("error", function(data, apicall) {
   *    console.log("Failed to get MyKey: %o", apicall.status);
   * }).on("unauthorized", function(data, apicall) {
   *    console.log("I'm not authorized to access 'appid'");
   * }).on(404, function(data, apicall) {
   *    console.log("Could not find 'MyKey'");
   * }).on("complete", function(data, apicall) {
   *    console.log("Finished get on 'MyKey':", data);
   * });
   * </pre>
   * <p>Refer to APICall's documentation for further information on events.
   *
   * @param {object} Default configuration for this WebService
   * @config {string} [appid] The application id for requests (Required)
   * @config {string} [apikey] The api key for requests (Required)
   * @config {string} [appname] An alphanumeric identifier for your app, used for stats purposes
   * @config {string} [appversion] A version identifier for you app, used for stats purposes
   * @config {boolean} [applevel] If true, always send requests to application.
   *                              If false, always send requests to user-level, trigger error if not logged in.
   *                              Otherwise, send requests to user-level if logged in.
   * @config {boolean} [savelogin] If true, session token and userid will be persisted between logins.
   * @config {integer} [limit] Set the default result limit for requests
   * @config {string} [sort] Set the field on which to sort results
   * @config {integer} [skip] Set the default number of results to skip for requests
   * @config {boolean} [count] Return the count of results for request.
   * @config {string} [snippet] Run the specified code snippet during the request.
   * @config {string|object} [params] Parameters to give the code snippet (applies only for code snippets)
   * @config {boolean} [dontwait] Don't wait for the result of the code snippet (applies only for code snippets)
   * @config {boolean} [resultsonly] Only return results from the code snippet (applies only for code snippets)
   * @name WebService
   * @constructor
   */
  function WebService(options) {
    this.options = opts(this, options);

    var src = this.options.appid;
    if (options.savelogin) {
      if (!this.options.userid) this.options.userid = retrieve('userid', src);
      if (!this.options.session_token) this.options.session_token = retrieve('session_token', src);
    }

    this.options.user_token = retrieve('ut', src) || store('ut', uuid(), src);
  }

  /** @namespace WebService.prototype */
  WebService.prototype = {
    /**
     * Get data from CloudMine.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|string[]|null} [keys] If set, return the specified keys, otherwise return all keys. 
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    get: function(keys, options) {
      if (isArray(keys)) keys = keys.join(',');
      else if (isObject(keys)) {
        options = keys;
        keys = null;
      }

      options = opts(this, options);
      var query = keys ? server_params(options, {keys: keys}) : null;
      return new APICall({
        action: 'text',
        type: 'GET',
        options: options,
        query: query
      });
    },

    /**
     * Create new data, and merge existing data.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} data An object hash where the top level properties are the keys.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name update
     * @memberOf WebService.prototype
     */
    /**
     * Create new data, and merge existing data.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|null} key The key to affect. If given null, a random key will be assigned.
     * @param {string|number|object} value The value of the object
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name update^2
     * @memberOf WebService.prototype
     */
    update: function(key, value, options) {
      if (isObject(key)) options = value;
      else {
        if (!key) key = uuid();
        var out = {};
        out[key] = value;
        key = out;
      }
      options = opts(this, options);

      return new APICall({
        action: 'text',
        type: 'POST',
        options: options,
        query: server_params(options),
        data: JSON.stringify(key)
      });
    },

    /**
     * Create or overwrite existing objects in CloudMine with the given key or keys.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} data An object hash where the top level properties are the keys.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name set
     * @memberOf WebService.prototype
     */
    /**
     * Create or overwrite existing objects in CloudMine with the given key or keys.
     * The data must be convertable to JSON.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|null} key The key to affect. If given null, a random key will be assigned.
     * @param {string|number|object} value The object to store.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name set^2
     * @memberOf WebService.prototype
     */
    set: function(key, value, options) {
      if (isObject(key)) options = value;
      else {
        if (!key) key = uuid();
        var out = {};
        out[key] = value;
        key = out;
      }
      options = opts(this, options);

      return new APICall({
        action: 'text',
        type: 'PUT',
        options: options,
        query: server_params(options),
        data: JSON.stringify(key)
      });
    },

    /**
     * Destroy one or more keys on the server.
     * If given null and options.all is true, delete all objects on the server.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string|string[]|null} keys The keys to delete on the server.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    destroy: function(keys, options) {
      options = opts(this, options);
      if (keys == null && options.all === true) keys = {all: true};
      else keys = {keys: (isArray(keys) ? keys.join(',') : keys)}

      return new APICall({
        action: 'data',
        type: 'DELETE',
        options: options,
        query: server_params(options, keys)
      });
    },

    
    /**
     * Run a code snippet directly.
     * Default http method is 'GET', to change the method set the method option for options.
     * @param {string} snippet The name of the code snippet to run.
     * @param {object} params Data to send to the code snippet (optional).
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    run: function(snippet, parameters, options) {
      options = opts(this, options);
      parameters = merge({}, options.params, parameters);
      options.params = null;
      options.snippet = null;

      return new APICall({
        action: 'run/' + snippet,
        type: options.method || 'GET',
        query: parameters,
        options: options
      });
    },

    /**
     * Search CloudMine for text objects.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string} query Query parameters to search for.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    search: function(query, options) {
      options = opts(this, options);
      query = {q: query != null ? convertQueryInput(query) : ''}
      return new APICall({
        action: 'search',
        type: 'GET',
        query: server_params(options, query),
        options: options
      });
    },

    /**
     * Search CloudMine explicitly querying for files.
     * Note: This does not search the contents of files.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string} query Additional query parameters to search for.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    searchFiles: function (query, options) {
      query = convertQueryInput(query);
      var newQuery = '[__type__ = "file"';

      if (!query || query.replace(/\s+/, '').length == 0) {
        newQuery += ']';
      } else if (query[0] != '[') {
        newQuery += '].' + query;
      } else {
        newQuery += (query[1] == ']' ? '' : ', ') + query.substring(1);
      }

      return this.search(newQuery, options);
    },

    /**
     * Search CloudMine user objects by custom attributes.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {string} query Additional query parameters to search for in [key="value", key="value"] format.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name searchUsers
     * @memberOf WebService.prototype
     */
    /**
     * Search CloudMine user objects by custom attributes.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} query Additional query parameters to search for in {key: value, key: value} format.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name searchUsers^2
     * @memberOf WebService.prototype
     */
    searchUsers: function(query, options) {
      query = {p: query != null ? convertQueryInput(query) : ''}
      options = opts(this, options);
      return new APICall({
        action: 'account/search',
        type: 'GET',
        query: server_params(options, query),
        options: options
      });
    },
    
    /**
     * Get all user objects.
     * Results may be affected by defaults and/or by the options parameter.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */

    allUsers: function(options) {
      options = opts(this, options);
      return new APICall({
        action: 'account',
        type: 'GET',
        query: server_params(options, ''),
        options: options
      }); 
    },

    /**
     * Get specific user by id.
     * @param {string} id User id being requested.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * Results may be affected by defaults and/or by the options parameter.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */

    getUser: function(id, options) {
      options = opts(this, options);
      return new APICall({
        action: 'account/' + id,
        type: 'GET',
        query: server_params(options, ''),
        options: options
      }); 
    },

    /**
     * Search using CloudMine's geoquery API.
     * @param {string} field Field to search on.
     * @param {number} longitude The longitude to search for objects at.
     * @param {number} latitude The latitude to search for objects at.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @param {string} [options.units = 'km'] The unit to use when not specified for. Can be 'km', 'mi', 'm', 'ft'.
     * @param {boolean} [options.distance = false] If true, include distance calculations in the meta result for objects.
     * @param {string|number} [options.radius] Distance around the target. If string, include units. If number, specify the unit in options.unit.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    /**
     * Search using CloudMine's geoquery API.
     * @param {string} field Field to search on.
     * @param {object} target A reference object that has geo-location data.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @param {string} [options.units = 'km'] The unit to use when not specified for. Can be 'km', 'mi', 'm', 'ft'.
     * @param {boolean} [options.distance = false] If true, include distance calculations in the meta result for objects.
     * @param {string|number} [options.radius] Distance around the target. If string, include units. If number, specify the unit in options.unit.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name searchGeo^2
     * @memberOf WebService.prototype
     */
    searchGeo: function(field, longitude, latitude, options) {
      var geo, options;

      // Source is 1 argument for object, 2 for lat/long
      if (isObject(longitude)) {
        options = latitude || {};
        geo = extractGeo(longitude, field);
      } else {
        if (!options) options = {};
        geo = extractGeo(longitude, latitude);
      }

      if (!geo) throw new TypeError('Parameters given do not provide geolocation data');
      
      // Handle radius formats
      var radius = options.radius;
      if (isNumber(radius)) radius = ', ' + radius + (options && options.units ? options.units : 'km');
      else if (isString(radius) && radius.length) {
        radius = ', ' + radius;
        if (!options.units) options.units = radius.match(/mi?|km|ft/)[0];
      }
      else radius = '';

      var locTerms = (field || 'location') + ' near (' + geo.longitude + ', ' + geo.latitude + ')' + radius;
      
      return this.search('[' + locTerms + ']', options);
    },
    
    /**
     * Upload a file stored in CloudMine.
     * @param {string} key The binary file's object key.
     * @param {file|string} file FileAPI: A HTML5 FileAPI File object, Node.js: The filename to upload.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    upload: function(key, file, options) {
      options = opts(this, options);
      if (!key) key = uuid();
      if (!options.filename) options.filename = key;

      // Warning: may not necessarily use ajax to perform upload.
      var apicall = new APICall({
        action: 'binary/' + key,
        type: 'post',
        later: true,
        encoding: 'binary',
        options: options,
        processResponse: APICall.basicResponse
      });

      function upload(data, type) {
        if (!options.contentType) options.contentType = type || defaultType;
        APICall.binaryUpload(apicall, data, options.filename, options.contentType).done();
      }

      if (isString(file) || (Buffer && file instanceof Buffer)) {
        // Upload by filename

        if (isNode) {
          if (isString(file)) file = fs.readFileSync(file);
          upload(file);
        }
        else NotSupported();
      } else if (file.toDataURL) {
        // Canvas will have a toDataURL function.
        upload(file, 'image/png');
      } else if (CanvasRenderingContext2D && file instanceof CanvasRenderingContext2D) {
        upload(file.canvas, 'image/png');
      } else if (isBinary(file)) {
        // Binary files are base64 encoded from a buffer.
        var reader = new FileReader();

        /** @private */
        reader.onabort = function() {
          apicall.setData("FileReader aborted").abort();
        }

        /** @private */
        reader.onerror = function(e) {
          apicall.setData(e.target.error).abort();
        }

        /** @private */
        reader.onload = function(e) {
          upload(e.target.result);
        }

        // Don't need to transform Files to Blobs.
        if (File && file instanceof File) {
          if (!options.contentType && file.type != "") options.contentType = file.type;
        } else {
          file = new Blob([ new Uint8Array(file) ], {type: options.contentType || defaultType});
        }

        reader.readAsDataURL(file);
      } else NotSupported();

      return apicall;
    },

    /**
     * Download a file stored in CloudMine.
     * @param {string} key The binary file's object key.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {string} [filename] If present, the file will be downloaded directly to the computer with the
     *                             filename given. This does not validate the filename given!
     * @config {string} [mode] If buffer, automatically move returning data to either an ArrayBuffer or Buffer
     *                         if supported. Otherwise the result will be a standard string.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    download: function(key, options) {
      options = opts(this, options);
      var response = {success: {}}, query;

      if (options.filename) {
        query = {
          force_download: true,
          apikey: options.apikey,
          session_token: options.session_token,
          filename: options.filename
        }
      }

      var apicall = new APICall({
        action: 'binary/' + key,
        type: 'GET',
        later: true,
        encoding: 'binary',
        options: options,
        query: query
      });

      // Download file directly to computer if given a filename.
      if (options.filename) {
        if (isNode) {
          apicall.setProcessor(function(data) {
            response.success[key] = fs.writeFileSync(options.filename, data, 'binary');
            return response;
          }).done();
        } else {
          function detach() {
            if (iframe.parentNode) document.body.removeChild(iframe);
          }
          var iframe = document.createElement('iframe');
          iframe.style.display = 'none';
          document.body.appendChild(iframe);
          setTimeout(function() { iframe.src = apicall.url; }, 25);
          iframe.onload = function() {
            clearTimeout(detach.timer);
            detach.timer = setTimeout(detach, 5000);
          };
          detach.timer = setTimeout(detach, 60000);
          response.success[key] = iframe;
        }

        apicall.done(response);
      } else if (options.mode === 'buffer' && (ArrayBuffer || Buffer)) {
        apicall.setProcessor(function(data) {
          var buffer;
          if (Buffer) {
            buffer = new Buffer(data, 'binary');
          } else {
            buffer = new ArrayBuffer(data.length);
            var charView = new Uint8Array(buffer);
            for (var i = 0; i < data.length; ++i) {
              charView[i] = data[i] & 0xFF;
            }
          }

          response.success[key] = buffer;
          return response;
        }).done();
      } else {
        // Raw data return. Do not attempt to process the result.
        apicall.setProcessor(function(data) {
          response.success[key] = data;
          return response;
        }).done();
      }

      return apicall;
    },


    /**
     * Create a new user.
     * @param {object} data An object with a userid and password field.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {object} [options.profile] Create a user with the given user profile.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name createUser
     * @memberOf WebService.prototype
     */
    /**
     * Create a new user.
     * @param {string} user The userid to login as.
     * @param {string} password The password to login as.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {object} [options.profile] Create a user with the given user profile.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name createUser^2
     * @memberOf WebService.prototype
     */
    createUser: function(user, password, options) {
      if (isObject(user)) options = password;
      else user = {userid: user, password: password};
      options = opts(this, options);
      options.applevel = true;

      var payload = JSON.stringify({
        credentials: {
          email: user.userid,
          password: <PASSWORD>
        },
        profile: options.profile
      });

      return new APICall({
        action: 'account/create',
        type: 'POST',
        options: options,
        processResponse: APICall.basicResponse,
        data: payload
      });
    },

    /**
     * Update user object of logged in user.
     * @param {object} data An object to merge into the logged in user object.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name updateUser
     * @memberOf WebService.prototype
     */
    /**
     * Update user object of logged in user.
     * @param {string} field The field to merge into the logged in user object.
     * @param {string} value The value to set the field to.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name updateUser^2
     * @memberOf WebService.prototype
     */

    updateUser: function(field, value, options) {
      if (isObject(field)) options = value;
      else {
        var out = {};
        out[field] = value;
        field = out;
      }
      options = opts(this, options);

      return new APICall({
        action: 'account',
        type: 'POST',
        options: options,
        query: server_params(options),
        data: JSON.stringify(field)
      });
    },


    /**
     * Change a user's password
     * @param {object} data An object with userid, password, and oldpassword fields.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name changePassword
     * @memberOf WebService.prototype
     */
    /**
     * Change a user's password
     * @param {string} user The userid to change the password.
     * @param {string} oldpassword The existing password for the user.
     * @param {string} password The new password for the user.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name changePassword^2
     * @memberOf WebService.prototype
     */
    changePassword: function(user, oldpassword, password, options) {
      if (isObject(user)) options = oldpassword;
      else user = {userid: user, oldpassword: <PASSWORD>, password: password};
      options = opts(this, options);
      options.applevel = true;

      var payload = JSON.stringify({
        password: <PASSWORD>
      });

      return new APICall({
        action: 'account/password/change',
        type: 'POST',
        data: payload,
        options: options,
        processResponse: APICall.basicResponse,
        username: user.userid,
        password: <PASSWORD>
      });
    },

    /**
     * Initiate a password reset request.
     * @param {string} userid The userid to send a reset password email to.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    resetPassword: function(userid, options) {
      options = opts(this, options);
      options.applevel = true;
      var payload = JSON.stringify({
        email: userid
      });

      return new APICall({ 
        action: 'account/password/reset',
        type: 'POST',
        options: options,
        processResponse: APICall.basicResponse,
        data: payload
      });
    },

    /**
     * Change the password for an account from the token received from password reset.
     * @param {string} token The token for password reset. Usually received by email.
     * @param {string} newPassword The password to assign to the user.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    confirmReset: function(token, newPassword, options) {
      options = opts(this, options);
      options.applevel = true;
      var payload = JSON.stringify({
        password: <PASSWORD>
      });

      return new APICall({
        action: "account/password/reset/" + token,
        type: 'POST',
        data: payload,
        processResponse: APICall.basicResponse,
        options: options
      });
    },

    /**
     * Login as a user to access user-level data.
     * @param {object} data An object hash with userid and password fields.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name login
     * @memberOf WebService.prototype
     */
    /**
     * Login as a user to access user-level data.
     * @param {string} user The user to login as
     * @param {string} password The <PASSWORD>
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name login^2
     * @memberOf WebService.prototype
     */
    login: function(user, password, options) {
      if (isObject(user)) options = password;
      else user = {userid: user, password: password};
      options = opts(this, options);
      options.applevel = true;

      // Wipe out existing login information.
      this.options.userid = null;
      this.options.session_token = null;

      if (options.savelogin) {
        store('userid', null, this.options.appid);
        store('session_token', null, this.options.appid);
      }

      var self = this;
      return new APICall({
        action: 'account/login',
        type: 'POST',
        options: options,
        username: user.userid,
        password: <PASSWORD>,
        processResponse: APICall.basicResponse
      }).on('success', function(data) {
        if (options.savelogin) {
          store('userid', user.userid, self.options.appid);
          store('session_token', data.session_token, self.options.appid);
        }
        self.options.userid = user.userid;
        self.options.session_token = data.session_token;
      });
    },

    /**
     * Login a user via a social network credentials.
     * This only works for browsers (i.e. not in a node.js environment) and requires user interaction (a browser window).
     * @param {string} network A network to authenticate against. @see WebService.SocialNetworks
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @config {string} [options.link] If false, do not link social network to currently logged in user.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    loginSocial: function(network, options) {
      // This does not work with Node.JS, or unrecognized services.
      if (isNode) NotSupported();
      options = opts(this, options);
      options.applevel = true;

      var challenge = uuid(), self = this;
      var apicall = new APICall({
        action: 'account/social/login/status/'+challenge,
        options: options,
        later: true,
        processResponse: function(data) {
          if (data.finished) {
            self.options.session_token = data.session_token;
            data = { success: data }
          }
          else {
            self.options.session_token = null;
            data = { errors: [ "Login unsuccessful." ] }
          }

          if (options.savelogin) {
            store('session_token', self.options.session_token, self.options.appid);
          }

          return data;
        }
      });

      var url = apiroot+"/v1/app/"+options.appid+"/account/social/login?service="+network+"&apikey="+options.apikey+"&challenge="+challenge;

      if (this.options.session_token && options.link !== false) {
        url += "&session_token=" + this.options.session_token;
      }

      var win = window.open(url, challenge, "width=600,height=400,menubar=0,location=0,toolbar=0,status=0");

      function checkOpen() {
        if (win.closed) {
          clearTimeout(checkOpen.interval);
          apicall.done();
        }
      }
      checkOpen.interval = setInterval(checkOpen, 50);

      return apicall;
    },

    /**
     * Query a social network.
     * Must be logged in as a user who has logged in to a social network.
     * @param {object} query An object with the parameters of the query.
     * @config {string} query.network A network to authenticate against. @see WebService.SocialNetworks
     * @config {string} query.endpoint The endpoint to hit, on the social network side. See the social network's documentation for more details.
     * @config {string} query.method HTTP verb to use when querying.
     * @config {object} query.headers Extra headers to pass in the HTTP request to the social network.
     * @config {object} query.params Extra parameters to pass in the HTTP request to the social network.
     * @config {string} query.data Data to pass in the body of the HTTP request.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    socialQuery: function(query, options) {
      options = opts(this, options);

      if(!options.session_token) throw new Error("Must be logged in to perform a social query");

      var url = "social/"+query.network+"/"+query.endpoint;

      if(query.headers) {
        if(url.indexOf('?') === -1) {
            url = url + "?";
        } else {
            url = url + "&";
        }

        url = url + "headers=" + JSON.stringify(query.headers);
      }

      if(query.params) {
        if(url.indexOf('?') === -1) {
            url = url + "?";
        } else {
            url = url + "&";
        }

        url = url + "params=" + JSON.stringify(query.params);
      }

      var apicall = new APICall({
        action: url,
        type: query.method,
        options: options,
        data: query.data
      });

      return apicall;
    },

    /**
     * Logout the current user.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     */
    logout: function(options) {
      options = opts(this, options);
      options.applevel = true;

      var token = this.options.session_token;
      this.options.userid = null;
      this.options.session_token = null;

      if (options.savelogin) {
        store('userid', null, this.options.appid);
        store('session_token', this.options.appid);
      }

      return new APICall({
        action: 'account/logout',
        type: 'POST',
        processResponse: APICall.basicResponse,
        headers: {
          'X-CloudMine-SessionToken': token
        },
        options: options
      });
    },

    /**
     * Verify if the given userid and password is valid.
     * @param {object} data An object with userid and password fields.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name verify
     * @memberOf WebService.prototype
     */
    /**
     * Verify if the given userid and password is valid.
     * @param {string} user The userid to login
     * @param {string} password The password of the user to login
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name verify^2
     * @memberOf WebService.prototype
     */
    verify: function(user, password, options) {
      if (isObject(user)) opts = password;
      else user = {userid: user, password: password};
      options = opts(this, options);
      options.applevel = true;

      return new APICall({
        action: 'account/login',
        type: 'POST',
        processResponse: APICall.basicResponse,
        username: user.userid,
        password: <PASSWORD>,
        options: options
      });
    },

    /**
     * Delete a user.
     * If you are using the master api key, omit the user password to delete the user.
     * If you are not using the master api key, provide the user name and password in the corresponding
     * userid and password fields.
     *
     * @param {object} data An object that may contain a userid field and optionally a password field.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name deleteUser
     * @memberOf WebService.prototype
     */
    /**
     * Delete a user.
     * If you are using the master api key, omit the user password to delete the user.
     * If you are not using the master api key, provide the user name and password in the corresponding
     * userid and password fields.
     *
     * @param {string} userid The username to delete. If using a master key use a user id.
     * @param {string} password The password for the account. Omit if using a master key.
     * @param {object} [options] Override defaults set on WebService. See WebService constructor for parameters.
     * @return {APICall} An APICall instance for the web service request used to attach events.
     *
     * @function
     * @name deleteUser^2
     * @memberOf WebService.prototype
     */
    deleteUser: function(userid, password, options) {
      if (isObject(userid)) options = password;
      else userid = {userid: userid, password: password}
      options = opts(this, options);
      options.applevel = true;

      var config = {
        action: 'account',
        type: 'DELETE',
        options: options,
        processResponse: APICall.basicResponse
      };

      // Drop session if we are referring to ourselves.
      if (userid.userid == this.options.userid) {
        this.options.session_token = null;
        this.options.userid = null;
        
        if (options.savelogin) {
          store('userid', null, this.options.appid);
          store('session_token', null, this.options.appid);
        }
      }

      if (userid.password) {
        // Non-master key access
        config.username = userid.userid;
        config.password = <PASSWORD>;
      } else {
        // Master key access
        config.action += '/' + userid.userid;
      }

      return new APICall(config);
    },

    /**
     * Check if the store has a logged in user.
     * @return {boolean} True if the user is logged in, false otherwise.
     */
    isLoggedIn: function() {
      return !!this.options.session_token;
    },

    /**
     * Get the current userid
     * @return {string} The logged in userid, if applicable.
     */
    getUserID: function() {
      return this.options.userid;
    },

    /**
     * Get the current session token.
     * @return {string} The current session token, if logged in.
     */
    getSessionToken: function() {
      return this.options.session_token;
    },

    /**
     * Get a default option that is sent to the server.
     * @param {string} option A default parameter to send to the server.
     * @return {*} The value of the default parameter.
     */
    getOption: function(option) {
      return (valid_params[option] ? this.options[option] : null);
    },

    /**
     * Set a default option that is sent to the server
     * @param {string} option A default parameter to send to the server.
     * @param {string} value The value of the option to set.
     * @return {boolean} true if the option was set, false for invalid options.
     */
    setOption: function(option, value) {
      if (valid_params[option]) {
        this.options[option] = value;
        return true;
      }
      return false;
    },

    /**
     * Set the application or user-level data mode for this store.
     * @param {boolean|undefined} state If true, this store will only operate in application data.
     *                            If false, this store will only operate in user-level data.
     *                            If null/undefined, this store will use user-level data if logged in,
     *                            application data otherwise.
     */
    useApplicationData: function(state) {
      this.options.applevel = (state === true || state === false) ? state : undefined;
    },

    /**
     * Determine if this store is using application data.
     * @return {boolean} true if this store is using application data, false if is using user-level data.
     */
    isApplicationData: function() {
      if (this.options.applevel === true || this.options.applevel === false) return this.options.applevel;
      return this.options.session_token == null;
    }
  };

  WebService.VERSION = version;

  // Supported Social Networks
  WebService.SocialNetworks = [
    'bodymedia',
    'dropbox',
    'facebook',
    'fitbit',
    'flickr',
    'foursquare',
    'gcontacts',
    'gdocs',
    'github',
    'gmail',
    'google',
    'gplus',
    'instagram',
    'linkedin',
    'meetup',
    'runkeeper',
    'tumblr',
    'twitter',
    'withings',
    'wordpress',
    'yammer',
    'zeo'
  ];


  /**
   * <p>WebService will return an instance of this class that should be used to interact with
   * the API. Upon completion of the AJAX call, this object will fire the event handlers based on
   * what were attached.
   *
   * <p>You may chain event creation.
   *
   * <p><b>Note:</b> It is recommended to avoid referring directly to the ajax implementation used by this
   *       function. Depending on environment, features on it may vary since this library only
   *       requires a small subset of jQuery-like AJAX functionality.
   *
   * <p>Event firing order:
   *    <div>Successes: HTTP Code String, HTTP Code Number, 'success'</div>
   *    <div>Meta: 'meta' - This is for operations that can write meta data.</div>
   *    <div>Result: 'result' - This is results from code snippets if used.</div>
   *    <div>Errors: HTTP Code String, HTTP Code Number, 'error'</div>
   *
   * <p>Event callback signatures:
   *    <div>HTTP Codes: function(keys, responseObject, statusCode)</div>
   *    <div>'error': function(keys, responesObject)</div>
   *    <div>'success': function(keys, responseObject)</div>
   *    <div>'meta': function(keys, responseObject)</div>
   *    <div>'result: function(keys, responseObject)</div>
   *    <div>'abort': function(keys, responseObject)</div>
   * @class
   * @name APICall
   */
  function APICall(config) {
    this.config = merge({}, defaultConfig, config);
    this._events = {};

    var agent = 'JS/' + version;
    var opts = this.config.options;
    if (opts.appname) {
      agent += ' ' + opts.appname.replace(agentInvalid, '_');
      if (opts.appversion) {
        agent += '/' + opts.appversion.replace(agentInvalid, '_');
      }
    }

    // Fields that are available at the completion of the api call.
    this.additionalData = this.config.callbackData;
    this.data = null;
    this.hasErrors = false;
    this.requestData = this.config.data;
    this.requestHeaders = {
      'X-CloudMine-ApiKey': opts.apikey,
      'X-CloudMine-Agent': agent,
      'X-CloudMine-UT': opts.user_token
    };

    this.responseHeaders = {};
    this.responseText = null;
    this.status = null;
    this.type = this.config.type || 'GET';

    // Build the URL and headers
    var query = stringify(server_params(opts, this.config.query));
    var root = '/', session = opts.session_token, applevel = opts.applevel;
    if (applevel === false || (applevel !== true && session != null)) {
      if (config.action.split('/')[0] !== 'account'){
        root = '/user/';
      }
      if (session != null) this.requestHeaders['X-CloudMine-SessionToken'] = session;
    }
    
    // Merge in headers in case-insensitive (if necessary) manner.
    for (var key in this.config.headers) {
      mapInsensitive(this.requestHeaders, key, this.config.headers[key]);
    }

    this.config.headers = this.requestHeaders;

    if (!isEmptyObject(perfComplete)) {
      this.config.headers['X-CloudMine-UT'] += ';' + stringify(perfComplete, ':', ',');
      perfComplete = {};
    }

    // CORS cripples the withCredentials flag to uselessness for no good reason.
    // W3C specs seriously needs a justifications section because what did they expect?
    if (config.username || config.password) {
      this.config.headers['Authorization'] = "Basic " + btoa(config.username + ':' + config.password);
{'fraction_non_alphanumeric': 0.10817197828534483, 'fraction_numerical': 0.0018504274933395758, 'mean_word_length': 2.4638403027143907, 'pattern_counts': {'":': 0, '<': 94, '<?xml version=': 0, '>': 92, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4284321', 'n_tokens_mistral': 25069, 'n_tokens_neox': 24609, 'n_words': 8806}