UNPKG

kaltura

Version:
669 lines (602 loc) 19.5 kB
/*jshint quotmark:true*/ // =================================================================================================== // _ __ _ _ // | |/ /__ _| | |_ _ _ _ _ __ _ // | ' </ _` | | _| || | '_/ _` | // |_|\_\__,_|_|\__|\_,_|_| \__,_| // // This file is part of the Kaltura Collaborative Media Suite which allows users // to do with audio, video, and animation what Wiki platfroms allow them to do with // text. // // Copyright (C) 2006-2011 Kaltura Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful,re // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. // // @ignore // =================================================================================================== var crypto = require('crypto'); var http = require('http'); var path = require('path'); var url = require('url'); var fs = require('fs'); /** * Generates a URL-encoded query string from the associative (or indexed) array provided. * Ported from PHP. * @param formdata May be an array or object containing properties. * @param numeric_prefix If numeric indices are used in the base array and this parameter is provided, it will be prepended to the numeric index for elements in the base array only. * @param arg_separator arg_separator.output is used to separate arguments, unless this parameter is specified, and is then used. * @return Returns a URL-encoded string. */ function http_build_query (formdata, numeric_prefix, arg_separator) { var value, tmp = []; var _http_build_query_helper = function (key, val, arg_separator) { var tmp = []; if (val === true) { val = "1"; } else if (val === false) { val = "0"; } if (val !== null && typeof(val) === "object") { for (var k in val) { if (val[k] !== null) { tmp.push(_http_build_query_helper(key + "[" + k + "]", val[k], arg_separator)); } } return tmp.join(arg_separator); } else if (typeof(val) !== "function") { return key + "=" + encodeURIComponent(val); } else { //throw new Error('There was an error processing for http_build_query().'); return ""; } }; if (!arg_separator) { arg_separator = "&"; } for (var key in formdata) { value = formdata[key]; if (numeric_prefix && !isNaN(key)) { key = String(numeric_prefix) + key; } tmp.push(_http_build_query_helper(key, value, arg_separator)); } return tmp.join(arg_separator); } /** * Getting the name of the constructor if the constructor hasn't been modified, * which if it has modified (and is therfor invalid to use), it falls back to using Object.prototype.toString * to get the class though it won't return the name of the constructor function that created it then. * If you absolutely need the constructor's name, pass true as the second argument, * and it will reset the constructor if it has been modified, to get the real constructor. * @param obj The object to get the constructor of. * @param forceConstructor preform a deep lookup for the real constructor. * @return The constructor of the given class. */ function getClass(obj, forceConstructor) { if ( typeof obj == "undefined" ) { return "undefined"; } if ( obj === null ) { return "null"; } if ( forceConstructor === true && obj.hasOwnProperty("constructor") ) { delete obj.constructor; } // reset constructor if ( forceConstructor !== false && !obj.hasOwnProperty("constructor") ) { return obj.constructor.name; } return Object.prototype.toString.call(obj) .match(/^\[object\s(.*)\]$/)[1]; } /** * validate a paramter's value is not null, if not null, add the parameter to the collection. * @param params the collection of parameters to send in a service action request. * @param paramName the new parameter name to add. * @param paramValue the new parameter value to add. */ function addIfNotNull(obj, params, paramName, paramValue) { if (paramValue !== null) { if(paramValue instanceof KalturaObjectBase) { params[paramName] = toParams(paramValue); } else { params[paramName] = paramValue; } } } /** * Serializes new object's parameters. * @param obj The object who's members to serialize. * @return a serialized object. */ var toParams = module.exports.toParams = function(obj) { var params = {}; params["objectType"] = getClass(obj); for(var prop in obj) { var val = obj[prop]; addIfNotNull(obj, params, prop, val); } return params; }; /** * Sorts an array by key, maintaining key to data correlations. This is useful mainly for associative arrays. * @param arr The array to sort. * @return The sorted array. */ function ksort(arr) { var sArr = []; var tArr = []; var n = 0; for (var i in arr) { tArr[n++] = i+"|"+arr[i]; } tArr = tArr.sort(); for (var j=0; j<tArr.length; j++) { var x = tArr[j].split("|"); sArr[x[0]] = x[1]; } return sArr; } /** * Construct new Kaltura service action call, if params array contain sub-arrays (for objects), it will be flattened. * @param string service The Kaltura service to use. * @param string action The service action to execute. * @param array params The parameters to pass to the service action. * @param array files Files to upload or manipulate. */ function KalturaServiceActionCall(service, action, params, files){ if(!params) { params = {}; } if(!files) { files = {}; } this.service = service; this.action = action; this.params = this.parseParams(params); this.files = files; } /** * Parse params array and sub arrays (clone objects) * @param array params the object to clone. * @return the newly cloned object from the input object. */ KalturaServiceActionCall.prototype.parseParams = function(params){ var newParams = {}; for(var key in params) { var val = params[key]; if (typeof(val) == "object") { newParams[key] = this.parseParams(val); } else { newParams[key] = val; } } return newParams; }; /** * Create params object for a multirequest call. * @param int multiRequestIndex the index of the call inside the multirequest. */ KalturaServiceActionCall.prototype.getParamsForMultiRequest = function(multiRequestIndex){ var multiRequestParams = {}; multiRequestParams[multiRequestIndex + ":service"] = this.service; multiRequestParams[multiRequestIndex + ":action"] = this.action; for(var key in this.params) { var val = this.params[key]; multiRequestParams[multiRequestIndex + ":" + key] = val; } return multiRequestParams; }; /** * Create files object for a multirequest call. * @param int multiRequestIndex the index of the call inside the multirequest. */ KalturaServiceActionCall.prototype.getFilesForMultiRequest = function(multiRequestIndex){ var multiRequestFiles = {}; for(var key in this.files) { var val = this.files[key]; multiRequestFiles[multiRequestIndex + ":" + key] = val; } return multiRequestFiles; }; /** * Implement to get Kaltura Client logs * */ var IKalturaLogger = module.exports.IKalturaLogger = function() {}; IKalturaLogger.prototype.log = function(msg){ if (console && console.log){ console.log(msg); } }; /** * Kaltura client constructor * */ var KalturaClientBase = module.exports.KalturaClientBase = function() {}; /** * Kaltura client init * @param KalturaConfiguration config */ KalturaClientBase.prototype.init = function(config){ this.config = config; this.logEnabled = false; this.debugEnabled = false; this.useMultiRequest = false; this.callsQueue = []; this.customHeaders = []; var logger = config.getLogger(); if (logger) { this.setLogger(logger); } }; KalturaClientBase.KALTURA_SERVICE_FORMAT_JSON = 1; KalturaClientBase.KALTURA_SERVICE_FORMAT_XML = 2; KalturaClientBase.KALTURA_SERVICE_FORMAT_PHP = 3; KalturaClientBase.KALTURA_SERVICE_FORMAT_JSONP = 9; KalturaClientBase.prototype.clientConfiguration = {}; KalturaClientBase.prototype.requestConfiguration = {}; /** * Set logger to get kaltura client debug logs. * @param IKalturaLogger log */ KalturaClientBase.prototype.setLogger = function(logger){ this.logger = logger; this.logEnabled = true; if(typeof logger.debug === 'function'){ this.debugEnabled = true; } }; /** * prepare a call for service action (queue the call and wait for doQueue). */ KalturaClientBase.prototype.queueServiceActionCall = function (service, action, params, files){ // in start session partner id is optional (default -1). if partner id was not set, use the one in the config for(var paramName in this.requestConfiguration){ this.addParam(params, paramName, this.requestConfiguration[paramName]); } var call = new KalturaServiceActionCall(service, action, params, files); this.callsQueue.push(call); }; /** * executes the actions queue. */ KalturaClientBase.prototype.doQueue = function(callback){ if (this.callsQueue.length === 0) { return null; } var params = {}; var files = {}; this.log("Service url: [" + this.config.serviceUrl + "]"); // append the basic params this.addParam(params, "format", this.config.format); for(var paramName in this.clientConfiguration){ this.addParam(params, paramName, this.clientConfiguration[paramName]); } var url = this.config.serviceUrl + this.config.serviceBase; var call = null; if (this.useMultiRequest){ url += "/multirequest"; var i = 1; for(var v = 0; v < this.callsQueue.length; v++){ call = this.callsQueue[v]; var callParams = call.getParamsForMultiRequest(i); for(var sv1 in callParams) { params[sv1] = callParams[sv1]; } var callFiles = call.getFilesForMultiRequest(i); for(var sv2 in callFiles) { files[sv2] = call.files[sv2]; } i++; } } else { call = this.callsQueue[0]; url += "/" + call.service + "/action/" + call.action; for(var sv3 in call.params) { params[sv3] = call.params[sv3]; } for(var sv4 in call.files) { files[sv4] = call.files[sv4]; } } // reset this.callsQueue = []; this.useMultiRequest = false; var signature = this.signature(params); this.addParam(params, "kalsig", signature); this.doHttpRequest(callback, url, params, files); this.resetRequest(); return true; }; /** * Clear all volatile configuration parameters */ KalturaClientBase.prototype.resetRequest = function(){ throw new Error('KalturaClientBase.resetRequest should be overriden by KalturaClient.'); }; /** * Sign array of parameters for requests validation (CRC). * @param array params service action call parameters that will be sent on the request. * @return string a hashed signed signature that can identify the sent request parameters. */ KalturaClientBase.prototype.signature = function(params){ params = ksort(params); var str = ""; for(var v in params) { var k = params[v]; str += k + v; } var md5sum = crypto.createHash("md5"); md5sum.update(str); return md5sum.digest("hex"); }; KalturaClientBase.requestIndex = 1; KalturaClientBase.prototype.encodeFile = function(boundary, type, name, filename) { var returnPart = '--' + boundary + '\r\n'; returnPart += 'Content-Disposition: form-data name="' + name + '" filename="' + filename + '"\r\n'; returnPart += 'Content-Type: ' + type + '\r\n\r\n'; return returnPart; }; KalturaClientBase.prototype.sendRequestHelper = function (options, body, requestIndex, onCompleteCallback, timeout) { var This = this; var request = http.request(options, function(response) { response.setEncoding('utf8'); var data = ''; response.on('data', function(chunk) { data += chunk; }); response.on('end', function() { var headers = []; for ( var header in response.headers) { headers.push(header + ': ' + response.headers[header]); } This.debug('Headers [' + requestIndex + ']: \n\t' + headers.join('\n\t')); This.debug('Response [' + requestIndex + ']: ' + data); if (This.config.format != KalturaClientBase.KALTURA_SERVICE_FORMAT_JSON){ onCompleteCallback(data); }else { var obj = JSON.parse(data); if (obj && This.isError(obj)) { if (!onCompleteCallback) { throw obj.code + ": " + obj.message; } onCompleteCallback(null, obj); } else { onCompleteCallback(obj); } } }); }); request.on('error', function(err) { console.log(err); if(!onCompleteCallback){ throw err; } onCompleteCallback(null, err); }); if (timeout) { request.setTimeout(timeout); } request.write(body); request.end(); } KalturaClientBase.prototype.isError = function(object) { if (object){ if( object.hasOwnProperty("objectType") && object.objectType == 'KalturaAPIException' ) { return true; } else return false; } } /** * send the http request. * @param string url the url to call. * @param parameters params the parameters to pass. * @return array the results and errors inside an array. */ KalturaClientBase.prototype.doHttpRequest = function (callCompletedCallback, requestUrl, params, files) { var requestIndex = KalturaClientBase.requestIndex++; var data = http_build_query(params); var debugUrl = requestUrl + '?' + data; var urlInfo = url.parse(debugUrl); var This = this; this.log('Request [' + requestIndex + ']: ' + debugUrl); var options = { host : urlInfo.host, path : urlInfo.path, method : 'POST', headers : {} }; Object.keys(this.customHeaders).forEach(function(key) { var currHeader = This.customHeaders[key]; options.headers[key] = currHeader.value; if (currHeader.volatile) This.removeCustomHeader(key); }); if(Object.keys(files).length > 0){ var crlf = '\r\n'; var boundary = '---------------------------' + Math.random(); var delimiter = crlf + '--' + boundary; var postData = []; for ( var key in files) { var filePath = files[key]; var fileName = path.basename(filePath); var data = fs.readFileSync(filePath); var headers = [ 'Content-Disposition: form-data; name="' + key + '"; filename="' + fileName + '"' + crlf, 'Content-Type: application/octet-stream' + crlf ]; postData.push(new Buffer(delimiter + crlf + headers.join('') + crlf)); postData.push(new Buffer(data)); } postData.push(new Buffer(delimiter + '--')); var multipartBody = Buffer.concat(postData); options.headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary; options.headers['Content-Length'] = multipartBody.length; this.sendRequestHelper(options, multipartBody, requestIndex, callCompletedCallback, this.config.timeout); } else { options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; options.headers['Content-Length'] = Buffer.byteLength(data); this.sendRequestHelper(options, data, requestIndex, callCompletedCallback); } }; /** * getter for the referenced configuration object. * @return KalturaConfiguration */ KalturaClientBase.prototype.getConfig = function() { return this.config; }; /** * @param KalturaConfiguration config setter for the referenced configuration object. */ KalturaClientBase.prototype.setConfig = function(config){ this.config = config; var logger = config.getLogger(); if (logger) { this.setLogger(logger); } }; /** * Add parameter to array of parameters that is passed by reference * @param array params array of parameters to pass to a call. * @param string paramName the name of the new parameter to add. * @param string paramValue the value of the new parameter to add. */ KalturaClientBase.prototype.addParam = function(params, paramName, paramValue){ if (paramValue === null) { return; } // native if(typeof(paramValue) != "object") { params[paramName] = paramValue; return; } var subParamValue, subParamName = null; // object if(isNaN(paramValue.length)){ if(!paramValue.objectType){ paramValue = toParams(paramValue); } for(subParamName in paramValue) { subParamValue = paramValue[subParamName]; this.addParam(params, paramName + ":" + subParamName, subParamValue); } return; } // array if(paramValue.length){ for(subParamName in paramValue) { subParamValue = paramValue[subParamName]; this.addParam(params, paramName + ":" + subParamName, subParamValue); } } else{ this.addParam(params, paramName + ":-", ""); } }; /** * set to true to indicate a multirequest is being defined. */ KalturaClientBase.prototype.startMultiRequest = function(){ this.useMultiRequest = true; }; /** * execute a multirequest. */ KalturaClientBase.prototype.doMultiRequest = function(callback){ return this.doQueue(callback); }; /** * indicate if current mode is constructing a multirequest or single requests. */ KalturaClientBase.prototype.isMultiRequest = function(){ return this.useMultiRequest; }; /** * @param string msg client logging utility. */ KalturaClientBase.prototype.log = function(msg){ if (this.logEnabled) { this.logger.log(msg); } }; /** * @param string msg client logging utility. */ KalturaClientBase.prototype.debug = function(msg){ if (this.debugEnabled) { this.logger.debug(msg); } }; /** * @param string msg client logging utility. */ KalturaClientBase.prototype.error = function(msg){ if (this.logEnabled) { this.logger.error(msg); } }; /** * @param key - name of the header you want to add to the client requests. * @param value - value of the header you want to add to the client requests * @param volatile - if true the header will be sent for the next request only, if false will be added to all requests by the client. */ KalturaClientBase.prototype.addCustomHeader = function(key, value, volatile) { this.customHeaders[key] = { 'value': value, 'volatile': volatile }; } /** * @param key - name of the custom header to be removed. */ KalturaClientBase.prototype.removeCustomHeader = function(key) { delete this.customHeaders[key]; } /** * Abstract base class for all client objects */ var KalturaObjectBase = module.exports.KalturaObjectBase = function() {}; /** * Abstract base class for all client services * Initialize the service keeping reference to the KalturaClient * @param KalturaClientm client */ var KalturaServiceBase = module.exports.KalturaServiceBase = function () {}; KalturaServiceBase.prototype.init = function(client){ this.client = client; }; /** * @param KalturaClient */ KalturaServiceBase.prototype.client = null; /** * Constructs new Kaltura configuration object */ var KalturaConfiguration = module.exports.KalturaConfiguration = function (){ this.serviceUrl = "http://www.kaltura.com"; this.serviceBase = "/api_v3/service"; this.format = KalturaClientBase.KALTURA_SERVICE_FORMAT_JSON; this.timeout = 90000; this.logger = null; }; /** * Set logger to get kaltura client debug logs. * @param IKalturaLogger log */ KalturaConfiguration.prototype.setLogger = function(logger){ this.logger = logger; }; /** * Gets the logger (Internal client use) * @return IKalturaLogger */ KalturaConfiguration.prototype.getLogger = function(){ return this.logger; };