/* 
 | 
 * Copyright (c) 2011, salesforce.com, inc. 
 | 
 * All rights reserved. 
 | 
 * 
 | 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided 
 | 
 * that the following conditions are met: 
 | 
 * 
 | 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the 
 | 
 * following disclaimer. 
 | 
 * 
 | 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
 | 
 * the following disclaimer in the documentation and/or other materials provided with the distribution. 
 | 
 * 
 | 
 * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 
 | 
 * promote products derived from this software without specific prior written permission. 
 | 
 * 
 | 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
 | 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
 | 
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
 | 
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
 | 
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 | 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 | 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 | 
 * POSSIBILITY OF SUCH DAMAGE. 
 | 
 */ 
 | 
  
 | 
/* JavaScript library to wrap REST API on Visualforce. Leverages Ajax Proxy 
 | 
 * (see http://bit.ly/sforce_ajax_proxy for details). 
 | 
 * 
 | 
 * Note that you must add the REST endpoint hostname for your instance (i.e.  
 | 
 * https://na1.salesforce.com/ or similar) as a remote site - in the admin 
 | 
 * console, go to Your Name | Setup | Security Controls | Remote Site Settings 
 | 
 */ 
 | 
  
 | 
var forcetk = window.forcetk; 
 | 
  
 | 
if (forcetk === undefined) { 
 | 
    forcetk = {}; 
 | 
} 
 | 
  
 | 
