Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
1056 lines
30 KiB
JavaScript
Executable file
1056 lines
30 KiB
JavaScript
Executable file
/* Copyright (c) 2012 Opera Software ASA
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* CHANGELOG
|
|
* - This file is changed by Rex Lee(http://github.com/rexboy7) to meet
|
|
* requriments of DLNA app in Gaia.
|
|
* - maintain a configDocument property,
|
|
* stop suppressing exceptions,
|
|
* add mozSystem: true to XMLHttpRequest,
|
|
* and missellaneous.
|
|
*/
|
|
/* jshint ignore:start */
|
|
!(function( global, undefined ) {
|
|
|
|
// *** rsvp.js (https://github.com/tildeio/rsvp.js)
|
|
|
|
(function(exports) { "use strict";
|
|
|
|
var MutationObserver = global.MutationObserver || global.WebKitMutationObserver;
|
|
var async;
|
|
|
|
if (typeof process !== 'undefined') {
|
|
async = function(callback, binding) {
|
|
process.nextTick(function() {
|
|
callback.call(binding);
|
|
});
|
|
};
|
|
} else if (MutationObserver) {
|
|
var queue = [];
|
|
|
|
var observer = new MutationObserver(function() {
|
|
var toProcess = queue.slice();
|
|
queue = [];
|
|
|
|
toProcess.forEach(function(tuple) {
|
|
var callback = tuple[0], binding = tuple[1];
|
|
callback.call(binding);
|
|
});
|
|
});
|
|
|
|
var element = global.document.createElement('div');
|
|
observer.observe(element, { attributes: true });
|
|
|
|
async = function(callback, binding) {
|
|
queue.push([callback, binding]);
|
|
element.setAttribute('drainQueue', 'drainQueue');
|
|
};
|
|
} else {
|
|
async = function(callback, binding) {
|
|
global.setTimeout(function() {
|
|
callback.call(binding);
|
|
}, 1);
|
|
};
|
|
}
|
|
|
|
exports.async = async;
|
|
|
|
var Event = exports.Event = function(type, options) {
|
|
this.type = type;
|
|
|
|
for (var option in options) {
|
|
if (!options.hasOwnProperty(option)) { continue; }
|
|
|
|
this[option] = options[option];
|
|
}
|
|
};
|
|
|
|
var indexOf = function(callbacks, callback) {
|
|
for (var i=0, l=callbacks.length; i<l; i++) {
|
|
if (callbacks[i][0] === callback) { return i; }
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
var callbacksFor = function(object) {
|
|
var callbacks = object._promiseCallbacks;
|
|
|
|
if (!callbacks) {
|
|
callbacks = object._promiseCallbacks = {};
|
|
}
|
|
|
|
return callbacks;
|
|
};
|
|
|
|
var EventTarget = exports.EventTarget = {
|
|
mixin: function(object) {
|
|
object.on = this.on;
|
|
object.off = this.off;
|
|
object.trigger = this.trigger;
|
|
return object;
|
|
},
|
|
|
|
on: function(eventName, callback, binding) {
|
|
var allCallbacks = callbacksFor(this), callbacks;
|
|
binding = binding || this;
|
|
|
|
callbacks = allCallbacks[eventName];
|
|
|
|
if (!callbacks) {
|
|
callbacks = allCallbacks[eventName] = [];
|
|
}
|
|
|
|
if (indexOf(callbacks, callback) === -1) {
|
|
callbacks.push([callback, binding]);
|
|
}
|
|
},
|
|
|
|
off: function(eventName, callback) {
|
|
var allCallbacks = callbacksFor(this), callbacks;
|
|
|
|
if (!callback) {
|
|
allCallbacks[eventName] = [];
|
|
return;
|
|
}
|
|
|
|
callbacks = allCallbacks[eventName];
|
|
|
|
var index = indexOf(callbacks, callback);
|
|
|
|
if (index !== -1) { callbacks.splice(index, 1); }
|
|
},
|
|
|
|
trigger: function(eventName, options) {
|
|
var allCallbacks = callbacksFor(this),
|
|
callbacks, callbackTuple, callback, binding, event;
|
|
|
|
if (callbacks = allCallbacks[eventName]) {
|
|
for (var i=0, l=callbacks.length; i<l; i++) {
|
|
callbackTuple = callbacks[i];
|
|
callback = callbackTuple[0];
|
|
binding = callbackTuple[1];
|
|
|
|
if (typeof options !== 'object') {
|
|
options = { detail: options };
|
|
}
|
|
|
|
event = new Event(eventName, options);
|
|
callback.call(binding, event);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var Promise = exports.Promise = function(binding) {
|
|
this.binding = binding || this;
|
|
|
|
this.on('promise:resolved', function(event) {
|
|
this.trigger('success', { detail: event.detail });
|
|
}, this);
|
|
|
|
this.on('promise:failed', function(event) {
|
|
this.trigger('error', { detail: event.detail });
|
|
}, this);
|
|
};
|
|
|
|
var noop = function() {};
|
|
|
|
var invokeCallback = function(type, promise, callback, event) {
|
|
var value, error;
|
|
|
|
if (callback) {
|
|
value = callback.call(promise.binding, event.detail);
|
|
} else {
|
|
value = event.detail;
|
|
}
|
|
|
|
if (value instanceof Promise) {
|
|
value.then(function(value) {
|
|
promise.resolve( value );
|
|
}, function(error) {
|
|
promise.reject( error );
|
|
});
|
|
} else if (callback && value) {
|
|
promise.resolve(value);
|
|
} else if (error) {
|
|
promise.reject(error);
|
|
} else {
|
|
promise[type](value);
|
|
}
|
|
};
|
|
|
|
Promise.prototype = {
|
|
then: function(done, fail) {
|
|
var thenPromise = new Promise(this.binding);
|
|
|
|
this.on('promise:resolved', function(event) {
|
|
invokeCallback('resolve', thenPromise, done, event);
|
|
});
|
|
|
|
this.on('promise:failed', function(event) {
|
|
invokeCallback('reject', thenPromise, fail, event);
|
|
});
|
|
|
|
return thenPromise;
|
|
},
|
|
|
|
resolve: function(value) {
|
|
exports.async(function() {
|
|
this.trigger('promise:resolved', { detail: value });
|
|
this.isResolved = value;
|
|
}, this);
|
|
|
|
this.resolve = noop;
|
|
this.reject = noop;
|
|
},
|
|
|
|
reject: function(value) {
|
|
exports.async(function() {
|
|
this.trigger('promise:failed', { detail: value });
|
|
this.isRejected = value;
|
|
}, this);
|
|
|
|
this.resolve = noop;
|
|
this.reject = noop;
|
|
}
|
|
};
|
|
|
|
EventTarget.mixin(Promise.prototype);
|
|
})( global.RSVP = {} );
|
|
|
|
// *** UPNP SERVICE WRAPPER ***
|
|
|
|
var UPnP = function( serviceObj, opts ) {
|
|
|
|
this.options = opts || { debug: false };
|
|
this.svc = serviceObj;
|
|
|
|
// API stub
|
|
this.action = function() {};
|
|
|
|
if( !this.svc || !(this.svc instanceof NetworkService) ) {
|
|
|
|
this.debugLog('First argument provided in constructor MUST be a valid NetworkService object');
|
|
|
|
return this;
|
|
}
|
|
|
|
if( !this.svc.type || this.svc.type.indexOf('upnp:') !== 0 ) {
|
|
|
|
this.debugLog('First argument provided in constructor MUST be a _UPnP_ NetworkService object');
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
this.svcType = this.svc.type.replace('upnp:', '');
|
|
|
|
this.svcUrl = this.svc.url;
|
|
|
|
var self = this;
|
|
|
|
// Full API
|
|
this.action = function( upnpAction, upnpParameters, callback ) {
|
|
|
|
var promise = new RSVP.Promise( this );
|
|
|
|
// Handle .action( name, callback )
|
|
if( !callback && Object.prototype.toString.call( upnpParameters ) == '[object Function]' ) {
|
|
callback = upnpParameters;
|
|
upnpParameters = {};
|
|
}
|
|
|
|
// Generate a callback stub if a Function has not been provided
|
|
if( !callback || Object.prototype.toString.call( callback ) !== '[object Function]' ) {
|
|
callback = function(e, response) {};
|
|
}
|
|
|
|
// Create a UPnP XML Request message
|
|
var svcMsg = this.createRequest( this.svcType, upnpAction, upnpParameters );
|
|
|
|
// Send the UPnP XML Request message to the current service
|
|
this.sendRequest( this.svcType, upnpAction, this.svcUrl, svcMsg, function(e, xmlResponse) {
|
|
|
|
if(e !== null) {
|
|
callback.apply(self, [ e, null ]);
|
|
promise.reject( e );
|
|
return;
|
|
}
|
|
|
|
// Parse UPnP XML Response message
|
|
self.handleResponse( upnpAction, xmlResponse.responseXML || "", upnpParameters, function(e, upnpResponse) {
|
|
|
|
// Fire callback
|
|
callback.apply(self, [e, upnpResponse]);
|
|
|
|
// Fire promise
|
|
if( e !== null) {
|
|
promise.reject( e );
|
|
} else {
|
|
promise.resolve( upnpResponse );
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return promise;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
UPnP.prototype.constructor = UPnP;
|
|
|
|
// UPnP XML MESSAGING TEMPLATES
|
|
|
|
UPnP.prototype.requestTemplate = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
|
|
"<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
|
|
"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
|
|
"\t<s:Body>\n" +
|
|
"\t\t<u:{ACTION_NAME} xmlns:u=\"{ACTION_TYPE}\">\n" +
|
|
"{ACTION_VARS}" +
|
|
"\t\t</u:{ACTION_NAME}>\n" +
|
|
"\t</s:Body>\n" +
|
|
"</s:Envelope>\n";
|
|
|
|
// UPNP PRIMITIVE VARIABLE TYPE CHECKERS
|
|
// from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf
|
|
UPnP.prototype.types = {
|
|
|
|
"i1": function(val, allowedValues, toNative) {
|
|
var i8 = new Int8Array(1);
|
|
i8[0] = val;
|
|
|
|
return UPnP.prototype.checkAllowedValues( i8[0] , allowedValues );
|
|
|
|
},
|
|
|
|
"i2": function(val, allowedValues, toNative) {
|
|
var i16 = new Int16Array(1);
|
|
i16[0] = val;
|
|
return UPnP.prototype.checkAllowedValues( i16[0] , allowedValues );
|
|
},
|
|
|
|
"i4": function(val, allowedValues, toNative) {
|
|
var i32 = new Int32Array(1);
|
|
i32[0] = val;
|
|
return UPnP.prototype.checkAllowedValues( i32[0] , allowedValues );
|
|
},
|
|
|
|
"ui1": function(val, allowedValues, toNative) {
|
|
var ui8 = new Uint8Array(1);
|
|
ui8[0] = val;
|
|
return UPnP.prototype.checkAllowedValues( ui8[0] , allowedValues );
|
|
},
|
|
|
|
"ui2": function(val, allowedValues, toNative) {
|
|
var ui16 = new Uint16Array(1);
|
|
ui16[0] = val;
|
|
return UPnP.prototype.checkAllowedValues( ui16[0] , allowedValues );
|
|
},
|
|
|
|
"ui4": function(val, allowedValues, toNative) {
|
|
var ui32 = new Uint32Array(1);
|
|
ui32[0] = val;
|
|
return UPnP.prototype.checkAllowedValues( ui32[0] , allowedValues );
|
|
},
|
|
|
|
"int": function(val, allowedValues, toNative) {
|
|
if(val === undefined || val === null || isNaN(val)) {
|
|
val = 0;
|
|
} else if (typeof val !== 'number') {
|
|
if(val === true) {
|
|
val = 1;
|
|
} else {
|
|
val = parseInt(val + "", 10);
|
|
}
|
|
}
|
|
if( !toNative ) {
|
|
val = expandExponential( val + "" );
|
|
}
|
|
if(isNaN(val)) val = 0;
|
|
return UPnP.prototype.checkAllowedValues( val , allowedValues );
|
|
},
|
|
|
|
"r4": function(val, allowedValues, toNative) {
|
|
var f32 = new Float32Array(1);
|
|
f32[0] = val;
|
|
if( !toNative ) {
|
|
return f32[0] ? expandExponential( f32[0] + "" ) : "0";
|
|
}
|
|
return UPnP.prototype.checkAllowedValues( f32[0] , allowedValues );
|
|
},
|
|
|
|
"r8": function(val, allowedValues, toNative) {
|
|
var f64 = new Float64Array(1);
|
|
f64[0] = val;
|
|
if( !toNative ) {
|
|
return f64[0] ? expandExponential( f64[0] + "" ) : "0";
|
|
}
|
|
return UPnP.prototype.checkAllowedValues( f64[0] , allowedValues );
|
|
},
|
|
|
|
"number": function(val, allowedValues, toNative) {
|
|
return UPnP.prototype.types['r8'](val, allowedValues, toNative);
|
|
},
|
|
|
|
"fixed_14_4": function(val, allowedValues, toNative) {
|
|
return UPnP.prototype.types['float'](val, allowedValues, toNative);
|
|
},
|
|
|
|
"float": function(val, allowedValues, toNative) {
|
|
var _float = parseFloat(val);
|
|
if( !toNative ) {
|
|
return _float ? expandExponential( _float + "" ) : "0";
|
|
}
|
|
return UPnP.prototype.checkAllowedValues( _float , allowedValues );
|
|
},
|
|
|
|
"char": function(val, allowedValues, toNative) {
|
|
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
|
|
val = "";
|
|
} else if(val === false || val === true || val == "true" || val == "yes" || val == "false" || val == "no") {
|
|
val = (val === true || val == "true" || val == "yes") ? "1" : "0";
|
|
} else {
|
|
val = val.toString();
|
|
}
|
|
if(val.length > 0) {
|
|
val = UPnP.prototype.checkAllowedValues( val, allowedValues );
|
|
return val.charAt(0);
|
|
}
|
|
return val;
|
|
},
|
|
|
|
"string": function(val, allowedValues, toNative) {
|
|
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
|
|
val = "";
|
|
}
|
|
val = val + "";
|
|
|
|
val = UPnP.prototype.checkAllowedValues( val, allowedValues );
|
|
return val;
|
|
},
|
|
|
|
"date": function(val, allowedValues, toNative) {
|
|
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
|
|
},
|
|
|
|
"dateTime": function(val, allowedValues, toNative) {
|
|
if( toNative ) { // Parse to ECMA Date object
|
|
if( Object.prototype.toString.call( val ) == '[object String]' ) {
|
|
val = new Date( parseDate( val ) );
|
|
if(isNaN(val)) val = "";
|
|
} else if( Object.prototype.toString.call( val ) == '[object Date]' ) {
|
|
val = val;
|
|
}
|
|
} else { // Parse to ISO-8601 string
|
|
if( Object.prototype.toString.call( val ) == '[object String]' ) {
|
|
val = val + "";
|
|
} else if( Object.prototype.toString.call( val ) == '[object Date]' ) {
|
|
if(val.toString() == "Invalid Date") { // opera specific functionality
|
|
val = "";
|
|
} else {
|
|
val = val.toISOString();
|
|
}
|
|
}
|
|
}
|
|
val = UPnP.prototype.checkAllowedValues( val, allowedValues );
|
|
return val;
|
|
},
|
|
|
|
"dateTime_tz": function(val, allowedValues, toNative) {
|
|
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
|
|
},
|
|
|
|
"time": function(val, allowedValues, toNative) {
|
|
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
|
|
},
|
|
|
|
"time_tz": function(val, allowedValues, toNative) {
|
|
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
|
|
},
|
|
|
|
"boolean": function(val, allowedValues, toNative) {
|
|
if(val == 'false' || val == 'f' || val == 'no' || val == 'n' || val instanceof RegExp || val <= 0 || !val) {
|
|
if(toNative) {
|
|
val = false;
|
|
} else {
|
|
val = "0";
|
|
}
|
|
} else {
|
|
if(toNative) {
|
|
val = true;
|
|
} else {
|
|
val = "1";
|
|
}
|
|
}
|
|
return val;
|
|
},
|
|
|
|
"bin_base64": function(val, allowedValues, toNative) {
|
|
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
|
|
val = "";
|
|
}
|
|
if(val === true || val === false ) {
|
|
val = val === true ? "1" : "0";
|
|
}
|
|
if( toNative ) { // convert to string
|
|
val = atob( val + "" );
|
|
} else { // convert to base64
|
|
val = btoa( val + "" );
|
|
}
|
|
return UPnP.prototype.checkAllowedValues( val, allowedValues );
|
|
},
|
|
|
|
"bin_hex": function(val, allowedValues, toNative) {
|
|
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
|
|
val = "";
|
|
}
|
|
if(val === true || val === false ) {
|
|
val = val === true ? "1" : "0";
|
|
}
|
|
if( toNative ) { // convert to string
|
|
val = htoa( val + "" );
|
|
} else { // convert to hex
|
|
val = atoh( val + "" );
|
|
}
|
|
return UPnP.prototype.checkAllowedValues( val, allowedValues );
|
|
},
|
|
|
|
"uri": function(val, allowedValues, toNative) {
|
|
if( toNative ) {
|
|
val = decodeURI(val + "");
|
|
}
|
|
return UPnP.prototype.types['string'](val, allowedValues); // No URI syntax checking
|
|
},
|
|
|
|
"uuid": function(val, allowedValues, toNative) {
|
|
return UPnP.prototype.types['string'](val, allowedValues); // No UUID syntax checking
|
|
}
|
|
|
|
};
|
|
|
|
// Override .types toString function
|
|
for(var type in UPnP.prototype.types) {
|
|
UPnP.prototype.types[ type ]._name = type;
|
|
UPnP.prototype.types[ type ]["toString"] = function() { return this._name; };
|
|
}
|
|
|
|
UPnP.prototype.createRequest = function( upnpServiceType, upnpActionName, parameters ) {
|
|
|
|
parameters = this.formatParameters( parameters );
|
|
|
|
this.debugLog( 'Creating UPnP XML Control Message for [' + upnpActionName + '] with parameters:' );
|
|
this.debugLog( JSON ? JSON.stringify( parameters ) : null );
|
|
|
|
// Build basic part of XML request
|
|
var svcMsg = this.requestTemplate.replace(/\{ACTION_TYPE\}/g, encodeXML(trim(upnpServiceType)));
|
|
|
|
svcMsg = svcMsg.replace(/\{ACTION_NAME\}/g, encodeXML(trim(upnpActionName)));
|
|
|
|
var svcInParams = parameters['request'],
|
|
svcInParams_xml = "";
|
|
|
|
if( svcInParams ) {
|
|
|
|
// Generate XML parameters syntax
|
|
for(var svcParam in svcInParams) {
|
|
|
|
// Perform type checking sanitization
|
|
if( svcInParams[ svcParam ]['type'] !== undefined && svcInParams[ svcParam ]['type'] !== null ) {
|
|
|
|
// get the primitive sanitization function
|
|
var typeCheck = this.types[ svcInParams[ svcParam ]['type'] ];
|
|
|
|
if( typeCheck ) {
|
|
svcInParams[ svcParam ]['value'] = typeCheck( svcInParams[ svcParam ]['value'], svcInParams[ svcParam ][ 'allowedValueList' ], false );
|
|
}
|
|
|
|
}
|
|
|
|
if( svcInParams[ svcParam ]['value'] === null || svcInParams[ svcParam ]['value'] === undefined ) {
|
|
|
|
svcInParams[ svcParam ]['value'] = ""; // pass through empty value
|
|
|
|
}
|
|
|
|
svcInParams_xml += "\t\t\t<" + svcParam + ">" + encodeXML(trim(svcInParams[ svcParam ]['value'] + "")) + "</" + svcParam + ">\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add ACTION_VARS, if any
|
|
svcMsg = svcMsg.replace(/\{ACTION_VARS\}/g, svcInParams_xml);
|
|
|
|
// DEBUG
|
|
this.debugLog( '[REQUEST] (SOAPAction: ' + upnpServiceType + '#' + upnpActionName + ') ' + svcMsg );
|
|
|
|
return svcMsg;
|
|
|
|
};
|
|
|
|
UPnP.prototype.sendRequest = function( upnpServiceType, upnpActionName, upnpEndpointURL, upnpRequestXML, onUPnPResponse ) {
|
|
|
|
// Send XML action message
|
|
|
|
try {
|
|
|
|
var xhr = xhrManager.get();
|
|
|
|
xhr.open( 'POST', upnpEndpointURL );
|
|
|
|
xhr.setRequestHeader('SOAPAction', '"' + upnpServiceType.replace(/[\"\#]/g, '') + '#' + upnpActionName.replace(/[\"\#]/g, '') + '"');
|
|
xhr.setRequestHeader('Content-Type', 'text/xml; charset="utf-8";');
|
|
|
|
xhr.onabort = xhr.onerror = function () {
|
|
|
|
onUPnPResponse.apply(undefined, [ new UPnPError('XmlHttpRequest failed'), null ]);
|
|
|
|
xhrManager.release( xhr );
|
|
|
|
};
|
|
|
|
var self = this;
|
|
|
|
xhr.onreadystatechange = function () {
|
|
|
|
if ( this.readyState < 4 ) return;
|
|
|
|
if( this.status !== 200 ) {
|
|
|
|
onUPnPResponse.apply(undefined, [ new UPnPError('XmlHttpRequest expected 200 OK. Received ' + this.status + ' response.'), null ]);
|
|
|
|
self.debugLog( "[ERROR-RESPONSE] " + this.responseText );
|
|
|
|
xhrManager.release( xhr );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self.debugLog( "[SUCCESS-RESPONSE] " + this.responseText );
|
|
|
|
onUPnPResponse.apply(this, [ null, this ]);
|
|
|
|
xhrManager.release( xhr );
|
|
|
|
};
|
|
|
|
xhr.send( upnpRequestXML );
|
|
|
|
} catch(e) {
|
|
|
|
onUPnPResponse.apply(undefined, [ new UPnPError( e ), null ]);
|
|
|
|
}
|
|
|
|
return this; // allow method chaining
|
|
|
|
};
|
|
|
|
UPnP.prototype.handleResponse = function( upnpActionName, upnpResponseXML, parameters, onUPnPResolved ) {
|
|
|
|
parameters = this.formatParameters( parameters );
|
|
|
|
var svcOutParams = parameters['response'];
|
|
|
|
var responseJSON = {};
|
|
|
|
// Process all XML response variables
|
|
var responseContainer = upnpResponseXML.getElementsByTagName ?
|
|
upnpResponseXML.getElementsByTagName( upnpActionName + "Response" ) : null;
|
|
|
|
// If we don't have any response variables, try again with the namespace
|
|
// attached (required for lib to work in Firefox)
|
|
if( responseContainer === null || responseContainer.length <= 0 ) {
|
|
responseContainer = upnpResponseXML.getElementsByTagName ?
|
|
upnpResponseXML.getElementsByTagName( "u:" + upnpActionName + "Response" ) : null;
|
|
}
|
|
|
|
if( responseContainer !== null && responseContainer.length > 0 ) {
|
|
|
|
var responseVars = responseContainer[0].childNodes;
|
|
|
|
if( responseVars && responseVars.length > 0 ) {
|
|
|
|
for(var i = 0, l = responseVars.length; i < l; i++ ) {
|
|
|
|
var varName = responseVars[i].nodeName;
|
|
var varVal = responseVars[i].textContent? trim(decodeXML(responseVars[i].textContent + "")) : null;
|
|
|
|
// Perform type checking sanitization if requested
|
|
if( svcOutParams[ varName ] && svcOutParams[ varName ]['type'] !== undefined && svcOutParams[ varName ]['type'] !== null ) {
|
|
|
|
// get the primitive sanitization function
|
|
var typeCheck = this.types[ svcOutParams[ varName ]['type'] ];
|
|
|
|
if( typeCheck ) {
|
|
varVal = typeCheck( varVal, svcOutParams[ varName ][ 'allowedValueList' ] || null, true );
|
|
}
|
|
|
|
}
|
|
|
|
responseJSON[ varName ] = (varVal !== undefined && varVal !== null) ? varVal : "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Fire callback with the parsed action response data
|
|
onUPnPResolved.apply(undefined, [ null, { data: responseJSON } ]);
|
|
|
|
return this; // allow method chaining
|
|
|
|
};
|
|
|
|
UPnP.prototype.formatParameter = function( parameter ) {
|
|
|
|
var param = {};
|
|
|
|
if( parameter['type'] !== undefined || parameter['value'] !== undefined || parameter['allowedValueList'] !== undefined ) {
|
|
|
|
param = parameter;
|
|
|
|
if( parameter['allowedValueList'] !== undefined && typeof parameter['allowedValueList'] !== 'array' ) {
|
|
parameter['allowedValueList'] = [];
|
|
}
|
|
|
|
} else {
|
|
|
|
param = { value: parameter };
|
|
|
|
}
|
|
|
|
// check type is valid, otherwise discard requested type
|
|
if( param['type'] !== undefined && param['type'] !== null ) {
|
|
|
|
if( !this.types[ (param['type'] + "") ] ) {
|
|
|
|
delete param['type'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return param;
|
|
|
|
};
|
|
|
|
// Normalize the input parameters argument
|
|
UPnP.prototype.formatParameters = function( parameters ) {
|
|
|
|
if(parameters === undefined || parameters === null) {
|
|
parameters = {};
|
|
}
|
|
|
|
if(!parameters['request']) {
|
|
parameters['request'] = {};
|
|
}
|
|
|
|
if(!parameters['response']) {
|
|
parameters['response'] = {};
|
|
}
|
|
|
|
for( var param in parameters ) {
|
|
|
|
if( param === 'request' || param === 'response' ) {
|
|
|
|
for(var processParam in parameters[param] ) {
|
|
|
|
parameters[param][ processParam ] = this.formatParameter( parameters[param][ processParam ] );
|
|
|
|
}
|
|
|
|
} else { // append as 'request' parameter and remove from root object
|
|
|
|
parameters['request'][ param ] = this.formatParameter( parameters[ param ] );
|
|
|
|
delete parameters[ param ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return parameters;
|
|
|
|
};
|
|
|
|
// ALLOWED VALUE LIST CHECKER
|
|
|
|
UPnP.prototype.checkAllowedValues = function( val, allowedValueList ) {
|
|
|
|
// Check against allowedValues if provided
|
|
if( val && allowedValueList && Object.prototype.toString.call( allowedValueList ) == '[object Array]' ) {
|
|
|
|
var matchFound = false;
|
|
|
|
for( var i = 0, l = allowedValueList.length; i < l; i++ ) {
|
|
|
|
if( val == allowedValueList[i] ) {
|
|
|
|
matchFound = true;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if( !matchFound ) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
return val;
|
|
|
|
};
|
|
|
|
// UPnP DEBUGGER
|
|
|
|
UPnP.prototype.debugLog = function( msg, level ) {
|
|
if( (this.options && this.options.debug) ) {
|
|
console[level || 'log']( "Plug.UPnP: " + msg );
|
|
}
|
|
};
|
|
|
|
// UPnP HTTP MESSAGING MANAGER
|
|
|
|
var xhrManager = ( function() {
|
|
var pool = [];
|
|
return {
|
|
get: function () {
|
|
return pool.pop() || new XMLHttpRequest({mozSystem: true});
|
|
},
|
|
release: function( xhr ) {
|
|
xhr.onreadystatechange = function () {};
|
|
pool.push( xhr );
|
|
}
|
|
};
|
|
}() );
|
|
|
|
// UPnP ERROR OBJECT
|
|
|
|
var UPnPError = function( description ) {
|
|
return {
|
|
'description': description
|
|
};
|
|
};
|
|
|
|
// *** CUSTOM TYPE FUNCTIONS ***
|
|
|
|
// BASE64 FUNCTIONS
|
|
// (forked from https://github.com/dankogai/js-base64/blob/master/base64.js)
|
|
|
|
var b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
|
|
var b64tab = (function(bin) {
|
|
var t = {};
|
|
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
|
|
return t;
|
|
})(b64chars);
|
|
|
|
var cb_encode = function(ccc) {
|
|
var padlen = [0, 2, 1][ccc.length % 3],
|
|
ord = ccc.charCodeAt(0) << 16
|
|
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
|
|
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
|
|
chars = [
|
|
b64chars.charAt( ord >>> 18),
|
|
b64chars.charAt((ord >>> 12) & 63),
|
|
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
|
|
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
|
|
];
|
|
return chars.join('');
|
|
};
|
|
|
|
var cb_decode = function(cccc) {
|
|
var len = cccc.length,
|
|
padlen = len % 4,
|
|
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
|
|
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
|
|
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
|
|
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
|
|
chars = [
|
|
String.fromCharCode( n >>> 16),
|
|
String.fromCharCode((n >>> 8) & 0xff),
|
|
String.fromCharCode( n & 0xff)
|
|
];
|
|
chars.length -= [0, 0, 2, 1][padlen];
|
|
return chars.join('');
|
|
};
|
|
|
|
var btoa = global.btoa || function(b) {
|
|
return b.replace(/[\s\S]{1,3}/g, cb_encode);
|
|
};
|
|
|
|
var atob = global.atob || function(a){
|
|
return a.replace(/[\s\S]{1,4}/g, cb_decode);
|
|
};
|
|
|
|
// HEX FUNCTIONS
|
|
|
|
var hexChars = '0123456789abcdef';
|
|
|
|
var hextab = (function(bin) {
|
|
var t = {};
|
|
for (var i = 0, l = bin.length; i < l; i++) t[i] = bin.charAt(i);
|
|
return t;
|
|
})(hexChars);
|
|
|
|
function toHex(n){
|
|
var result = '';
|
|
var start = true;
|
|
for (var i=32; i>0;){
|
|
i-=4;
|
|
var digit = (n>>i) & 0xf;
|
|
if (!start || digit != 0){
|
|
start = false;
|
|
result += hextab[digit];
|
|
}
|
|
}
|
|
return (result==''?'0':result);
|
|
}
|
|
|
|
function pad(str, len, pad){
|
|
var result = str;
|
|
for (var i=str.length; i<len; i++){
|
|
result = pad + result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function ntos(n){
|
|
n=n.toString(16);
|
|
if (n.length == 1) n="0"+n;
|
|
n="%"+n;
|
|
return unescape(n);
|
|
}
|
|
|
|
function htoa(str){
|
|
str = str.toUpperCase().replace(new RegExp("s/[^0-9A-Z]//g"));
|
|
var result = "";
|
|
var nextchar = "";
|
|
for (var i=0; i<str.length; i++){
|
|
nextchar += str.charAt(i);
|
|
if (nextchar.length == 2){
|
|
result += ntos( parseInt(nextchar, 16) );
|
|
nextchar = "";
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function atoh(str){
|
|
var result = "";
|
|
for (var i=0; i<str.length; i++){
|
|
result += pad(toHex(str.charCodeAt(i)&0xff),2,'0');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// NUMBER FUNCTIONS
|
|
|
|
function expandExponential( str ){
|
|
return str.replace(/^([+-])?(\d+).?(\d*)[eE]([-+]?\d+)$/, function(x, s, n, f, c){
|
|
var l = +c < 0, i = n.length + +c, x = (l ? n : f).length,
|
|
c = ((c = Math.abs(c)) >= x ? c - x + l : 0),
|
|
z = (new Array(c + 1)).join("0"), r = n + f;
|
|
return (s || "") + (l ? r = z + r : r += z).substr(0, i += l ? z.length : 0) + (i < r.length ? "." + r.substr(i) : "");
|
|
});
|
|
}
|
|
|
|
// STRING FUNCTIONS
|
|
|
|
var trim = function( str ) {
|
|
if(String.prototype.trim) {
|
|
return str.trim();
|
|
} else {
|
|
return str.replace(/^\s+|\s+$/g, "");
|
|
}
|
|
};
|
|
|
|
var encodeXML = function ( str ) {
|
|
return str.replace(/\&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/'/g, ''')
|
|
.replace(/"/g, '"');
|
|
};
|
|
|
|
var decodeXML = function ( str ) {
|
|
return str.replace(/\"/g, '"')
|
|
.replace(/\&\#39;/g, '\'')
|
|
.replace(/\>/g, '>')
|
|
.replace(/\</g, '<')
|
|
.replace(/\&/g, '&');
|
|
};
|
|
|
|
/**
|
|
* Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601>
|
|
* © 2011 Colin Snover <http://zetafleet.com>
|
|
* Released under MIT license.
|
|
*
|
|
* 2012 - Modifications to accept times without dates - by Rich Tibbett
|
|
*/
|
|
|
|
var parseDate = function (date) {
|
|
var timestamp, struct, minutesOffset = 0, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
|
|
|
|
if(typeof date !== 'string') date += "";
|
|
|
|
if ((struct = /^((\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:[T\s]?(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{4}))?)?$/.exec(date))) {
|
|
|
|
struct = struct.slice(1, struct.length);
|
|
|
|
// avoid NaN timestamps caused by “undefined” values being
|
|
// passed to Date.UTC
|
|
for (var i = 0, k; (k = numericKeys[i]); ++i) {
|
|
struct[k] = +struct[k] || 0;
|
|
}
|
|
|
|
// allow undefined days and months
|
|
struct[2] = (+struct[2] || 1) - 1;
|
|
struct[3] = +struct[3] || 1;
|
|
|
|
if (struct[8] !== 'Z' && struct[9] !== undefined) {
|
|
minutesOffset = (( struct[10] / 100 ) - (( struct[10] / 100 ) % 1)) * 60 + (struct[10] % 100);
|
|
|
|
if (struct[9] === '+') {
|
|
minutesOffset = 0 - minutesOffset;
|
|
}
|
|
}
|
|
|
|
timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
|
|
}
|
|
else {
|
|
timestamp = Date.parse ? Date.parse(date) : NaN;
|
|
}
|
|
|
|
return timestamp;
|
|
};
|
|
|
|
// *** GLOBAL ASSIGNMENT ***
|
|
|
|
if( !global.Plug ) {
|
|
global.Plug = {};
|
|
}
|
|
|
|
if( !global.Plug.UPnP ) {
|
|
global.Plug.UPnP = UPnP;
|
|
}
|
|
|
|
})( this );
|