PHP Classes

File: engine/class.www-wrapper.js

Recommend this page to a friend!
  Classes of Kristo Vaher   Wave Framework   engine/class.www-wrapper.js   Download  
File: engine/class.www-wrapper.js
Role: Auxiliary data
Content type: text/plain
Description: JavaScript Wrapper
Class: Wave Framework
MVC framework for building Web sites and APIs
Author: By
Last change: Update of engine/class.www-wrapper.js
Date: 2 months ago
Size: 49,525 bytes
 

Contents

Class file image Download
/* * Wave Framework <http://github.com/kristovaher/Wave-Framework> * JavaScript API Wrapper Class * * Main purpose of an API Wrapper is to make it easier to make API requests over HTTP to a system * built on Wave Framework. API Wrapper class does everything for the developer without requiring * the developer to learn the ins and outs of technical details about how to build an API request. * Wave Framework comes with two separate API authentication methods, one more secure than the * other, both which are handled by this Wrapper class. JavaScript API Wrapper does not support * sending data in encrypted form or decrypting encrypted data from a response. * * @package API * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/wrapper_js.htm * @since 2.0.1 * @version 3.6.9 */ /* * Wrapper object creation requires an 'address', which is the address that Wrapper will make * API requests to. If this is not defined, then 'address' assumes that the system it makes * requests to is the same where the API is loaded from. 'language' is a language keyword from * the system that API makes a connection with and is used whenever language-specific results * are returned from API. * * @param string $address API address, default value is current domain presumed API address * @param string $language language keyword, default value is current document language * @return object */ function WWW_Wrapper(address,language){ // Finding a default, if not set if(address==null && document.baseURI!=undefined){ address=document.baseURI+'json.api'; } else if(address==null){ // This is primarily for Internet Explorer that does not have baseURI var baseTags=document.getElementsByTagName('base'); if(baseTags.length > 0) { address=baseTags[0].href+'json.api'; } else { address='json.api'; } } // Finding a default, if not set if(language==null && document.documentElement.lang!=null){ language=document.documentElement.lang; } /* * This is the address and URL of the API that the Wrapper will connect to. The API address * must be for Wave Framework API. This value is set either in object creation or when * setting 'www-address' input variable. */ var apiAddress=address; /* * This holds the current language of the API, it can be useful if the API commands return * language-specific responses and translations from the API. This variable is set by sending * 'www-language' input variable. */ var apiLanguage=language; /* * This holds information about current API state, such as profile name, secret key and * various API-related flags for callbacks, asyncrhonous status and more. This variable is * passed around per each API call. */ var apiState={ apiProfile:false, apiSecretKey:false, apiToken:false, apiPublicToken:false, apiHashValidation:true, apiStateKey:false, apiVersion:false, returnHash:false, headers:false, returnTimestamp:false, trueCallback:false, falseCallback:false, errorCallback:false, trueCallbackParameters:false, falseCallbackParameters:false, timestampDuration:60, hiddenWindowCounter:0, hiddenScriptCounter:0, apiSubmitFormId:false, asynchronous:false, unserialize:true, JSONP:false, JSONV:false } /* * This variable holds the last known error message returned from the API. */ var errorMessage=false; /* * This variable holds the last known response code returned from the API. */ var responseCode=false; /* * Input data is a variable that stores all the plain-text input sent with the API request, * it's a key-value pair of variables and their values for the API. */ var inputData=new Object(); /* * This is an array that gathers log information about the requests made through the API * that can be used for debugging purposes should something go wrong. */ var log=new Array(); /* * This is the user-agent string of the API Wrapper. At the moment it is not possible to * set custom headers with AJAX requests, so this variable is unused in the class and only * defined for future purpose. */ var userAgent='WWWFramework/3.6.9 (JS)'; /* * This is the GET string maximum length. Most servers should easily be able to deal with * 2048 bytes of request string length, but this value can be changed by submitting a * different length with 'www-get-length' input value. */ var getLimit=2048; /* * If this value is set, then API log will be reset after each API request. This value can * be sent with 'www-reset-log' keyword sent to Wrapper. */ var resetLog=true; // Log entry log.push('Wave API Wrapper object created with API address: '+address); // This is used to refer to current object var that=this; // SETTINGS /* * This method returns current log of the API wrapper. If 'implode' is set, then the * value of 'implode' is used as a character to implode the log with. Otherwise the log * is returned as an array. * * @param string $implode String to implode the log entries with * @return array or string, depending on implode setting */ this.returnLog=function(implode){ log.push('Returning log'); // Imploding, if requested if(implode==null){ return log; } else { return log.join(implode); } } /* * This method clears the API log. This method can be called manually or is called * automatically if log is assigned to be reset with each new API request made by the * object. * * @return boolean */ this.clearLog=function(){ log=new Array(); log.push('Log cleared'); return true; } /* * This method returns currently used token, if one exists. This can be stored for * subsequent requests with Wrapper (or manually over HTTP). * * @return string or false if token does not exist */ this.getToken=function(){ // Returning from the state return apiState.apiToken; } // INPUT /* * This method is used to set an input value in the API Wrapper. 'input' is the key * to set and 'value' is the value of the input key. 'input' can also be an array, * in which case multiple input values will be set in the same call. This method calls * private inputSetter() function that checks the input value for any internal flags * that might not actually be sent as an input to the API. * * @param string/object $input input data keyword or an object of input data * @param string $value input value * @return boolean */ this.setInput=function(input,value){ //Default value if(value==null || value==false){ value=0; } // If this is an array then it populates input array recursively if(typeof(input)==='object'){ for(var node in input){ inputSetter(node,input[node]); } } else { inputSetter(input,value); } return true; } /* * This is a helper function that setInput() method uses to actually assign 'value' * to the 'input' keyword. A lot of the keywords set carry additional functionality * that may entirely be API Wrapper specific. This method also creates a log entry * for any value that is changed or set. * * @param string $input input data key * @param string $value value to be set * @return boolean */ var inputSetter=function(input,value){ // Input is set based on key, as some keys have additional functionality switch(input){ case 'www-api': apiState.apiAddress=value; log.push('API address changed to: '+value); break; case 'www-hash-validation': apiState.apiHashValidation=value; if(value){ log.push('API hash validation is used'); } else { log.push('API hash validation is not used'); } break; case 'www-secret-key': apiState.apiSecretKey=value; log.push('API secret key set to: '+value); break; case 'www-token': apiState.apiToken=value; log.push('API session token set to: '+value); break; case 'www-profile': apiState.apiProfile=value; log.push('API profile set to: '+value); break; case 'www-version': apiState.apiVersion=value; log.push('API version set to: '+value); break; case 'www-jsonp': if(value){ apiState.JSONP=value; log.push('Request is made using JSONP'); } else { apiState.JSONP=false; log.push('JSONP is not used for the request'); } break; case 'www-jsonv': if(value){ apiState.JSONV=value; log.push('Request is made using JSONV'); } else { apiState.JSONV=false; log.push('JSONV is not used for the request'); } break; case 'www-state': apiState.apiStateKey=value; log.push('API state check key set to: '+value); break; case 'www-headers': // Headers cannot be set for responses log.push('Cannot request framework specific headers to be sent as headers from server'); break; case 'www-unserialize': if(value){ apiState.unserialize=true; log.push('Returned result will be automatically unserialized'); } else { apiState.unserialize=false; log.push('Returned result will not be automatically unserialized'); } break; case 'www-asynchronous': if(value){ apiState.asynchronous=true; log.push('Request will be made asynchronously'); } else { apiState.asynchronous=false; log.push('Request will not be made asynchronously'); } break; case 'www-return-hash': apiState.returnHash=value; if(value){ log.push('API request will require hash validation'); } else { log.push('API request will not require hash validation'); } break; case 'www-return-timestamp': apiState.returnTimestamp=value; if(value){ log.push('API request will require timestamp validation'); } else { log.push('API request will not require timestamp validation'); } break; case 'www-public-token': apiState.apiPublicToken=value; if(value){ log.push('API public token set to: '+value); } else { log.push('API public token unset'); } break; case 'www-return-type': inputData[input]=value; log.push('Input value of "'+input+'" set to: '+value); if(value!='json'){ apiState.unserialize=false; log.push('API result cannot be unserialized, setting unserialize flag to false'); } break; case 'www-language': apiLanguage=value; if(value){ log.push('API result language set to: '+value); } else { log.push('API result language uninitialized'); } break; case 'www-true-callback': apiState.trueCallback=value; if(value){ if(typeof(value)!=='function'){ log.push('API return true/success callback set to: '+value+'()'); } else { log.push('API return true/success callback uses an anonymous function'); } } break; case 'www-false-callback': apiState.falseCallback=value; if(value){ if(typeof(value)!=='function'){ log.push('API return false/failure callback set to: '+value+'()'); } else { log.push('API return false/failure callback uses an anonymous function'); } } break; case 'www-error-callback': apiState.errorCallback=value; if(value){ if(typeof(value)!=='function'){ log.push('API return error callback set to: '+value+'()'); } else { log.push('API return error uses an anonymous function'); } } break; case 'www-true-callback-parameters': apiState.trueCallbackParameters=value; if(value){ log.push('API return true/success callback parameters set'); } break; case 'www-false-callback-parameters': apiState.falseCallbackParameters=value; if(value){ log.push('API return false/failure callback parameters set'); } break; case 'www-get-limit': getLimit=value; log.push('Maximum GET string length is set to: '+value); break; case 'www-reset-log': resetLog=value; if(value){ log.push('Log is reset after each new request'); } else { log.push('Log is kept for multiple requests'); } break; case 'www-timestamp-duration': apiState.timestampDuration=value; log.push('API valid timestamp duration set to: '+value); break; case 'www-output': log.push('Ignoring www-output setting, wrapper always requires output to be set to true'); break; case 'www-form': if(value){ that.setForm(value); } else if(value==false){ that.clearForm(); } break; default: if(value==true){ value=1; } else if(value==false){ value=0; } inputData[input]=value; log.push('Input value of "'+input+'" set to: '+value); break; } return true; } /* * This method sets the form ID that is used to fetch input data from. This form can * be used for uploading files with JavaScript API Wrapper or making it easy to send * large form-based requests to API over AJAX. * * @param string $formId form ID value * @return boolean */ this.setForm=function(formId){ // Sets the form handler apiState.apiSubmitFormId=formId; // This forces another content type to stop browsers from pre-formatting the hidden iFrame content inputData['www-content-type']='text/html'; log.push('Form ID set: '+formId); return true; } /* * This method unsets the attached form from the API request. * * @return boolean */ this.clearForm=function(){ // Resetting content type if(inputData['www-content-type']!=null){ delete inputData['www-content-type']; } log.push('Form ID removed'); apiState.apiSubmitFormId=false; return true; } /* * This function simply deletes current input values * * @param boolean $reset whether to also reset authentication and other state data * @return boolean */ this.clearInput=function(reset){ // If authentication was also set for deletion if(reset!=null && reset==true){ // Settings apiState.apiProfile=false; apiState.apiSecretKey=false; apiState.apiToken=false; apiState.apiPublicToken=false; apiState.apiHashValidation=true; apiState.apiVersion=false; apiState.returnHash=false; apiState.returnTimestamp=false; apiState.timestampDuration=60; apiState.JSONP=false; apiState.JSONV=false; } // Resetting the API state test key apiState.apiStateKey=false; // Neutralizing state settings apiState.unserialize=true; apiState.asynchronous=false; // Neutralizing callbacks and submit form apiState.trueCallback=false; apiState.falseCallback=false; apiState.errorCallback=false; apiState.trueCallbackParameters=false; apiState.falseCallbackParameters=false; apiState.apiSubmitFormId=false; // Input data inputData=new Object(); // Log entry log.push('Input data, crypted input and file data is unset'); return true; } // SENDING REQUEST /* * This method executes the API request by building the request based on set input data * and set forms and sending it to API using XmlHttpRequest() or through hidden iFrame * forms. It also builds all validations as well as validates the returned response * from the server and calls callback functions, if they are set. It is possible to * send input variables directly with a single call by supplying the 'variable' array. * Form ID can also be sent with the request directly. * * @param object $variables object of keys and values to use as input data * @param string $formId form ID value * @return object/void returns object on only non-async requests */ this.sendRequest=function(variables,formId){ // If log is assigned to be reset with each new API request if(resetLog){ that.clearLog(); } // In case variables have been sent with a single request if(variables!=null && typeof(variables)=='object'){ for(var key in variables){ // Setting variable through input setter that.setInput(key,variables[key]); } } // In case form has been set if(formId!=null){ that.setForm(formId); } // Storing input data var thisInputData=clone(inputData); // Current state settings var thisApiState=clone(apiState); // Assigning authentication options that are sent with the request if(thisApiState.apiProfile!=false){ thisInputData['www-profile']=thisApiState.apiProfile; } // Assigning API version, if it is set if(thisApiState.apiVersion!=false){ thisInputData['www-version']=thisApiState.apiVersion; } // Assigning the state check key if(thisApiState.apiStateKey!=false){ thisInputData['www-state']=thisApiState.apiStateKey; } // Assigning return-timestamp flag to request if(thisApiState.returnTimestamp==true || thisApiState.returnTimestamp==1){ thisInputData['www-return-timestamp']=1; } // Assigning return-hash flag to request if(thisApiState.returnHash==true || thisApiState.returnHash==1){ thisInputData['www-return-hash']=1; } // Assigning public API token as part of the request if(thisApiState.apiPublicToken){ thisInputData['www-public-token']=thisApiState.apiPublicToken; } // If language is set if(apiLanguage!=null && apiLanguage!=false){ thisInputData['www-language']=apiLanguage; } // If JSONP is used, then assigning the JSONP function name that will be called if(thisApiState.JSONP==true){ if(typeof thisApiState.JSONP=='string'){ thisInputData['www-jsonp']=thisApiState.JSONP; } else { thisInputData['www-jsonp']='wwwJSONP'; } log.push('JSONP return method is set as '+thisInputData['www-jsonp']); } else if(thisApiState.JSONV==true){ if(typeof thisApiState.JSONV=='string'){ thisInputData['www-jsonv']=thisApiState.JSONV; } else { thisInputData['www-jsonv']='wwwJSONV'; } log.push('JSONV variable name is set as '+thisInputData['www-jsonv']); } // Clearing input data that.clearInput(false); // Log entry log.push('Starting to build request'); // Correct request requires command to be set if(thisInputData['www-command']==null){ return errorHandler(thisInputData,201,'API command is not set, this is required',thisApiState.errorCallback); } // If default value is set, then it is removed if(thisInputData['www-return-type']!=null && thisInputData['www-return-type']=='json'){ log.push('Since www-return-type is set to default value, it is removed from input data'); delete thisInputData['www-return-type']; } // If default value is set, then it is removed if(thisInputData['www-cache-timeout']!=null && thisInputData['www-cache-timeout']==0){ log.push('Since www-cache-timeout is set to default value, it is removed from input data'); delete thisInputData['www-cache-timeout']; } // If default value is set, then it is removed if(thisInputData['www-minify']!=null && thisInputData['www-minify']==0){ log.push('Since www-minify is set to default value, it is removed from input data'); delete thisInputData['www-minify']; } // If profile is used, then timestamp will also be sent with the request if(thisApiState.apiProfile){ // Timestamp is required in API requests since it will be used for request validation and replay attack protection if(thisInputData['www-timestamp']==null){ thisInputData['www-timestamp']=Math.floor(new Date().getTime()/1000); } } // If API profile and secret key are set, then wrapper assumes that non-public profile is used, thus hash and timestamp have to be included if(thisApiState.apiSecretKey){ // Log entry log.push('API secret key set, hash authentication will be used'); // If API hash validation is used if(thisApiState.apiHashValidation){ // Validation hash is generated based on current serialization option if(thisInputData['www-hash']==null){ // Validation requires a different hash var validationData=clone(thisInputData); // Calculating validation hash if(thisApiState.apiToken && thisInputData['www-command']!='www-create-session'){ thisInputData['www-hash']=validationHash(validationData,thisApiState.apiToken+thisApiState.apiSecretKey); } else { thisInputData['www-hash']=validationHash(validationData,thisApiState.apiSecretKey); } } // Log entry if(thisApiState.apiToken){ log.push('Validation hash created using JSON encoded input data, API token and secret key'); } else { log.push('Validation hash created using JSON encoded input data and secret key'); } } else { // Attaching secret key or token to the request if(thisInputData['www-command']=='www-create-session' && thisApiState.apiSecretKey){ thisInputData['www-secret-key']=thisApiState.apiSecretKey; log.push('Validation will be secret key based'); } else if(thisApiState.apiToken){ thisInputData['www-token']=thisApiState.apiToken; log.push('Validation will be session token based'); } } } else { // Token-only validation means that token will be sent to the server, but data itself will not be hashed. This works like a cookie. if(thisApiState.apiToken){ // Adding token to input if it is set thisInputData['www-token']=thisApiState.apiToken; // Log entry log.push('Validation will be session token based'); } else { // Log entry log.push('API secret key is not set, hash validation will not be used'); } } // MAKING A REQUEST // Command is made slightly differently depending on whether files are to be uploaded or not if(thisApiState.apiSubmitFormId==false){ // Default method var method='GET'; // Getting input variables var requestData=buildRequestData(thisInputData); // Request string var requestString=apiAddress+'?'+requestData; // Creating request handler var XMLHttp=new XMLHttpRequest(); // POST request is made if the URL is longer than 2048 bytes (2KB). // While servers can easily handle 8KB of data, servers are recommended to be vary if the GET request is longer than 2KB if(requestString.length>getLimit){ // Log entries log.push('More than '+getLimit+' bytes would be sent, POST request will be used'); method='POST'; // JSONP cannot be used to submit a form if(thisApiState.JSONP==true || thisApiState.JSONV==true){ return errorHandler(thisInputData,218,'JSONP/JSONV cannot be used for POST requests',thisApiState.errorCallback); } } // Separate functionality for JSONP, JSONV, synchronous and asynchronous requests if(thisApiState.JSONP==true){ // Hidden JSONP script that makes the request apiState.hiddenScriptCounter++; var hiddenScript=document.createElement('script'); var hiddenScriptName='WWW_API_Wrapper_Hidden_JSONP_'+apiState.hiddenScriptCounter; hiddenScript.type='text/javascript'; hiddenScript.id=hiddenScriptName; hiddenScript.name=hiddenScriptName; hiddenScript.src=requestString; // This cleans up after itself and removes both new script tags hiddenScript.onload=function(){ log.push('JSONP temporary script has been removed'); document.head.removeChild(hiddenScript); }; // This creates the script which makes the request and parses the result document.head.appendChild(hiddenScript); // Adding a log entry log.push('Making the JSONP request to URL: '+apiAddress); // Returning true, since the rest is out of the hands of this class return true; } else if(thisApiState.JSONV==true){ // Hidden JSONP script that makes the request apiState.hiddenScriptCounter++; var hiddenScript=document.createElement('script'); var hiddenScriptName='WWW_API_Wrapper_Hidden_JSONV_'+apiState.hiddenScriptCounter; hiddenScript.type='text/javascript'; hiddenScript.id=hiddenScriptName; hiddenScript.name=hiddenScriptName; hiddenScript.src=requestString; // This cleans up after itself and removes both new script tags hiddenScript.onload=function(){ eval('parseResult('+thisInputData['www-jsonv']+',thisInputData,thisApiState);'); eval('delete '+thisInputData['www-jsonv']+';'); document.head.removeChild(hiddenScript); }; // This creates the script which makes the request and parses the result document.head.appendChild(hiddenScript); // Adding a log entry log.push('Making the JSONV request to URL: '+apiAddress); // Returning true, since the rest is out of the hands of this class return true; } else if(thisApiState.asynchronous){ // Log entry log.push('Making '+method+' request to URL: '+apiAddress); // AJAX states XMLHttp.onreadystatechange=function(){ if(XMLHttp.readyState===4){ // Result based on status if(XMLHttp.status===200 || XMLHttp.status===304){ log.push('Result of the request: '+XMLHttp.responseText); return parseResult(XMLHttp.responseText,thisInputData,thisApiState); } else { if(method=='POST'){ return errorHandler(thisInputData,205,'POST request failed: '+XMLHttp.statusText,thisApiState.errorCallback); } else { return errorHandler(thisInputData,204,'GET request failed: '+XMLHttp.statusText,thisApiState.errorCallback); } } } }; // Sending the request if(method=='POST'){ XMLHttp.open(method,apiAddress,true); // Request header and method for POST XMLHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); // Making the request XMLHttp.send(requestData); } else { XMLHttp.open(method,requestString,true); // Making the request XMLHttp.send(null); } } else { // Log entry log.push('Making '+method+' request to URL: '+apiAddress); // Sending the request if(method=='POST'){ XMLHttp.open(method,apiAddress,false); // Request header and method for POST XMLHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); // Making the request XMLHttp.send(requestData); } else { XMLHttp.open(method,requestString,false); // Making the request XMLHttp.send(null); } // Result based on status if(XMLHttp.status===200 || XMLHttp.status===304){ log.push('Result of the request: '+XMLHttp.responseText); return parseResult(XMLHttp.responseText,thisInputData,thisApiState); } else { if(method=='POST'){ return errorHandler(thisInputData,205,'POST request failed: '+XMLHttp.statusText,thisApiState.errorCallback); } else { return errorHandler(thisInputData,204,'GET request failed: '+XMLHttp.statusText,thisApiState.errorCallback); } } } } else { // JSONP cannot be used to submit a form if(thisApiState.JSONP==true){ return errorHandler(thisInputData,217,'JSONP cannot be used to submit a form',thisApiState.errorCallback); } // Getting the hidden form var apiSubmitForm=document.getElementById(thisApiState.apiSubmitFormId); if(apiSubmitForm==null){ return errorHandler(thisInputData,215,'Form not found: '+thisApiState.apiSubmitFormId,thisApiState.errorCallback); } // Hidden iFrame apiState.hiddenWindowCounter++; var hiddenWindow=document.createElement('iframe'); var hiddenWindowName='WWW_API_Wrapper_Hidden_iFrame_'+apiState.hiddenWindowCounter; hiddenWindow.id=hiddenWindowName; hiddenWindow.name=hiddenWindowName; hiddenWindow.style.display='none'; apiSubmitForm.appendChild(hiddenWindow); //Old parameters var old_formAction=apiSubmitForm.action; var old_formTarget=apiSubmitForm.target; var old_formMethod=apiSubmitForm.method; var old_formEnctype=apiSubmitForm.enctype; // Preparing form submission apiSubmitForm.method='POST'; apiSubmitForm.action=apiAddress; apiSubmitForm.setAttribute('enctype','multipart/form-data'); // Done differently because of IE8 apiSubmitForm.setAttribute('encoding','multipart/form-data'); // IE6 wants this apiSubmitForm.target=hiddenWindowName; // Input data var counter=0; var hiddenFields=new Object(); for(var node in thisInputData){ counter++; hiddenFields[counter]=document.createElement('input'); hiddenFields[counter].id=('www_hidden_form_data_'+counter); hiddenFields[counter].name=node; hiddenFields[counter].value=thisInputData[node]; hiddenFields[counter].type='hidden'; apiSubmitForm.appendChild(hiddenFields[counter]); // Log entry log.push('Attaching variable to form request: '+node+'='+thisInputData[node]); } // This is on-load function for iFrame var onLoad=function(){ // Resetting the form data if(old_formMethod!=null){ apiSubmitForm.method=old_formMethod; } else { apiSubmitForm.method=''; } if(old_formAction!=null){ apiSubmitForm.action=old_formAction; } else { apiSubmitForm.action=''; } if(old_formEnctype!=null){ apiSubmitForm.setAttribute('enctype',old_formEnctype); apiSubmitForm.setAttribute('encoding',old_formEnctype); } else { apiSubmitForm.setAttribute('enctype',''); apiSubmitForm.setAttribute('encoding',''); } if(old_formTarget!=null){ apiSubmitForm.target=old_formTarget; } else { apiSubmitForm.target=''; } // Removing created elements for(var i=1;i<=counter;i++){ if(hiddenFields[i]!=null){ hiddenFields[i].parentNode.removeChild(hiddenFields[i]); } } // Parsing the result var resultData=hiddenWindow.contentWindow.document.body.innerHTML; // Log entry log.push('Result of the request: '+resultData); resultData=parseResult(resultData,thisInputData,thisApiState); // Removing hidden iFrame setTimeout(function(){apiSubmitForm.removeChild(hiddenWindow);},100); return resultData; } // Hidden iFrame onload function // Two versions, one IE compatible, another one not if(hiddenWindow.attachEvent==null){ hiddenWindow.onload=onLoad; } else { hiddenWindow.attachEvent('onload',onLoad); } // Log entry log.push('Making POST request to URL: '+apiAddress); // Submitting form apiSubmitForm.submit(); } } /* * JavaScript API Wrapper handles asynchronous requests and all of request callbacks, * which allows to make multiple API requests at the same time or in sequence. This * method validates the response data, if validation is requested and executes set * callbacks with the results. 'resultData' is the response from the API call, * 'thisInputData' is the original input sent to the request and 'thisApiState' is * the API Wrapper state at the time of the request. * * @param string $resultData result string from response * @param object $thisInputData data that was sent as input * @param object $thisApiState api state for this request * @return object/string response data from request depending on settings */ var parseResult=function(resultData,thisInputData,thisApiState){ // Returning the result directly if the result is not intended to be unserialized if(!thisApiState.unserialize){ // Log entry for returning data log.push('Returning result without unserializing'); // Data is simply returned if serialization was not requested return resultData; } else { // PARSING REQUEST RESULT // The result is only parsed if it is not already an object if(typeof(resultData)!=='object'){ // If unserialize command was set and the data type was JSON or serialized array, then it is returned as serialized if(thisInputData['www-return-type']==null || thisInputData['www-return-type']=='json'){ // JSON support is required if(typeof(JSON)!=='undefined'){ resultData=JSON.parse(resultData); } else if(typeof(jQuery)!=='undefined'){ // Attempting to unserialize with jQuery resultData=jQuery.parseJSON(resultData); } else { // Could not unserialize return errorHandler(thisInputData,207,'Cannot unserialize returned data',thisApiState.errorCallback); } log.push('Returning JSON object'); } else if(thisApiState.unserialize){ // Every other unserialization attempt fails return errorHandler(thisInputData,207,'Cannot unserialize returned data',thisApiState.errorCallback); } } // If error was detected if(resultData['www-response-code']!=null && resultData['www-response-code']<400){ if(resultData['www-message']!=null){ return errorHandler(thisInputData,resultData['www-response-code'],resultData['www-message'],thisApiState.errorCallback); } else { return errorHandler(thisInputData,resultData['www-response-code'],'',thisApiState.errorCallback); } } // RESULT VALIDATION // Result validation only applies to non-public profiles if(thisApiState.apiProfile && (thisApiState.returnTimestamp || thisApiState.returnHash)){ // If it was requested that validation timestamp is returned if(thisApiState.returnTimestamp){ if(resultData['www-timestamp']!=null){ // Making sure that the returned result is within accepted time limit if(((Math.floor(new Date().getTime()/1000))-thisApiState.timestampDuration)>resultData['www-timestamp']){ return errorHandler(thisInputData,209,'Validation timestamp is too old',thisApiState.errorCallback); } } else { return errorHandler(thisInputData,208,'Validation data missing: Timestamp was not returned',thisApiState.errorCallback); } } // If it was requested that validation timestamp is returned if(thisApiState.apiStateKey){ if(resultData['www-state']==null || resultData['www-state']!=thisApiState.apiStateKey){ return errorHandler(thisInputData,210,'Validation state keys do not match',thisApiState.errorCallback); } } // If it was requested that validation hash is returned if(thisApiState.returnHash){ // Hash and timestamp have to be defined in response if(resultData['www-hash']!=null){ // Assigning returned array to hash validation array var validationData=clone(resultData); // Hash itself is removed from validation delete validationData['www-hash']; // Validation depends on whether session creation or destruction commands were called if(thisInputData['www-command']=='www-create-session'){ var resultHash=validationHash(validationData,thisApiState.apiSecretKey); } else { var resultHash=validationHash(validationData,thisApiState.apiToken+thisApiState.apiSecretKey); } // If sent hash is the same as calculated hash if(resultHash==resultData['www-hash']){ log.push('Hash validation successful'); } else { return errorHandler(thisInputData,210,'Hash validation failed',thisApiState.errorCallback); } } else { return errorHandler(thisInputData,208,'Validation data missing: Hash was not returned',thisApiState.errorCallback); } } } // Resetting the error variables responseCode=false; errorMessage=false; // If this command was to create a token if(thisInputData['www-command']=='www-create-session' && resultData['www-token']!=null){ apiState.apiToken=resultData['www-token']; log.push('Session token was found in reply, API session token set to: '+resultData['www-token']); } else if(thisInputData['www-command']=='www-destroy-session'){ apiState.apiToken=false; log.push('Session has been destroyed'); } // If callback function is set if(thisApiState.trueCallback && resultData['www-response-code']!=null && resultData['www-response-code']>=500){ // If the callback is a function name and not a function itself if(typeof(thisApiState.trueCallback)!=='function'){ // Calling user function var thisCallback=this.window[thisApiState.trueCallback]; if(typeof(thisCallback)==='function'){ log.push('Sending data to callback: '+thisApiState.trueCallback+'()'); // Callback execution if(thisApiState.trueCallbackParameters!=false){ return thisCallback.call(this,resultData,thisApiState.trueCallbackParameters); } else { return thisCallback.call(this,resultData); } } else { return errorHandler(thisInputData,216,'Callback method not found: '+thisApiState.trueCallback+'()',thisApiState.errorCallback); } } else { // Returning data from callback if(thisApiState.trueCallbackParameters!=false){ return thisApiState.trueCallback(resultData,thisApiState.trueCallbackParameters); } else { return thisApiState.trueCallback(resultData); } } } else if(thisApiState.falseCallback && resultData['www-response-code']!=null && resultData['www-response-code']<500){ // If the callback is a function name and not a function itself if(typeof(thisApiState.falseCallback)!=='function'){ // Calling user function var thisCallback=this.window[thisApiState.falseCallback]; if(typeof(thisCallback)==='function'){ log.push('Sending failure data to callback: '+thisApiState.falseCallback+'()'); // Callback execution if(thisApiState.falseCallbackParameters!=false){ return thisCallback.call(this,resultData,thisApiState.falseCallbackParameters); } else { return thisCallback.call(this,resultData); } } else { return errorHandler(thisInputData,216,'Callback method not found: '+thisApiState.falseCallback+'()',thisApiState.errorCallback); } } else { // Returning data from callback if(thisApiState.falseCallbackParameters!=false){ return thisApiState.falseCallback(resultData,thisApiState.falseCallbackParameters); } else { return thisApiState.falseCallback(resultData); } } } else { // Returning request result return resultData; } } } // REQUIRED FUNCTIONS /* * This method is simply meant for returning a result if there was an error in the sent request * * @param object $thisInputData input data sent to the request * @param string $thisResponseCode response code value * @param string $thisErrorMessage returned error message text * @param string/function $thisErrorCallback anonymous function or function name to be called * @return boolean/mixed depending on whether callback function is called or not */ var errorHandler=function(thisInputData,thisResponseCode,thisErrorMessage,thisErrorCallback){ // Assigning error details to object state responseCode=thisResponseCode; errorMessage=thisErrorMessage; log.push(errorMessage); // If failure callback has been defined if(thisErrorCallback){ // If the callback is a function name and not a function itself if(typeof(thisErrorCallback)!=='function'){ // Looking for function of that name var thisCallback=this.window[thisErrorCallback]; if(typeof(thisCallback)==='function'){ log.push('Sending failure data to callback: '+thisErrorCallback+'()'); // Callback execution return thisCallback.call(this,{'www-input':thisInputData,'www-response-code':responseCode,'www-message':errorMessage}); } else { responseCode=216; errorMessage='Callback method not found: '+thisErrorCallback+'()'; log.push(errorMessage); return false; } } else { // Returning data from callback thisErrorCallback(thisInputData); } } else { return false; } } /* * This helper method is used to clone one JavaScript object to another 'object' is * the JavaScript object to be converted. * * @param object $object object to be cloned * @return object */ var clone=function(object){ // Cloning based on type if(object==null || typeof(object)!=='object'){ return object; } var tmp=object.constructor(); for(var key in object){ tmp[key]=clone(object[key]); } return tmp; } /* * This method is used to build an input data validation hash string for authenticating * API requests. The entire input array of 'validationData' is serialized and hashed * with SHA-1 and a salt string set in 'postFix'. This is used for all API requests * where input has to be validated. * * @param object $validationData data to be used for hash generation * @param string $postFix will be appended prior to hash being generated * @return string */ var validationHash=function(validationData,postFix){ // Sorting and encoding the output data validationData=ksortArray(validationData); // Returning validation hash return sha1(buildRequestData(validationData)+postFix); } /* * This is a helper function used by validationHash() function to serialize an array * recursively. It applies ksort() to main method as well as to all sub-arrays. 'data' * is the array or object to be sorted. * * @param object/mixed $data variable to be sorted * @return mixed */ var ksortArray=function(data){ // Method is based on the current data type if(typeof(data)==='array' || typeof(data)==='object'){ // Sorting the current array data=ksort(data); // Sorting every sub-array, if it is one for(var i in data){ data[i]=ksortArray(data[i]); } } return data; } /* * This is a method that is similar to PHP http_build_query() function. It builds a * GET request string of input variables set in 'data'. * * @param object $data object to build request data string from * @return string */ var buildRequestData=function(data){ // variables are stored in array var variables=new Array(); for(var i in data){ // Using the helper function var query=subRequestData(i,data[i]); if(query!=''){ variables.push(query); } } return variables.join('&'); } /* * This is a helper function for buildRequestData() method, it converts between * different ways data is represented in a GET request string. * * @param string $key key value * @param mixed $value variable value * @return string */ var subRequestData=function(key,value){ // variables are stored in array var variables=new Array(); if(value!=null){ // Converting true/false to numeric if(value===true){ value='1'; } else if(value===false){ value='0'; } // Object will be parsed through subRequestData recursively if(typeof(value)==='object'){ for(var i in value){ if(value[i]!=null){ variables.push(subRequestData(key+'['+i+']',value[i])); } } return variables.join('&'); } else { return encodeValue(key)+'='+encodeValue(value); } } else { return ''; } } /* * This helper method converts certain characters into their suitable form that would * be accepted and same as in PHP. This is a modified version of encodeURIComponent() * function. 'data' is the string to be converted. * * @param string $data string to encode * @return string */ var encodeValue=function(data){ // Series of filters applied to the value data=encodeURIComponent(data); data=data.replace('\'','%27'); data=data.replace('!','%21'); data=data.replace('(','%28'); data=data.replace(')','%29'); data=data.replace('*','%2A'); data=data.replace('%20','+'); return data; } /* * This is a JavaScript method that works similarly to PHP's ksort() function and * applies to JavaScript objects. 'object' is the object to be sorted. * * @param object $object object to sort by keys * @return object */ var ksort=function(object){ // Result will be gathered here var keys=new Array(); var sorted=new Object(); // Sorted keys gathered in sorting array for(var i in object){ keys.push(i); } // Sorting the keys keys.sort(); // Building a new object based on sorted keys for(var i in keys){ sorted[keys[i]]=object[keys[i]]; } return sorted; } /* * This is a JavaScript equivalent of PHP's sha1() function. It calculates a hash * string from 'msg' string. * * @author http://www.webtoolkit.info/javascript-sha1.html * @param string $msg string to hash * @return string */ var sha1=function(msg){ function rotate_left(n,s) { var t4 = ( n<<s ) | (n>>>(32-s)); return t4; } function lsb_hex(val) { var str=""; var i; var vh; var vl; for( i=0; i<=6; i+=2 ) { vh = (val>>>(i*4+4))&0x0f; vl = (val>>>(i*4))&0x0f; str += vh.toString(16) + vl.toString(16); } return str; } function cvt_hex(val) { var str=""; var i; var v; for( i=7; i>=0; i-- ) { v = (val>>>(i*4))&0x0f; str += v.toString(16); } return str; } function Utf8Encode(string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; } var blockstart; var i, j; var W = new Array(80); var H0 = 0x67452301; var H1 = 0xEFCDAB89; var H2 = 0x98BADCFE; var H3 = 0x10325476; var H4 = 0xC3D2E1F0; var A, B, C, D, E; var temp; msg = Utf8Encode(msg); var msg_len = msg.length; var word_array = new Array(); for( i=0; i<msg_len-3; i+=4 ) { j = msg.charCodeAt(i)<<24 | msg.charCodeAt(i+1)<<16 | msg.charCodeAt(i+2)<<8 | msg.charCodeAt(i+3); word_array.push( j ); } switch( msg_len % 4 ) { case 0: i = 0x080000000; break; case 1: i = msg.charCodeAt(msg_len-1)<<24 | 0x0800000; break; case 2: i = msg.charCodeAt(msg_len-2)<<24 | msg.charCodeAt(msg_len-1)<<16 | 0x08000; break; case 3: i = msg.charCodeAt(msg_len-3)<<24 | msg.charCodeAt(msg_len-2)<<16 | msg.charCodeAt(msg_len-1)<<8 | 0x80; break; } word_array.push( i ); while( (word_array.length % 16) != 14 ) word_array.push( 0 ); word_array.push(msg_len>>>29); word_array.push((msg_len<<3)&0x0ffffffff); for ( blockstart=0; blockstart<word_array.length; blockstart+=16 ){ for( i=0; i<16; i++ ) W[i] = word_array[blockstart+i]; for( i=16; i<=79; i++ ) W[i] = rotate_left(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1); A = H0; B = H1; C = H2; D = H3; E = H4; for( i= 0; i<=19; i++ ) { temp = (rotate_left(A,5) + ((B&C) | (~B&D)) + E + W[i] + 0x5A827999) & 0x0ffffffff; E = D; D = C; C = rotate_left(B,30); B = A; A = temp; } for( i=20; i<=39; i++ ) { temp = (rotate_left(A,5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff; E = D; D = C; C = rotate_left(B,30); B = A; A = temp; } for( i=40; i<=59; i++ ) { temp = (rotate_left(A,5) + ((B&C) | (B&D) | (C&D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff; E = D; D = C; C = rotate_left(B,30); B = A; A = temp; } for( i=60; i<=79; i++ ) { temp = (rotate_left(A,5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff; E = D; D = C; C = rotate_left(B,30); B = A; A = temp; } H0 = (H0 + A) & 0x0ffffffff; H1 = (H1 + B) & 0x0ffffffff; H2 = (H2 + C) & 0x0ffffffff; H3 = (H3 + D) & 0x0ffffffff; H4 = (H4 + E) & 0x0ffffffff; } var temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4); return temp.toLowerCase(); } }