if (forcetk.Client === undefined) { 
 | 
  
 | 
    // We use $j rather than $ for jQuery so it works in Visualforce 
 | 
    if (window.$j === undefined) { 
 | 
        $j = $; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * The Client provides a convenient wrapper for the Force.com REST API,  
 | 
     * allowing JavaScript in Visualforce pages to use the API via the Ajax 
 | 
     * Proxy. 
 | 
     * @param [clientId=null] 'Consumer Key' in the Remote Access app settings 
 | 
     * @param [loginUrl='https://login.salesforce.com/'] Login endpoint 
 | 
     * @param [proxyUrl=null] Proxy URL. Omit if running on Visualforce or  
 | 
     *                  PhoneGap etc 
 | 
     * @constructor 
 | 
     */ 
 | 
    forcetk.Client = function(clientId, loginUrl, proxyUrl) { 
 | 
        this.clientId = clientId; 
 | 
        this.loginUrl = loginUrl || 'https://login.salesforce.com/'; 
 | 
        if (typeof proxyUrl === 'undefined' || proxyUrl === null) { 
 | 
            if (location.protocol === 'file:') { 
 | 
                // In PhoneGap 
 | 
                this.proxyUrl = null; 
 | 
            } else { 
 | 
                // In Visualforce 
 | 
                this.proxyUrl = location.protocol + "//" + location.hostname 
 | 
                    + "/services/proxy"; 
 | 
            } 
 | 
            this.authzHeader = "Authorization"; 
 | 
        } else { 
 | 
            // On a server outside VF 
 | 
            this.proxyUrl = proxyUrl; 
 | 
            this.authzHeader = "X-Authorization"; 
 | 
        } 
 | 
        this.refreshToken = null; 
 | 
        this.sessionId = null; 
 | 
        this.apiVersion = null; 
 | 
        this.instanceUrl = null; 
 | 
        this.asyncAjax = true; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set a refresh token in the client. 
 | 
     * @param refreshToken an OAuth refresh token 
 | 
     */ 
 | 
    forcetk.Client.prototype.setRefreshToken = function(refreshToken) { 
 | 
        this.refreshToken = refreshToken; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Refresh the access token. 
 | 
     * @param callback function to call on success 
 | 
     * @param error function to call on failure 
 | 
     */ 
 | 
    forcetk.Client.prototype.refreshAccessToken = function(callback, error) { 
 | 
        var that = this; 
 | 
        var url = this.loginUrl + '/services/oauth2/token'; 
 | 
        return $j.ajax({ 
 | 
            type: 'POST', 
 | 
            url: (this.proxyUrl !== null) ? this.proxyUrl: url, 
 | 
            cache: false, 
 | 
            processData: false, 
 | 
            data: 'grant_type=refresh_token&client_id=' + this.clientId + '&refresh_token=' + this.refreshToken, 
 | 
            success: callback, 
 | 
            error: error, 
 | 
            dataType: "json", 
 | 
            beforeSend: function(xhr) { 
 | 
                if (that.proxyUrl !== null) { 
 | 
                    xhr.setRequestHeader('SalesforceProxy-Endpoint', url); 
 | 
                } 
 | 
            } 
 | 
        }); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set a session token and the associated metadata in the client. 
 | 
     * @param sessionId a salesforce.com session ID. In a Visualforce page, 
 | 
     *                   use '{!$Api.sessionId}' to obtain a session ID. 
 | 
     * @param [apiVersion="21.0"] Force.com API version 
 | 
     * @param [instanceUrl] Omit this if running on Visualforce; otherwise  
 | 
     *                   use the value from the OAuth token. 
 | 
     */ 
 | 
    forcetk.Client.prototype.setSessionToken = function(sessionId, apiVersion, instanceUrl) { 
 | 
        this.sessionId = sessionId; 
 | 
        this.apiVersion = (typeof apiVersion === 'undefined' || apiVersion === null) 
 | 
        ? 'v27.0': apiVersion; 
 | 
        if (typeof instanceUrl === 'undefined' || instanceUrl == null) { 
 | 
            // location.hostname can be of the form 'abc.na1.visual.force.com', 
 | 
            // 'na1.salesforce.com' or 'abc.my.salesforce.com' (custom domains).  
 | 
            // Split on '.', and take the [1] or [0] element as appropriate 
 | 
            var elements = location.hostname.split("."); 
 | 
             
 | 
            var instance = null; 
 | 
            if(elements.length == 4 && elements[1] === 'my') { 
 | 
                instance = elements[0] + '.' + elements[1]; 
 | 
            } else if(elements.length == 3){ 
 | 
                instance = elements[0]; 
 | 
            } else { 
 | 
                instance = elements[1]; 
 | 
            } 
 | 
             
 | 
            this.instanceUrl = "https://" + instance + ".salesforce.com"; 
 | 
        } else { 
 | 
            this.instanceUrl = instanceUrl; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Low level utility function to call the Salesforce endpoint. 
 | 
     * @param path resource path relative to /services/data 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     * @param [method="GET"] HTTP method for call 
 | 
     * @param [payload=null] payload for POST/PATCH etc 
 | 
     */ 
 | 
    forcetk.Client.prototype.ajax = function(path, callback, error, method, payload, retry) { 
 | 
        var that = this; 
 | 
        var url = this.instanceUrl + '/services/data' + path; 
 | 
  
 | 
        return $j.ajax({ 
 | 
            type: method || "GET", 
 | 
            async: this.asyncAjax, 
 | 
            url: (this.proxyUrl !== null) ? this.proxyUrl: url, 
 | 
            contentType: method == "DELETE"  ? null : 'application/json', 
 | 
            cache: false, 
 | 
            processData: false, 
 | 
            data: payload, 
 | 
            success: callback, 
 | 
            error: (!this.refreshToken || retry ) ? error : function(jqXHR, textStatus, errorThrown) { 
 | 
                if (jqXHR.status === 401) { 
 | 
                    that.refreshAccessToken(function(oauthResponse) { 
 | 
                        that.setSessionToken(oauthResponse.access_token, null, 
 | 
                        oauthResponse.instance_url); 
 | 
                        that.ajax(path, callback, error, method, payload, true); 
 | 
                    }, 
 | 
                    error); 
 | 
                } else { 
 | 
                    error(jqXHR, textStatus, errorThrown); 
 | 
                } 
 | 
            }, 
 | 
            dataType: "json", 
 | 
            beforeSend: function(xhr) { 
 | 
                if (that.proxyUrl !== null) { 
 | 
                    xhr.setRequestHeader('SalesforceProxy-Endpoint', url); 
 | 
                } 
 | 
                xhr.setRequestHeader(that.authzHeader, "OAuth " + that.sessionId); 
 | 
                xhr.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); 
 | 
            } 
 | 
        }); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Utility function to query the Chatter API and download a file 
 | 
     * Note, raw XMLHttpRequest because JQuery mangles the arraybuffer 
 | 
     * This should work on any browser that supports XMLHttpRequest 2 because arraybuffer is required.  
 | 
     * For mobile, that means iOS >= 5 and Android >= Honeycomb 
 | 
     * @author Tom Gersic 
 | 
     * @param path resource path relative to /services/data 
 | 
     * @param mimetype of the file 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which request will be passed in case of error 
 | 
     * @param rety true if we've already tried refresh token flow once 
 | 
     **/ 
 | 
    forcetk.Client.prototype.getChatterFile = function(path,mimeType,callback,error,retry) { 
 | 
        var that = this; 
 | 
        var url = this.instanceUrl + path; 
 | 
  
 | 
        var request = new XMLHttpRequest(); 
 | 
                   
 | 
        request.open("GET",  (this.proxyUrl !== null) ? this.proxyUrl: url, true); 
 | 
        request.responseType = "arraybuffer"; 
 | 
         
 | 
        request.setRequestHeader(that.authzHeader, "OAuth " + that.sessionId); 
 | 
        request.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); 
 | 
        if (this.proxyUrl !== null) { 
 | 
            request.setRequestHeader('SalesforceProxy-Endpoint', url); 
 | 
        } 
 | 
         
 | 
        request.onreadystatechange = function() { 
 | 
            // continue if the process is completed 
 | 
            if (request.readyState == 4) { 
 | 
                // continue only if HTTP status is "OK" 
 | 
                if (request.status == 200) { 
 | 
                    try { 
 | 
                        // retrieve the response 
 | 
                        callback(request.response); 
 | 
                    } 
 | 
                    catch(e) { 
 | 
                        // display error message 
 | 
                        alert("Error reading the response: " + e.toString()); 
 | 
                    } 
 | 
                } 
 | 
                //refresh token in 401 
 | 
                else if(request.status == 401 && !retry) { 
 | 
                    that.refreshAccessToken(function(oauthResponse) { 
 | 
                        that.setSessionToken(oauthResponse.access_token, null,oauthResponse.instance_url); 
 | 
                        that.getChatterFile(path, mimeType, callback, error, true); 
 | 
                    }, 
 | 
                    error); 
 | 
                }  
 | 
                else { 
 | 
                    // display status message 
 | 
                    error(request,request.statusText,request.response); 
 | 
                } 
 | 
            }             
 | 
             
 | 
        } 
 | 
  
 | 
        request.send(); 
 | 
         
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Low level utility function to call the Salesforce endpoint specific for Apex REST API. 
 | 
     * @param path resource path relative to /services/apexrest 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     * @param [method="GET"] HTTP method for call 
 | 
     * @param [payload=null] payload for POST/PATCH etc 
 | 
     * @param [paramMap={}] parameters to send as header values for POST/PATCH etc 
 | 
     * @param [retry] specifies whether to retry on error 
 | 
     */ 
 | 
    forcetk.Client.prototype.apexrest = function(path, callback, error, method, payload, paramMap, retry) { 
 | 
        var that = this; 
 | 
        var url = this.instanceUrl + '/services/apexrest' + path; 
 | 
  
 | 
        return $j.ajax({ 
 | 
            type: method || "GET", 
 | 
            async: this.asyncAjax, 
 | 
            url: (this.proxyUrl !== null) ? this.proxyUrl: url, 
 | 
            contentType: 'application/json', 
 | 
            cache: false, 
 | 
            processData: false, 
 | 
            data: payload, 
 | 
            success: callback, 
 | 
            error: (!this.refreshToken || retry ) ? error : function(jqXHR, textStatus, errorThrown) { 
 | 
                if (jqXHR.status === 401) { 
 | 
                    that.refreshAccessToken(function(oauthResponse) { 
 | 
                        that.setSessionToken(oauthResponse.access_token, null, 
 | 
                        oauthResponse.instance_url); 
 | 
                        that.apexrest(path, callback, error, method, payload, paramMap, true); 
 | 
                    }, 
 | 
                    error); 
 | 
                } else { 
 | 
                    error(jqXHR, textStatus, errorThrown); 
 | 
                } 
 | 
            }, 
 | 
            dataType: "json", 
 | 
            beforeSend: function(xhr) { 
 | 
                if (that.proxyUrl !== null) { 
 | 
                    xhr.setRequestHeader('SalesforceProxy-Endpoint', url); 
 | 
                } 
 | 
                //Add any custom headers 
 | 
                if (paramMap === null) { 
 | 
                    paramMap = {}; 
 | 
                } 
 | 
                for (paramName in paramMap) { 
 | 
                    xhr.setRequestHeader(paramName, paramMap[paramName]); 
 | 
                } 
 | 
                xhr.setRequestHeader(that.authzHeader, "OAuth " + that.sessionId); 
 | 
                xhr.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + that.apiVersion); 
 | 
            } 
 | 
        }); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Lists summary information about each Salesforce.com version currently  
 | 
     * available, including the version, label, and a link to each version's 
 | 
     * root. 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.versions = function(callback, error) { 
 | 
        return this.ajax('/', callback, error); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Lists available resources for the client's API version, including  
 | 
     * resource name and URI. 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.resources = function(callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/', callback, error); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Lists the available objects and their metadata for your organization's  
 | 
     * data. 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.describeGlobal = function(callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/sobjects/', callback, error); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Describes the individual metadata for the specified object. 
 | 
     * @param objtype object type; e.g. "Account" 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.metadata = function(objtype, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' 
 | 
        , callback, error); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Completely describes the individual metadata at all levels for the  
 | 
     * specified object. 
 | 
     * @param objtype object type; e.g. "Account" 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.describe = function(objtype, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype 
 | 
        + '/describe/', callback, error); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Creates a new record of the given type. 
 | 
     * @param objtype object type; e.g. "Account" 
 | 
     * @param fields an object containing initial field names and values for  
 | 
     *               the record, e.g. {:Name "salesforce.com", :TickerSymbol  
 | 
     *               "CRM"} 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.create = function(objtype, fields, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' 
 | 
        , callback, error, "POST", JSON.stringify(fields)); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Retrieves field values for a record of the given type. 
 | 
     * @param objtype object type; e.g. "Account" 
 | 
     * @param id the record's object ID 
 | 
     * @param [fields=null] optional comma-separated list of fields for which  
 | 
     *               to return values; e.g. Name,Industry,TickerSymbol 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.retrieve = function(objtype, id, fieldlist, callback, error) { 
 | 
        if (arguments.length == 4) { 
 | 
            error = callback; 
 | 
            callback = fieldlist; 
 | 
            fieldlist = null; 
 | 
        } 
 | 
        var fields = fieldlist ? '?fields=' + fieldlist : ''; 
 | 
        this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id 
 | 
        + fields, callback, error); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Upsert - creates or updates record of the given type, based on the  
 | 
     * given external Id. 
 | 
     * @param objtype object type; e.g. "Account" 
 | 
     * @param externalIdField external ID field name; e.g. "accountMaster__c" 
 | 
     * @param externalId the record's external ID value 
 | 
     * @param fields an object containing field names and values for  
 | 
     *               the record, e.g. {:Name "salesforce.com", :TickerSymbol  
 | 
     *               "CRM"} 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.upsert = function(objtype, externalIdField, externalId, fields, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + externalIdField + '/' + externalId  
 | 
        + '?_HttpMethod=PATCH', callback, error, "POST", JSON.stringify(fields)); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Updates field values on a record of the given type. 
 | 
     * @param objtype object type; e.g. "Account" 
 | 
     * @param id the record's object ID 
 | 
     * @param fields an object containing initial field names and values for  
 | 
     *               the record, e.g. {:Name "salesforce.com", :TickerSymbol  
 | 
     *               "CRM"} 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.update = function(objtype, id, fields, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id  
 | 
        + '?_HttpMethod=PATCH', callback, error, "POST", JSON.stringify(fields)); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Deletes a record of the given type. Unfortunately, 'delete' is a  
 | 
     * reserved word in JavaScript. 
 | 
     * @param objtype object type; e.g. "Account" 
 | 
     * @param id the record's object ID 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.del = function(objtype, id, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/sobjects/' + objtype + '/' + id 
 | 
        , callback, error, "DELETE"); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Executes the specified SOQL query. 
 | 
     * @param soql a string containing the query to execute - e.g. "SELECT Id,  
 | 
     *             Name from Account ORDER BY Name LIMIT 20" 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.query = function(soql, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/query?q=' + escape(soql) 
 | 
        , callback, error); 
 | 
    } 
 | 
     
 | 
    /* 
 | 
     * Queries the next set of records based on pagination. 
 | 
     * <p>This should be used if performing a query that retrieves more than can be returned 
 | 
     * in accordance with http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_query.htm</p> 
 | 
     * <p>Ex: forcetkClient.queryMore( successResponse.nextRecordsUrl, successHandler, failureHandler )</p> 
 | 
     *  
 | 
     * @param url - the url retrieved from nextRecordsUrl or prevRecordsUrl 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.queryMore = function( url, callback, error ){ 
 | 
        //-- ajax call adds on services/data to the url call, so only send the url after 
 | 
        var serviceData = "services/data"; 
 | 
        var index = url.indexOf( serviceData ); 
 | 
         
 | 
        if( index > -1 ){ 
 | 
            url = url.substr( index + serviceData.length ); 
 | 
        } else { 
 | 
            //-- leave alone 
 | 
        } 
 | 
         
 | 
        return this.ajax( url, callback, error ); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * Executes the specified SOSL search. 
 | 
     * @param sosl a string containing the search to execute - e.g. "FIND  
 | 
     *             {needle}" 
 | 
     * @param callback function to which response will be passed 
 | 
     * @param [error=null] function to which jqXHR will be passed in case of error 
 | 
     */ 
 | 
    forcetk.Client.prototype.search = function(sosl, callback, error) { 
 | 
        return this.ajax('/' + this.apiVersion + '/search?q=' + escape(sosl) 
 | 
        , callback, error); 
 | 
    } 
 | 
} 
 |