--- a/browser/components/loop/content/shared/libs/sdk.js
+++ b/browser/components/loop/content/shared/libs/sdk.js
@@ -1,31 +1,28 @@
/**
- * @license OpenTok JavaScript Library v2.5.2 f4508e1 2015Q1.patch.1
- * http://www.tokbox.com/
- *
- * Copyright (c) 2014 TokBox, Inc.
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
- *
- * Date: July 13 05:38:08 2015
+ * @license OpenTok.js v2.6.8 fae7901 HEAD
+ *
+ * Copyright (c) 2010-2015 TokBox, Inc.
+ * Subject to the applicable Software Development Kit (SDK) License Agreement:
+ * https://tokbox.com/support/sdk_license
+ *
+ * Date: October 28 03:45:23 2015
*/
-
-
!(function(window) {
!(function(window, OTHelpers, undefined) {
/**
- * @license Common JS Helpers on OpenTok 0.3.0 f151b47 HEAD
+ * @license Common JS Helpers on OpenTok 0.4.1 259ca46 v0.4.1-branch
* http://www.tokbox.com/
*
* Copyright (c) 2015 TokBox, Inc.
*
- * Date: July 13 05:37:51 2015
+ * Date: October 28 03:45:12 2015
*
*/
// OT Helper Methods
//
// helpers.js <- the root file
// helpers/lib/{helper topic}.js <- specialised helpers for specific tasks/topics
@@ -551,16 +548,32 @@ if (!Object.create) {
// Invert the keys and values of an object. The values must be serializable.
OTHelpers.invert = function(obj) {
var result = {};
for (var key in obj) if (obj.hasOwnProperty(key)) result[obj[key]] = key;
return result;
};
+
+// A helper for the common case of making a simple promise that is either
+// resolved or rejected straight away.
+//
+// If the +err+ param is provide then the promise will be rejected, otherwise
+// it will resolve.
+//
+OTHelpers.makeSimplePromise = function(err) {
+ return new OTHelpers.RSVP.Promise(function(resolve, reject) {
+ if (err === void 0) {
+ resolve();
+ } else {
+ reject(err);
+ }
+ });
+};
// tb_require('../../../helpers.js')
/* exported EventableEvent */
OTHelpers.Event = function() {
return function (type, cancelable) {
this.type = type;
this.cancelable = cancelable !== undefined ? cancelable : true;
@@ -629,16 +642,1929 @@ OTHelpers.statable = function(self, poss
//
self.isNot = function (/* state0:String, state1:String, ..., stateN:String */) {
return OTHelpers.arrayIndexOf(arguments, currentState) === -1;
};
return setState;
};
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+
+
+var getErrorLocation;
+
+// Properties that we'll acknowledge from the JS Error object
+var safeErrorProps = [
+ 'description',
+ 'fileName',
+ 'lineNumber',
+ 'message',
+ 'name',
+ 'number',
+ 'stack'
+];
+
+
+// OTHelpers.Error
+//
+// A construct to contain error information that also helps with extracting error
+// context, such as stack trace.
+//
+// @constructor
+// @memberof OTHelpers
+// @method Error
+//
+// @param {String} message
+// Optional. The error message
+//
+// @param {Object} props
+// Optional. A dictionary of properties containing extra Error info.
+//
+//
+// @example Create a simple error with juts a custom message
+// var error = new OTHelpers.Error('Something Broke!');
+// error.message === 'Something Broke!';
+//
+// @example Create an Error with a message and a name
+// var error = new OTHelpers.Error('Something Broke!', 'FooError');
+// error.message === 'Something Broke!';
+// error.name === 'FooError';
+//
+// @example Create an Error with a message, name, and custom properties
+// var error = new OTHelpers.Error('Something Broke!', 'FooError', {
+// foo: 'bar',
+// listOfImportantThings: [1,2,3,4]
+// });
+// error.message === 'Something Broke!';
+// error.name === 'FooError';
+// error.foo === 'bar';
+// error.listOfImportantThings == [1,2,3,4];
+//
+// @example Create an Error from a Javascript Error
+// var error = new OTHelpers.Error(domSyntaxError);
+// error.message === domSyntaxError.message;
+// error.name === domSyntaxError.name === 'SyntaxError';
+// // ...continues for each properties of domSyntaxError
+//
+// @references
+// * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
+// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack
+// * http://www.w3.org/TR/dom/#interface-domerror
+//
+//
+// @todo
+// * update usage in OTMedia
+// * replace error handling in OT.js
+// * normalise stack behaviour under Chrome/Node/Safari with other browsers
+// * unit test for stack parsing
+//
+//
+OTHelpers.Error = function (message, name, props) {
+ switch (arguments.length) {
+ case 1:
+ if ($.isObject(message)) {
+ props = message;
+ name = void 0;
+ message = void 0;
+ }
+ // Otherwise it's the message
+ break;
+
+ case 2:
+ if ($.isObject(name)) {
+ props = name;
+ name = void 0;
+ }
+ // Otherwise name is actually the name
+
+ break;
+ }
+
+ if ( props instanceof Error) {
+ // Special handling of this due to Chrome weirdness. It seems that
+ // properties of the Error object, and it's children, are not
+ // enumerable in Chrome?
+ for (var i = 0, num = safeErrorProps.length; i < num; ++i) {
+ this[safeErrorProps[i]] = props[safeErrorProps[i]];
+ }
+ }
+ else if ( $.isObject(props)) {
+ // Use an custom properties that are provided
+ for (var key in props) {
+ if (props.hasOwnProperty(key)) {
+ this[key] = props[key];
+ }
+ }
+ }
+
+ // If any of the fundamental properties are missing then try and
+ // extract them.
+ if ( !(this.fileName && this.lineNumber && this.columnNumber && this.stack) ) {
+ var err = getErrorLocation();
+
+ if (!this.fileName && err.fileName) {
+ this.fileName = err.fileName;
+ }
+
+ if (!this.lineNumber && err.lineNumber) {
+ this.lineNumber = err.lineNumber;
+ }
+
+ if (!this.columnNumber && err.columnNumber) {
+ this.columnNumber = err.columnNumber;
+ }
+
+ if (!this.stack && err.stack) {
+ this.stack = err.stack;
+ }
+ }
+
+ if (!this.message && message) this.message = message;
+ if (!this.name && name) this.name = name;
+};
+
+OTHelpers.Error.prototype.toString =
+OTHelpers.Error.prototype.valueOf = function() {
+ var locationDetails = '';
+ if (this.fileName) locationDetails += ' ' + this.fileName;
+ if (this.lineNumber) {
+ locationDetails += ' ' + this.lineNumber;
+ if (this.columnNumber) locationDetails += ':' + this.columnNumber;
+ }
+
+ return '<' + (this.name ? this.name + ' ' : '') + this.message + locationDetails + '>';
+};
+
+
+// Normalise err.stack so that it is the same format as the other browsers
+// We skip the first two frames so that we don't capture getErrorLocation() and
+// the callee.
+//
+// Used by Environments that support the StackTrace API. (Chrome, Node, Opera)
+//
+var prepareStackTrace = function prepareStackTrace (_, stack){
+ return $.map(stack.slice(2), function(frame) {
+ var _f = {
+ fileName: frame.getFileName(),
+ linenumber: frame.getLineNumber(),
+ columnNumber: frame.getColumnNumber()
+ };
+
+ if (frame.getFunctionName()) _f.functionName = frame.getFunctionName();
+ if (frame.getMethodName()) _f.methodName = frame.getMethodName();
+ if (frame.getThis()) _f.self = frame.getThis();
+
+ return _f;
+ });
+};
+
+
+// Black magic to retrieve error location info for various environments
+getErrorLocation = function getErrorLocation () {
+ var info = {},
+ callstack,
+ errLocation,
+ err;
+
+ switch ($.env.name) {
+ case 'Firefox':
+ case 'Safari':
+ case 'IE':
+
+ if ($.env.name !== 'IE') {
+ err = new Error();
+ }
+ else {
+ try {
+ window.call.js.is.explody;
+ }
+ catch(e) { err = e; }
+ }
+
+ callstack = (err.stack || '').split('\n');
+
+ //Remove call to getErrorLocation() and the callee
+ callstack.shift();
+ callstack.shift();
+
+ info.stack = callstack;
+
+ if ($.env.name === 'IE') {
+ // IE also includes the error message in it's stack trace
+ info.stack.shift();
+
+ // each line begins with some amounts of spaces and 'at', we remove
+ // these to normalise with the other browsers.
+ info.stack = $.map(callstack, function(call) {
+ return call.replace(/^\s+at\s+/g, '');
+ });
+ }
+
+ errLocation = /@(.+?):([0-9]+)(:([0-9]+))?$/.exec(callstack[0]);
+ if (errLocation) {
+ info.fileName = errLocation[1];
+ info.lineNumber = parseInt(errLocation[2], 10);
+ if (errLocation.length > 3) info.columnNumber = parseInt(errLocation[4], 10);
+ }
+ break;
+
+ case 'Chrome':
+ case 'Node':
+ case 'Opera':
+ var currentPST = Error.prepareStackTrace;
+ Error.prepareStackTrace = prepareStackTrace;
+ err = new Error();
+ info.stack = err.stack;
+ Error.prepareStackTrace = currentPST;
+
+ var topFrame = info.stack[0];
+ info.lineNumber = topFrame.lineNumber;
+ info.columnNumber = topFrame.columnNumber;
+ info.fileName = topFrame.fileName;
+ if (topFrame.functionName) info.functionName = topFrame.functionName;
+ if (topFrame.methodName) info.methodName = topFrame.methodName;
+ if (topFrame.self) info.self = topFrame.self;
+ break;
+
+ default:
+ err = new Error();
+ if (err.stack) info.stack = err.stack.split('\n');
+ break;
+ }
+
+ if (err.message) info.message = err.message;
+ return info;
+};
+
+
+/*jshint browser:true, smarttabs:true*/
+/* global process */
+
+// tb_require('../helpers.js')
+
+
+// OTHelpers.env
+//
+// Contains information about the current environment.
+// * **OTHelpers.env.name** The name of the Environment (Chrome, FF, Node, etc)
+// * **OTHelpers.env.version** Usually a Float, except in Node which uses a String
+// * **OTHelpers.env.userAgent** The raw user agent
+// * **OTHelpers.env.versionGreaterThan** A helper method that returns true if the
+// current version is greater than the argument
+//
+// Example
+// if (OTHelpers.env.versionGreaterThan('0.10.30')) {
+// // do something
+// }
+//
+(function() {
+ // @todo make exposing userAgent unnecessary
+ var version = -1;
+
+ // Returns true if otherVersion is greater than the current environment
+ // version.
+ var versionGEThan = function versionGEThan (otherVersion) {
+ if (otherVersion === version) return true;
+
+ if (typeof(otherVersion) === 'number' && typeof(version) === 'number') {
+ return otherVersion > version;
+ }
+
+ // The versions have multiple components (i.e. 0.10.30) and
+ // must be compared piecewise.
+ // Note: I'm ignoring the case where one version has multiple
+ // components and the other doesn't.
+ var v1 = otherVersion.split('.'),
+ v2 = version.split('.'),
+ versionLength = (v1.length > v2.length ? v2 : v1).length;
+
+ for (var i = 0; i < versionLength; ++i) {
+ if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
+ return true;
+ }
+ }
+
+ // Special case, v1 has extra components but the initial components
+ // were identical, we assume this means newer but it might also mean
+ // that someone changed versioning systems.
+ if (i < v1.length) {
+ return true;
+ }
+
+ return false;
+ };
+
+ var env = function() {
+ if (typeof(process) !== 'undefined' &&
+ typeof(process.versions) !== 'undefined' &&
+ typeof(process.versions.node) === 'string') {
+
+ version = process.versions.node;
+ if (version.substr(1) === 'v') version = version.substr(1);
+
+ // Special casing node to avoid gating window.navigator.
+ // Version will be a string rather than a float.
+ return {
+ name: 'Node',
+ version: version,
+ userAgent: 'Node ' + version,
+ iframeNeedsLoad: false,
+ versionGreaterThan: versionGEThan
+ };
+ }
+
+ var userAgent = window.navigator.userAgent.toLowerCase(),
+ appName = window.navigator.appName,
+ navigatorVendor,
+ name = 'unknown';
+
+ if (userAgent.indexOf('opera') > -1 || userAgent.indexOf('opr') > -1) {
+ name = 'Opera';
+
+ if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (userAgent.indexOf('firefox') > -1) {
+ name = 'Firefox';
+
+ if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (appName === 'Microsoft Internet Explorer') {
+ // IE 10 and below
+ name = 'IE';
+
+ if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) {
+ // IE 11+
+
+ name = 'IE';
+
+ if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (userAgent.indexOf('chrome') > -1) {
+ name = 'Chrome';
+
+ if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if ((navigatorVendor = window.navigator.vendor) &&
+ navigatorVendor.toLowerCase().indexOf('apple') > -1) {
+ name = 'Safari';
+
+ if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+ }
+
+ return {
+ name: name,
+ version: version,
+ userAgent: window.navigator.userAgent,
+ iframeNeedsLoad: userAgent.indexOf('webkit') < 0,
+ versionGreaterThan: versionGEThan
+ };
+ }();
+
+
+ OTHelpers.env = env;
+
+ OTHelpers.browser = function() {
+ return OTHelpers.env.name;
+ };
+
+ OTHelpers.browserVersion = function() {
+ return OTHelpers.env;
+ };
+
+})();
+// tb_require('../../environment.js')
+// tb_require('./event.js')
+
+var nodeEventing;
+
+if($.env.name === 'Node') {
+ (function() {
+ var EventEmitter = require('events').EventEmitter,
+ util = require('util');
+
+ // container for the EventEmitter behaviour. This prevents tight coupling
+ // caused by accidentally bleeding implementation details and API into whatever
+ // objects nodeEventing is applied to.
+ var NodeEventable = function NodeEventable () {
+ EventEmitter.call(this);
+
+ this.events = {};
+ };
+ util.inherits(NodeEventable, EventEmitter);
+
+
+ nodeEventing = function nodeEventing (/* self */) {
+ var api = new NodeEventable(),
+ _on = api.on,
+ _off = api.removeListener;
+
+
+ api.addListeners = function (eventNames, handler, context, closure) {
+ var listener = {handler: handler};
+ if (context) listener.context = context;
+ if (closure) listener.closure = closure;
+
+ $.forEach(eventNames, function(name) {
+ if (!api.events[name]) api.events[name] = [];
+ api.events[name].push(listener);
+
+ _on(name, handler);
+
+ var addedListener = name + ':added';
+ if (api.events[addedListener]) {
+ api.emit(addedListener, api.events[name].length);
+ }
+ });
+ };
+
+ api.removeAllListenersNamed = function (eventNames) {
+ var _eventNames = eventNames.split(' ');
+ api.removeAllListeners(_eventNames);
+
+ $.forEach(_eventNames, function(name) {
+ if (api.events[name]) delete api.events[name];
+ });
+ };
+
+ api.removeListeners = function (eventNames, handler, closure) {
+ function filterHandlers(listener) {
+ return !(listener.handler === handler && listener.closure === closure);
+ }
+
+ $.forEach(eventNames.split(' '), function(name) {
+ if (api.events[name]) {
+ _off(name, handler);
+ api.events[name] = $.filter(api.events[name], filterHandlers);
+ if (api.events[name].length === 0) delete api.events[name];
+
+ var removedListener = name + ':removed';
+ if (api.events[removedListener]) {
+ api.emit(removedListener, api.events[name] ? api.events[name].length : 0);
+ }
+ }
+ });
+ };
+
+ api.removeAllListeners = function () {
+ api.events = {};
+ api.removeAllListeners();
+ };
+
+ api.dispatchEvent = function(event, defaultAction) {
+ this.emit(event.type, event);
+
+ if (defaultAction) {
+ defaultAction.call(null, event);
+ }
+ };
+
+ api.trigger = $.bind(api.emit, api);
+
+
+ return api;
+ };
+ })();
+}
+
+// tb_require('../../environment.js')
+// tb_require('./event.js')
+
+var browserEventing;
+
+if($.env.name !== 'Node') {
+
+ browserEventing = function browserEventing (self, syncronous) {
+ var api = {
+ events: {}
+ };
+
+
+ // Call the defaultAction, passing args
+ function executeDefaultAction(defaultAction, args) {
+ if (!defaultAction) return;
+
+ defaultAction.apply(null, args.slice());
+ }
+
+ // Execute each handler in +listeners+ with +args+.
+ //
+ // Each handler will be executed async. On completion the defaultAction
+ // handler will be executed with the args.
+ //
+ // @param [Array] listeners
+ // An array of functions to execute. Each will be passed args.
+ //
+ // @param [Array] args
+ // An array of arguments to execute each function in +listeners+ with.
+ //
+ // @param [String] name
+ // The name of this event.
+ //
+ // @param [Function, Null, Undefined] defaultAction
+ // An optional function to execute after every other handler. This will execute even
+ // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
+ // handler would.
+ //
+ // @return Undefined
+ //
+ function executeListenersAsyncronously(name, args, defaultAction) {
+ var listeners = api.events[name];
+ if (!listeners || listeners.length === 0) return;
+
+ var listenerAcks = listeners.length;
+
+ $.forEach(listeners, function(listener) { // , index
+ function filterHandlers(_listener) {
+ return _listener.handler === listener.handler;
+ }
+
+ // We run this asynchronously so that it doesn't interfere with execution if an
+ // error happens
+ $.callAsync(function() {
+ try {
+ // have to check if the listener has not been removed
+ if (api.events[name] && $.some(api.events[name], filterHandlers)) {
+ (listener.closure || listener.handler).apply(listener.context || null, args);
+ }
+ }
+ finally {
+ listenerAcks--;
+
+ if (listenerAcks === 0) {
+ executeDefaultAction(defaultAction, args);
+ }
+ }
+ });
+ });
+ }
+
+
+ // This is identical to executeListenersAsyncronously except that handlers will
+ // be executed syncronously.
+ //
+ // On completion the defaultAction handler will be executed with the args.
+ //
+ // @param [Array] listeners
+ // An array of functions to execute. Each will be passed args.
+ //
+ // @param [Array] args
+ // An array of arguments to execute each function in +listeners+ with.
+ //
+ // @param [String] name
+ // The name of this event.
+ //
+ // @param [Function, Null, Undefined] defaultAction
+ // An optional function to execute after every other handler. This will execute even
+ // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
+ // handler would.
+ //
+ // @return Undefined
+ //
+ function executeListenersSyncronously(name, args) { // defaultAction is not used
+ var listeners = api.events[name];
+ if (!listeners || listeners.length === 0) return;
+
+ $.forEach(listeners, function(listener) { // index
+ (listener.closure || listener.handler).apply(listener.context || null, args);
+ });
+ }
+
+ var executeListeners = syncronous === true ?
+ executeListenersSyncronously : executeListenersAsyncronously;
+
+
+ api.addListeners = function (eventNames, handler, context, closure) {
+ var listener = {handler: handler};
+ if (context) listener.context = context;
+ if (closure) listener.closure = closure;
+
+ $.forEach(eventNames, function(name) {
+ if (!api.events[name]) api.events[name] = [];
+ api.events[name].push(listener);
+
+ var addedListener = name + ':added';
+ if (api.events[addedListener]) {
+ executeListeners(addedListener, [api.events[name].length]);
+ }
+ });
+ };
+
+ api.removeListeners = function(eventNames, handler, context) {
+ function filterListeners(listener) {
+ var isCorrectHandler = (
+ listener.handler.originalHandler === handler ||
+ listener.handler === handler
+ );
+
+ return !(isCorrectHandler && listener.context === context);
+ }
+
+ $.forEach(eventNames, function(name) {
+ if (api.events[name]) {
+ api.events[name] = $.filter(api.events[name], filterListeners);
+ if (api.events[name].length === 0) delete api.events[name];
+
+ var removedListener = name + ':removed';
+ if (api.events[ removedListener]) {
+ executeListeners(removedListener, [api.events[name] ? api.events[name].length : 0]);
+ }
+ }
+ });
+ };
+
+ api.removeAllListenersNamed = function (eventNames) {
+ $.forEach(eventNames, function(name) {
+ if (api.events[name]) {
+ delete api.events[name];
+ }
+ });
+ };
+
+ api.removeAllListeners = function () {
+ api.events = {};
+ };
+
+ api.dispatchEvent = function(event, defaultAction) {
+ if (!api.events[event.type] || api.events[event.type].length === 0) {
+ executeDefaultAction(defaultAction, [event]);
+ return;
+ }
+
+ executeListeners(event.type, [event], defaultAction);
+ };
+
+ api.trigger = function(eventName, args) {
+ if (!api.events[eventName] || api.events[eventName].length === 0) {
+ return;
+ }
+
+ executeListeners(eventName, args);
+ };
+
+
+ return api;
+ };
+}
+
+/*jshint browser:false, smarttabs:true*/
+/* global window, require */
+
+// tb_require('../../helpers.js')
+// tb_require('../environment.js')
+
+if (window.OTHelpers.env.name === 'Node') {
+ var request = require('request');
+
+ OTHelpers.request = function(url, options, callback) {
+ var completion = function(error, response, body) {
+ var event = {response: response, body: body};
+
+ // We need to detect things that Request considers a success,
+ // but we consider to be failures.
+ if (!error && response.statusCode >= 200 &&
+ (response.statusCode < 300 || response.statusCode === 304) ) {
+ callback(null, event);
+ } else {
+ callback(error, event);
+ }
+ };
+
+ if (options.method.toLowerCase() === 'get') {
+ request.get(url, completion);
+ }
+ else {
+ request.post(url, options.body, completion);
+ }
+ };
+
+ OTHelpers.getJSON = function(url, options, callback) {
+ var extendedHeaders = require('underscore').extend(
+ {
+ 'Accept': 'application/json'
+ },
+ options.headers || {}
+ );
+
+ request.get({
+ url: url,
+ headers: extendedHeaders,
+ json: true
+ }, function(err, response) {
+ callback(err, response && response.body);
+ });
+ };
+}
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../../helpers.js')
+// tb_require('../environment.js')
+
+function formatPostData(data) { //, contentType
+ // If it's a string, we assume it's properly encoded
+ if (typeof(data) === 'string') return data;
+
+ var queryString = [];
+
+ for (var key in data) {
+ queryString.push(
+ encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
+ );
+ }
+
+ return queryString.join('&').replace(/\+/g, '%20');
+}
+
+if (window.OTHelpers.env.name !== 'Node') {
+
+ OTHelpers.xdomainRequest = function(url, options, callback) {
+ /*global XDomainRequest*/
+ var xdr = new XDomainRequest(),
+ _options = options || {},
+ _method = _options.method.toLowerCase();
+
+ if(!_method) {
+ callback(new Error('No HTTP method specified in options'));
+ return;
+ }
+
+ _method = _method.toUpperCase();
+
+ if(!(_method === 'GET' || _method === 'POST')) {
+ callback(new Error('HTTP method can only be '));
+ return;
+ }
+
+ function done(err, event) {
+ xdr.onload = xdr.onerror = xdr.ontimeout = function() {};
+ xdr = void 0;
+ callback(err, event);
+ }
+
+
+ xdr.onload = function() {
+ done(null, {
+ target: {
+ responseText: xdr.responseText,
+ headers: {
+ 'content-type': xdr.contentType
+ }
+ }
+ });
+ };
+
+ xdr.onerror = function() {
+ done(new Error('XDomainRequest of ' + url + ' failed'));
+ };
+
+ xdr.ontimeout = function() {
+ done(new Error('XDomainRequest of ' + url + ' timed out'));
+ };
+
+ xdr.open(_method, url);
+ xdr.send(options.body && formatPostData(options.body));
+
+ };
+
+ OTHelpers.request = function(url, options, callback) {
+ var request = new XMLHttpRequest(),
+ _options = options || {},
+ _method = _options.method;
+
+ if(!_method) {
+ callback(new Error('No HTTP method specified in options'));
+ return;
+ }
+
+ if (options.overrideMimeType) {
+ if (request.overrideMimeType) {
+ request.overrideMimeType(options.overrideMimeType);
+ }
+ delete options.overrideMimeType;
+ }
+
+ // Setup callbacks to correctly respond to success and error callbacks. This includes
+ // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore
+ // by default.
+ if (callback) {
+ OTHelpers.on(request, 'load', function(event) {
+ var status = event.target.status;
+
+ // We need to detect things that XMLHttpRequest considers a success,
+ // but we consider to be failures.
+ if ( status >= 200 && (status < 300 || status === 304) ) {
+ callback(null, event);
+ } else {
+ callback(event);
+ }
+ });
+
+ OTHelpers.on(request, 'error', callback);
+ }
+
+ request.open(options.method, url, true);
+
+ if (!_options.headers) _options.headers = {};
+
+ for (var name in _options.headers) {
+ if (!Object.prototype.hasOwnProperty.call(_options.headers, name)) {
+ continue;
+ }
+ request.setRequestHeader(name, _options.headers[name]);
+ }
+
+ request.send(options.body && formatPostData(options.body));
+ };
+
+
+ OTHelpers.getJSON = function(url, options, callback) {
+ options = options || {};
+
+ var done = function(error, event) {
+ if(error) {
+ callback(error, event && event.target && event.target.responseText);
+ } else {
+ var response;
+
+ try {
+ response = JSON.parse(event.target.responseText);
+ } catch(e) {
+ // Badly formed JSON
+ callback(e, event && event.target && event.target.responseText);
+ return;
+ }
+
+ callback(null, response, event);
+ }
+ };
+
+ if(options.xdomainrequest) {
+ OTHelpers.xdomainRequest(url, { method: 'GET' }, done);
+ } else {
+ var extendedHeaders = OTHelpers.extend({
+ 'Accept': 'application/json'
+ }, options.headers || {});
+
+ OTHelpers.get(url, OTHelpers.extend(options || {}, {
+ headers: extendedHeaders
+ }), done);
+ }
+
+ };
+
+}
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./environment.js')
+
+
+// Log levels for OTLog.setLogLevel
+var LOG_LEVEL_DEBUG = 5,
+ LOG_LEVEL_LOG = 4,
+ LOG_LEVEL_INFO = 3,
+ LOG_LEVEL_WARN = 2,
+ LOG_LEVEL_ERROR = 1,
+ LOG_LEVEL_NONE = 0;
+
+
+// There is a single global log level for every component that uses
+// the logs.
+var _logLevel = LOG_LEVEL_NONE;
+
+var setLogLevel = function setLogLevel (level) {
+ _logLevel = typeof(level) === 'number' ? level : 0;
+ return _logLevel;
+};
+
+
+OTHelpers.useLogHelpers = function(on){
+
+ // Log levels for OTLog.setLogLevel
+ on.DEBUG = LOG_LEVEL_DEBUG;
+ on.LOG = LOG_LEVEL_LOG;
+ on.INFO = LOG_LEVEL_INFO;
+ on.WARN = LOG_LEVEL_WARN;
+ on.ERROR = LOG_LEVEL_ERROR;
+ on.NONE = LOG_LEVEL_NONE;
+
+ var _logs = [],
+ _canApplyConsole = true;
+
+ try {
+ Function.prototype.bind.call(window.console.log, window.console);
+ } catch (err) {
+ _canApplyConsole = false;
+ }
+
+ // Some objects can't be logged in the console, mostly these are certain
+ // types of native objects that are exposed to JS. This is only really a
+ // problem with IE, hence only the IE version does anything.
+ var makeLogArgumentsSafe = function(args) { return args; };
+
+ if (OTHelpers.env.name === 'IE') {
+ makeLogArgumentsSafe = function(args) {
+ return [toDebugString(prototypeSlice.apply(args))];
+ };
+ }
+
+ // Generates a logging method for a particular method and log level.
+ //
+ // Attempts to handle the following cases:
+ // * the desired log method doesn't exist, call fallback (if available) instead
+ // * the console functionality isn't available because the developer tools (in IE)
+ // aren't open, call fallback (if available)
+ // * attempt to deal with weird IE hosted logging methods as best we can.
+ //
+ function generateLoggingMethod(method, level, fallback) {
+ return function() {
+ if (on.shouldLog(level)) {
+ var cons = window.console,
+ args = makeLogArgumentsSafe(arguments);
+
+ // In IE, window.console may not exist if the developer tools aren't open
+ // This also means that cons and cons[method] can appear at any moment
+ // hence why we retest this every time.
+ if (cons && cons[method]) {
+ // the desired console method isn't a real object, which means
+ // that we can't use apply on it. We force it to be a real object
+ // using Function.bind, assuming that's available.
+ if (cons[method].apply || _canApplyConsole) {
+ if (!cons[method].apply) {
+ cons[method] = Function.prototype.bind.call(cons[method], cons);
+ }
+
+ cons[method].apply(cons, args);
+ }
+ else {
+ // This isn't the same result as the above, but it's better
+ // than nothing.
+ cons[method](args);
+ }
+ }
+ else if (fallback) {
+ fallback.apply(on, args);
+
+ // Skip appendToLogs, we delegate entirely to the fallback
+ return;
+ }
+
+ appendToLogs(method, makeLogArgumentsSafe(arguments));
+ }
+ };
+ }
+
+ on.log = generateLoggingMethod('log', on.LOG);
+
+ // Generate debug, info, warn, and error logging methods, these all fallback to on.log
+ on.debug = generateLoggingMethod('debug', on.DEBUG, on.log);
+ on.info = generateLoggingMethod('info', on.INFO, on.log);
+ on.warn = generateLoggingMethod('warn', on.WARN, on.log);
+ on.error = generateLoggingMethod('error', on.ERROR, on.log);
+
+
+ on.setLogLevel = function(level) {
+ on.debug('TB.setLogLevel(' + _logLevel + ')');
+ return setLogLevel(level);
+ };
+
+ on.getLogs = function() {
+ return _logs;
+ };
+
+ // Determine if the level is visible given the current logLevel.
+ on.shouldLog = function(level) {
+ return _logLevel >= level;
+ };
+
+ // Format the current time nicely for logging. Returns the current
+ // local time.
+ function formatDateStamp() {
+ var now = new Date();
+ return now.toLocaleTimeString() + now.getMilliseconds();
+ }
+
+ function toJson(object) {
+ try {
+ return JSON.stringify(object);
+ } catch(e) {
+ return object.toString();
+ }
+ }
+
+ function toDebugString(object) {
+ var components = [];
+
+ if (typeof(object) === 'undefined') {
+ // noop
+ }
+ else if (object === null) {
+ components.push('NULL');
+ }
+ else if (OTHelpers.isArray(object)) {
+ for (var i=0; i<object.length; ++i) {
+ components.push(toJson(object[i]));
+ }
+ }
+ else if (OTHelpers.isObject(object)) {
+ for (var key in object) {
+ var stringValue;
+
+ if (!OTHelpers.isFunction(object[key])) {
+ stringValue = toJson(object[key]);
+ }
+ else if (object.hasOwnProperty(key)) {
+ stringValue = 'function ' + key + '()';
+ }
+
+ components.push(key + ': ' + stringValue);
+ }
+ }
+ else if (OTHelpers.isFunction(object)) {
+ try {
+ components.push(object.toString());
+ } catch(e) {
+ components.push('function()');
+ }
+ }
+ else {
+ components.push(object.toString());
+ }
+
+ return components.join(', ');
+ }
+
+ // Append +args+ to logs, along with the current log level and the a date stamp.
+ function appendToLogs(level, args) {
+ if (!args) return;
+
+ var message = toDebugString(args);
+ if (message.length <= 2) return;
+
+ _logs.push(
+ [level, formatDateStamp(), message]
+ );
+ }
+};
+
+OTHelpers.useLogHelpers(OTHelpers);
+OTHelpers.setLogLevel(OTHelpers.ERROR);
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+
+// DOM helpers
+
+// Helper function for adding event listeners to dom elements.
+// WARNING: This doesn't preserve event types, your handler could
+// be getting all kinds of different parameters depending on the browser.
+// You also may have different scopes depending on the browser and bubbling
+// and cancelable are not supported.
+ElementCollection.prototype.on = function (eventName, handler) {
+ return this.forEach(function(element) {
+ if (element.addEventListener) {
+ element.addEventListener(eventName, handler, false);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + eventName, handler);
+ } else {
+ var oldHandler = element['on'+eventName];
+ element['on'+eventName] = function() {
+ handler.apply(this, arguments);
+ if (oldHandler) oldHandler.apply(this, arguments);
+ };
+ }
+ });
+};
+
+// Helper function for removing event listeners from dom elements.
+ElementCollection.prototype.off = function (eventName, handler) {
+ return this.forEach(function(element) {
+ if (element.removeEventListener) {
+ element.removeEventListener (eventName, handler,false);
+ }
+ else if (element.detachEvent) {
+ element.detachEvent('on' + eventName, handler);
+ }
+ });
+};
+
+ElementCollection.prototype.once = function (eventName, handler) {
+ var removeAfterTrigger = $.bind(function() {
+ this.off(eventName, removeAfterTrigger);
+ handler.apply(null, arguments);
+ }, this);
+
+ return this.on(eventName, removeAfterTrigger);
+};
+
+// @remove
+OTHelpers.on = function(element, eventName, handler) {
+ return $(element).on(eventName, handler);
+};
+
+// @remove
+OTHelpers.off = function(element, eventName, handler) {
+ return $(element).off(eventName, handler);
+};
+
+// @remove
+OTHelpers.once = function (element, eventName, handler) {
+ return $(element).once(eventName, handler);
+};
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./dom_events.js')
+
+(function() {
+
+ var _domReady = typeof(document) === 'undefined' ||
+ document.readyState === 'complete' ||
+ (document.readyState === 'interactive' && document.body),
+
+ _loadCallbacks = [],
+ _unloadCallbacks = [],
+ _domUnloaded = false,
+
+ onDomReady = function() {
+ _domReady = true;
+
+ if (typeof(document) !== 'undefined') {
+ if ( document.addEventListener ) {
+ document.removeEventListener('DOMContentLoaded', onDomReady, false);
+ window.removeEventListener('load', onDomReady, false);
+ } else {
+ document.detachEvent('onreadystatechange', onDomReady);
+ window.detachEvent('onload', onDomReady);
+ }
+ }
+
+ // This is making an assumption about there being only one 'window'
+ // that we care about.
+ OTHelpers.on(window, 'unload', onDomUnload);
+
+ OTHelpers.forEach(_loadCallbacks, function(listener) {
+ listener[0].call(listener[1]);
+ });
+
+ _loadCallbacks = [];
+ },
+
+ onDomUnload = function() {
+ _domUnloaded = true;
+
+ OTHelpers.forEach(_unloadCallbacks, function(listener) {
+ listener[0].call(listener[1]);
+ });
+
+ _unloadCallbacks = [];
+ };
+
+
+ OTHelpers.onDOMLoad = function(cb, context) {
+ if (OTHelpers.isReady()) {
+ cb.call(context);
+ return;
+ }
+
+ _loadCallbacks.push([cb, context]);
+ };
+
+ OTHelpers.onDOMUnload = function(cb, context) {
+ if (this.isDOMUnloaded()) {
+ cb.call(context);
+ return;
+ }
+
+ _unloadCallbacks.push([cb, context]);
+ };
+
+ OTHelpers.isReady = function() {
+ return !_domUnloaded && _domReady;
+ };
+
+ OTHelpers.isDOMUnloaded = function() {
+ return _domUnloaded;
+ };
+
+ if (_domReady) {
+ onDomReady();
+ } else if(typeof(document) !== 'undefined') {
+ if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', onDomReady, false);
+
+ // fallback
+ window.addEventListener( 'load', onDomReady, false );
+
+ } else if (document.attachEvent) {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState === 'complete') onDomReady();
+ });
+
+ // fallback
+ window.attachEvent( 'onload', onDomReady );
+ }
+ }
+
+})();
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+
+OTHelpers.setCookie = function(key, value) {
+ try {
+ localStorage.setItem(key, value);
+ } catch (err) {
+ // Store in browser cookie
+ var date = new Date();
+ date.setTime(date.getTime()+(365*24*60*60*1000));
+ var expires = '; expires=' + date.toGMTString();
+ document.cookie = key + '=' + value + expires + '; path=/';
+ }
+};
+
+OTHelpers.getCookie = function(key) {
+ var value;
+
+ try {
+ value = localStorage.getItem(key);
+ return value;
+ } catch (err) {
+ // Check browser cookies
+ var nameEQ = key + '=';
+ var ca = document.cookie.split(';');
+ for(var i=0;i < ca.length;i++) {
+ var c = ca[i];
+ while (c.charAt(0) === ' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ value = c.substring(nameEQ.length,c.length);
+ }
+ }
+
+ if (value) {
+ return value;
+ }
+ }
+
+ return null;
+};
+
+// tb_require('../helpers.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+
+
+OTHelpers.Collection = function(idField) {
+ var _models = [],
+ _byId = {},
+ _idField = idField || 'id';
+
+ OTHelpers.eventing(this, true);
+
+ var modelProperty = function(model, property) {
+ if(OTHelpers.isFunction(model[property])) {
+ return model[property]();
+ } else {
+ return model[property];
+ }
+ };
+
+ var onModelUpdate = OTHelpers.bind(function onModelUpdate (event) {
+ this.trigger('update', event);
+ this.trigger('update:'+event.target.id, event);
+ }, this),
+
+ onModelDestroy = OTHelpers.bind(function onModelDestroyed (event) {
+ this.remove(event.target, event.reason);
+ }, this);
+
+
+ this.reset = function() {
+ // Stop listening on the models, they are no longer our problem
+ OTHelpers.forEach(_models, function(model) {
+ model.off('updated', onModelUpdate, this);
+ model.off('destroyed', onModelDestroy, this);
+ }, this);
+
+ _models = [];
+ _byId = {};
+ };
+
+ this.destroy = function(reason) {
+ OTHelpers.forEach(_models, function(model) {
+ if(model && typeof model.destroy === 'function') {
+ model.destroy(reason, true);
+ }
+ });
+
+ this.reset();
+ this.off();
+ };
+
+ this.get = function(id) { return id && _byId[id] !== void 0 ? _models[_byId[id]] : void 0; };
+ this.has = function(id) { return id && _byId[id] !== void 0; };
+
+ this.toString = function() { return _models.toString(); };
+
+ // Return only models filtered by either a dict of properties
+ // or a filter function.
+ //
+ // @example Return all publishers with a streamId of 1
+ // OT.publishers.where({streamId: 1})
+ //
+ // @example The same thing but filtering using a filter function
+ // OT.publishers.where(function(publisher) {
+ // return publisher.stream.id === 4;
+ // });
+ //
+ // @example The same thing but filtering using a filter function
+ // executed with a specific this
+ // OT.publishers.where(function(publisher) {
+ // return publisher.stream.id === 4;
+ // }, self);
+ //
+ this.where = function(attrsOrFilterFn, context) {
+ if (OTHelpers.isFunction(attrsOrFilterFn)) {
+ return OTHelpers.filter(_models, attrsOrFilterFn, context);
+ }
+
+ return OTHelpers.filter(_models, function(model) {
+ for (var key in attrsOrFilterFn) {
+ if(!attrsOrFilterFn.hasOwnProperty(key)) {
+ continue;
+ }
+ if (modelProperty(model, key) !== attrsOrFilterFn[key]) return false;
+ }
+
+ return true;
+ });
+ };
+
+ // Similar to where in behaviour, except that it only returns
+ // the first match.
+ this.find = function(attrsOrFilterFn, context) {
+ var filterFn;
+
+ if (OTHelpers.isFunction(attrsOrFilterFn)) {
+ filterFn = attrsOrFilterFn;
+ }
+ else {
+ filterFn = function(model) {
+ for (var key in attrsOrFilterFn) {
+ if(!attrsOrFilterFn.hasOwnProperty(key)) {
+ continue;
+ }
+ if (modelProperty(model, key) !== attrsOrFilterFn[key]) return false;
+ }
+
+ return true;
+ };
+ }
+
+ filterFn = OTHelpers.bind(filterFn, context);
+
+ for (var i=0; i<_models.length; ++i) {
+ if (filterFn(_models[i]) === true) return _models[i];
+ }
+
+ return null;
+ };
+
+ this.forEach = function(fn, context) {
+ OTHelpers.forEach(_models, fn, context);
+ return this;
+ };
+
+ this.add = function(model) {
+ var id = modelProperty(model, _idField);
+
+ if (this.has(id)) {
+ OTHelpers.warn('Model ' + id + ' is already in the collection', _models);
+ return this;
+ }
+
+ _byId[id] = _models.push(model) - 1;
+
+ model.on('updated', onModelUpdate, this);
+ model.on('destroyed', onModelDestroy, this);
+
+ this.trigger('add', model);
+ this.trigger('add:'+id, model);
+
+ return this;
+ };
+
+ this.remove = function(model, reason) {
+ var id = modelProperty(model, _idField);
+
+ _models.splice(_byId[id], 1);
+
+ // Shuffle everyone down one
+ for (var i=_byId[id]; i<_models.length; ++i) {
+ _byId[_models[i][_idField]] = i;
+ }
+
+ delete _byId[id];
+
+ model.off('updated', onModelUpdate, this);
+ model.off('destroyed', onModelDestroy, this);
+
+ this.trigger('remove', model, reason);
+ this.trigger('remove:'+id, model, reason);
+
+ return this;
+ };
+
+ // Retrigger the add event behaviour for each model. You can also
+ // select a subset of models to trigger using the same arguments
+ // as the #where method.
+ this._triggerAddEvents = function() {
+ var models = this.where.apply(this, arguments);
+ OTHelpers.forEach(models, function(model) {
+ this.trigger('add', model);
+ this.trigger('add:' + modelProperty(model, _idField), model);
+ }, this);
+ };
+
+ this.length = function() {
+ return _models.length;
+ };
+};
+
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+
+OTHelpers.castToBoolean = function(value, defaultValue) {
+ if (value === undefined) return defaultValue;
+ return value === 'true' || value === true;
+};
+
+OTHelpers.roundFloat = function(value, places) {
+ return Number(value.toFixed(places));
+};
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+
+(function() {
+
+ var capabilities = {};
+
+ // Registers a new capability type and a function that will indicate
+ // whether this client has that capability.
+ //
+ // OTHelpers.registerCapability('bundle', function() {
+ // return OTHelpers.hasCapabilities('webrtc') &&
+ // (OTHelpers.env.name === 'Chrome' || TBPlugin.isInstalled());
+ // });
+ //
+ OTHelpers.registerCapability = function(name, callback) {
+ var _name = name.toLowerCase();
+
+ if (capabilities.hasOwnProperty(_name)) {
+ OTHelpers.error('Attempted to register', name, 'capability more than once');
+ return;
+ }
+
+ if (!OTHelpers.isFunction(callback)) {
+ OTHelpers.error('Attempted to register', name,
+ 'capability with a callback that isn\' a function');
+ return;
+ }
+
+ memoriseCapabilityTest(_name, callback);
+ };
+
+
+ // Wrap up a capability test in a function that memorises the
+ // result.
+ var memoriseCapabilityTest = function (name, callback) {
+ capabilities[name] = function() {
+ var result = callback();
+ capabilities[name] = function() {
+ return result;
+ };
+
+ return result;
+ };
+ };
+
+ var testCapability = function (name) {
+ return capabilities[name]();
+ };
+
+
+ // Returns true if all of the capability names passed in
+ // exist and are met.
+ //
+ // OTHelpers.hasCapabilities('bundle', 'rtcpMux')
+ //
+ OTHelpers.hasCapabilities = function(/* capability1, capability2, ..., capabilityN */) {
+ var capNames = prototypeSlice.call(arguments),
+ name;
+
+ for (var i=0; i<capNames.length; ++i) {
+ name = capNames[i].toLowerCase();
+
+ if (!capabilities.hasOwnProperty(name)) {
+ OTHelpers.error('hasCapabilities was called with an unknown capability: ' + name);
+ return false;
+ }
+ else if (testCapability(name) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+})();
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./capabilities.js')
+
+// Indicates if the client supports WebSockets.
+OTHelpers.registerCapability('websockets', function() {
+ return 'WebSocket' in window && window.WebSocket !== void 0;
+});
+// tb_require('../helpers.js')
+
+/**@licence
+ * Copyright (c) 2010 Caolan McMahon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ **/
+
+
+(function() {
+
+ OTHelpers.setImmediate = (function() {
+ if (typeof process === 'undefined' || !(process.nextTick)) {
+ if (typeof setImmediate === 'function') {
+ return function (fn) {
+ // not a direct alias for IE10 compatibility
+ setImmediate(fn);
+ };
+ }
+ return function (fn) {
+ setTimeout(fn, 0);
+ };
+ }
+ if (typeof setImmediate !== 'undefined') {
+ return setImmediate;
+ }
+ return process.nextTick;
+ })();
+
+ OTHelpers.iterator = function(tasks) {
+ var makeCallback = function (index) {
+ var fn = function () {
+ if (tasks.length) {
+ tasks[index].apply(null, arguments);
+ }
+ return fn.next();
+ };
+ fn.next = function () {
+ return (index < tasks.length - 1) ? makeCallback(index + 1) : null;
+ };
+ return fn;
+ };
+ return makeCallback(0);
+ };
+
+ OTHelpers.waterfall = function(array, done) {
+ done = done || function () {};
+ if (array.constructor !== Array) {
+ return done(new Error('First argument to waterfall must be an array of functions'));
+ }
+
+ if (!array.length) {
+ return done();
+ }
+
+ var next = function(iterator) {
+ return function (err) {
+ if (err) {
+ done.apply(null, arguments);
+ done = function () {};
+ } else {
+ var args = prototypeSlice.call(arguments, 1),
+ nextFn = iterator.next();
+ if (nextFn) {
+ args.push(next(nextFn));
+ } else {
+ args.push(done);
+ }
+ OTHelpers.setImmediate(function() {
+ iterator.apply(null, args);
+ });
+ }
+ };
+ };
+
+ next(OTHelpers.iterator(array))();
+ };
+
+})();
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+
+(function() {
+
+ var requestAnimationFrame = window.requestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.msRequestAnimationFrame;
+
+ if (requestAnimationFrame) {
+ requestAnimationFrame = OTHelpers.bind(requestAnimationFrame, window);
+ }
+ else {
+ var lastTime = 0;
+ var startTime = OTHelpers.now();
+
+ requestAnimationFrame = function(callback){
+ var currTime = OTHelpers.now();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function() { callback(currTime - startTime); }, timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ }
+
+ OTHelpers.requestAnimationFrame = requestAnimationFrame;
+})();
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+
+(function() {
+
+ // Singleton interval
+ var logQueue = [],
+ queueRunning = false;
+
+ OTHelpers.Analytics = function(loggingUrl, debugFn) {
+
+ var endPoint = loggingUrl + '/logging/ClientEvent',
+ endPointQos = loggingUrl + '/logging/ClientQos',
+
+ reportedErrors = {},
+
+ send = function(data, isQos, callback) {
+ OTHelpers.post((isQos ? endPointQos : endPoint) + '?_=' + OTHelpers.uuid.v4(), {
+ body: data,
+ xdomainrequest: ($.env.name === 'IE' && $.env.version < 10),
+ overrideMimeType: 'text/plain',
+ headers: {
+ 'Accept': 'text/plain',
+ 'Content-Type': 'application/json'
+ }
+ }, callback);
+ },
+
+ throttledPost = function() {
+ // Throttle logs so that they only happen 1 at a time
+ if (!queueRunning && logQueue.length > 0) {
+ queueRunning = true;
+ var curr = logQueue[0];
+
+ // Remove the current item and send the next log
+ var processNextItem = function() {
+ logQueue.shift();
+ queueRunning = false;
+ throttledPost();
+ };
+
+ if (curr) {
+ send(curr.data, curr.isQos, function(err) {
+ if (err) {
+ var debugMsg = 'Failed to send ClientEvent, moving on to the next item.';
+ if (debugFn) {
+ debugFn(debugMsg);
+ } else {
+ console.log(debugMsg);
+ }
+ if (curr.onComplete) {
+ curr.onComplete(err);
+ }
+ // There was an error, move onto the next item
+ }
+ if (curr.onComplete) {
+ curr.onComplete(err);
+ }
+ setTimeout(processNextItem, 50);
+ });
+ }
+ }
+ },
+
+ post = function(data, onComplete, isQos) {
+ logQueue.push({
+ data: data,
+ onComplete: onComplete,
+ isQos: isQos
+ });
+
+ throttledPost();
+ },
+
+ shouldThrottleError = function(code, type, partnerId) {
+ if (!partnerId) return false;
+
+ var errKey = [partnerId, type, code].join('_'),
+ //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId);
+ msgLimit = 100;
+ if (msgLimit === null || msgLimit === undefined) return false;
+ return (reportedErrors[errKey] || 0) <= msgLimit;
+ };
+
+ // Log an error via ClientEvents.
+ //
+ // @param [String] code
+ // @param [String] type
+ // @param [String] message
+ // @param [Hash] details additional error details
+ //
+ // @param [Hash] options the options to log the client event with.
+ // @option options [String] action The name of the Event that we are logging. E.g.
+ // 'TokShowLoaded'. Required.
+ // @option options [String] variation Usually used for Split A/B testing, when you
+ // have multiple variations of the +_action+.
+ // @option options [String] payload The payload. Required.
+ // @option options [String] sessionId The active OpenTok session, if there is one
+ // @option options [String] connectionId The active OpenTok connectionId, if there is one
+ // @option options [String] partnerId
+ // @option options [String] guid ...
+ // @option options [String] streamId ...
+ // @option options [String] section ...
+ // @option options [String] clientVersion ...
+ //
+ // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner
+ // from the dynamic config for X) of each error type for each partner. Reports can be
+ // disabled/enabled globally or on a per partner basis (per partner settings
+ // take precedence) using exceptionLogging.enabled.
+ //
+ this.logError = function(code, type, message, details, options) {
+ if (!options) options = {};
+ var partnerId = options.partnerId;
+
+ if (shouldThrottleError(code, type, partnerId)) {
+ //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' +
+ // code + ' for partner ' + (partnerId || 'No Partner Id'));
+ return;
+ }
+
+ var errKey = [partnerId, type, code].join('_'),
+ payload = details ? details : null;
+
+ reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ?
+ reportedErrors[errKey] + 1 : 1;
+ this.logEvent(OTHelpers.extend(options, {
+ action: type + '.' + code,
+ payload: payload
+ }), false);
+ };
+
+ // Log a client event to the analytics backend.
+ //
+ // @example Logs a client event called 'foo'
+ // this.logEvent({
+ // action: 'foo',
+ // payload: 'bar',
+ // sessionId: sessionId,
+ // connectionId: connectionId
+ // }, false)
+ //
+ // @param [Hash] data the data to log the client event with.
+ // @param [Boolean] qos Whether this is a QoS event.
+ // @param [Boolean] throttle A number specifying the ratio of events to be sent
+ // out of the total number of events (other events are not ignored). If not
+ // set to a number, all events are sent.
+ // @param [Number] completionHandler A completion handler function to call when the
+ // client event POST request succeeds or fails. If it fails, an error
+ // object is passed into the function. (See throttledPost().)
+ //
+ this.logEvent = function(data, qos, throttle, completionHandler) {
+ if (!qos) qos = false;
+
+ if (throttle && !isNaN(throttle)) {
+ if (Math.random() > throttle) {
+ return;
+ }
+ }
+
+ // remove properties that have null values:
+ for (var key in data) {
+ if (data.hasOwnProperty(key) && data[key] === null) {
+ delete data[key];
+ }
+ }
+
+ // TODO: catch error when stringifying an object that has a circular reference
+ data = JSON.stringify(data);
+
+ post(data, completionHandler, qos);
+ };
+
+ // Log a client QOS to the analytics backend.
+ // Log a client QOS to the analytics backend.
+ // @option options [String] action The name of the Event that we are logging.
+ // E.g. 'TokShowLoaded'. Required.
+ // @option options [String] variation Usually used for Split A/B testing, when
+ // you have multiple variations of the +_action+.
+ // @option options [String] payload The payload. Required.
+ // @option options [String] sessionId The active OpenTok session, if there is one
+ // @option options [String] connectionId The active OpenTok connectionId, if there is one
+ // @option options [String] partnerId
+ // @option options [String] guid ...
+ // @option options [String] streamId ...
+ // @option options [String] section ...
+ // @option options [String] clientVersion ...
+ //
+ this.logQOS = function(options) {
+ this.logEvent(options, true);
+ };
+ };
+
+})();
+
+// AJAX helpers
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./ajax/node.js')
+// tb_require('./ajax/browser.js')
+
+OTHelpers.get = function(url, options, callback) {
+ var _options = OTHelpers.extend(options || {}, {
+ method: 'GET'
+ });
+ OTHelpers.request(url, _options, callback);
+};
+
+
+OTHelpers.post = function(url, options, callback) {
+ var _options = OTHelpers.extend(options || {}, {
+ method: 'POST'
+ });
+
+ if(_options.xdomainrequest) {
+ OTHelpers.xdomainRequest(url, _options, callback);
+ } else {
+ OTHelpers.request(url, _options, callback);
+ }
+};
+
/*!
* This is a modified version of Robert Kieffer awesome uuid.js library.
* The only modifications we've made are to remove the Node.js specific
* parts of the code and the UUID version 1 generator (which we don't
* use). The original copyright notice is below.
*
* node-uuid/uuid.js
*
@@ -770,16 +2696,1370 @@ OTHelpers.statable = function(self, poss
// Export RNG options
uuid.mathRNG = mathRNG;
uuid.whatwgRNG = whatwgRNG;
OTHelpers.uuid = uuid;
}());
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('../vendor/uuid.js')
+// tb_require('./dom_events.js')
+
+(function() {
+
+ var _callAsync;
+
+ // Is true if window.postMessage is supported.
+ // This is not quite as simple as just looking for
+ // window.postMessage as some older versions of IE
+ // have a broken implementation of it.
+ //
+ var supportsPostMessage = (function () {
+ if (window.postMessage) {
+ // Check to see if postMessage fires synchronously,
+ // if it does, then the implementation of postMessage
+ // is broken.
+ var postMessageIsAsynchronous = true;
+ var oldOnMessage = window.onmessage;
+ window.onmessage = function() {
+ postMessageIsAsynchronous = false;
+ };
+ window.postMessage('', '*');
+ window.onmessage = oldOnMessage;
+ return postMessageIsAsynchronous;
+ }
+ })();
+
+ if (supportsPostMessage) {
+ var timeouts = [],
+ messageName = 'OTHelpers.' + OTHelpers.uuid.v4() + '.zero-timeout';
+
+ var removeMessageHandler = function() {
+ timeouts = [];
+
+ if(window.removeEventListener) {
+ window.removeEventListener('message', handleMessage);
+ } else if(window.detachEvent) {
+ window.detachEvent('onmessage', handleMessage);
+ }
+ };
+
+ var handleMessage = function(event) {
+ if (event.source === window &&
+ event.data === messageName) {
+
+ if(OTHelpers.isFunction(event.stopPropagation)) {
+ event.stopPropagation();
+ }
+ event.cancelBubble = true;
+
+ if (!window.___othelpers) {
+ removeMessageHandler();
+ return;
+ }
+
+ if (timeouts.length > 0) {
+ var args = timeouts.shift(),
+ fn = args.shift();
+
+ fn.apply(null, args);
+ }
+ }
+ };
+
+ // Ensure that we don't receive messages after unload
+ // Yes, this seems to really happen in IE sometimes, usually
+ // when iFrames are involved.
+ OTHelpers.on(window, 'unload', removeMessageHandler);
+
+ if(window.addEventListener) {
+ window.addEventListener('message', handleMessage, true);
+ } else if(window.attachEvent) {
+ window.attachEvent('onmessage', handleMessage);
+ }
+
+ _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
+ timeouts.push(prototypeSlice.call(arguments));
+ window.postMessage(messageName, '*');
+ };
+ }
+ else {
+ _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
+ var args = prototypeSlice.call(arguments),
+ fn = args.shift();
+
+ setTimeout(function() {
+ fn.apply(null, args);
+ }, 0);
+ };
+ }
+
+
+ // Calls the function +fn+ asynchronously with the current execution.
+ // This is most commonly used to execute something straight after
+ // the current function.
+ //
+ // Any arguments in addition to +fn+ will be passed to +fn+ when it's
+ // called.
+ //
+ // You would use this inplace of setTimeout(fn, 0) type constructs. callAsync
+ // is preferable as it executes in a much more predictable time window,
+ // unlike setTimeout which could execute anywhere from 2ms to several thousand
+ // depending on the browser/context.
+ //
+ // It does this using window.postMessage, although if postMessage won't
+ // work it will fallback to setTimeout.
+ //
+ OTHelpers.callAsync = _callAsync;
+
+
+ // Wraps +handler+ in a function that will execute it asynchronously
+ // so that it doesn't interfere with it's exceution context if it raises
+ // an exception.
+ OTHelpers.createAsyncHandler = function(handler) {
+ return function() {
+ var args = prototypeSlice.call(arguments);
+
+ OTHelpers.callAsync(function() {
+ handler.apply(null, args);
+ });
+ };
+ };
+
+})();
+
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./callbacks.js')
+// tb_require('./dom_events.js')
+
+OTHelpers.createElement = function(nodeName, attributes, children, doc) {
+ var element = (doc || document).createElement(nodeName);
+
+ if (attributes) {
+ for (var name in attributes) {
+ if (typeof(attributes[name]) === 'object') {
+ if (!element[name]) element[name] = {};
+
+ var subAttrs = attributes[name];
+ for (var n in subAttrs) {
+ element[name][n] = subAttrs[n];
+ }
+ }
+ else if (name === 'className') {
+ element.className = attributes[name];
+ }
+ else {
+ element.setAttribute(name, attributes[name]);
+ }
+ }
+ }
+
+ var setChildren = function(child) {
+ if(typeof child === 'string') {
+ element.innerHTML = element.innerHTML + child;
+ } else {
+ element.appendChild(child);
+ }
+ };
+
+ if($.isArray(children)) {
+ $.forEach(children, setChildren);
+ } else if(children) {
+ setChildren(children);
+ }
+
+ return element;
+};
+
+OTHelpers.createButton = function(innerHTML, attributes, events) {
+ var button = $.createElement('button', attributes, innerHTML);
+
+ if (events) {
+ for (var name in events) {
+ if (events.hasOwnProperty(name)) {
+ $.on(button, name, events[name]);
+ }
+ }
+
+ button._boundEvents = events;
+ }
+
+ return button;
+};
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./callbacks.js')
+
+// DOM helpers
+
+var firstElementChild;
+
+// This mess is for IE8
+if( typeof(document) !== 'undefined' &&
+ document.createElement('div').firstElementChild !== void 0 ){
+ firstElementChild = function firstElementChild (parentElement) {
+ return parentElement.firstElementChild;
+ };
+}
+else {
+ firstElementChild = function firstElementChild (parentElement) {
+ var el = parentElement.firstChild;
+
+ do {
+ if(el.nodeType===1){
+ return el;
+ }
+ el = el.nextSibling;
+ } while(el);
+
+ return null;
+ };
+}
+
+
+ElementCollection.prototype.appendTo = function(parentElement) {
+ if (!parentElement) throw new Error('appendTo requires a DOMElement to append to.');
+
+ return this.forEach(function(child) {
+ parentElement.appendChild(child);
+ });
+};
+
+ElementCollection.prototype.append = function() {
+ var parentElement = this.first;
+ if (!parentElement) return this;
+
+ $.forEach(prototypeSlice.call(arguments), function(child) {
+ parentElement.appendChild(child);
+ });
+
+ return this;
+};
+
+ElementCollection.prototype.prepend = function() {
+ if (arguments.length === 0) return this;
+
+ var parentElement = this.first,
+ elementsToPrepend;
+
+ if (!parentElement) return this;
+
+ elementsToPrepend = prototypeSlice.call(arguments);
+
+ if (!firstElementChild(parentElement)) {
+ parentElement.appendChild(elementsToPrepend.shift());
+ }
+
+ $.forEach(elementsToPrepend, function(element) {
+ parentElement.insertBefore(element, firstElementChild(parentElement));
+ });
+
+ return this;
+};
+
+ElementCollection.prototype.after = function(prevElement) {
+ if (!prevElement) throw new Error('after requires a DOMElement to insert after');
+
+ return this.forEach(function(element) {
+ if (element.parentElement) {
+ if (prevElement !== element.parentNode.lastChild) {
+ element.parentElement.insertBefore(element, prevElement);
+ }
+ else {
+ element.parentElement.appendChild(element);
+ }
+ }
+ });
+};
+
+ElementCollection.prototype.before = function(nextElement) {
+ if (!nextElement) {
+ throw new Error('before requires a DOMElement to insert before');
+ }
+
+ return this.forEach(function(element) {
+ if (element.parentElement) {
+ element.parentElement.insertBefore(element, nextElement);
+ }
+ });
+};
+
+ElementCollection.prototype.remove = function () {
+ return this.forEach(function(element) {
+ if (element.parentNode) {
+ element.parentNode.removeChild(element);
+ }
+ });
+};
+
+ElementCollection.prototype.empty = function () {
+ return this.forEach(function(element) {
+ // elements is a "live" NodesList collection. Meaning that the collection
+ // itself will be mutated as we remove elements from the DOM. This means
+ // that "while there are still elements" is safer than "iterate over each
+ // element" as the collection length and the elements indices will be modified
+ // with each iteration.
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+ });
+};
+
+
+// Detects when an element is not part of the document flow because
+// it or one of it's ancesters has display:none.
+ElementCollection.prototype.isDisplayNone = function() {
+ return this.some(function(element) {
+ if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
+ $(element).css('display') === 'none') return true;
+
+ if (element.parentNode && element.parentNode.style) {
+ return $(element.parentNode).isDisplayNone();
+ }
+ });
+};
+
+ElementCollection.prototype.findElementWithDisplayNone = function(element) {
+ return $.findElementWithDisplayNone(element);
+};
+
+
+
+OTHelpers.isElementNode = function(node) {
+ return node && typeof node === 'object' && node.nodeType === 1;
+};
+
+
+// @remove
+OTHelpers.removeElement = function(element) {
+ $(element).remove();
+};
+
+// @remove
+OTHelpers.removeElementById = function(elementId) {
+ return $('#'+elementId).remove();
+};
+
+// @remove
+OTHelpers.removeElementsByType = function(parentElem, type) {
+ return $(type, parentElem).remove();
+};
+
+// @remove
+OTHelpers.emptyElement = function(element) {
+ return $(element).empty();
+};
+
+
+
+
+
+// @remove
+OTHelpers.isDisplayNone = function(element) {
+ return $(element).isDisplayNone();
+};
+
+OTHelpers.findElementWithDisplayNone = function(element) {
+ if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
+ $.css(element, 'display') === 'none') return element;
+
+ if (element.parentNode && element.parentNode.style) {
+ return $.findElementWithDisplayNone(element.parentNode);
+ }
+
+ return null;
+};
+
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./environment.js')
+// tb_require('./dom.js')
+
+OTHelpers.Modal = function(options) {
+
+ OTHelpers.eventing(this, true);
+
+ var callback = arguments[arguments.length - 1];
+
+ if(!OTHelpers.isFunction(callback)) {
+ throw new Error('OTHelpers.Modal2 must be given a callback');
+ }
+
+ if(arguments.length < 2) {
+ options = {};
+ }
+
+ var domElement = document.createElement('iframe');
+
+ domElement.id = options.id || OTHelpers.uuid();
+ domElement.style.position = 'absolute';
+ domElement.style.position = 'fixed';
+ domElement.style.height = '100%';
+ domElement.style.width = '100%';
+ domElement.style.top = '0px';
+ domElement.style.left = '0px';
+ domElement.style.right = '0px';
+ domElement.style.bottom = '0px';
+ domElement.style.zIndex = 1000;
+ domElement.style.border = '0';
+
+ try {
+ domElement.style.backgroundColor = 'rgba(0,0,0,0.2)';
+ } catch (err) {
+ // Old IE browsers don't support rgba and we still want to show the upgrade message
+ // but we just make the background of the iframe completely transparent.
+ domElement.style.backgroundColor = 'transparent';
+ domElement.setAttribute('allowTransparency', 'true');
+ }
+
+ domElement.scrolling = 'no';
+ domElement.setAttribute('scrolling', 'no');
+
+ // This is necessary for IE, as it will not inherit it's doctype from
+ // the parent frame.
+ var frameContent = '<!DOCTYPE html><html><head>' +
+ '<meta http-equiv="x-ua-compatible" content="IE=Edge">' +
+ '<meta http-equiv="Content-type" content="text/html; charset=utf-8">' +
+ '<title></title></head><body></body></html>';
+
+ var wrappedCallback = function() {
+ var doc = domElement.contentDocument || domElement.contentWindow.document;
+
+ if (OTHelpers.env.iframeNeedsLoad) {
+ doc.body.style.backgroundColor = 'transparent';
+ doc.body.style.border = 'none';
+
+ if (OTHelpers.env.name !== 'IE') {
+ // Skip this for IE as we use the bookmarklet workaround
+ // for THAT browser.
+ doc.open();
+ doc.write(frameContent);
+ doc.close();
+ }
+ }
+
+ callback(
+ domElement.contentWindow,
+ doc
+ );
+ };
+
+ document.body.appendChild(domElement);
+
+ if(OTHelpers.env.iframeNeedsLoad) {
+ if (OTHelpers.env.name === 'IE') {
+ // This works around some issues with IE and document.write.
+ // Basically this works by slightly abusing the bookmarklet/scriptlet
+ // functionality that all browsers support.
+ domElement.contentWindow.contents = frameContent;
+ /*jshint scripturl:true*/
+ domElement.src = 'javascript:window["contents"]';
+ /*jshint scripturl:false*/
+ }
+
+ OTHelpers.on(domElement, 'load', wrappedCallback);
+ } else {
+ setTimeout(wrappedCallback, 0);
+ }
+
+ this.close = function() {
+ OTHelpers.removeElement(domElement);
+ this.trigger('closed');
+ this.element = domElement = null;
+ return this;
+ };
+
+ this.element = domElement;
+
+};
+
+/*
+ * getComputedStyle from
+ * https://github.com/jonathantneal/Polyfills-for-IE8/blob/master/getComputedStyle.js
+
+// tb_require('../helpers.js')
+// tb_require('./dom.js')
+
+/*jshint strict: false, eqnull: true, browser:true, smarttabs:true*/
+
+(function() {
+
+ /*jshint eqnull: true, browser: true */
+
+
+ function getPixelSize(element, style, property, fontSize) {
+ var sizeWithSuffix = style[property],
+ size = parseFloat(sizeWithSuffix),
+ suffix = sizeWithSuffix.split(/\d/)[0],
+ rootSize;
+
+ fontSize = fontSize != null ?
+ fontSize : /%|em/.test(suffix) && element.parentElement ?
+ getPixelSize(element.parentElement, element.parentElement.currentStyle, 'fontSize', null) :
+ 16;
+ rootSize = property === 'fontSize' ?
+ fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight;
+
+ return (suffix === 'em') ?
+ size * fontSize : (suffix === 'in') ?
+ size * 96 : (suffix === 'pt') ?
+ size * 96 / 72 : (suffix === '%') ?
+ size / 100 * rootSize : size;
+ }
+
+ function setShortStyleProperty(style, property) {
+ var
+ borderSuffix = property === 'border' ? 'Width' : '',
+ t = property + 'Top' + borderSuffix,
+ r = property + 'Right' + borderSuffix,
+ b = property + 'Bottom' + borderSuffix,
+ l = property + 'Left' + borderSuffix;
+
+ style[property] = (style[t] === style[r] === style[b] === style[l] ? [style[t]]
+ : style[t] === style[b] && style[l] === style[r] ? [style[t], style[r]]
+ : style[l] === style[r] ? [style[t], style[r], style[b]]
+ : [style[t], style[r], style[b], style[l]]).join(' ');
+ }
+
+ function CSSStyleDeclaration(element) {
+ var currentStyle = element.currentStyle,
+ style = this,
+ fontSize = getPixelSize(element, currentStyle, 'fontSize', null),
+ property;
+
+ for (property in currentStyle) {
+ if (/width|height|margin.|padding.|border.+W/.test(property) && style[property] !== 'auto') {
+ style[property] = getPixelSize(element, currentStyle, property, fontSize) + 'px';
+ } else if (property === 'styleFloat') {
+ /*jshint -W069 */
+ style['float'] = currentStyle[property];
+ } else {
+ style[property] = currentStyle[property];
+ }
+ }
+
+ setShortStyleProperty(style, 'margin');
+ setShortStyleProperty(style, 'padding');
+ setShortStyleProperty(style, 'border');
+
+ style.fontSize = fontSize + 'px';
+
+ return style;
+ }
+
+ CSSStyleDeclaration.prototype = {
+ constructor: CSSStyleDeclaration,
+ getPropertyPriority: function () {},
+ getPropertyValue: function ( prop ) {
+ return this[prop] || '';
+ },
+ item: function () {},
+ removeProperty: function () {},
+ setProperty: function () {},
+ getPropertyCSSValue: function () {}
+ };
+
+ function getComputedStyle(element) {
+ return new CSSStyleDeclaration(element);
+ }
+
+
+ OTHelpers.getComputedStyle = function(element) {
+ if(element &&
+ element.ownerDocument &&
+ element.ownerDocument.defaultView &&
+ element.ownerDocument.defaultView.getComputedStyle) {
+ return element.ownerDocument.defaultView.getComputedStyle(element);
+ } else {
+ return getComputedStyle(element);
+ }
+ };
+
+})();
+
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./callbacks.js')
+// tb_require('./dom.js')
+
+var observeStyleChanges = function observeStyleChanges (element, stylesToObserve, onChange) {
+ var oldStyles = {};
+
+ var getStyle = function getStyle(style) {
+ switch (style) {
+ case 'width':
+ return $(element).width();
+
+ case 'height':
+ return $(element).height();
+
+ default:
+ return $(element).css(style);
+ }
+ };
+
+ // get the inital values
+ $.forEach(stylesToObserve, function(style) {
+ oldStyles[style] = getStyle(style);
+ });
+
+ var observer = new MutationObserver(function(mutations) {
+ var changeSet = {};
+
+ $.forEach(mutations, function(mutation) {
+ if (mutation.attributeName !== 'style') return;
+
+ var isHidden = $.isDisplayNone(element);
+
+ $.forEach(stylesToObserve, function(style) {
+ if(isHidden && (style === 'width' || style === 'height')) return;
+
+ var newValue = getStyle(style);
+
+ if (newValue !== oldStyles[style]) {
+ changeSet[style] = [oldStyles[style], newValue];
+ oldStyles[style] = newValue;
+ }
+ });
+ });
+
+ if (!$.isEmpty(changeSet)) {
+ // Do this after so as to help avoid infinite loops of mutations.
+ $.callAsync(function() {
+ onChange.call(null, changeSet);
+ });
+ }
+ });
+
+ observer.observe(element, {
+ attributes:true,
+ attributeFilter: ['style'],
+ childList:false,
+ characterData:false,
+ subtree:false
+ });
+
+ return observer;
+};
+
+var observeNodeOrChildNodeRemoval = function observeNodeOrChildNodeRemoval (element, onChange) {
+ var observer = new MutationObserver(function(mutations) {
+ var removedNodes = [];
+
+ $.forEach(mutations, function(mutation) {
+ if (mutation.removedNodes.length) {
+ removedNodes = removedNodes.concat(prototypeSlice.call(mutation.removedNodes));
+ }
+ });
+
+ if (removedNodes.length) {
+ // Do this after so as to help avoid infinite loops of mutations.
+ $.callAsync(function() {
+ onChange($(removedNodes));
+ });
+ }
+ });
+
+ observer.observe(element, {
+ attributes:false,
+ childList:true,
+ characterData:false,
+ subtree:true
+ });
+
+ return observer;
+};
+
+var observeSize = function (element, onChange) {
+ var previousSize = {
+ width: 0,
+ height: 0
+ };
+
+ var interval = setInterval(function() {
+ var rect = element.getBoundingClientRect();
+ if (previousSize.width !== rect.width || previousSize.height !== rect.height) {
+ onChange(rect, previousSize);
+ previousSize = {
+ width: rect.width,
+ height: rect.height
+ };
+ }
+ }, 1000 / 5);
+
+ return {
+ disconnect: function() {
+ clearInterval(interval);
+ }
+ };
+};
+
+// Allows an +onChange+ callback to be triggered when specific style properties
+// of +element+ are notified. The callback accepts a single parameter, which is
+// a hash where the keys are the style property that changed and the values are
+// an array containing the old and new values ([oldValue, newValue]).
+//
+// Width and Height changes while the element is display: none will not be
+// fired until such time as the element becomes visible again.
+//
+// This function returns the MutationObserver itself. Once you no longer wish
+// to observe the element you should call disconnect on the observer.
+//
+// Observing changes:
+// // observe changings to the width and height of object
+// dimensionsObserver = OTHelpers(object).observeStyleChanges(,
+// ['width', 'height'], function(changeSet) {
+// OT.debug("The new width and height are " +
+// changeSet.width[1] + ',' + changeSet.height[1]);
+// });
+//
+// Cleaning up
+// // stop observing changes
+// dimensionsObserver.disconnect();
+// dimensionsObserver = null;
+//
+ElementCollection.prototype.observeStyleChanges = function(stylesToObserve, onChange) {
+ var observers = [];
+
+ this.forEach(function(element) {
+ observers.push(
+ observeStyleChanges(element, stylesToObserve, onChange)
+ );
+ });
+
+ return observers;
+};
+
+// trigger the +onChange+ callback whenever
+// 1. +element+ is removed
+// 2. or an immediate child of +element+ is removed.
+//
+// This function returns the MutationObserver itself. Once you no longer wish
+// to observe the element you should call disconnect on the observer.
+//
+// Observing changes:
+// // observe changings to the width and height of object
+// nodeObserver = OTHelpers(object).observeNodeOrChildNodeRemoval(function(removedNodes) {
+// OT.debug("Some child nodes were removed");
+// removedNodes.forEach(function(node) {
+// OT.debug(node);
+// });
+// });
+//
+// Cleaning up
+// // stop observing changes
+// nodeObserver.disconnect();
+// nodeObserver = null;
+//
+ElementCollection.prototype.observeNodeOrChildNodeRemoval = function(onChange) {
+ var observers = [];
+
+ this.forEach(function(element) {
+ observers.push(
+ observeNodeOrChildNodeRemoval(element, onChange)
+ );
+ });
+
+ return observers;
+};
+
+// trigger the +onChange+ callback whenever the width or the height of the element changes
+//
+// Once you no longer wish to observe the element you should call disconnect on the observer.
+//
+// Observing changes:
+// // observe changings to the width and height of object
+// sizeObserver = OTHelpers(object).observeSize(function(newSize, previousSize) {
+// OT.debug("The new width and height are " +
+// newSize.width + ',' + newSize.height);
+// });
+//
+// Cleaning up
+// // stop observing changes
+// sizeObserver.disconnect();
+// sizeObserver = null;
+//
+ElementCollection.prototype.observeSize = function(onChange) {
+ var observers = [];
+
+ this.forEach(function(element) {
+ observers.push(
+ observeSize(element, onChange)
+ );
+ });
+
+ return observers;
+};
+
+
+// @remove
+OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) {
+ return $(element).observeStyleChanges(stylesToObserve, onChange)[0];
+};
+
+// @remove
+OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) {
+ return $(element).observeNodeOrChildNodeRemoval(onChange)[0];
+};
+
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./dom.js')
+// tb_require('./capabilities.js')
+
+// Returns true if the client supports element.classList
+OTHelpers.registerCapability('classList', function() {
+ return (typeof document !== 'undefined') && ('classList' in document.createElement('a'));
+});
+
+
+function hasClass (element, className) {
+ if (!className) return false;
+
+ if ($.hasCapabilities('classList')) {
+ return element.classList.contains(className);
+ }
+
+ return element.className.indexOf(className) > -1;
+}
+
+function toggleClasses (element, classNames) {
+ if (!classNames || classNames.length === 0) return;
+
+ // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
+ if (element.nodeType !== 1) {
+ return;
+ }
+
+ var numClasses = classNames.length,
+ i = 0;
+
+ if ($.hasCapabilities('classList')) {
+ for (; i<numClasses; ++i) {
+ element.classList.toggle(classNames[i]);
+ }
+
+ return;
+ }
+
+ var className = (' ' + element.className + ' ').replace(/[\s+]/, ' ');
+
+
+ for (; i<numClasses; ++i) {
+ if (hasClass(element, classNames[i])) {
+ className = className.replace(' ' + classNames[i] + ' ', ' ');
+ }
+ else {
+ className += classNames[i] + ' ';
+ }
+ }
+
+ element.className = $.trim(className);
+}
+
+function addClass (element, classNames) {
+ if (!classNames || classNames.length === 0) return;
+
+ // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
+ if (element.nodeType !== 1) {
+ return;
+ }
+
+ var numClasses = classNames.length,
+ i = 0;
+
+ if ($.hasCapabilities('classList')) {
+ for (; i<numClasses; ++i) {
+ element.classList.add(classNames[i]);
+ }
+
+ return;
+ }
+
+ // Here's our fallback to browsers that don't support element.classList
+
+ if (!element.className && classNames.length === 1) {
+ element.className = classNames.join(' ');
+ }
+ else {
+ var setClass = ' ' + element.className + ' ';
+
+ for (; i<numClasses; ++i) {
+ if ( !~setClass.indexOf( ' ' + classNames[i] + ' ')) {
+ setClass += classNames[i] + ' ';
+ }
+ }
+
+ element.className = $.trim(setClass);
+ }
+}
+
+function removeClass (element, classNames) {
+ if (!classNames || classNames.length === 0) return;
+
+ // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
+ if (element.nodeType !== 1) {
+ return;
+ }
+
+ var numClasses = classNames.length,
+ i = 0;
+
+ if ($.hasCapabilities('classList')) {
+ for (; i<numClasses; ++i) {
+ element.classList.remove(classNames[i]);
+ }
+
+ return;
+ }
+
+ var className = (' ' + element.className + ' ').replace(/[\s+]/, ' ');
+
+ for (; i<numClasses; ++i) {
+ className = className.replace(' ' + classNames[i] + ' ', ' ');
+ }
+
+ element.className = $.trim(className);
+}
+
+ElementCollection.prototype.addClass = function (value) {
+ if (value) {
+ var classNames = $.trim(value).split(/\s+/);
+
+ this.forEach(function(element) {
+ addClass(element, classNames);
+ });
+ }
+
+ return this;
+};
+
+ElementCollection.prototype.removeClass = function (value) {
+ if (value) {
+ var classNames = $.trim(value).split(/\s+/);
+
+ this.forEach(function(element) {
+ removeClass(element, classNames);
+ });
+ }
+
+ return this;
+};
+
+ElementCollection.prototype.toggleClass = function (value) {
+ if (value) {
+ var classNames = $.trim(value).split(/\s+/);
+
+ this.forEach(function(element) {
+ toggleClasses(element, classNames);
+ });
+ }
+
+ return this;
+};
+
+ElementCollection.prototype.hasClass = function (value) {
+ return this.some(function(element) {
+ return hasClass(element, value);
+ });
+};
+
+
+// @remove
+OTHelpers.addClass = function(element, className) {
+ return $(element).addClass(className);
+};
+
+// @remove
+OTHelpers.removeClass = function(element, value) {
+ return $(element).removeClass(value);
+};
+
+
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./dom.js')
+// tb_require('./capabilities.js')
+
+var specialDomProperties = {
+ 'for': 'htmlFor',
+ 'class': 'className'
+};
+
+
+// Gets or sets the attribute called +name+ for the first element in the collection
+ElementCollection.prototype.attr = function (name, value) {
+ if (OTHelpers.isObject(name)) {
+ var actualName;
+
+ for (var key in name) {
+ actualName = specialDomProperties[key] || key;
+ this.first.setAttribute(actualName, name[key]);
+ }
+ }
+ else if (value === void 0) {
+ return this.first.getAttribute(specialDomProperties[name] || name);
+ }
+ else {
+ this.first.setAttribute(specialDomProperties[name] || name, value);
+ }
+
+ return this;
+};
+
+
+// Removes an attribute called +name+ for the every element in the collection.
+ElementCollection.prototype.removeAttr = function (name) {
+ var actualName = specialDomProperties[name] || name;
+
+ this.forEach(function(element) {
+ element.removeAttribute(actualName);
+ });
+
+ return this;
+};
+
+
+// Gets, and optionally sets, the html body of the first element
+// in the collection. If the +html+ is provided then the first
+// element's html body will be replaced with it.
+//
+ElementCollection.prototype.html = function (html) {
+ if (html !== void 0) {
+ this.first.innerHTML = html;
+ }
+
+ return this.first.innerHTML;
+};
+
+
+// Centers +element+ within the window. You can pass through the width and height
+// if you know it, if you don't they will be calculated for you.
+ElementCollection.prototype.center = function (width, height) {
+ var $element;
+
+ this.forEach(function(element) {
+ $element = $(element);
+ if (!width) width = parseInt($element.width(), 10);
+ if (!height) height = parseInt($element.height(), 10);
+
+ var marginLeft = -0.5 * width + 'px';
+ var marginTop = -0.5 * height + 'px';
+
+ $element.css('margin', marginTop + ' 0 0 ' + marginLeft)
+ .addClass('OT_centered');
+ });
+
+ return this;
+};
+
+
+// @remove
+// Centers +element+ within the window. You can pass through the width and height
+// if you know it, if you don't they will be calculated for you.
+OTHelpers.centerElement = function(element, width, height) {
+ return $(element).center(width, height);
+};
+
+ /**
+ * Methods to calculate element widths and heights.
+ */
+(function() {
+
+ var _width = function(element) {
+ if (element.offsetWidth > 0) {
+ return element.offsetWidth + 'px';
+ }
+
+ return $(element).css('width');
+ },
+
+ _height = function(element) {
+ if (element.offsetHeight > 0) {
+ return element.offsetHeight + 'px';
+ }
+
+ return $(element).css('height');
+ };
+
+ ElementCollection.prototype.width = function (newWidth) {
+ if (newWidth) {
+ this.css('width', newWidth);
+ return this;
+ }
+ else {
+ if (this.isDisplayNone()) {
+ return this.makeVisibleAndYield(function(element) {
+ return _width(element);
+ })[0];
+ }
+ else {
+ return _width(this.get(0));
+ }
+ }
+ };
+
+ ElementCollection.prototype.height = function (newHeight) {
+ if (newHeight) {
+ this.css('height', newHeight);
+ return this;
+ }
+ else {
+ if (this.isDisplayNone()) {
+ // We can't get the height, probably since the element is hidden.
+ return this.makeVisibleAndYield(function(element) {
+ return _height(element);
+ })[0];
+ }
+ else {
+ return _height(this.get(0));
+ }
+ }
+ };
+
+ // @remove
+ OTHelpers.width = function(element, newWidth) {
+ var ret = $(element).width(newWidth);
+ return newWidth ? OTHelpers : ret;
+ };
+
+ // @remove
+ OTHelpers.height = function(element, newHeight) {
+ var ret = $(element).height(newHeight);
+ return newHeight ? OTHelpers : ret;
+ };
+
+})();
+
+
+// CSS helpers helpers
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./dom.js')
+// tb_require('./getcomputedstyle.js')
+
+(function() {
+
+ var displayStateCache = {},
+ defaultDisplays = {};
+
+ var defaultDisplayValueForElement = function (element) {
+ if (defaultDisplays[element.ownerDocument] &&
+ defaultDisplays[element.ownerDocument][element.nodeName]) {
+ return defaultDisplays[element.ownerDocument][element.nodeName];
+ }
+
+ if (!defaultDisplays[element.ownerDocument]) defaultDisplays[element.ownerDocument] = {};
+
+ // We need to know what display value to use for this node. The easiest way
+ // is to actually create a node and read it out.
+ var testNode = element.ownerDocument.createElement(element.nodeName),
+ defaultDisplay;
+
+ element.ownerDocument.body.appendChild(testNode);
+ defaultDisplay = defaultDisplays[element.ownerDocument][element.nodeName] =
+ $(testNode).css('display');
+
+ $(testNode).remove();
+ testNode = null;
+
+ return defaultDisplay;
+ };
+
+ var isHidden = function (element) {
+ var computedStyle = $.getComputedStyle(element);
+ return computedStyle.getPropertyValue('display') === 'none';
+ };
+
+ var setCssProperties = function (element, hash) {
+ var style = element.style;
+
+ for (var cssName in hash) {
+ if (hash.hasOwnProperty(cssName)) {
+ style[cssName] = hash[cssName];
+ }
+ }
+ };
+
+ var setCssProperty = function (element, name, value) {
+ element.style[name] = value;
+ };
+
+ var getCssProperty = function (element, unnormalisedName) {
+ // Normalise vendor prefixes from the form MozTranform to -moz-transform
+ // except for ms extensions, which are weird...
+
+ var name = unnormalisedName.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(),
+ computedStyle = $.getComputedStyle(element),
+ currentValue = computedStyle.getPropertyValue(name);
+
+ if (currentValue === '') {
+ currentValue = element.style[name];
+ }
+
+ return currentValue;
+ };
+
+ var applyCSS = function(element, styles, callback) {
+ var oldStyles = {},
+ name,
+ ret;
+
+ // Backup the old styles
+ for (name in styles) {
+ if (styles.hasOwnProperty(name)) {
+ // We intentionally read out of style here, instead of using the css
+ // helper. This is because the css helper uses querySelector and we
+ // only want to pull values out of the style (domeElement.style) hash.
+ oldStyles[name] = element.style[name];
+
+ $(element).css(name, styles[name]);
+ }
+ }
+
+ ret = callback(element);
+
+ // Restore the old styles
+ for (name in styles) {
+ if (styles.hasOwnProperty(name)) {
+ $(element).css(name, oldStyles[name] || '');
+ }
+ }
+
+ return ret;
+ };
+
+ ElementCollection.prototype.show = function() {
+ return this.forEach(function(element) {
+ var display = element.style.display;
+
+ if (display === '' || display === 'none') {
+ element.style.display = displayStateCache[element] || '';
+ delete displayStateCache[element];
+ }
+
+ if (isHidden(element)) {
+ // It's still hidden so there's probably a stylesheet that declares this
+ // element as display:none;
+ displayStateCache[element] = 'none';
+
+ element.style.display = defaultDisplayValueForElement(element);
+ }
+ });
+ };
+
+ ElementCollection.prototype.hide = function() {
+ return this.forEach(function(element) {
+ if (element.style.display === 'none') return;
+
+ displayStateCache[element] = element.style.display;
+ element.style.display = 'none';
+ });
+ };
+
+ ElementCollection.prototype.css = function(nameOrHash, value) {
+ if (this.length === 0) return;
+
+ if (typeof(nameOrHash) !== 'string') {
+
+ return this.forEach(function(element) {
+ setCssProperties(element, nameOrHash);
+ });
+
+ } else if (value !== undefined) {
+
+ return this.forEach(function(element) {
+ setCssProperty(element, nameOrHash, value);
+ });
+
+ } else {
+ return getCssProperty(this.first, nameOrHash, value);
+ }
+ };
+
+ // Apply +styles+ to +element+ while executing +callback+, restoring the previous
+ // styles after the callback executes.
+ ElementCollection.prototype.applyCSS = function (styles, callback) {
+ var results = [];
+
+ this.forEach(function(element) {
+ results.push(applyCSS(element, styles, callback));
+ });
+
+ return results;
+ };
+
+
+ // Make +element+ visible while executing +callback+.
+ ElementCollection.prototype.makeVisibleAndYield = function (callback) {
+ var hiddenVisually = {
+ display: 'block',
+ visibility: 'hidden'
+ },
+ results = [];
+
+ this.forEach(function(element) {
+ // find whether it's the element or an ancestor that's display none and
+ // then apply to whichever it is
+ var targetElement = $.findElementWithDisplayNone(element);
+ if (!targetElement) {
+ results.push(void 0);
+ }
+ else {
+ results.push(
+ applyCSS(targetElement, hiddenVisually, callback)
+ );
+ }
+ });
+
+ return results;
+ };
+
+
+ // @remove
+ OTHelpers.show = function(element) {
+ return $(element).show();
+ };
+
+ // @remove
+ OTHelpers.hide = function(element) {
+ return $(element).hide();
+ };
+
+ // @remove
+ OTHelpers.css = function(element, nameOrHash, value) {
+ return $(element).css(nameOrHash, value);
+ };
+
+ // @remove
+ OTHelpers.applyCSS = function(element, styles, callback) {
+ return $(element).applyCSS(styles, callback);
+ };
+
+ // @remove
+ OTHelpers.makeVisibleAndYield = function(element, callback) {
+ return $(element).makeVisibleAndYield(callback);
+ };
+
+})();
+
// tb_require('../helpers.js')
/*!
* @overview RSVP - a tiny implementation of Promises/A+.
* @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
* @license Licensed under MIT license
* See https://raw.githubusercontent.com/tildeio/rsvp.js/master/LICENSE
* @version 3.0.16
@@ -2440,1695 +5720,16 @@ OTHelpers.statable = function(self, poss
'async': lib$rsvp$$async
};
OTHelpers.RSVP = lib$rsvp$umd$$RSVP;
}).call(this);
/* jshint ignore:end */
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-
-
-var getErrorLocation;
-
-// Properties that we'll acknowledge from the JS Error object
-var safeErrorProps = [
- 'description',
- 'fileName',
- 'lineNumber',
- 'message',
- 'name',
- 'number',
- 'stack'
-];
-
-
-// OTHelpers.Error
-//
-// A construct to contain error information that also helps with extracting error
-// context, such as stack trace.
-//
-// @constructor
-// @memberof OTHelpers
-// @method Error
-//
-// @param {String} message
-// Optional. The error message
-//
-// @param {Object} props
-// Optional. A dictionary of properties containing extra Error info.
-//
-//
-// @example Create a simple error with juts a custom message
-// var error = new OTHelpers.Error('Something Broke!');
-// error.message === 'Something Broke!';
-//
-// @example Create an Error with a message and a name
-// var error = new OTHelpers.Error('Something Broke!', 'FooError');
-// error.message === 'Something Broke!';
-// error.name === 'FooError';
-//
-// @example Create an Error with a message, name, and custom properties
-// var error = new OTHelpers.Error('Something Broke!', 'FooError', {
-// foo: 'bar',
-// listOfImportantThings: [1,2,3,4]
-// });
-// error.message === 'Something Broke!';
-// error.name === 'FooError';
-// error.foo === 'bar';
-// error.listOfImportantThings == [1,2,3,4];
-//
-// @example Create an Error from a Javascript Error
-// var error = new OTHelpers.Error(domSyntaxError);
-// error.message === domSyntaxError.message;
-// error.name === domSyntaxError.name === 'SyntaxError';
-// // ...continues for each properties of domSyntaxError
-//
-// @references
-// * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
-// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack
-// * http://www.w3.org/TR/dom/#interface-domerror
-//
-//
-// @todo
-// * update usage in OTMedia
-// * replace error handling in OT.js
-// * normalise stack behaviour under Chrome/Node/Safari with other browsers
-// * unit test for stack parsing
-//
-//
-OTHelpers.Error = function (message, name, props) {
- switch (arguments.length) {
- case 1:
- if ($.isObject(message)) {
- props = message;
- name = void 0;
- message = void 0;
- }
- // Otherwise it's the message
- break;
-
- case 2:
- if ($.isObject(name)) {
- props = name;
- name = void 0;
- }
- // Otherwise name is actually the name
-
- break;
- }
-
- if ( props instanceof Error) {
- // Special handling of this due to Chrome weirdness. It seems that
- // properties of the Error object, and it's children, are not
- // enumerable in Chrome?
- for (var i = 0, num = safeErrorProps.length; i < num; ++i) {
- this[safeErrorProps[i]] = props[safeErrorProps[i]];
- }
- }
- else if ( $.isObject(props)) {
- // Use an custom properties that are provided
- for (var key in props) {
- if (props.hasOwnProperty(key)) {
- this[key] = props[key];
- }
- }
- }
-
- // If any of the fundamental properties are missing then try and
- // extract them.
- if ( !(this.fileName && this.lineNumber && this.columnNumber && this.stack) ) {
- var err = getErrorLocation();
-
- if (!this.fileName && err.fileName) {
- this.fileName = err.fileName;
- }
-
- if (!this.lineNumber && err.lineNumber) {
- this.lineNumber = err.lineNumber;
- }
-
- if (!this.columnNumber && err.columnNumber) {
- this.columnNumber = err.columnNumber;
- }
-
- if (!this.stack && err.stack) {
- this.stack = err.stack;
- }
- }
-
- if (!this.message && message) this.message = message;
- if (!this.name && name) this.name = name;
-};
-
-OTHelpers.Error.prototype.toString =
-OTHelpers.Error.prototype.valueOf = function() {
- var locationDetails = '';
- if (this.fileName) locationDetails += ' ' + this.fileName;
- if (this.lineNumber) {
- locationDetails += ' ' + this.lineNumber;
- if (this.columnNumber) locationDetails += ':' + this.columnNumber;
- }
-
- return '<' + (this.name ? this.name + ' ' : '') + this.message + locationDetails + '>';
-};
-
-
-// Normalise err.stack so that it is the same format as the other browsers
-// We skip the first two frames so that we don't capture getErrorLocation() and
-// the callee.
-//
-// Used by Environments that support the StackTrace API. (Chrome, Node, Opera)
-//
-var prepareStackTrace = function prepareStackTrace (_, stack){
- return $.map(stack.slice(2), function(frame) {
- var _f = {
- fileName: frame.getFileName(),
- linenumber: frame.getLineNumber(),
- columnNumber: frame.getColumnNumber()
- };
-
- if (frame.getFunctionName()) _f.functionName = frame.getFunctionName();
- if (frame.getMethodName()) _f.methodName = frame.getMethodName();
- if (frame.getThis()) _f.self = frame.getThis();
-
- return _f;
- });
-};
-
-
-// Black magic to retrieve error location info for various environments
-getErrorLocation = function getErrorLocation () {
- var info = {},
- callstack,
- errLocation,
- err;
-
- switch ($.env.name) {
- case 'Firefox':
- case 'Safari':
- case 'IE':
-
- if ($.env.name === 'IE') {
- err = new Error();
- }
- else {
- try {
- window.call.js.is.explody;
- }
- catch(e) { err = e; }
- }
-
- callstack = err.stack.split('\n');
-
- //Remove call to getErrorLocation() and the callee
- callstack.shift();
- callstack.shift();
-
- info.stack = callstack;
-
- if ($.env.name === 'IE') {
- // IE also includes the error message in it's stack trace
- info.stack.shift();
-
- // each line begins with some amounts of spaces and 'at', we remove
- // these to normalise with the other browsers.
- info.stack = $.map(callstack, function(call) {
- return call.replace(/^\s+at\s+/g, '');
- });
- }
-
- errLocation = /@(.+?):([0-9]+)(:([0-9]+))?$/.exec(callstack[0]);
- if (errLocation) {
- info.fileName = errLocation[1];
- info.lineNumber = parseInt(errLocation[2], 10);
- if (errLocation.length > 3) info.columnNumber = parseInt(errLocation[4], 10);
- }
- break;
-
- case 'Chrome':
- case 'Node':
- case 'Opera':
- var currentPST = Error.prepareStackTrace;
- Error.prepareStackTrace = prepareStackTrace;
- err = new Error();
- info.stack = err.stack;
- Error.prepareStackTrace = currentPST;
-
- var topFrame = info.stack[0];
- info.lineNumber = topFrame.lineNumber;
- info.columnNumber = topFrame.columnNumber;
- info.fileName = topFrame.fileName;
- if (topFrame.functionName) info.functionName = topFrame.functionName;
- if (topFrame.methodName) info.methodName = topFrame.methodName;
- if (topFrame.self) info.self = topFrame.self;
- break;
-
- default:
- err = new Error();
- if (err.stack) info.stack = err.stack.split('\n');
- break;
- }
-
- if (err.message) info.message = err.message;
- return info;
-};
-
-
-/*jshint browser:true, smarttabs:true*/
-/* global process */
-
-// tb_require('../helpers.js')
-
-
-// OTHelpers.env
-//
-// Contains information about the current environment.
-// * **OTHelpers.env.name** The name of the Environment (Chrome, FF, Node, etc)
-// * **OTHelpers.env.version** Usually a Float, except in Node which uses a String
-// * **OTHelpers.env.userAgent** The raw user agent
-// * **OTHelpers.env.versionGreaterThan** A helper method that returns true if the
-// current version is greater than the argument
-//
-// Example
-// if (OTHelpers.env.versionGreaterThan('0.10.30')) {
-// // do something
-// }
-//
-(function() {
- // @todo make exposing userAgent unnecessary
- var version = -1;
-
- // Returns true if otherVersion is greater than the current environment
- // version.
- var versionGEThan = function versionGEThan (otherVersion) {
- if (otherVersion === version) return true;
-
- if (typeof(otherVersion) === 'number' && typeof(version) === 'number') {
- return otherVersion > version;
- }
-
- // The versions have multiple components (i.e. 0.10.30) and
- // must be compared piecewise.
- // Note: I'm ignoring the case where one version has multiple
- // components and the other doesn't.
- var v1 = otherVersion.split('.'),
- v2 = version.split('.'),
- versionLength = (v1.length > v2.length ? v2 : v1).length;
-
- for (var i = 0; i < versionLength; ++i) {
- if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
- return true;
- }
- }
-
- // Special case, v1 has extra components but the initial components
- // were identical, we assume this means newer but it might also mean
- // that someone changed versioning systems.
- if (i < v1.length) {
- return true;
- }
-
- return false;
- };
-
- var env = function() {
- if (typeof(process) !== 'undefined' &&
- typeof(process.versions) !== 'undefined' &&
- typeof(process.versions.node) === 'string') {
-
- version = process.versions.node;
- if (version.substr(1) === 'v') version = version.substr(1);
-
- // Special casing node to avoid gating window.navigator.
- // Version will be a string rather than a float.
- return {
- name: 'Node',
- version: version,
- userAgent: 'Node ' + version,
- iframeNeedsLoad: false,
- versionGreaterThan: versionGEThan
- };
- }
-
- var userAgent = window.navigator.userAgent.toLowerCase(),
- appName = window.navigator.appName,
- navigatorVendor,
- name = 'unknown';
-
- if (userAgent.indexOf('opera') > -1 || userAgent.indexOf('opr') > -1) {
- name = 'Opera';
-
- if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (userAgent.indexOf('firefox') > -1) {
- name = 'Firefox';
-
- if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (appName === 'Microsoft Internet Explorer') {
- // IE 10 and below
- name = 'IE';
-
- if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) {
- // IE 11+
-
- name = 'IE';
-
- if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (userAgent.indexOf('chrome') > -1) {
- name = 'Chrome';
-
- if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if ((navigatorVendor = window.navigator.vendor) &&
- navigatorVendor.toLowerCase().indexOf('apple') > -1) {
- name = 'Safari';
-
- if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
- }
-
- return {
- name: name,
- version: version,
- userAgent: window.navigator.userAgent,
- iframeNeedsLoad: userAgent.indexOf('webkit') < 0,
- versionGreaterThan: versionGEThan
- };
- }();
-
-
- OTHelpers.env = env;
-
- OTHelpers.browser = function() {
- return OTHelpers.env.name;
- };
-
- OTHelpers.browserVersion = function() {
- return OTHelpers.env;
- };
-
-})();
-// tb_require('../../environment.js')
-// tb_require('./event.js')
-
-var nodeEventing;
-
-if($.env.name === 'Node') {
- (function() {
- var EventEmitter = require('events').EventEmitter,
- util = require('util');
-
- // container for the EventEmitter behaviour. This prevents tight coupling
- // caused by accidentally bleeding implementation details and API into whatever
- // objects nodeEventing is applied to.
- var NodeEventable = function NodeEventable () {
- EventEmitter.call(this);
-
- this.events = {};
- };
- util.inherits(NodeEventable, EventEmitter);
-
-
- nodeEventing = function nodeEventing (/* self */) {
- var api = new NodeEventable(),
- _on = api.on,
- _off = api.removeListener;
-
-
- api.addListeners = function (eventNames, handler, context, closure) {
- var listener = {handler: handler};
- if (context) listener.context = context;
- if (closure) listener.closure = closure;
-
- $.forEach(eventNames, function(name) {
- if (!api.events[name]) api.events[name] = [];
- api.events[name].push(listener);
-
- _on(name, handler);
-
- var addedListener = name + ':added';
- if (api.events[addedListener]) {
- api.emit(addedListener, api.events[name].length);
- }
- });
- };
-
- api.removeAllListenersNamed = function (eventNames) {
- var _eventNames = eventNames.split(' ');
- api.removeAllListeners(_eventNames);
-
- $.forEach(_eventNames, function(name) {
- if (api.events[name]) delete api.events[name];
- });
- };
-
- api.removeListeners = function (eventNames, handler, closure) {
- function filterHandlers(listener) {
- return !(listener.handler === handler && listener.closure === closure);
- }
-
- $.forEach(eventNames.split(' '), function(name) {
- if (api.events[name]) {
- _off(name, handler);
- api.events[name] = $.filter(api.events[name], filterHandlers);
- if (api.events[name].length === 0) delete api.events[name];
-
- var removedListener = name + ':removed';
- if (api.events[removedListener]) {
- api.emit(removedListener, api.events[name] ? api.events[name].length : 0);
- }
- }
- });
- };
-
- api.removeAllListeners = function () {
- api.events = {};
- api.removeAllListeners();
- };
-
- api.dispatchEvent = function(event, defaultAction) {
- this.emit(event.type, event);
-
- if (defaultAction) {
- defaultAction.call(null, event);
- }
- };
-
- api.trigger = $.bind(api.emit, api);
-
-
- return api;
- };
- })();
-}
-
-// tb_require('../../environment.js')
-// tb_require('./event.js')
-
-var browserEventing;
-
-if($.env.name !== 'Node') {
-
- browserEventing = function browserEventing (self, syncronous) {
- var api = {
- events: {}
- };
-
-
- // Call the defaultAction, passing args
- function executeDefaultAction(defaultAction, args) {
- if (!defaultAction) return;
-
- defaultAction.apply(null, args.slice());
- }
-
- // Execute each handler in +listeners+ with +args+.
- //
- // Each handler will be executed async. On completion the defaultAction
- // handler will be executed with the args.
- //
- // @param [Array] listeners
- // An array of functions to execute. Each will be passed args.
- //
- // @param [Array] args
- // An array of arguments to execute each function in +listeners+ with.
- //
- // @param [String] name
- // The name of this event.
- //
- // @param [Function, Null, Undefined] defaultAction
- // An optional function to execute after every other handler. This will execute even
- // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
- // handler would.
- //
- // @return Undefined
- //
- function executeListenersAsyncronously(name, args, defaultAction) {
- var listeners = api.events[name];
- if (!listeners || listeners.length === 0) return;
-
- var listenerAcks = listeners.length;
-
- $.forEach(listeners, function(listener) { // , index
- function filterHandlers(_listener) {
- return _listener.handler === listener.handler;
- }
-
- // We run this asynchronously so that it doesn't interfere with execution if an
- // error happens
- $.callAsync(function() {
- try {
- // have to check if the listener has not been removed
- if (api.events[name] && $.some(api.events[name], filterHandlers)) {
- (listener.closure || listener.handler).apply(listener.context || null, args);
- }
- }
- finally {
- listenerAcks--;
-
- if (listenerAcks === 0) {
- executeDefaultAction(defaultAction, args);
- }
- }
- });
- });
- }
-
-
- // This is identical to executeListenersAsyncronously except that handlers will
- // be executed syncronously.
- //
- // On completion the defaultAction handler will be executed with the args.
- //
- // @param [Array] listeners
- // An array of functions to execute. Each will be passed args.
- //
- // @param [Array] args
- // An array of arguments to execute each function in +listeners+ with.
- //
- // @param [String] name
- // The name of this event.
- //
- // @param [Function, Null, Undefined] defaultAction
- // An optional function to execute after every other handler. This will execute even
- // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
- // handler would.
- //
- // @return Undefined
- //
- function executeListenersSyncronously(name, args) { // defaultAction is not used
- var listeners = api.events[name];
- if (!listeners || listeners.length === 0) return;
-
- $.forEach(listeners, function(listener) { // index
- (listener.closure || listener.handler).apply(listener.context || null, args);
- });
- }
-
- var executeListeners = syncronous === true ?
- executeListenersSyncronously : executeListenersAsyncronously;
-
-
- api.addListeners = function (eventNames, handler, context, closure) {
- var listener = {handler: handler};
- if (context) listener.context = context;
- if (closure) listener.closure = closure;
-
- $.forEach(eventNames, function(name) {
- if (!api.events[name]) api.events[name] = [];
- api.events[name].push(listener);
-
- var addedListener = name + ':added';
- if (api.events[addedListener]) {
- executeListeners(addedListener, [api.events[name].length]);
- }
- });
- };
-
- api.removeListeners = function(eventNames, handler, context) {
- function filterListeners(listener) {
- var isCorrectHandler = (
- listener.handler.originalHandler === handler ||
- listener.handler === handler
- );
-
- return !(isCorrectHandler && listener.context === context);
- }
-
- $.forEach(eventNames, function(name) {
- if (api.events[name]) {
- api.events[name] = $.filter(api.events[name], filterListeners);
- if (api.events[name].length === 0) delete api.events[name];
-
- var removedListener = name + ':removed';
- if (api.events[ removedListener]) {
- executeListeners(removedListener, [api.events[name] ? api.events[name].length : 0]);
- }
- }
- });
- };
-
- api.removeAllListenersNamed = function (eventNames) {
- $.forEach(eventNames, function(name) {
- if (api.events[name]) {
- delete api.events[name];
- }
- });
- };
-
- api.removeAllListeners = function () {
- api.events = {};
- };
-
- api.dispatchEvent = function(event, defaultAction) {
- if (!api.events[event.type] || api.events[event.type].length === 0) {
- executeDefaultAction(defaultAction, [event]);
- return;
- }
-
- executeListeners(event.type, [event], defaultAction);
- };
-
- api.trigger = function(eventName, args) {
- if (!api.events[eventName] || api.events[eventName].length === 0) {
- return;
- }
-
- executeListeners(eventName, args);
- };
-
-
- return api;
- };
-}
-
-/*jshint browser:false, smarttabs:true*/
-/* global window, require */
-
-// tb_require('../../helpers.js')
-// tb_require('../environment.js')
-
-if (window.OTHelpers.env.name === 'Node') {
- var request = require('request');
-
- OTHelpers.request = function(url, options, callback) {
- var completion = function(error, response, body) {
- var event = {response: response, body: body};
-
- // We need to detect things that Request considers a success,
- // but we consider to be failures.
- if (!error && response.statusCode >= 200 &&
- (response.statusCode < 300 || response.statusCode === 304) ) {
- callback(null, event);
- } else {
- callback(error, event);
- }
- };
-
- if (options.method.toLowerCase() === 'get') {
- request.get(url, completion);
- }
- else {
- request.post(url, options.body, completion);
- }
- };
-
- OTHelpers.getJSON = function(url, options, callback) {
- var extendedHeaders = require('underscore').extend(
- {
- 'Accept': 'application/json'
- },
- options.headers || {}
- );
-
- request.get({
- url: url,
- headers: extendedHeaders,
- json: true
- }, function(err, response) {
- callback(err, response && response.body);
- });
- };
-}
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../../helpers.js')
-// tb_require('../environment.js')
-
-function formatPostData(data) { //, contentType
- // If it's a string, we assume it's properly encoded
- if (typeof(data) === 'string') return data;
-
- var queryString = [];
-
- for (var key in data) {
- queryString.push(
- encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
- );
- }
-
- return queryString.join('&').replace(/\+/g, '%20');
-}
-
-if (window.OTHelpers.env.name !== 'Node') {
-
- OTHelpers.xdomainRequest = function(url, options, callback) {
- /*global XDomainRequest*/
- var xdr = new XDomainRequest(),
- _options = options || {},
- _method = _options.method.toLowerCase();
-
- if(!_method) {
- callback(new Error('No HTTP method specified in options'));
- return;
- }
-
- _method = _method.toUpperCase();
-
- if(!(_method === 'GET' || _method === 'POST')) {
- callback(new Error('HTTP method can only be '));
- return;
- }
-
- function done(err, event) {
- xdr.onload = xdr.onerror = xdr.ontimeout = function() {};
- xdr = void 0;
- callback(err, event);
- }
-
-
- xdr.onload = function() {
- done(null, {
- target: {
- responseText: xdr.responseText,
- headers: {
- 'content-type': xdr.contentType
- }
- }
- });
- };
-
- xdr.onerror = function() {
- done(new Error('XDomainRequest of ' + url + ' failed'));
- };
-
- xdr.ontimeout = function() {
- done(new Error('XDomainRequest of ' + url + ' timed out'));
- };
-
- xdr.open(_method, url);
- xdr.send(options.body && formatPostData(options.body));
-
- };
-
- OTHelpers.request = function(url, options, callback) {
- var request = new XMLHttpRequest(),
- _options = options || {},
- _method = _options.method;
-
- if(!_method) {
- callback(new Error('No HTTP method specified in options'));
- return;
- }
-
- // Setup callbacks to correctly respond to success and error callbacks. This includes
- // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore
- // by default.
- if(callback) {
- OTHelpers.on(request, 'load', function(event) {
- var status = event.target.status;
-
- // We need to detect things that XMLHttpRequest considers a success,
- // but we consider to be failures.
- if ( status >= 200 && (status < 300 || status === 304) ) {
- callback(null, event);
- } else {
- callback(event);
- }
- });
-
- OTHelpers.on(request, 'error', callback);
- }
-
- request.open(options.method, url, true);
-
- if (!_options.headers) _options.headers = {};
-
- for (var name in _options.headers) {
- request.setRequestHeader(name, _options.headers[name]);
- }
-
- request.send(options.body && formatPostData(options.body));
- };
-
-
- OTHelpers.getJSON = function(url, options, callback) {
- options = options || {};
-
- var done = function(error, event) {
- if(error) {
- callback(error, event && event.target && event.target.responseText);
- } else {
- var response;
-
- try {
- response = JSON.parse(event.target.responseText);
- } catch(e) {
- // Badly formed JSON
- callback(e, event && event.target && event.target.responseText);
- return;
- }
-
- callback(null, response, event);
- }
- };
-
- if(options.xdomainrequest) {
- OTHelpers.xdomainRequest(url, { method: 'GET' }, done);
- } else {
- var extendedHeaders = OTHelpers.extend({
- 'Accept': 'application/json'
- }, options.headers || {});
-
- OTHelpers.get(url, OTHelpers.extend(options || {}, {
- headers: extendedHeaders
- }), done);
- }
-
- };
-
-}
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./environment.js')
-
-
-// Log levels for OTLog.setLogLevel
-var LOG_LEVEL_DEBUG = 5,
- LOG_LEVEL_LOG = 4,
- LOG_LEVEL_INFO = 3,
- LOG_LEVEL_WARN = 2,
- LOG_LEVEL_ERROR = 1,
- LOG_LEVEL_NONE = 0;
-
-
-// There is a single global log level for every component that uses
-// the logs.
-var _logLevel = LOG_LEVEL_NONE;
-
-var setLogLevel = function setLogLevel (level) {
- _logLevel = typeof(level) === 'number' ? level : 0;
- return _logLevel;
-};
-
-
-OTHelpers.useLogHelpers = function(on){
-
- // Log levels for OTLog.setLogLevel
- on.DEBUG = LOG_LEVEL_DEBUG;
- on.LOG = LOG_LEVEL_LOG;
- on.INFO = LOG_LEVEL_INFO;
- on.WARN = LOG_LEVEL_WARN;
- on.ERROR = LOG_LEVEL_ERROR;
- on.NONE = LOG_LEVEL_NONE;
-
- var _logs = [],
- _canApplyConsole = true;
-
- try {
- Function.prototype.bind.call(window.console.log, window.console);
- } catch (err) {
- _canApplyConsole = false;
- }
-
- // Some objects can't be logged in the console, mostly these are certain
- // types of native objects that are exposed to JS. This is only really a
- // problem with IE, hence only the IE version does anything.
- var makeLogArgumentsSafe = function(args) { return args; };
-
- if (OTHelpers.env.name === 'IE') {
- makeLogArgumentsSafe = function(args) {
- return [toDebugString(prototypeSlice.apply(args))];
- };
- }
-
- // Generates a logging method for a particular method and log level.
- //
- // Attempts to handle the following cases:
- // * the desired log method doesn't exist, call fallback (if available) instead
- // * the console functionality isn't available because the developer tools (in IE)
- // aren't open, call fallback (if available)
- // * attempt to deal with weird IE hosted logging methods as best we can.
- //
- function generateLoggingMethod(method, level, fallback) {
- return function() {
- if (on.shouldLog(level)) {
- var cons = window.console,
- args = makeLogArgumentsSafe(arguments);
-
- // In IE, window.console may not exist if the developer tools aren't open
- // This also means that cons and cons[method] can appear at any moment
- // hence why we retest this every time.
- if (cons && cons[method]) {
- // the desired console method isn't a real object, which means
- // that we can't use apply on it. We force it to be a real object
- // using Function.bind, assuming that's available.
- if (cons[method].apply || _canApplyConsole) {
- if (!cons[method].apply) {
- cons[method] = Function.prototype.bind.call(cons[method], cons);
- }
-
- cons[method].apply(cons, args);
- }
- else {
- // This isn't the same result as the above, but it's better
- // than nothing.
- cons[method](args);
- }
- }
- else if (fallback) {
- fallback.apply(on, args);
-
- // Skip appendToLogs, we delegate entirely to the fallback
- return;
- }
-
- appendToLogs(method, makeLogArgumentsSafe(arguments));
- }
- };
- }
-
- on.log = generateLoggingMethod('log', on.LOG);
-
- // Generate debug, info, warn, and error logging methods, these all fallback to on.log
- on.debug = generateLoggingMethod('debug', on.DEBUG, on.log);
- on.info = generateLoggingMethod('info', on.INFO, on.log);
- on.warn = generateLoggingMethod('warn', on.WARN, on.log);
- on.error = generateLoggingMethod('error', on.ERROR, on.log);
-
-
- on.setLogLevel = function(level) {
- on.debug('TB.setLogLevel(' + _logLevel + ')');
- return setLogLevel(level);
- };
-
- on.getLogs = function() {
- return _logs;
- };
-
- // Determine if the level is visible given the current logLevel.
- on.shouldLog = function(level) {
- return _logLevel >= level;
- };
-
- // Format the current time nicely for logging. Returns the current
- // local time.
- function formatDateStamp() {
- var now = new Date();
- return now.toLocaleTimeString() + now.getMilliseconds();
- }
-
- function toJson(object) {
- try {
- return JSON.stringify(object);
- } catch(e) {
- return object.toString();
- }
- }
-
- function toDebugString(object) {
- var components = [];
-
- if (typeof(object) === 'undefined') {
- // noop
- }
- else if (object === null) {
- components.push('NULL');
- }
- else if (OTHelpers.isArray(object)) {
- for (var i=0; i<object.length; ++i) {
- components.push(toJson(object[i]));
- }
- }
- else if (OTHelpers.isObject(object)) {
- for (var key in object) {
- var stringValue;
-
- if (!OTHelpers.isFunction(object[key])) {
- stringValue = toJson(object[key]);
- }
- else if (object.hasOwnProperty(key)) {
- stringValue = 'function ' + key + '()';
- }
-
- components.push(key + ': ' + stringValue);
- }
- }
- else if (OTHelpers.isFunction(object)) {
- try {
- components.push(object.toString());
- } catch(e) {
- components.push('function()');
- }
- }
- else {
- components.push(object.toString());
- }
-
- return components.join(', ');
- }
-
- // Append +args+ to logs, along with the current log level and the a date stamp.
- function appendToLogs(level, args) {
- if (!args) return;
-
- var message = toDebugString(args);
- if (message.length <= 2) return;
-
- _logs.push(
- [level, formatDateStamp(), message]
- );
- }
-};
-
-OTHelpers.useLogHelpers(OTHelpers);
-OTHelpers.setLogLevel(OTHelpers.ERROR);
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-// DOM helpers
-
-// Helper function for adding event listeners to dom elements.
-// WARNING: This doesn't preserve event types, your handler could
-// be getting all kinds of different parameters depending on the browser.
-// You also may have different scopes depending on the browser and bubbling
-// and cancelable are not supported.
-ElementCollection.prototype.on = function (eventName, handler) {
- return this.forEach(function(element) {
- if (element.addEventListener) {
- element.addEventListener(eventName, handler, false);
- } else if (element.attachEvent) {
- element.attachEvent('on' + eventName, handler);
- } else {
- var oldHandler = element['on'+eventName];
- element['on'+eventName] = function() {
- handler.apply(this, arguments);
- if (oldHandler) oldHandler.apply(this, arguments);
- };
- }
- });
-};
-
-// Helper function for removing event listeners from dom elements.
-ElementCollection.prototype.off = function (eventName, handler) {
- return this.forEach(function(element) {
- if (element.removeEventListener) {
- element.removeEventListener (eventName, handler,false);
- }
- else if (element.detachEvent) {
- element.detachEvent('on' + eventName, handler);
- }
- });
-};
-
-ElementCollection.prototype.once = function (eventName, handler) {
- var removeAfterTrigger = $.bind(function() {
- this.off(eventName, removeAfterTrigger);
- handler.apply(null, arguments);
- }, this);
-
- return this.on(eventName, removeAfterTrigger);
-};
-
-// @remove
-OTHelpers.on = function(element, eventName, handler) {
- return $(element).on(eventName, handler);
-};
-
-// @remove
-OTHelpers.off = function(element, eventName, handler) {
- return $(element).off(eventName, handler);
-};
-
-// @remove
-OTHelpers.once = function (element, eventName, handler) {
- return $(element).once(eventName, handler);
-};
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./dom_events.js')
-
-(function() {
-
- var _domReady = typeof(document) === 'undefined' ||
- document.readyState === 'complete' ||
- (document.readyState === 'interactive' && document.body),
-
- _loadCallbacks = [],
- _unloadCallbacks = [],
- _domUnloaded = false,
-
- onDomReady = function() {
- _domReady = true;
-
- if (typeof(document) !== 'undefined') {
- if ( document.addEventListener ) {
- document.removeEventListener('DOMContentLoaded', onDomReady, false);
- window.removeEventListener('load', onDomReady, false);
- } else {
- document.detachEvent('onreadystatechange', onDomReady);
- window.detachEvent('onload', onDomReady);
- }
- }
-
- // This is making an assumption about there being only one 'window'
- // that we care about.
- OTHelpers.on(window, 'unload', onDomUnload);
-
- OTHelpers.forEach(_loadCallbacks, function(listener) {
- listener[0].call(listener[1]);
- });
-
- _loadCallbacks = [];
- },
-
- onDomUnload = function() {
- _domUnloaded = true;
-
- OTHelpers.forEach(_unloadCallbacks, function(listener) {
- listener[0].call(listener[1]);
- });
-
- _unloadCallbacks = [];
- };
-
-
- OTHelpers.onDOMLoad = function(cb, context) {
- if (OTHelpers.isReady()) {
- cb.call(context);
- return;
- }
-
- _loadCallbacks.push([cb, context]);
- };
-
- OTHelpers.onDOMUnload = function(cb, context) {
- if (this.isDOMUnloaded()) {
- cb.call(context);
- return;
- }
-
- _unloadCallbacks.push([cb, context]);
- };
-
- OTHelpers.isReady = function() {
- return !_domUnloaded && _domReady;
- };
-
- OTHelpers.isDOMUnloaded = function() {
- return _domUnloaded;
- };
-
- if (_domReady) {
- onDomReady();
- } else if(typeof(document) !== 'undefined') {
- if (document.addEventListener) {
- document.addEventListener('DOMContentLoaded', onDomReady, false);
-
- // fallback
- window.addEventListener( 'load', onDomReady, false );
-
- } else if (document.attachEvent) {
- document.attachEvent('onreadystatechange', function() {
- if (document.readyState === 'complete') onDomReady();
- });
-
- // fallback
- window.attachEvent( 'onload', onDomReady );
- }
- }
-
-})();
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-OTHelpers.setCookie = function(key, value) {
- try {
- localStorage.setItem(key, value);
- } catch (err) {
- // Store in browser cookie
- var date = new Date();
- date.setTime(date.getTime()+(365*24*60*60*1000));
- var expires = '; expires=' + date.toGMTString();
- document.cookie = key + '=' + value + expires + '; path=/';
- }
-};
-
-OTHelpers.getCookie = function(key) {
- var value;
-
- try {
- value = localStorage.getItem(key);
- return value;
- } catch (err) {
- // Check browser cookies
- var nameEQ = key + '=';
- var ca = document.cookie.split(';');
- for(var i=0;i < ca.length;i++) {
- var c = ca[i];
- while (c.charAt(0) === ' ') {
- c = c.substring(1,c.length);
- }
- if (c.indexOf(nameEQ) === 0) {
- value = c.substring(nameEQ.length,c.length);
- }
- }
-
- if (value) {
- return value;
- }
- }
-
- return null;
-};
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-OTHelpers.castToBoolean = function(value, defaultValue) {
- if (value === undefined) return defaultValue;
- return value === 'true' || value === true;
-};
-
-OTHelpers.roundFloat = function(value, places) {
- return Number(value.toFixed(places));
-};
-
-// tb_require('../helpers.js')
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-
-
-OTHelpers.Collection = function(idField) {
- var _models = [],
- _byId = {},
- _idField = idField || 'id';
-
- OTHelpers.eventing(this, true);
-
- var modelProperty = function(model, property) {
- if(OTHelpers.isFunction(model[property])) {
- return model[property]();
- } else {
- return model[property];
- }
- };
-
- var onModelUpdate = OTHelpers.bind(function onModelUpdate (event) {
- this.trigger('update', event);
- this.trigger('update:'+event.target.id, event);
- }, this),
-
- onModelDestroy = OTHelpers.bind(function onModelDestroyed (event) {
- this.remove(event.target, event.reason);
- }, this);
-
-
- this.reset = function() {
- // Stop listening on the models, they are no longer our problem
- OTHelpers.forEach(_models, function(model) {
- model.off('updated', onModelUpdate, this);
- model.off('destroyed', onModelDestroy, this);
- }, this);
-
- _models = [];
- _byId = {};
- };
-
- this.destroy = function(reason) {
- OTHelpers.forEach(_models, function(model) {
- if(model && typeof model.destroy === 'function') {
- model.destroy(reason, true);
- }
- });
-
- this.reset();
- this.off();
- };
-
- this.get = function(id) { return id && _byId[id] !== void 0 ? _models[_byId[id]] : void 0; };
- this.has = function(id) { return id && _byId[id] !== void 0; };
-
- this.toString = function() { return _models.toString(); };
-
- // Return only models filtered by either a dict of properties
- // or a filter function.
- //
- // @example Return all publishers with a streamId of 1
- // OT.publishers.where({streamId: 1})
- //
- // @example The same thing but filtering using a filter function
- // OT.publishers.where(function(publisher) {
- // return publisher.stream.id === 4;
- // });
- //
- // @example The same thing but filtering using a filter function
- // executed with a specific this
- // OT.publishers.where(function(publisher) {
- // return publisher.stream.id === 4;
- // }, self);
- //
- this.where = function(attrsOrFilterFn, context) {
- if (OTHelpers.isFunction(attrsOrFilterFn)) {
- return OTHelpers.filter(_models, attrsOrFilterFn, context);
- }
-
- return OTHelpers.filter(_models, function(model) {
- for (var key in attrsOrFilterFn) {
- if(!attrsOrFilterFn.hasOwnProperty(key)) {
- continue;
- }
- if (modelProperty(model, key) !== attrsOrFilterFn[key]) return false;
- }
-
- return true;
- });
- };
-
- // Similar to where in behaviour, except that it only returns
- // the first match.
- this.find = function(attrsOrFilterFn, context) {
- var filterFn;
-
- if (OTHelpers.isFunction(attrsOrFilterFn)) {
- filterFn = attrsOrFilterFn;
- }
- else {
- filterFn = function(model) {
- for (var key in attrsOrFilterFn) {
- if(!attrsOrFilterFn.hasOwnProperty(key)) {
- continue;
- }
- if (modelProperty(model, key) !== attrsOrFilterFn[key]) return false;
- }
-
- return true;
- };
- }
-
- filterFn = OTHelpers.bind(filterFn, context);
-
- for (var i=0; i<_models.length; ++i) {
- if (filterFn(_models[i]) === true) return _models[i];
- }
-
- return null;
- };
-
- this.add = function(model) {
- var id = modelProperty(model, _idField);
-
- if (this.has(id)) {
- OTHelpers.warn('Model ' + id + ' is already in the collection', _models);
- return this;
- }
-
- _byId[id] = _models.push(model) - 1;
-
- model.on('updated', onModelUpdate, this);
- model.on('destroyed', onModelDestroy, this);
-
- this.trigger('add', model);
- this.trigger('add:'+id, model);
-
- return this;
- };
-
- this.remove = function(model, reason) {
- var id = modelProperty(model, _idField);
-
- _models.splice(_byId[id], 1);
-
- // Shuffle everyone down one
- for (var i=_byId[id]; i<_models.length; ++i) {
- _byId[_models[i][_idField]] = i;
- }
-
- delete _byId[id];
-
- model.off('updated', onModelUpdate, this);
- model.off('destroyed', onModelDestroy, this);
-
- this.trigger('remove', model, reason);
- this.trigger('remove:'+id, model, reason);
-
- return this;
- };
-
- // Retrigger the add event behaviour for each model. You can also
- // select a subset of models to trigger using the same arguments
- // as the #where method.
- this._triggerAddEvents = function() {
- var models = this.where.apply(this, arguments);
- OTHelpers.forEach(models, function(model) {
- this.trigger('add', model);
- this.trigger('add:' + modelProperty(model, _idField), model);
- }, this);
- };
-
- this.length = function() {
- return _models.length;
- };
-};
-
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-(function() {
-
- var capabilities = {};
-
- // Registers a new capability type and a function that will indicate
- // whether this client has that capability.
- //
- // OTHelpers.registerCapability('bundle', function() {
- // return OTHelpers.hasCapabilities('webrtc') &&
- // (OTHelpers.env.name === 'Chrome' || TBPlugin.isInstalled());
- // });
- //
- OTHelpers.registerCapability = function(name, callback) {
- var _name = name.toLowerCase();
-
- if (capabilities.hasOwnProperty(_name)) {
- OTHelpers.error('Attempted to register', name, 'capability more than once');
- return;
- }
-
- if (!OTHelpers.isFunction(callback)) {
- OTHelpers.error('Attempted to register', name,
- 'capability with a callback that isn\' a function');
- return;
- }
-
- memoriseCapabilityTest(_name, callback);
- };
-
-
- // Wrap up a capability test in a function that memorises the
- // result.
- var memoriseCapabilityTest = function (name, callback) {
- capabilities[name] = function() {
- var result = callback();
- capabilities[name] = function() {
- return result;
- };
-
- return result;
- };
- };
-
- var testCapability = function (name) {
- return capabilities[name]();
- };
-
-
- // Returns true if all of the capability names passed in
- // exist and are met.
- //
- // OTHelpers.hasCapabilities('bundle', 'rtcpMux')
- //
- OTHelpers.hasCapabilities = function(/* capability1, capability2, ..., capabilityN */) {
- var capNames = prototypeSlice.call(arguments),
- name;
-
- for (var i=0; i<capNames.length; ++i) {
- name = capNames[i].toLowerCase();
-
- if (!capabilities.hasOwnProperty(name)) {
- OTHelpers.error('hasCapabilities was called with an unknown capability: ' + name);
- return false;
- }
- else if (testCapability(name) === false) {
- return false;
- }
- }
-
- return true;
- };
-
-})();
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./capabilities.js')
-
-// Indicates if the client supports WebSockets.
-OTHelpers.registerCapability('websockets', function() {
- return 'WebSocket' in window && window.WebSocket !== void 0;
-});
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('../vendor/uuid.js')
-// tb_require('./dom_events.js')
-
-(function() {
-
- var _callAsync;
-
- // Is true if window.postMessage is supported.
- // This is not quite as simple as just looking for
- // window.postMessage as some older versions of IE
- // have a broken implementation of it.
- //
- var supportsPostMessage = (function () {
- if (window.postMessage) {
- // Check to see if postMessage fires synchronously,
- // if it does, then the implementation of postMessage
- // is broken.
- var postMessageIsAsynchronous = true;
- var oldOnMessage = window.onmessage;
- window.onmessage = function() {
- postMessageIsAsynchronous = false;
- };
- window.postMessage('', '*');
- window.onmessage = oldOnMessage;
- return postMessageIsAsynchronous;
- }
- })();
-
- if (supportsPostMessage) {
- var timeouts = [],
- messageName = 'OTHelpers.' + OTHelpers.uuid.v4() + '.zero-timeout';
-
- var removeMessageHandler = function() {
- timeouts = [];
-
- if(window.removeEventListener) {
- window.removeEventListener('message', handleMessage);
- } else if(window.detachEvent) {
- window.detachEvent('onmessage', handleMessage);
- }
- };
-
- var handleMessage = function(event) {
- if (event.source === window &&
- event.data === messageName) {
-
- if(OTHelpers.isFunction(event.stopPropagation)) {
- event.stopPropagation();
- }
- event.cancelBubble = true;
-
- if (!window.___othelpers) {
- removeMessageHandler();
- return;
- }
-
- if (timeouts.length > 0) {
- var args = timeouts.shift(),
- fn = args.shift();
-
- fn.apply(null, args);
- }
- }
- };
-
- // Ensure that we don't receive messages after unload
- // Yes, this seems to really happen in IE sometimes, usually
- // when iFrames are involved.
- OTHelpers.on(window, 'unload', removeMessageHandler);
-
- if(window.addEventListener) {
- window.addEventListener('message', handleMessage, true);
- } else if(window.attachEvent) {
- window.attachEvent('onmessage', handleMessage);
- }
-
- _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
- timeouts.push(prototypeSlice.call(arguments));
- window.postMessage(messageName, '*');
- };
- }
- else {
- _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
- var args = prototypeSlice.call(arguments),
- fn = args.shift();
-
- setTimeout(function() {
- fn.apply(null, args);
- }, 0);
- };
- }
-
-
- // Calls the function +fn+ asynchronously with the current execution.
- // This is most commonly used to execute something straight after
- // the current function.
- //
- // Any arguments in addition to +fn+ will be passed to +fn+ when it's
- // called.
- //
- // You would use this inplace of setTimeout(fn, 0) type constructs. callAsync
- // is preferable as it executes in a much more predictable time window,
- // unlike setTimeout which could execute anywhere from 2ms to several thousand
- // depending on the browser/context.
- //
- // It does this using window.postMessage, although if postMessage won't
- // work it will fallback to setTimeout.
- //
- OTHelpers.callAsync = _callAsync;
-
-
- // Wraps +handler+ in a function that will execute it asynchronously
- // so that it doesn't interfere with it's exceution context if it raises
- // an exception.
- OTHelpers.createAsyncHandler = function(handler) {
- return function() {
- var args = prototypeSlice.call(arguments);
-
- OTHelpers.callAsync(function() {
- handler.apply(null, args);
- });
- };
- };
-
-})();
-
/*global nodeEventing:true, browserEventing:true */
// tb_require('../../helpers.js')
// tb_require('../callbacks.js')
// tb_require('../../vendor/rsvp.js')
// tb_require('./eventing/event.js')
// tb_require('./eventing/node.js')
// tb_require('./eventing/browser.js')
@@ -4529,1693 +6130,126 @@ OTHelpers.eventing = function(self, sync
self.removeEventListener = function(eventName, handler, context) {
$.warn('The removeEventListener() method is deprecated. Use off() instead.');
return self.off(eventName, handler, context);
};
return self;
};
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./callbacks.js')
-// tb_require('./dom_events.js')
-
-OTHelpers.createElement = function(nodeName, attributes, children, doc) {
- var element = (doc || document).createElement(nodeName);
-
- if (attributes) {
- for (var name in attributes) {
- if (typeof(attributes[name]) === 'object') {
- if (!element[name]) element[name] = {};
-
- var subAttrs = attributes[name];
- for (var n in subAttrs) {
- element[name][n] = subAttrs[n];
- }
- }
- else if (name === 'className') {
- element.className = attributes[name];
- }
- else {
- element.setAttribute(name, attributes[name]);
- }
- }
- }
-
- var setChildren = function(child) {
- if(typeof child === 'string') {
- element.innerHTML = element.innerHTML + child;
- } else {
- element.appendChild(child);
- }
- };
-
- if($.isArray(children)) {
- $.forEach(children, setChildren);
- } else if(children) {
- setChildren(children);
- }
-
- return element;
-};
-
-OTHelpers.createButton = function(innerHTML, attributes, events) {
- var button = $.createElement('button', attributes, innerHTML);
-
- if (events) {
- for (var name in events) {
- if (events.hasOwnProperty(name)) {
- $.on(button, name, events[name]);
- }
- }
-
- button._boundEvents = events;
- }
-
- return button;
-};
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./callbacks.js')
-
-// DOM helpers
-
-var firstElementChild;
-
-// This mess is for IE8
-if( typeof(document) !== 'undefined' &&
- document.createElement('div').firstElementChild !== void 0 ){
- firstElementChild = function firstElementChild (parentElement) {
- return parentElement.firstElementChild;
- };
-}
-else {
- firstElementChild = function firstElementChild (parentElement) {
- var el = parentElement.firstChild;
-
- do {
- if(el.nodeType===1){
- return el;
- }
- el = el.nextSibling;
- } while(el);
-
- return null;
- };
-}
-
-
-ElementCollection.prototype.appendTo = function(parentElement) {
- if (!parentElement) throw new Error('appendTo requires a DOMElement to append to.');
-
- return this.forEach(function(child) {
- parentElement.appendChild(child);
- });
-};
-
-ElementCollection.prototype.append = function() {
- var parentElement = this.first;
- if (!parentElement) return this;
-
- $.forEach(prototypeSlice.call(arguments), function(child) {
- parentElement.appendChild(child);
- });
-
- return this;
-};
-
-ElementCollection.prototype.prepend = function() {
- if (arguments.length === 0) return this;
-
- var parentElement = this.first,
- elementsToPrepend;
-
- if (!parentElement) return this;
-
- elementsToPrepend = prototypeSlice.call(arguments);
-
- if (!firstElementChild(parentElement)) {
- parentElement.appendChild(elementsToPrepend.shift());
- }
-
- $.forEach(elementsToPrepend, function(element) {
- parentElement.insertBefore(element, firstElementChild(parentElement));
- });
-
- return this;
-};
-
-ElementCollection.prototype.after = function(prevElement) {
- if (!prevElement) throw new Error('after requires a DOMElement to insert after');
-
- return this.forEach(function(element) {
- if (element.parentElement) {
- if (prevElement !== element.parentNode.lastChild) {
- element.parentElement.insertBefore(element, prevElement);
- }
- else {
- element.parentElement.appendChild(element);
- }
- }
- });
-};
-
-ElementCollection.prototype.before = function(nextElement) {
- if (!nextElement) {
- throw new Error('before requires a DOMElement to insert before');
- }
-
- return this.forEach(function(element) {
- if (element.parentElement) {
- element.parentElement.insertBefore(element, nextElement);
- }
- });
-};
-
-ElementCollection.prototype.remove = function () {
- return this.forEach(function(element) {
- if (element.parentNode) {
- element.parentNode.removeChild(element);
- }
- });
-};
-
-ElementCollection.prototype.empty = function () {
- return this.forEach(function(element) {
- // elements is a "live" NodesList collection. Meaning that the collection
- // itself will be mutated as we remove elements from the DOM. This means
- // that "while there are still elements" is safer than "iterate over each
- // element" as the collection length and the elements indices will be modified
- // with each iteration.
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
- });
-};
-
-
-// Detects when an element is not part of the document flow because
-// it or one of it's ancesters has display:none.
-ElementCollection.prototype.isDisplayNone = function() {
- return this.some(function(element) {
- if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
- $(element).css('display') === 'none') return true;
-
- if (element.parentNode && element.parentNode.style) {
- return $(element.parentNode).isDisplayNone();
- }
- });
-};
-
-ElementCollection.prototype.findElementWithDisplayNone = function(element) {
- return $.findElementWithDisplayNone(element);
-};
-
-
-
-OTHelpers.isElementNode = function(node) {
- return node && typeof node === 'object' && node.nodeType === 1;
-};
-
-
-// @remove
-OTHelpers.removeElement = function(element) {
- $(element).remove();
-};
-
-// @remove
-OTHelpers.removeElementById = function(elementId) {
- return $('#'+elementId).remove();
-};
-
-// @remove
-OTHelpers.removeElementsByType = function(parentElem, type) {
- return $(type, parentElem).remove();
-};
-
-// @remove
-OTHelpers.emptyElement = function(element) {
- return $(element).empty();
-};
-
-
-
-
-
-// @remove
-OTHelpers.isDisplayNone = function(element) {
- return $(element).isDisplayNone();
-};
-
-OTHelpers.findElementWithDisplayNone = function(element) {
- if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
- $.css(element, 'display') === 'none') return element;
-
- if (element.parentNode && element.parentNode.style) {
- return $.findElementWithDisplayNone(element.parentNode);
- }
-
- return null;
-};
-
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./environment.js')
-// tb_require('./dom.js')
-
-OTHelpers.Modal = function(options) {
-
- OTHelpers.eventing(this, true);
-
- var callback = arguments[arguments.length - 1];
-
- if(!OTHelpers.isFunction(callback)) {
- throw new Error('OTHelpers.Modal2 must be given a callback');
- }
-
- if(arguments.length < 2) {
- options = {};
- }
-
- var domElement = document.createElement('iframe');
-
- domElement.id = options.id || OTHelpers.uuid();
- domElement.style.position = 'absolute';
- domElement.style.position = 'fixed';
- domElement.style.height = '100%';
- domElement.style.width = '100%';
- domElement.style.top = '0px';
- domElement.style.left = '0px';
- domElement.style.right = '0px';
- domElement.style.bottom = '0px';
- domElement.style.zIndex = 1000;
- domElement.style.border = '0';
-
- try {
- domElement.style.backgroundColor = 'rgba(0,0,0,0.2)';
- } catch (err) {
- // Old IE browsers don't support rgba and we still want to show the upgrade message
- // but we just make the background of the iframe completely transparent.
- domElement.style.backgroundColor = 'transparent';
- domElement.setAttribute('allowTransparency', 'true');
- }
-
- domElement.scrolling = 'no';
- domElement.setAttribute('scrolling', 'no');
-
- // This is necessary for IE, as it will not inherit it's doctype from
- // the parent frame.
- var frameContent = '<!DOCTYPE html><html><head>' +
- '<meta http-equiv="x-ua-compatible" content="IE=Edge">' +
- '<meta http-equiv="Content-type" content="text/html; charset=utf-8">' +
- '<title></title></head><body></body></html>';
-
- var wrappedCallback = function() {
- var doc = domElement.contentDocument || domElement.contentWindow.document;
-
- if (OTHelpers.env.iframeNeedsLoad) {
- doc.body.style.backgroundColor = 'transparent';
- doc.body.style.border = 'none';
-
- if (OTHelpers.env.name !== 'IE') {
- // Skip this for IE as we use the bookmarklet workaround
- // for THAT browser.
- doc.open();
- doc.write(frameContent);
- doc.close();
- }
- }
-
- callback(
- domElement.contentWindow,
- doc
- );
- };
-
- document.body.appendChild(domElement);
-
- if(OTHelpers.env.iframeNeedsLoad) {
- if (OTHelpers.env.name === 'IE') {
- // This works around some issues with IE and document.write.
- // Basically this works by slightly abusing the bookmarklet/scriptlet
- // functionality that all browsers support.
- domElement.contentWindow.contents = frameContent;
- /*jshint scripturl:true*/
- domElement.src = 'javascript:window["contents"]';
- /*jshint scripturl:false*/
- }
-
- OTHelpers.on(domElement, 'load', wrappedCallback);
- } else {
- setTimeout(wrappedCallback, 0);
- }
-
- this.close = function() {
- OTHelpers.removeElement(domElement);
- this.trigger('closed');
- this.element = domElement = null;
- return this;
- };
-
- this.element = domElement;
-
-};
-
-/*
- * getComputedStyle from
- * https://github.com/jonathantneal/Polyfills-for-IE8/blob/master/getComputedStyle.js
-
-// tb_require('../helpers.js')
-// tb_require('./dom.js')
-
-/*jshint strict: false, eqnull: true, browser:true, smarttabs:true*/
-
-(function() {
-
- /*jshint eqnull: true, browser: true */
-
-
- function getPixelSize(element, style, property, fontSize) {
- var sizeWithSuffix = style[property],
- size = parseFloat(sizeWithSuffix),
- suffix = sizeWithSuffix.split(/\d/)[0],
- rootSize;
-
- fontSize = fontSize != null ?
- fontSize : /%|em/.test(suffix) && element.parentElement ?
- getPixelSize(element.parentElement, element.parentElement.currentStyle, 'fontSize', null) :
- 16;
- rootSize = property === 'fontSize' ?
- fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight;
-
- return (suffix === 'em') ?
- size * fontSize : (suffix === 'in') ?
- size * 96 : (suffix === 'pt') ?
- size * 96 / 72 : (suffix === '%') ?
- size / 100 * rootSize : size;
- }
-
- function setShortStyleProperty(style, property) {
- var
- borderSuffix = property === 'border' ? 'Width' : '',
- t = property + 'Top' + borderSuffix,
- r = property + 'Right' + borderSuffix,
- b = property + 'Bottom' + borderSuffix,
- l = property + 'Left' + borderSuffix;
-
- style[property] = (style[t] === style[r] === style[b] === style[l] ? [style[t]]
- : style[t] === style[b] && style[l] === style[r] ? [style[t], style[r]]
- : style[l] === style[r] ? [style[t], style[r], style[b]]
- : [style[t], style[r], style[b], style[l]]).join(' ');
- }
-
- function CSSStyleDeclaration(element) {
- var currentStyle = element.currentStyle,
- style = this,
- fontSize = getPixelSize(element, currentStyle, 'fontSize', null),
- property;
-
- for (property in currentStyle) {
- if (/width|height|margin.|padding.|border.+W/.test(property) && style[property] !== 'auto') {
- style[property] = getPixelSize(element, currentStyle, property, fontSize) + 'px';
- } else if (property === 'styleFloat') {
- /*jshint -W069 */
- style['float'] = currentStyle[property];
- } else {
- style[property] = currentStyle[property];
- }
- }
-
- setShortStyleProperty(style, 'margin');
- setShortStyleProperty(style, 'padding');
- setShortStyleProperty(style, 'border');
-
- style.fontSize = fontSize + 'px';
-
- return style;
- }
-
- CSSStyleDeclaration.prototype = {
- constructor: CSSStyleDeclaration,
- getPropertyPriority: function () {},
- getPropertyValue: function ( prop ) {
- return this[prop] || '';
- },
- item: function () {},
- removeProperty: function () {},
- setProperty: function () {},
- getPropertyCSSValue: function () {}
- };
-
- function getComputedStyle(element) {
- return new CSSStyleDeclaration(element);
- }
-
-
- OTHelpers.getComputedStyle = function(element) {
- if(element &&
- element.ownerDocument &&
- element.ownerDocument.defaultView &&
- element.ownerDocument.defaultView.getComputedStyle) {
- return element.ownerDocument.defaultView.getComputedStyle(element);
- } else {
- return getComputedStyle(element);
- }
- };
-
-})();
-
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./callbacks.js')
-// tb_require('./dom.js')
-
-var observeStyleChanges = function observeStyleChanges (element, stylesToObserve, onChange) {
- var oldStyles = {};
-
- var getStyle = function getStyle(style) {
- switch (style) {
- case 'width':
- return $(element).width();
-
- case 'height':
- return $(element).height();
-
- default:
- return $(element).css(style);
- }
- };
-
- // get the inital values
- $.forEach(stylesToObserve, function(style) {
- oldStyles[style] = getStyle(style);
- });
-
- var observer = new MutationObserver(function(mutations) {
- var changeSet = {};
-
- $.forEach(mutations, function(mutation) {
- if (mutation.attributeName !== 'style') return;
-
- var isHidden = $.isDisplayNone(element);
-
- $.forEach(stylesToObserve, function(style) {
- if(isHidden && (style === 'width' || style === 'height')) return;
-
- var newValue = getStyle(style);
-
- if (newValue !== oldStyles[style]) {
- changeSet[style] = [oldStyles[style], newValue];
- oldStyles[style] = newValue;
- }
- });
- });
-
- if (!$.isEmpty(changeSet)) {
- // Do this after so as to help avoid infinite loops of mutations.
- $.callAsync(function() {
- onChange.call(null, changeSet);
- });
- }
- });
-
- observer.observe(element, {
- attributes:true,
- attributeFilter: ['style'],
- childList:false,
- characterData:false,
- subtree:false
- });
-
- return observer;
-};
-
-var observeNodeOrChildNodeRemoval = function observeNodeOrChildNodeRemoval (element, onChange) {
- var observer = new MutationObserver(function(mutations) {
- var removedNodes = [];
-
- $.forEach(mutations, function(mutation) {
- if (mutation.removedNodes.length) {
- removedNodes = removedNodes.concat(prototypeSlice.call(mutation.removedNodes));
- }
- });
-
- if (removedNodes.length) {
- // Do this after so as to help avoid infinite loops of mutations.
- $.callAsync(function() {
- onChange($(removedNodes));
- });
- }
- });
-
- observer.observe(element, {
- attributes:false,
- childList:true,
- characterData:false,
- subtree:true
- });
-
- return observer;
-};
-
-var observeSize = function (element, onChange) {
- var previousSize = {
- width: 0,
- height: 0
- };
-
- var interval = setInterval(function() {
- var rect = element.getBoundingClientRect();
- if (previousSize.width !== rect.width || previousSize.height !== rect.height) {
- onChange(rect, previousSize);
- previousSize = {
- width: rect.width,
- height: rect.height
- };
- }
- }, 1000 / 5);
-
- return {
- disconnect: function() {
- clearInterval(interval);
- }
- };
-};
-
-// Allows an +onChange+ callback to be triggered when specific style properties
-// of +element+ are notified. The callback accepts a single parameter, which is
-// a hash where the keys are the style property that changed and the values are
-// an array containing the old and new values ([oldValue, newValue]).
-//
-// Width and Height changes while the element is display: none will not be
-// fired until such time as the element becomes visible again.
-//
-// This function returns the MutationObserver itself. Once you no longer wish
-// to observe the element you should call disconnect on the observer.
-//
-// Observing changes:
-// // observe changings to the width and height of object
-// dimensionsObserver = OTHelpers(object).observeStyleChanges(,
-// ['width', 'height'], function(changeSet) {
-// OT.debug("The new width and height are " +
-// changeSet.width[1] + ',' + changeSet.height[1]);
-// });
-//
-// Cleaning up
-// // stop observing changes
-// dimensionsObserver.disconnect();
-// dimensionsObserver = null;
-//
-ElementCollection.prototype.observeStyleChanges = function(stylesToObserve, onChange) {
- var observers = [];
-
- this.forEach(function(element) {
- observers.push(
- observeStyleChanges(element, stylesToObserve, onChange)
- );
- });
-
- return observers;
-};
-
-// trigger the +onChange+ callback whenever
-// 1. +element+ is removed
-// 2. or an immediate child of +element+ is removed.
-//
-// This function returns the MutationObserver itself. Once you no longer wish
-// to observe the element you should call disconnect on the observer.
-//
-// Observing changes:
-// // observe changings to the width and height of object
-// nodeObserver = OTHelpers(object).observeNodeOrChildNodeRemoval(function(removedNodes) {
-// OT.debug("Some child nodes were removed");
-// removedNodes.forEach(function(node) {
-// OT.debug(node);
-// });
-// });
-//
-// Cleaning up
-// // stop observing changes
-// nodeObserver.disconnect();
-// nodeObserver = null;
-//
-ElementCollection.prototype.observeNodeOrChildNodeRemoval = function(onChange) {
- var observers = [];
-
- this.forEach(function(element) {
- observers.push(
- observeNodeOrChildNodeRemoval(element, onChange)
- );
- });
-
- return observers;
-};
-
-// trigger the +onChange+ callback whenever the width or the height of the element changes
-//
-// Once you no longer wish to observe the element you should call disconnect on the observer.
-//
-// Observing changes:
-// // observe changings to the width and height of object
-// sizeObserver = OTHelpers(object).observeSize(function(newSize, previousSize) {
-// OT.debug("The new width and height are " +
-// newSize.width + ',' + newSize.height);
-// });
-//
-// Cleaning up
-// // stop observing changes
-// sizeObserver.disconnect();
-// sizeObserver = null;
-//
-ElementCollection.prototype.observeSize = function(onChange) {
- var observers = [];
-
- this.forEach(function(element) {
- observers.push(
- observeSize(element, onChange)
- );
- });
-
- return observers;
-};
-
-
-// @remove
-OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) {
- return $(element).observeStyleChanges(stylesToObserve, onChange)[0];
-};
-
-// @remove
-OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) {
- return $(element).observeNodeOrChildNodeRemoval(onChange)[0];
-};
-
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./dom.js')
-// tb_require('./capabilities.js')
-
-// Returns true if the client supports element.classList
-OTHelpers.registerCapability('classList', function() {
- return (typeof document !== 'undefined') && ('classList' in document.createElement('a'));
-});
-
-
-function hasClass (element, className) {
- if (!className) return false;
-
- if ($.hasCapabilities('classList')) {
- return element.classList.contains(className);
- }
-
- return element.className.indexOf(className) > -1;
-}
-
-function toggleClasses (element, classNames) {
- if (!classNames || classNames.length === 0) return;
-
- // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
- if (element.nodeType !== 1) {
- return;
- }
-
- var numClasses = classNames.length,
- i = 0;
-
- if ($.hasCapabilities('classList')) {
- for (; i<numClasses; ++i) {
- element.classList.toggle(classNames[i]);
- }
-
- return;
- }
-
- var className = (' ' + element.className + ' ').replace(/[\s+]/, ' ');
-
-
- for (; i<numClasses; ++i) {
- if (hasClass(element, classNames[i])) {
- className = className.replace(' ' + classNames[i] + ' ', ' ');
- }
- else {
- className += classNames[i] + ' ';
- }
- }
-
- element.className = $.trim(className);
-}
-
-function addClass (element, classNames) {
- if (!classNames || classNames.length === 0) return;
-
- // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
- if (element.nodeType !== 1) {
- return;
- }
-
- var numClasses = classNames.length,
- i = 0;
-
- if ($.hasCapabilities('classList')) {
- for (; i<numClasses; ++i) {
- element.classList.add(classNames[i]);
- }
-
- return;
- }
-
- // Here's our fallback to browsers that don't support element.classList
-
- if (!element.className && classNames.length === 1) {
- element.className = classNames.join(' ');
- }
- else {
- var setClass = ' ' + element.className + ' ';
-
- for (; i<numClasses; ++i) {
- if ( !~setClass.indexOf( ' ' + classNames[i] + ' ')) {
- setClass += classNames[i] + ' ';
- }
- }
-
- element.className = $.trim(setClass);
- }
-}
-
-function removeClass (element, classNames) {
- if (!classNames || classNames.length === 0) return;
-
- // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
- if (element.nodeType !== 1) {
- return;
- }
-
- var numClasses = classNames.length,
- i = 0;
-
- if ($.hasCapabilities('classList')) {
- for (; i<numClasses; ++i) {
- element.classList.remove(classNames[i]);
- }
-
- return;
- }
-
- var className = (' ' + element.className + ' ').replace(/[\s+]/, ' ');
-
- for (; i<numClasses; ++i) {
- className = className.replace(' ' + classNames[i] + ' ', ' ');
- }
-
- element.className = $.trim(className);
-}
-
-ElementCollection.prototype.addClass = function (value) {
- if (value) {
- var classNames = $.trim(value).split(/\s+/);
-
- this.forEach(function(element) {
- addClass(element, classNames);
- });
- }
-
- return this;
-};
-
-ElementCollection.prototype.removeClass = function (value) {
- if (value) {
- var classNames = $.trim(value).split(/\s+/);
-
- this.forEach(function(element) {
- removeClass(element, classNames);
- });
- }
-
- return this;
-};
-
-ElementCollection.prototype.toggleClass = function (value) {
- if (value) {
- var classNames = $.trim(value).split(/\s+/);
-
- this.forEach(function(element) {
- toggleClasses(element, classNames);
- });
- }
-
- return this;
-};
-
-ElementCollection.prototype.hasClass = function (value) {
- return this.some(function(element) {
- return hasClass(element, value);
- });
-};
-
-
-// @remove
-OTHelpers.addClass = function(element, className) {
- return $(element).addClass(className);
-};
-
-// @remove
-OTHelpers.removeClass = function(element, value) {
- return $(element).removeClass(value);
-};
-
-
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./dom.js')
-// tb_require('./capabilities.js')
-
-var specialDomProperties = {
- 'for': 'htmlFor',
- 'class': 'className'
-};
-
-
-// Gets or sets the attribute called +name+ for the first element in the collection
-ElementCollection.prototype.attr = function (name, value) {
- if (OTHelpers.isObject(name)) {
- var actualName;
-
- for (var key in name) {
- actualName = specialDomProperties[key] || key;
- this.first.setAttribute(actualName, name[key]);
- }
- }
- else if (value === void 0) {
- return this.first.getAttribute(specialDomProperties[name] || name);
- }
- else {
- this.first.setAttribute(specialDomProperties[name] || name, value);
- }
-
- return this;
-};
-
-
-// Removes an attribute called +name+ for the every element in the collection.
-ElementCollection.prototype.removeAttr = function (name) {
- var actualName = specialDomProperties[name] || name;
-
- this.forEach(function(element) {
- element.removeAttribute(actualName);
- });
-
- return this;
-};
-
-
-// Gets, and optionally sets, the html body of the first element
-// in the collection. If the +html+ is provided then the first
-// element's html body will be replaced with it.
-//
-ElementCollection.prototype.html = function (html) {
- if (html !== void 0) {
- this.first.innerHTML = html;
- }
-
- return this.first.innerHTML;
-};
-
-
-// Centers +element+ within the window. You can pass through the width and height
-// if you know it, if you don't they will be calculated for you.
-ElementCollection.prototype.center = function (width, height) {
- var $element;
-
- this.forEach(function(element) {
- $element = $(element);
- if (!width) width = parseInt($element.width(), 10);
- if (!height) height = parseInt($element.height(), 10);
-
- var marginLeft = -0.5 * width + 'px';
- var marginTop = -0.5 * height + 'px';
-
- $element.css('margin', marginTop + ' 0 0 ' + marginLeft)
- .addClass('OT_centered');
- });
-
- return this;
-};
-
-
-// @remove
-// Centers +element+ within the window. You can pass through the width and height
-// if you know it, if you don't they will be calculated for you.
-OTHelpers.centerElement = function(element, width, height) {
- return $(element).center(width, height);
-};
-
- /**
- * Methods to calculate element widths and heights.
- */
-(function() {
-
- var _width = function(element) {
- if (element.offsetWidth > 0) {
- return element.offsetWidth + 'px';
- }
-
- return $(element).css('width');
- },
-
- _height = function(element) {
- if (element.offsetHeight > 0) {
- return element.offsetHeight + 'px';
- }
-
- return $(element).css('height');
- };
-
- ElementCollection.prototype.width = function (newWidth) {
- if (newWidth) {
- this.css('width', newWidth);
- return this;
- }
- else {
- if (this.isDisplayNone()) {
- return this.makeVisibleAndYield(function(element) {
- return _width(element);
- })[0];
- }
- else {
- return _width(this.get(0));
- }
- }
- };
-
- ElementCollection.prototype.height = function (newHeight) {
- if (newHeight) {
- this.css('height', newHeight);
- return this;
- }
- else {
- if (this.isDisplayNone()) {
- // We can't get the height, probably since the element is hidden.
- return this.makeVisibleAndYield(function(element) {
- return _height(element);
- })[0];
- }
- else {
- return _height(this.get(0));
- }
- }
- };
-
- // @remove
- OTHelpers.width = function(element, newWidth) {
- var ret = $(element).width(newWidth);
- return newWidth ? OTHelpers : ret;
- };
-
- // @remove
- OTHelpers.height = function(element, newHeight) {
- var ret = $(element).height(newHeight);
- return newHeight ? OTHelpers : ret;
- };
-
-})();
-
-
-// CSS helpers helpers
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./dom.js')
-// tb_require('./getcomputedstyle.js')
-
-(function() {
-
- var displayStateCache = {},
- defaultDisplays = {};
-
- var defaultDisplayValueForElement = function (element) {
- if (defaultDisplays[element.ownerDocument] &&
- defaultDisplays[element.ownerDocument][element.nodeName]) {
- return defaultDisplays[element.ownerDocument][element.nodeName];
- }
-
- if (!defaultDisplays[element.ownerDocument]) defaultDisplays[element.ownerDocument] = {};
-
- // We need to know what display value to use for this node. The easiest way
- // is to actually create a node and read it out.
- var testNode = element.ownerDocument.createElement(element.nodeName),
- defaultDisplay;
-
- element.ownerDocument.body.appendChild(testNode);
- defaultDisplay = defaultDisplays[element.ownerDocument][element.nodeName] =
- $(testNode).css('display');
-
- $(testNode).remove();
- testNode = null;
-
- return defaultDisplay;
- };
-
- var isHidden = function (element) {
- var computedStyle = $.getComputedStyle(element);
- return computedStyle.getPropertyValue('display') === 'none';
- };
-
- var setCssProperties = function (element, hash) {
- var style = element.style;
-
- for (var cssName in hash) {
- if (hash.hasOwnProperty(cssName)) {
- style[cssName] = hash[cssName];
- }
- }
- };
-
- var setCssProperty = function (element, name, value) {
- element.style[name] = value;
- };
-
- var getCssProperty = function (element, unnormalisedName) {
- // Normalise vendor prefixes from the form MozTranform to -moz-transform
- // except for ms extensions, which are weird...
-
- var name = unnormalisedName.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(),
- computedStyle = $.getComputedStyle(element),
- currentValue = computedStyle.getPropertyValue(name);
-
- if (currentValue === '') {
- currentValue = element.style[name];
- }
-
- return currentValue;
- };
-
- var applyCSS = function(element, styles, callback) {
- var oldStyles = {},
- name,
- ret;
-
- // Backup the old styles
- for (name in styles) {
- if (styles.hasOwnProperty(name)) {
- // We intentionally read out of style here, instead of using the css
- // helper. This is because the css helper uses querySelector and we
- // only want to pull values out of the style (domeElement.style) hash.
- oldStyles[name] = element.style[name];
-
- $(element).css(name, styles[name]);
- }
- }
-
- ret = callback(element);
-
- // Restore the old styles
- for (name in styles) {
- if (styles.hasOwnProperty(name)) {
- $(element).css(name, oldStyles[name] || '');
- }
- }
-
- return ret;
- };
-
- ElementCollection.prototype.show = function() {
- return this.forEach(function(element) {
- var display = element.style.display;
-
- if (display === '' || display === 'none') {
- element.style.display = displayStateCache[element] || '';
- delete displayStateCache[element];
- }
-
- if (isHidden(element)) {
- // It's still hidden so there's probably a stylesheet that declares this
- // element as display:none;
- displayStateCache[element] = 'none';
-
- element.style.display = defaultDisplayValueForElement(element);
- }
- });
- };
-
- ElementCollection.prototype.hide = function() {
- return this.forEach(function(element) {
- if (element.style.display === 'none') return;
-
- displayStateCache[element] = element.style.display;
- element.style.display = 'none';
- });
- };
-
- ElementCollection.prototype.css = function(nameOrHash, value) {
- if (this.length === 0) return;
-
- if (typeof(nameOrHash) !== 'string') {
-
- return this.forEach(function(element) {
- setCssProperties(element, nameOrHash);
- });
-
- } else if (value !== undefined) {
-
- return this.forEach(function(element) {
- setCssProperty(element, nameOrHash, value);
- });
-
- } else {
- return getCssProperty(this.first, nameOrHash, value);
- }
- };
-
- // Apply +styles+ to +element+ while executing +callback+, restoring the previous
- // styles after the callback executes.
- ElementCollection.prototype.applyCSS = function (styles, callback) {
- var results = [];
-
- this.forEach(function(element) {
- results.push(applyCSS(element, styles, callback));
- });
-
- return results;
- };
-
-
- // Make +element+ visible while executing +callback+.
- ElementCollection.prototype.makeVisibleAndYield = function (callback) {
- var hiddenVisually = {
- display: 'block',
- visibility: 'hidden'
- },
- results = [];
-
- this.forEach(function(element) {
- // find whether it's the element or an ancestor that's display none and
- // then apply to whichever it is
- var targetElement = $.findElementWithDisplayNone(element);
- if (!targetElement) {
- results.push(void 0);
- }
- else {
- results.push(
- applyCSS(targetElement, hiddenVisually, callback)
- );
- }
- });
-
- return results;
- };
-
-
- // @remove
- OTHelpers.show = function(element) {
- return $(element).show();
- };
-
- // @remove
- OTHelpers.hide = function(element) {
- return $(element).hide();
- };
-
- // @remove
- OTHelpers.css = function(element, nameOrHash, value) {
- return $(element).css(nameOrHash, value);
- };
-
- // @remove
- OTHelpers.applyCSS = function(element, styles, callback) {
- return $(element).applyCSS(styles, callback);
- };
-
- // @remove
- OTHelpers.makeVisibleAndYield = function(element, callback) {
- return $(element).makeVisibleAndYield(callback);
- };
-
-})();
-
-// tb_require('../helpers.js')
-
-/**@licence
- * Copyright (c) 2010 Caolan McMahon
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- **/
-
-
-(function() {
-
- OTHelpers.setImmediate = (function() {
- if (typeof process === 'undefined' || !(process.nextTick)) {
- if (typeof setImmediate === 'function') {
- return function (fn) {
- // not a direct alias for IE10 compatibility
- setImmediate(fn);
- };
- }
- return function (fn) {
- setTimeout(fn, 0);
- };
- }
- if (typeof setImmediate !== 'undefined') {
- return setImmediate;
- }
- return process.nextTick;
- })();
-
- OTHelpers.iterator = function(tasks) {
- var makeCallback = function (index) {
- var fn = function () {
- if (tasks.length) {
- tasks[index].apply(null, arguments);
- }
- return fn.next();
- };
- fn.next = function () {
- return (index < tasks.length - 1) ? makeCallback(index + 1) : null;
- };
- return fn;
- };
- return makeCallback(0);
- };
-
- OTHelpers.waterfall = function(array, done) {
- done = done || function () {};
- if (array.constructor !== Array) {
- return done(new Error('First argument to waterfall must be an array of functions'));
- }
-
- if (!array.length) {
- return done();
- }
-
- var next = function(iterator) {
- return function (err) {
- if (err) {
- done.apply(null, arguments);
- done = function () {};
- } else {
- var args = prototypeSlice.call(arguments, 1),
- nextFn = iterator.next();
- if (nextFn) {
- args.push(next(nextFn));
- } else {
- args.push(done);
- }
- OTHelpers.setImmediate(function() {
- iterator.apply(null, args);
- });
- }
- };
- };
-
- next(OTHelpers.iterator(array))();
- };
-
-})();
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-(function() {
-
- var requestAnimationFrame = window.requestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.msRequestAnimationFrame;
-
- if (requestAnimationFrame) {
- requestAnimationFrame = OTHelpers.bind(requestAnimationFrame, window);
- }
- else {
- var lastTime = 0;
- var startTime = OTHelpers.now();
-
- requestAnimationFrame = function(callback){
- var currTime = OTHelpers.now();
- var timeToCall = Math.max(0, 16 - (currTime - lastTime));
- var id = window.setTimeout(function() { callback(currTime - startTime); }, timeToCall);
- lastTime = currTime + timeToCall;
- return id;
- };
- }
-
- OTHelpers.requestAnimationFrame = requestAnimationFrame;
-})();
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-(function() {
-
- // Singleton interval
- var logQueue = [],
- queueRunning = false;
-
- OTHelpers.Analytics = function(loggingUrl, debugFn) {
-
- var endPoint = loggingUrl + '/logging/ClientEvent',
- endPointQos = loggingUrl + '/logging/ClientQos',
-
- reportedErrors = {},
-
- send = function(data, isQos, callback) {
- OTHelpers.post((isQos ? endPointQos : endPoint) + '?_=' + OTHelpers.uuid.v4(), {
- body: data,
- xdomainrequest: ($.env.name === 'IE' && $.env.version < 10),
- headers: {
- 'Content-Type': 'application/json'
- }
- }, callback);
- },
-
- throttledPost = function() {
- // Throttle logs so that they only happen 1 at a time
- if (!queueRunning && logQueue.length > 0) {
- queueRunning = true;
- var curr = logQueue[0];
-
- // Remove the current item and send the next log
- var processNextItem = function() {
- logQueue.shift();
- queueRunning = false;
- throttledPost();
- };
-
- if (curr) {
- send(curr.data, curr.isQos, function(err) {
- if (err) {
- var debugMsg = 'Failed to send ClientEvent, moving on to the next item.';
- if (debugFn) {
- debugFn(debugMsg);
- } else {
- console.log(debugMsg);
- }
- // There was an error, move onto the next item
- } else {
- curr.onComplete();
- }
- setTimeout(processNextItem, 50);
- });
- }
- }
- },
-
- post = function(data, onComplete, isQos) {
- logQueue.push({
- data: data,
- onComplete: onComplete,
- isQos: isQos
- });
-
- throttledPost();
- },
-
- shouldThrottleError = function(code, type, partnerId) {
- if (!partnerId) return false;
-
- var errKey = [partnerId, type, code].join('_'),
- //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId);
- msgLimit = 100;
- if (msgLimit === null || msgLimit === undefined) return false;
- return (reportedErrors[errKey] || 0) <= msgLimit;
- };
-
- // Log an error via ClientEvents.
- //
- // @param [String] code
- // @param [String] type
- // @param [String] message
- // @param [Hash] details additional error details
- //
- // @param [Hash] options the options to log the client event with.
- // @option options [String] action The name of the Event that we are logging. E.g.
- // 'TokShowLoaded'. Required.
- // @option options [String] variation Usually used for Split A/B testing, when you
- // have multiple variations of the +_action+.
- // @option options [String] payload The payload. Required.
- // @option options [String] sessionId The active OpenTok session, if there is one
- // @option options [String] connectionId The active OpenTok connectionId, if there is one
- // @option options [String] partnerId
- // @option options [String] guid ...
- // @option options [String] streamId ...
- // @option options [String] section ...
- // @option options [String] clientVersion ...
- //
- // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner
- // from the dynamic config for X) of each error type for each partner. Reports can be
- // disabled/enabled globally or on a per partner basis (per partner settings
- // take precedence) using exceptionLogging.enabled.
- //
- this.logError = function(code, type, message, details, options) {
- if (!options) options = {};
- var partnerId = options.partnerId;
-
- if (shouldThrottleError(code, type, partnerId)) {
- //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' +
- // code + ' for partner ' + (partnerId || 'No Partner Id'));
- return;
- }
-
- var errKey = [partnerId, type, code].join('_'),
- payload = details ? details : null;
-
- reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ?
- reportedErrors[errKey] + 1 : 1;
- this.logEvent(OTHelpers.extend(options, {
- action: type + '.' + code,
- payload: payload
- }), false);
- };
-
- // Log a client event to the analytics backend.
- //
- // @example Logs a client event called 'foo'
- // this.logEvent({
- // action: 'foo',
- // payload: 'bar',
- // sessionId: sessionId,
- // connectionId: connectionId
- // }, false)
- //
- // @param [Hash] data the data to log the client event with.
- // @param [Boolean] qos Whether this is a QoS event.
- //
- this.logEvent = function(data, qos, throttle) {
- if (!qos) qos = false;
-
- if (throttle && !isNaN(throttle)) {
- if (Math.random() > throttle) {
- return;
- }
- }
-
- // remove properties that have null values:
- for (var key in data) {
- if (data.hasOwnProperty(key) && data[key] === null) {
- delete data[key];
- }
- }
-
- // TODO: catch error when stringifying an object that has a circular reference
- data = JSON.stringify(data);
-
- var onComplete = function() {
- // OT.log('logged: ' + '{action: ' + data['action'] + ', variation: ' + data['variation']
- // + ', payload: ' + data['payload'] + '}');
- };
-
- post(data, onComplete, qos);
- };
-
- // Log a client QOS to the analytics backend.
- // Log a client QOS to the analytics backend.
- // @option options [String] action The name of the Event that we are logging.
- // E.g. 'TokShowLoaded'. Required.
- // @option options [String] variation Usually used for Split A/B testing, when
- // you have multiple variations of the +_action+.
- // @option options [String] payload The payload. Required.
- // @option options [String] sessionId The active OpenTok session, if there is one
- // @option options [String] connectionId The active OpenTok connectionId, if there is one
- // @option options [String] partnerId
- // @option options [String] guid ...
- // @option options [String] streamId ...
- // @option options [String] section ...
- // @option options [String] clientVersion ...
- //
- this.logQOS = function(options) {
- this.logEvent(options, true);
- };
- };
-
-})();
-
-// AJAX helpers
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./ajax/node.js')
-// tb_require('./ajax/browser.js')
-
-OTHelpers.get = function(url, options, callback) {
- var _options = OTHelpers.extend(options || {}, {
- method: 'GET'
- });
- OTHelpers.request(url, _options, callback);
-};
-
-
-OTHelpers.post = function(url, options, callback) {
- var _options = OTHelpers.extend(options || {}, {
- method: 'POST'
- });
-
- if(_options.xdomainrequest) {
- OTHelpers.xdomainRequest(url, _options, callback);
- } else {
- OTHelpers.request(url, _options, callback);
- }
-};
-
})(window, window.OTHelpers);
/**
- * @license TB Plugin 0.4.0.10 6935b20 HEAD
+ * @license TB Plugin 0.4.0.12 6e40a4e v0.4.0.12-branch
* http://www.tokbox.com/
*
* Copyright (c) 2015 TokBox, Inc.
*
- * Date: July 13 05:38:06 2015
+ * Date: October 28 03:45:04 2015
*
*/
/* global scope:true */
/* exported OTPlugin */
/* jshint ignore:start */
(function(scope) {
/* jshint ignore:end */
// If we've already be setup, bail
if (scope.OTPlugin !== void 0) return;
var $ = OTHelpers;
+// Magic number to avoid plugin crashes through a settimeout call
+window.EmpiricDelay = 3000;
+
// TB must exist first, otherwise we can't do anything
// if (scope.OT === void 0) return;
// Establish the environment that we're running in
// Note: we don't currently support 64bit IE
-var isSupported = $.env.name === 'Safari' ||
- ($.env.name === 'IE' && $.env.version >= 8 &&
+var isSupported = ($.env.name === 'IE' && $.env.version >= 8 &&
$.env.userAgent.indexOf('x64') === -1),
pluginIsReady = false;
-
var OTPlugin = {
- isSupported: function () { return isSupported; },
+ isSupported: function() { return isSupported; },
isReady: function() { return pluginIsReady; },
meta: {
- mimeType: 'application/x-opentokie,version=0.4.0.10',
- activeXName: 'TokBox.OpenTokIE.0.4.0.10',
- version: '0.4.0.10'
+ mimeType: 'application/x-opentokplugin,version=0.4.0.12',
+ activeXName: 'TokBox.OpenTokPlugin.0.4.0.12',
+ version: '0.4.0.12'
},
useLoggingFrom: function(host) {
// TODO there's no way to revert this, should there be?
OTPlugin.log = $.bind(host.log, host);
OTPlugin.debug = $.bind(host.debug, host);
OTPlugin.info = $.bind(host.info, host);
OTPlugin.warn = $.bind(host.warn, host);
OTPlugin.error = $.bind(host.error, host);
}
};
-
// Add logging methods
$.useLogHelpers(OTPlugin);
-
scope.OTPlugin = OTPlugin;
$.registerCapability('otplugin', function() {
return OTPlugin.isInstalled();
});
// If this client isn't supported we still make sure that OTPlugin is defined
// and the basic API (isSupported() and isInstalled()) is created.
if (!OTPlugin.isSupported()) {
- OTPlugin.isInstalled = function isInstalled () { return false; };
+ OTPlugin.isInstalled = function isInstalled() { return false; };
return;
}
// tb_require('./header.js')
/* exported shim */
// Shims for various missing things from JS
// Applied only after init is called to prevent unnecessary polution
-var shim = function shim () {
+var shim = function shim() {
if (!Function.prototype.bind) {
- Function.prototype.bind = function (oThis) {
+ Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
- FNOP = function () {},
- fBound = function () {
+ FNOP = function() {},
+ fBound = function() {
return fToBind.apply(this instanceof FNOP && oThis ?
this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
};
FNOP.prototype = this.prototype;
fBound.prototype = new FNOP();
return fBound;
};
}
- if(!Array.isArray) {
- Array.isArray = function (vArg) {
+ if (!Array.isArray) {
+ Array.isArray = function(vArg) {
return Object.prototype.toString.call(vArg) === '[object Array]';
};
}
if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function (searchElement, fromIndex) {
+ Array.prototype.indexOf = function(searchElement, fromIndex) {
var i,
pivot = (fromIndex) ? fromIndex : 0,
length;
if (!this) {
throw new TypeError();
}
@@ -6265,43 +6299,69 @@ var shim = function shim () {
if (i in t)
res[i] = fun.call(thisArg, t[i], i, t);
}
return res;
};
}
};
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+
+/* exported TaskQueue */
+
+var TaskQueue = function() {
+ var Proto = function TaskQueue() {},
+ api = new Proto(),
+ tasks = [];
+
+ api.add = function(fn, context) {
+ if (context) {
+ tasks.push($.bind(fn, context));
+ } else {
+ tasks.push(fn);
+ }
+ };
+
+ api.runAll = function() {
+ var task;
+ while ((task = tasks.shift())) {
+ task();
+ }
+ };
+
+ return api;
+};
+
// tb_require('./header.js')
// tb_require('./shims.js')
/* global curryCallAsync:true */
/* exported RumorSocket */
-var RumorSocket = function (plugin, server) {
- var Proto = function RumorSocket () {},
+var RumorSocket = function(plugin, server) {
+ var Proto = function RumorSocket() {},
api = new Proto(),
connected = false,
rumorID,
_onOpen,
_onClose;
-
try {
rumorID = plugin._.RumorInit(server, '');
- }
- catch(e) {
+ } catch (e) {
OTPlugin.error('Error creating the Rumor Socket: ', e.message);
}
- if(!rumorID) {
+ if (!rumorID) {
throw new Error('Could not initialise OTPlugin rumor connection');
}
-
api.open = function() {
connected = true;
plugin._.RumorOpen(rumorID);
};
api.close = function(code, reason) {
if (connected) {
connected = false;
@@ -6354,299 +6414,325 @@ var RumorSocket = function (plugin, serv
return api;
};
// tb_require('./header.js')
// tb_require('./shims.js')
/* exported refCountBehaviour */
-var refCountBehaviour = function refCountBehaviour (api) {
+var refCountBehaviour = function refCountBehaviour(api) {
var _liveObjects = [];
- api.addRef = function (ref) {
+ api.addRef = function(ref) {
_liveObjects.push(ref);
return api;
};
- api.removeRef = function (ref) {
+ api.removeRef = function(ref) {
if (_liveObjects.length === 0) return;
var index = _liveObjects.indexOf(ref);
if (index !== -1) {
_liveObjects.splice(index, 1);
}
if (_liveObjects.length === 0) {
api.destroy();
}
return api;
};
- api.removeAllRefs = function () {
+ api.removeAllRefs = function() {
while (_liveObjects.length) {
_liveObjects.shift().destroy();
}
};
};
// tb_require('./header.js')
// tb_require('./shims.js')
-
-/* global curryCallAsync:true */
+// tb_require('./task_queue.js')
+
+/* global curryCallAsync:true, TaskQueue:true */
/* exported pluginEventingBehaviour */
-var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
- var eventHandlers = {};
+var pluginEventingBehaviour = function pluginEventingBehaviour(api) {
+ var eventHandlers = {},
+ pendingTasks = new TaskQueue(),
+ isReady = false;
var onCustomEvent = function() {
var args = Array.prototype.slice.call(arguments);
api.emit(args.shift(), args);
};
- api.on = function (name, callback, context) {
+ var devicesChanged = function() {
+ var args = Array.prototype.slice.call(arguments);
+ OTPlugin.debug(args);
+ api.emit('devicesChanged', args);
+ };
+
+ api.on = function(name, callback, context) {
if (!eventHandlers.hasOwnProperty(name)) {
eventHandlers[name] = [];
}
eventHandlers[name].push([callback, context]);
return api;
};
- api.off = function (name, callback, context) {
+ api.off = function(name, callback, context) {
if (!eventHandlers.hasOwnProperty(name) ||
eventHandlers[name].length === 0) {
return;
}
$.filter(eventHandlers[name], function(listener) {
return listener[0] === callback &&
listener[1] === context;
});
return api;
};
- api.once = function (name, callback, context) {
- var fn = function () {
+ api.once = function(name, callback, context) {
+ var fn = function() {
api.off(name, fn);
return callback.apply(context, arguments);
};
api.on(name, fn);
return api;
};
- api.emit = function (name, args) {
+ api.emit = function(name, args) {
$.callAsync(function() {
if (!eventHandlers.hasOwnProperty(name) && eventHandlers[name].length) {
return;
}
$.forEach(eventHandlers[name], function(handler) {
handler[0].apply(handler[1], args);
});
});
return api;
};
- var onReady = function onReady (readyCallback) {
+ // Calling this will bind a listener to the devicesChanged events that
+ // the plugin emits and then rebroadcast them.
+ api.listenForDeviceChanges = function() {
+ if (!isReady) {
+ pendingTasks.add(api.listenForDeviceChanges, api);
+ return;
+ }
+
+ setTimeout(function() {
+ api._.registerXCallback('devicesChanged', devicesChanged);
+ }, window.EmpiricDelay);
+ };
+
+ var onReady = function onReady(readyCallback) {
if (api._.on) {
// If the plugin supports custom events we'll use them
api._.on(-1, {
customEvent: curryCallAsync(onCustomEvent)
});
}
+ var internalReadyCallback = function() {
+ // It's not safe to do most plugin operations until the plugin
+ // is ready for us to do so. We use isReady as a guard in
+ isReady = true;
+
+ pendingTasks.runAll();
+ readyCallback.call(api);
+ };
+
// Only the main plugin has an initialise method
if (api._.initialise) {
- api.on('ready', curryCallAsync(readyCallback));
+ api.on('ready', curryCallAsync(internalReadyCallback));
api._.initialise();
- }
- else {
- readyCallback.call(api);
- }
- };
-
- return function (completion) {
+ } else {
+ internalReadyCallback.call(api);
+ }
+ };
+
+ return function(completion) {
onReady(function(err) {
if (err) {
OTPlugin.error('Error while starting up plugin ' + api.uuid + ': ' + err);
completion(err);
return;
}
OTPlugin.debug('Plugin ' + api.id + ' is loaded');
completion(void 0, api);
});
};
};
+
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./ref_count_behaviour.js')
// tb_require('./plugin_eventing_behaviour.js')
/* global refCountBehaviour:true, pluginEventingBehaviour:true, scope:true */
/* exported createPluginProxy, curryCallAsync, makeMediaPeerProxy, makeMediaCapturerProxy */
var PROXY_LOAD_TIMEOUT = 5000;
var objectTimeouts = {};
-var curryCallAsync = function curryCallAsync (fn) {
+var curryCallAsync = function curryCallAsync(fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(fn);
$.callAsync.apply($, args);
};
};
-var clearGlobalCallback = function clearGlobalCallback (callbackId) {
+var clearGlobalCallback = function clearGlobalCallback(callbackId) {
if (!callbackId) return;
if (objectTimeouts[callbackId]) {
clearTimeout(objectTimeouts[callbackId]);
objectTimeouts[callbackId] = null;
}
if (scope[callbackId]) {
try {
delete scope[callbackId];
} catch (err) {
scope[callbackId] = void 0;
}
}
};
-var waitOnGlobalCallback = function waitOnGlobalCallback (callbackId, completion) {
+var waitOnGlobalCallback = function waitOnGlobalCallback(callbackId, completion) {
objectTimeouts[callbackId] = setTimeout(function() {
clearGlobalCallback(callbackId);
completion('The object timed out while loading.');
}, PROXY_LOAD_TIMEOUT);
scope[callbackId] = function() {
clearGlobalCallback(callbackId);
var args = Array.prototype.slice.call(arguments);
args.unshift(null);
completion.apply(null, args);
};
};
-var generateCallbackID = function generateCallbackID () {
+var generateCallbackID = function generateCallbackID() {
return 'OTPlugin_loaded_' + $.uuid().replace(/\-+/g, '');
};
-var generateObjectHtml = function generateObjectHtml (callbackId, options) {
+var generateObjectHtml = function generateObjectHtml(callbackId, options) {
options = options || {};
var objBits = [],
attrs = [
'type="' + options.mimeType + '"',
'id="' + callbackId + '_obj"',
'tb_callback_id="' + callbackId + '"',
'width="0" height="0"'
],
params = {
userAgent: $.env.userAgent.toLowerCase(),
windowless: options.windowless,
onload: callbackId
};
-
if (options.isVisible !== true) {
attrs.push('visibility="hidden"');
}
objBits.push('<object ' + attrs.join(' ') + '>');
for (var name in params) {
if (params.hasOwnProperty(name)) {
objBits.push('<param name="' + name + '" value="' + params[name] + '" />');
}
}
objBits.push('</object>');
return objBits.join('');
};
-
-
-var createObject = function createObject (callbackId, options, completion) {
+var createObject = function createObject(callbackId, options, completion) {
options = options || {};
var html = generateObjectHtml(callbackId, options),
doc = options.doc || scope.document;
// if (options.robust !== false) {
// new createFrame(html, callbackId, scope[callbackId], function(frame, win, doc) {
// var object = doc.getElementById(callbackId+'_obj');
// object.removeAttribute('id');
// completion(void 0, object, frame);
// });
// }
// else {
-
doc.body.insertAdjacentHTML('beforeend', html);
- var object = doc.body.querySelector('#'+callbackId+'_obj');
+ var object = doc.body.querySelector('#' + callbackId + '_obj');
// object.setAttribute('type', options.mimeType);
completion(void 0, object);
// }
};
// Reference counted wrapper for a plugin object
-var createPluginProxy = function (options, completion) {
+var createPluginProxy = function(options, completion) {
var Proto = function PluginProxy() {},
api = new Proto(),
waitForReadySignal = pluginEventingBehaviour(api);
refCountBehaviour(api);
// Assign +plugin+ to this object and setup all the public
// accessors that relate to the DOM Object.
//
- var setPlugin = function setPlugin (plugin) {
+ var setPlugin = function setPlugin(plugin) {
if (plugin) {
api._ = plugin;
api.parentElement = plugin.parentElement;
api.$ = $(plugin);
- }
- else {
+ } else {
api._ = null;
api.parentElement = null;
api.$ = $();
}
};
-
api.uuid = generateCallbackID();
api.isValid = function() {
return api._.valid;
};
api.destroy = function() {
api.removeAllRefs();
setPlugin(null);
// Let listeners know that they should do any final book keeping
// that relates to us.
api.emit('destroy');
};
-
+ api.enumerateDevices = function(completion) {
+ api._.enumerateDevices(completion);
+ };
/// Initialise
-
// The next statement creates the raw plugin object accessor on the Proxy.
// This is null until we actually have created the Object.
setPlugin(null);
waitOnGlobalCallback(api.uuid, function(err) {
if (err) {
completion('The plugin with the mimeType of ' +
options.mimeType + ' timed out while loading: ' + err);
@@ -6673,244 +6759,231 @@ var createPluginProxy = function (option
createObject(api.uuid, options, function(err, plugin) {
setPlugin(plugin);
});
return api;
};
-
-
-
// Specialisation for the MediaCapturer API surface
-var makeMediaCapturerProxy = function makeMediaCapturerProxy (api) {
-
+var makeMediaCapturerProxy = function makeMediaCapturerProxy(api) {
api.selectSources = function() {
return api._.selectSources.apply(api._, arguments);
};
+ api.listenForDeviceChanges();
return api;
};
-
// Specialisation for the MediaPeer API surface
-var makeMediaPeerProxy = function makeMediaPeerProxy (api) {
+var makeMediaPeerProxy = function makeMediaPeerProxy(api) {
api.setStream = function(stream, completion) {
api._.setStream(stream);
if (completion) {
if (stream.hasVideo()) {
// FIX ME renderingStarted currently doesn't first
// api.once('renderingStarted', completion);
var verifyStream = function() {
if (!api._) {
completion(new $.Error('The plugin went away before the stream could be bound.'));
return;
}
if (api._.videoWidth > 0) {
// This fires a little too soon.
setTimeout(completion, 200);
- }
- else {
+ } else {
setTimeout(verifyStream, 500);
}
};
setTimeout(verifyStream, 500);
- }
- else {
+ } else {
// TODO Investigate whether there is a good way to detect
// when the audio is ready. Does it even matter?
// This fires a little too soon.
setTimeout(completion, 200);
}
}
return api;
};
return api;
};
-
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./proxy.js')
/* exported VideoContainer */
-var VideoContainer = function (plugin, stream) {
- var Proto = function VideoContainer () {},
+var VideoContainer = function(plugin, stream) {
+ var Proto = function VideoContainer() {},
api = new Proto();
api.domElement = plugin._;
api.$ = $(plugin._);
api.parentElement = plugin._.parentNode;
plugin.addRef(api);
- api.appendTo = function (parentDomElement) {
+ api.appendTo = function(parentDomElement) {
if (parentDomElement && plugin._.parentNode !== parentDomElement) {
OTPlugin.debug('VideoContainer appendTo', parentDomElement);
parentDomElement.appendChild(plugin._);
api.parentElement = parentDomElement;
}
};
- api.show = function (completion) {
+ api.show = function(completion) {
OTPlugin.debug('VideoContainer show');
plugin._.removeAttribute('width');
plugin._.removeAttribute('height');
plugin.setStream(stream, completion);
$.show(plugin._);
return api;
};
api.setSize = function(width, height) {
plugin._.setAttribute('width', width);
plugin._.setAttribute('height', height);
return api;
};
- api.width = function (newWidth) {
+ api.width = function(newWidth) {
if (newWidth !== void 0) {
OTPlugin.debug('VideoContainer set width to ' + newWidth);
plugin._.setAttribute('width', newWidth);
}
return plugin._.getAttribute('width');
};
- api.height = function (newHeight) {
+ api.height = function(newHeight) {
if (newHeight !== void 0) {
OTPlugin.debug('VideoContainer set height to ' + newHeight);
plugin._.setAttribute('height', newHeight);
}
return plugin._.getAttribute('height');
};
- api.volume = function (newVolume) {
+ api.volume = function(newVolume) {
if (newVolume !== void 0) {
// TODO
OTPlugin.debug('VideoContainer setVolume not implemented: called with ' + newVolume);
- }
- else {
+ } else {
OTPlugin.debug('VideoContainer getVolume not implemented');
}
return 0.5;
};
- api.getImgData = function () {
+ api.getImgData = function() {
return plugin._.getImgData('image/png');
};
- api.videoWidth = function () {
+ api.videoWidth = function() {
return plugin._.videoWidth;
};
- api.videoHeight = function () {
+ api.videoHeight = function() {
return plugin._.videoHeight;
};
- api.destroy = function () {
+ api.destroy = function() {
plugin._.setStream(null);
plugin.removeRef(api);
};
return api;
};
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./proxy.js')
/* exported RTCStatsReport */
-var RTCStatsReport = function RTCStatsReport (reports) {
+var RTCStatsReport = function RTCStatsReport(reports) {
for (var id in reports) {
if (reports.hasOwnProperty(id)) {
this[id] = reports[id];
}
}
};
-RTCStatsReport.prototype.forEach = function (callback, context) {
+RTCStatsReport.prototype.forEach = function(callback, context) {
for (var id in this) {
if (this.hasOwnProperty(id)) {
callback.call(context, this[id]);
}
}
};
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./proxy.js')
/* global createPluginProxy:true, makeMediaPeerProxy:true, makeMediaCapturerProxy:true */
/* exported PluginProxies */
-
var PluginProxies = (function() {
- var Proto = function PluginProxies () {},
+ var Proto = function PluginProxies() {},
api = new Proto(),
proxies = {};
-
/// Private API
// This is called whenever a Proxy's destroy event fires.
- var cleanupProxyOnDestroy = function cleanupProxyOnDestroy (object) {
+ var cleanupProxyOnDestroy = function cleanupProxyOnDestroy(object) {
if (api.mediaCapturer && api.mediaCapturer.id === object.id) {
api.mediaCapturer = null;
- }
- else if (proxies.hasOwnProperty(object.id)) {
+ } else if (proxies.hasOwnProperty(object.id)) {
delete proxies[object.id];
}
if (object.$) {
object.$.remove();
}
};
-
/// Public API
-
// Public accessor for the MediaCapturer
api.mediaCapturer = null;
- api.removeAll = function removeAll () {
+ api.removeAll = function removeAll() {
for (var id in proxies) {
if (proxies.hasOwnProperty(id)) {
proxies[id].destroy();
}
}
if (api.mediaCapturer) api.mediaCapturer.destroy();
};
- api.create = function create (options, completion) {
+ api.create = function create(options, completion) {
var proxy = createPluginProxy(options, completion);
proxies[proxy.uuid] = proxy;
// Clean up after this Proxy when it's destroyed.
proxy.on('destroy', function() {
cleanupProxyOnDestroy(proxy);
});
return proxy;
};
- api.createMediaPeer = function createMediaPeer (options, completion) {
+ api.createMediaPeer = function createMediaPeer(options, completion) {
if ($.isFunction(options)) {
completion = options;
options = {};
}
var mediaPeer = api.create($.extend(options || {}, {
mimeType: OTPlugin.meta.mimeType,
isVisible: true,
@@ -6923,17 +6996,17 @@ var PluginProxies = (function() {
proxies[mediaPeer.id] = mediaPeer;
completion.call(OTPlugin, void 0, mediaPeer);
});
makeMediaPeerProxy(mediaPeer);
};
- api.createMediaCapturer = function createMediaCapturer (completion) {
+ api.createMediaCapturer = function createMediaCapturer(completion) {
if (api.mediaCapturer) {
completion.call(OTPlugin, void 0, api.mediaCapturer);
return api;
}
api.mediaCapturer = api.create({
mimeType: OTPlugin.meta.mimeType,
isVisible: false,
@@ -6954,18 +7027,18 @@ var PluginProxies = (function() {
// tb_require('./stats.js')
/* global MediaStream:true, RTCStatsReport:true, curryCallAsync:true */
/* exported PeerConnection */
// Our RTCPeerConnection shim, it should look like a normal PeerConection
// from the outside, but it actually delegates to our plugin.
//
-var PeerConnection = function (iceServers, options, plugin, ready) {
- var Proto = function PeerConnection () {},
+var PeerConnection = function(iceServers, options, plugin, ready) {
+ var Proto = function PeerConnection() {},
api = new Proto(),
id = $.uuid(),
hasLocalDescription = false,
hasRemoteDescription = false,
candidates = [],
inited = false,
deferMethods = [],
events;
@@ -6975,246 +7048,237 @@ var PeerConnection = function (iceServer
events = {
addstream: [],
removestream: [],
icecandidate: [],
signalingstatechange: [],
iceconnectionstatechange: []
};
- var onAddIceCandidate = function onAddIceCandidate () {/* success */},
-
- onAddIceCandidateFailed = function onAddIceCandidateFailed (err) {
+ var onAddIceCandidate = function onAddIceCandidate() {/* success */},
+
+ onAddIceCandidateFailed = function onAddIceCandidateFailed(err) {
OTPlugin.error('Failed to process candidate');
OTPlugin.error(err);
},
- processPendingCandidates = function processPendingCandidates () {
- for (var i=0; i<candidates.length; ++i) {
+ processPendingCandidates = function processPendingCandidates() {
+ for (var i = 0; i < candidates.length; ++i) {
plugin._.addIceCandidate(id, candidates[i], onAddIceCandidate, onAddIceCandidateFailed);
}
},
-
- deferMethod = function deferMethod (method) {
+ deferMethod = function deferMethod(method) {
return function() {
if (inited === true) {
return method.apply(api, arguments);
}
deferMethods.push([method, arguments]);
};
},
- processDeferredMethods = function processDeferredMethods () {
+ processDeferredMethods = function processDeferredMethods() {
var m;
- while ( (m = deferMethods.shift()) ) {
+ while ((m = deferMethods.shift())) {
m[0].apply(api, m[1]);
}
},
- triggerEvent = function triggerEvent (/* eventName [, arg1, arg2, ..., argN] */) {
+ triggerEvent = function triggerEvent(/* eventName [, arg1, arg2, ..., argN] */) {
var args = Array.prototype.slice.call(arguments),
eventName = args.shift();
if (!events.hasOwnProperty(eventName)) {
OTPlugin.error('PeerConnection does not have an event called: ' + eventName);
return;
}
$.forEach(events[eventName], function(listener) {
listener.apply(null, args);
});
},
- bindAndDelegateEvents = function bindAndDelegateEvents (events) {
+ bindAndDelegateEvents = function bindAndDelegateEvents(events) {
for (var name in events) {
if (events.hasOwnProperty(name)) {
events[name] = curryCallAsync(events[name]);
}
}
plugin._.on(id, events);
},
- addStream = function addStream (streamJson) {
+ addStream = function addStream(streamJson) {
setTimeout(function() {
var stream = MediaStream.fromJson(streamJson, plugin),
event = {stream: stream, target: api};
if (api.onaddstream && $.isFunction(api.onaddstream)) {
$.callAsync(api.onaddstream, event);
}
triggerEvent('addstream', event);
- }, 3000);
- },
-
- removeStream = function removeStream (streamJson) {
+ }, window.EmpiricDelay);
+ },
+
+ removeStream = function removeStream(streamJson) {
var stream = MediaStream.fromJson(streamJson, plugin),
event = {stream: stream, target: api};
if (api.onremovestream && $.isFunction(api.onremovestream)) {
$.callAsync(api.onremovestream, event);
}
triggerEvent('removestream', event);
},
- iceCandidate = function iceCandidate (candidateSdp, sdpMid, sdpMLineIndex) {
+ iceCandidate = function iceCandidate(candidateSdp, sdpMid, sdpMLineIndex) {
var candidate = new OTPlugin.RTCIceCandidate({
candidate: candidateSdp,
sdpMid: sdpMid,
sdpMLineIndex: sdpMLineIndex
});
var event = {candidate: candidate, target: api};
if (api.onicecandidate && $.isFunction(api.onicecandidate)) {
$.callAsync(api.onicecandidate, event);
}
triggerEvent('icecandidate', event);
},
- signalingStateChange = function signalingStateChange (state) {
+ signalingStateChange = function signalingStateChange(state) {
api.signalingState = state;
var event = {state: state, target: api};
if (api.onsignalingstatechange &&
$.isFunction(api.onsignalingstatechange)) {
$.callAsync(api.onsignalingstatechange, event);
}
triggerEvent('signalingstate', event);
},
- iceConnectionChange = function iceConnectionChange (state) {
+ iceConnectionChange = function iceConnectionChange(state) {
api.iceConnectionState = state;
var event = {state: state, target: api};
if (api.oniceconnectionstatechange &&
$.isFunction(api.oniceconnectionstatechange)) {
$.callAsync(api.oniceconnectionstatechange, event);
}
triggerEvent('iceconnectionstatechange', event);
};
- api.createOffer = deferMethod(function (success, error, constraints) {
+ api.createOffer = deferMethod(function(success, error, constraints) {
OTPlugin.debug('createOffer', constraints);
plugin._.createOffer(id, function(type, sdp) {
success(new OTPlugin.RTCSessionDescription({
type: type,
sdp: sdp
}));
}, error, constraints || {});
});
- api.createAnswer = deferMethod(function (success, error, constraints) {
+ api.createAnswer = deferMethod(function(success, error, constraints) {
OTPlugin.debug('createAnswer', constraints);
plugin._.createAnswer(id, function(type, sdp) {
success(new OTPlugin.RTCSessionDescription({
type: type,
sdp: sdp
}));
}, error, constraints || {});
});
- api.setLocalDescription = deferMethod( function (description, success, error) {
+ api.setLocalDescription = deferMethod(function(description, success, error) {
OTPlugin.debug('setLocalDescription');
plugin._.setLocalDescription(id, description, function() {
hasLocalDescription = true;
if (hasRemoteDescription) processPendingCandidates();
if (success) success.call(null);
}, error);
});
- api.setRemoteDescription = deferMethod( function (description, success, error) {
+ api.setRemoteDescription = deferMethod(function(description, success, error) {
OTPlugin.debug('setRemoteDescription');
plugin._.setRemoteDescription(id, description, function() {
hasRemoteDescription = true;
if (hasLocalDescription) processPendingCandidates();
if (success) success.call(null);
}, error);
});
- api.addIceCandidate = deferMethod( function (candidate) {
+ api.addIceCandidate = deferMethod(function(candidate) {
OTPlugin.debug('addIceCandidate');
if (hasLocalDescription && hasRemoteDescription) {
plugin._.addIceCandidate(id, candidate, onAddIceCandidate, onAddIceCandidateFailed);
- }
- else {
+ } else {
candidates.push(candidate);
}
});
- api.addStream = deferMethod( function (stream) {
+ api.addStream = deferMethod(function(stream) {
var constraints = {};
plugin._.addStream(id, stream, constraints);
});
- api.removeStream = deferMethod( function (stream) {
+ api.removeStream = deferMethod(function(stream) {
plugin._.removeStream(id, stream);
});
-
- api.getRemoteStreams = function () {
+ api.getRemoteStreams = function() {
return $.map(plugin._.getRemoteStreams(id), function(stream) {
return MediaStream.fromJson(stream, plugin);
});
};
- api.getLocalStreams = function () {
+ api.getLocalStreams = function() {
return $.map(plugin._.getLocalStreams(id), function(stream) {
return MediaStream.fromJson(stream, plugin);
});
};
- api.getStreamById = function (streamId) {
+ api.getStreamById = function(streamId) {
return MediaStream.fromJson(plugin._.getStreamById(id, streamId), plugin);
};
- api.getStats = deferMethod( function (mediaStreamTrack, success, error) {
+ api.getStats = deferMethod(function(mediaStreamTrack, success, error) {
plugin._.getStats(id, mediaStreamTrack || null, curryCallAsync(function(statsReportJson) {
var report = new RTCStatsReport(JSON.parse(statsReportJson));
success(report);
}), error);
});
- api.close = function () {
+ api.close = function() {
plugin._.destroyPeerConnection(id);
plugin.removeRef(this);
};
- api.destroy = function () {
+ api.destroy = function() {
api.close();
};
- api.addEventListener = function (event, handler /* [, useCapture] we ignore this */) {
+ api.addEventListener = function(event, handler /* [, useCapture] we ignore this */) {
if (events[event] === void 0) {
- OTPlugin.error('Could not bind invalid event "' + event + '" to PeerConnection. ' +
- 'The valid event types are:');
- OTPlugin.error('\t' + $.keys(events).join(', '));
return;
}
events[event].push(handler);
};
- api.removeEventListener = function (event, handler /* [, useCapture] we ignore this */) {
+ api.removeEventListener = function(event, handler /* [, useCapture] we ignore this */) {
if (events[event] === void 0) {
- OTPlugin.error('Could not unbind invalid event "' + event + '" to PeerConnection. ' +
- 'The valid event types are:');
- OTPlugin.error('\t' + $.keys(events).join(', '));
return;
}
events[event] = $.filter(events[event], handler);
};
// These should appear to be null, instead of undefined, if no
// callbacks are assigned. This more closely matches how the native
@@ -7248,110 +7312,108 @@ var PeerConnection = function (iceServer
inited = true;
processDeferredMethods();
ready(void 0, api);
return api;
};
-PeerConnection.create = function (iceServers, options, plugin, ready) {
+PeerConnection.create = function(iceServers, options, plugin, ready) {
new PeerConnection(iceServers, options, plugin, ready);
};
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./proxy.js')
// tb_require('./video_container.js')
/* global VideoContainer:true */
/* exported MediaStream */
-
-var MediaStreamTrack = function (mediaStreamId, options, plugin) {
- var Proto = function MediaStreamTrack () {},
+var MediaStreamTrack = function(mediaStreamId, options, plugin) {
+ var Proto = function MediaStreamTrack() {},
api = new Proto();
api.id = options.id;
api.kind = options.kind;
api.label = options.label;
api.enabled = $.castToBoolean(options.enabled);
api.streamId = mediaStreamId;
- api.setEnabled = function (enabled) {
+ api.setEnabled = function(enabled) {
api.enabled = $.castToBoolean(enabled);
if (api.enabled) {
plugin._.enableMediaStreamTrack(mediaStreamId, api.id);
- }
- else {
+ } else {
plugin._.disableMediaStreamTrack(mediaStreamId, api.id);
}
};
return api;
};
-var MediaStream = function (options, plugin) {
- var Proto = function MediaStream () {},
+var MediaStream = function(options, plugin) {
+ var Proto = function MediaStream() {},
api = new Proto(),
audioTracks = [],
videoTracks = [];
api.id = options.id;
plugin.addRef(api);
// TODO
// api.ended =
// api.onended =
if (options.videoTracks) {
options.videoTracks.map(function(track) {
- videoTracks.push( new MediaStreamTrack(options.id, track, plugin) );
+ videoTracks.push(new MediaStreamTrack(options.id, track, plugin));
});
}
if (options.audioTracks) {
options.audioTracks.map(function(track) {
- audioTracks.push( new MediaStreamTrack(options.id, track, plugin) );
- });
- }
-
- var hasTracksOfType = function (type) {
+ audioTracks.push(new MediaStreamTrack(options.id, track, plugin));
+ });
+ }
+
+ var hasTracksOfType = function(type) {
var tracks = type === 'video' ? videoTracks : audioTracks;
return $.some(tracks, function(track) {
return track.enabled;
});
};
- api.getVideoTracks = function () { return videoTracks; };
- api.getAudioTracks = function () { return audioTracks; };
-
- api.getTrackById = function (id) {
+ api.getVideoTracks = function() { return videoTracks; };
+ api.getAudioTracks = function() { return audioTracks; };
+
+ api.getTrackById = function(id) {
videoTracks.concat(audioTracks).forEach(function(track) {
if (track.id === id) return track;
});
return null;
};
- api.hasVideo = function () {
+ api.hasVideo = function() {
return hasTracksOfType('video');
};
- api.hasAudio = function () {
+ api.hasAudio = function() {
return hasTracksOfType('audio');
};
- api.addTrack = function (/* MediaStreamTrack */) {
+ api.addTrack = function(/* MediaStreamTrack */) {
// TODO
};
- api.removeTrack = function (/* MediaStreamTrack */) {
+ api.removeTrack = function(/* MediaStreamTrack */) {
// TODO
};
api.stop = function() {
plugin._.stopMediaStream(api.id);
plugin.removeRef(api);
};
@@ -7367,20 +7429,19 @@ var MediaStream = function (options, plu
render: function() {
return new VideoContainer(plugin, api);
}
};
return api;
};
-
-MediaStream.fromJson = function (json, plugin) {
+MediaStream.fromJson = function(json, plugin) {
if (!json) return null;
- return new MediaStream( JSON.parse(json), plugin );
+ return new MediaStream(JSON.parse(json), plugin);
};
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./proxy.js')
// tb_require('./video_container.js')
/* exported MediaConstraints */
@@ -7398,18 +7459,18 @@ var MediaConstraints = function(userCons
constraints.video.mandatory = {};
}
if (this.hasAudio && !constraints.audio.mandatory) {
constraints.audio.mandatory = {};
}
this.screenSharing = this.hasVideo &&
- ( constraints.video.mandatory.chromeMediaSource === 'screen' ||
- constraints.video.mandatory.chromeMediaSource === 'window' );
+ (constraints.video.mandatory.chromeMediaSource === 'screen' ||
+ constraints.video.mandatory.chromeMediaSource === 'window');
this.audio = constraints.audio;
this.video = constraints.video;
this.setVideoSource = function(sourceId) {
if (sourceId !== void 0) constraints.video.mandatory.sourceId = sourceId;
else delete constraints.video;
};
@@ -7421,353 +7482,22 @@ var MediaConstraints = function(userCons
this.toHash = function() {
return constraints;
};
};
// tb_require('./header.js')
// tb_require('./shims.js')
-// tb_require('./plugin_proxies.js')
-
-/* global OT:true, OTPlugin:true, ActiveXObject:true,
- PluginProxies:true, curryCallAsync:true */
-
-/* exported AutoUpdater:true */
-var AutoUpdater;
-
-(function() {
-
- var autoUpdaterController,
- updaterMimeType, // <- cached version, use getInstallerMimeType instead
- installedVersion = -1; // <- cached version, use getInstallerMimeType instead
-
- var versionGreaterThan = function versionGreaterThan (version1, version2) {
- if (version1 === version2) return false;
- if (version1 === -1) return version2;
- if (version2 === -1) return version1;
-
- if (version1.indexOf('.') === -1 && version2.indexOf('.') === -1) {
- return version1 > version2;
- }
-
- // The versions have multiple components (i.e. 0.10.30) and
- // must be compared piecewise.
- // Note: I'm ignoring the case where one version has multiple
- // components and the other doesn't.
- var v1 = version1.split('.'),
- v2 = version2.split('.'),
- versionLength = (v1.length > v2.length ? v2 : v1).length;
-
-
- for (var i = 0; i < versionLength; ++i) {
- if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
- return true;
- }
- }
-
- // Special case, v1 has extra components but the initial components
- // were identical, we assume this means newer but it might also mean
- // that someone changed versioning systems.
- if (i < v1.length) {
- return true;
- }
-
- return false;
- };
-
-
- // Work out the full mimeType (including the currently installed version)
- // of the installer.
- var findMimeTypeAndVersion = function findMimeTypeAndVersion () {
-
- if (updaterMimeType !== void 0) {
- return updaterMimeType;
- }
-
- var activeXControlId = 'TokBox.otiePluginInstaller',
- installPluginName = 'otiePluginInstaller',
- unversionedMimeType = 'application/x-otieplugininstaller',
- plugin = navigator.plugins[activeXControlId] || navigator.plugins[installPluginName];
-
- installedVersion = -1;
-
- if (plugin) {
- // Look through the supported mime-types for the version
- // There should only be one mime-type in our use case, and
- // if there's more than one they should all have the same
- // version.
- var numMimeTypes = plugin.length,
- extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') +
- ',version=([0-9a-zA-Z-_.]+)', 'i'),
- mimeType,
- bits;
-
-
- for (var i=0; i<numMimeTypes; ++i) {
- mimeType = plugin[i];
-
- // Look through the supported mimeTypes and find
- // the newest one.
- if (mimeType && mimeType.enabledPlugin &&
- (mimeType.enabledPlugin.name === plugin.name) &&
- mimeType.type.indexOf(unversionedMimeType) !== -1) {
-
- bits = extractVersion.exec(mimeType.type);
-
- if (bits !== null && versionGreaterThan(bits[1], installedVersion)) {
- installedVersion = bits[1];
- }
- }
- }
- }
- else if ($.env.name === 'IE') {
- // This may mean that the installer plugin is not installed.
- // Although it could also mean that we're on IE 9 and below,
- // which does not support navigator.plugins. Fallback to
- // using 'ActiveXObject' instead.
- try {
- plugin = new ActiveXObject(activeXControlId);
- installedVersion = plugin.getMasterVersion();
- } catch(e) {
- }
- }
-
- updaterMimeType = installedVersion !== -1 ?
- unversionedMimeType + ',version=' + installedVersion :
- null;
- };
-
- var getInstallerMimeType = function getInstallerMimeType () {
- if (updaterMimeType === void 0) {
- findMimeTypeAndVersion();
- }
-
- return updaterMimeType;
- };
-
- var getInstalledVersion = function getInstalledVersion () {
- if (installedVersion === void 0) {
- findMimeTypeAndVersion();
- }
-
- return installedVersion;
- };
-
- // Version 0.4.0.4 autoupdate was broken. We want to prompt
- // for install on 0.4.0.4 or earlier. We're also including
- // earlier versions just in case. Version 0.4.0.10 also
- // had a broken updater, we'll treat that version the same
- // way.
- var hasBrokenUpdater = function () {
- var _broken = getInstalledVersion() === '0.4.0.9' ||
- !versionGreaterThan(getInstalledVersion(), '0.4.0.4');
-
- hasBrokenUpdater = function() { return _broken; };
- return _broken;
- };
-
-
- AutoUpdater = function () {
- var plugin;
-
- var getControllerCurry = function getControllerFirstCurry (fn) {
- return function() {
- if (plugin) {
- return fn(void 0, arguments);
- }
-
- PluginProxies.create({
- mimeType: getInstallerMimeType(),
- isVisible: false,
- windowless: false
- }, function(err, p) {
- plugin = p;
-
- if (err) {
- OTPlugin.error('Error while loading the AutoUpdater: ' + err);
- return;
- }
-
- return fn.apply(void 0, arguments);
- });
- };
- };
-
- // Returns true if the version of the plugin installed on this computer
- // does not match the one expected by this version of OTPlugin.
- this.isOutOfDate = function () {
- return versionGreaterThan(OTPlugin.meta.version, getInstalledVersion());
- };
-
- this.autoUpdate = getControllerCurry(function () {
- var modal = OT.Dialogs.Plugin.updateInProgress(),
- analytics = new OT.Analytics(),
- payload = {
- ieVersion: $.env.version,
- pluginOldVersion: OTPlugin.installedVersion(),
- pluginNewVersion: OTPlugin.version()
- };
-
- var success = curryCallAsync(function() {
- analytics.logEvent({
- action: 'OTPluginAutoUpdate',
- variation: 'Success',
- partnerId: OT.APIKEY,
- payload: JSON.stringify(payload)
- });
-
- plugin.destroy();
-
- modal.close();
- OT.Dialogs.Plugin.updateComplete().on({
- reload: function() {
- window.location.reload();
- }
- });
- }),
-
- error = curryCallAsync(function(errorCode, errorMessage, systemErrorCode) {
- payload.errorCode = errorCode;
- payload.systemErrorCode = systemErrorCode;
-
- analytics.logEvent({
- action: 'OTPluginAutoUpdate',
- variation: 'Failure',
- partnerId: OT.APIKEY,
- payload: JSON.stringify(payload)
- });
-
- plugin.destroy();
-
- modal.close();
- var updateMessage = errorMessage + ' (' + errorCode +
- '). Please restart your browser and try again.';
-
- modal = OT.Dialogs.Plugin.updateComplete(updateMessage).on({
- 'reload': function() {
- modal.close();
- }
- });
-
- OTPlugin.error('autoUpdate failed: ' + errorMessage + ' (' + errorCode +
- '). Please restart your browser and try again.');
- // TODO log client event
- }),
-
- progress = curryCallAsync(function(progress) {
- modal.setUpdateProgress(progress.toFixed());
- // modalBody.innerHTML = 'Updating...' + progress.toFixed() + '%';
- });
-
- plugin._.updatePlugin(OTPlugin.pathToInstaller(), success, error, progress);
- });
-
- this.destroy = function() {
- if (plugin) plugin.destroy();
- };
-
- // Refresh the plugin list so that we'll hopefully detect newer versions
- if (navigator.plugins) {
- navigator.plugins.refresh(false);
- }
- };
-
- AutoUpdater.get = function (completion) {
- if (!autoUpdaterController) {
- if (!this.isinstalled()) {
- completion.call(null, 'Plugin was not installed');
- return;
- }
-
- autoUpdaterController = new AutoUpdater();
- }
-
- completion.call(null, void 0, autoUpdaterController);
- };
-
- AutoUpdater.isinstalled = function () {
- return getInstallerMimeType() !== null && !hasBrokenUpdater();
- };
-
- AutoUpdater.installedVersion = function () {
- return getInstalledVersion();
- };
-
-})();
-
-// tb_require('./header.js')
-// tb_require('./shims.js')
-// tb_require('./proxy.js')
-// tb_require('./auto_updater.js')
-// tb_require('./media_constraints.js')
-// tb_require('./peer_connection.js')
-// tb_require('./media_stream.js')
-// tb_require('./video_container.js')
-// tb_require('./rumor.js')
-
-/* global scope, shim, pluginIsReady:true, PluginProxies, AutoUpdater */
-/* export registerReadyListener, notifyReadyListeners, onDomReady */
-
-var readyCallbacks = [];
-
-var // jshint -W098
- destroy = function destroy () {
- PluginProxies.removeAll();
- },
-
- registerReadyListener = function registerReadyListener (callback) {
- readyCallbacks.push(callback);
- },
-
- notifyReadyListeners = function notifyReadyListeners (err) {
- var callback;
-
- while ( (callback = readyCallbacks.pop()) && $.isFunction(callback) ) {
- callback.call(OTPlugin, err);
- }
- },
-
- onDomReady = function onDomReady () {
- AutoUpdater.get(function(err, updater) {
- if (err) {
- OTPlugin.error('Error while loading the AutoUpdater: ' + err);
- notifyReadyListeners('Error while loading the AutoUpdater: ' + err);
- return;
- }
-
- // If the plugin is out of date then we kick off the
- // auto update process and then bail out.
- if (updater.isOutOfDate()) {
- updater.autoUpdate();
- return;
- }
-
- // Inject the controller object into the page, wait for it to load or timeout...
- PluginProxies.createMediaCapturer(function(err) {
- if (!err && (PluginProxies.mediaCapturer && !PluginProxies.mediaCapturer.isValid())) {
- err = 'The TB Plugin failed to load properly';
- }
-
- pluginIsReady = true;
- notifyReadyListeners(err);
-
- $.onDOMUnload(destroy);
- });
- });
- };
-
-// tb_require('./header.js')
-// tb_require('./shims.js')
/* global scope:true */
/* exported createFrame */
-var createFrame = function createFrame (bodyContent, callbackId, callback) {
- var Proto = function Frame () {},
+var createFrame = function createFrame(bodyContent, callbackId, callback) {
+ var Proto = function Frame() {},
api = new Proto(),
domElement = scope.document.createElement('iframe');
domElement.id = 'OTPlugin_frame_' + $.uuid().replace(/\-+/g, '');
domElement.style.border = '0';
try {
domElement.style.backgroundColor = 'rgba(0,0,0,0)';
@@ -7814,97 +7544,477 @@ var createFrame = function createFrame (
domElement.contentWindow,
doc
);
}
};
scope.document.body.appendChild(domElement);
- if($.env.iframeNeedsLoad) {
+ if ($.env.iframeNeedsLoad) {
if ($.env.name === 'IE') {
// This works around some issues with IE and document.write.
// Basically this works by slightly abusing the bookmarklet/scriptlet
// functionality that all browsers support.
domElement.contentWindow.contents = frameContent;
/*jshint scripturl:true*/
domElement.src = 'javascript:window["contents"]';
/*jshint scripturl:false*/
}
$.on(domElement, 'load', wrappedCallback);
} else {
setTimeout(wrappedCallback, 0);
}
- api.reparent = function reparent (target) {
+ api.reparent = function reparent(target) {
// document.adoptNode(domElement);
target.appendChild(domElement);
};
api.element = domElement;
return api;
};
// tb_require('./header.js')
// tb_require('./shims.js')
+// tb_require('./plugin_proxies.js')
+
+/* global OT:true, OTPlugin:true, ActiveXObject:true,
+ PluginProxies:true, curryCallAsync:true */
+
+/* exported AutoUpdater:true */
+var AutoUpdater;
+
+(function() {
+
+ var autoUpdaterController,
+ updaterMimeType, // <- cached version, use getInstallerMimeType instead
+ installedVersion = -1; // <- cached version, use getInstallerMimeType instead
+
+ var versionGreaterThan = function versionGreaterThan(version1, version2) {
+ if (version1 === version2) return false;
+ if (version1 === -1) return version2;
+ if (version2 === -1) return version1;
+
+ if (version1.indexOf('.') === -1 && version2.indexOf('.') === -1) {
+ return version1 > version2;
+ }
+
+ // The versions have multiple components (i.e. 0.10.30) and
+ // must be compared piecewise.
+ // Note: I'm ignoring the case where one version has multiple
+ // components and the other doesn't.
+ var v1 = version1.split('.'),
+ v2 = version2.split('.'),
+ versionLength = (v1.length > v2.length ? v2 : v1).length;
+
+ for (var i = 0; i < versionLength; ++i) {
+ if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
+ return true;
+ }
+ }
+
+ // Special case, v1 has extra components but the initial components
+ // were identical, we assume this means newer but it might also mean
+ // that someone changed versioning systems.
+ if (i < v1.length) {
+ return true;
+ }
+
+ return false;
+ };
+
+ // Work out the full mimeType (including the currently installed version)
+ // of the installer.
+ var findMimeTypeAndVersion = function findMimeTypeAndVersion() {
+
+ if (updaterMimeType !== void 0) {
+ return updaterMimeType;
+ }
+
+ var activeXControlId = 'TokBox.OpenTokPluginInstaller',
+ installPluginName = 'OpenTokPluginInstaller',
+ unversionedMimeType = 'application/x-opentokplugininstaller',
+ plugin = navigator.plugins[activeXControlId] || navigator.plugins[installPluginName];
+
+ installedVersion = -1;
+
+ if (plugin) {
+ // Look through the supported mime-types for the version
+ // There should only be one mime-type in our use case, and
+ // if there's more than one they should all have the same
+ // version.
+ var numMimeTypes = plugin.length,
+ extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') +
+ ',version=([0-9a-zA-Z-_.]+)', 'i'),
+ mimeType,
+ bits;
+
+ for (var i = 0; i < numMimeTypes; ++i) {
+ mimeType = plugin[i];
+
+ // Look through the supported mimeTypes and find
+ // the newest one.
+ if (mimeType && mimeType.enabledPlugin &&
+ (mimeType.enabledPlugin.name === plugin.name) &&
+ mimeType.type.indexOf(unversionedMimeType) !== -1) {
+
+ bits = extractVersion.exec(mimeType.type);
+
+ if (bits !== null && versionGreaterThan(bits[1], installedVersion)) {
+ installedVersion = bits[1];
+ }
+ }
+ }
+ } else if ($.env.name === 'IE') {
+ // This may mean that the installer plugin is not installed.
+ // Although it could also mean that we're on IE 9 and below,
+ // which does not support navigator.plugins. Fallback to
+ // using 'ActiveXObject' instead.
+ try {
+ plugin = new ActiveXObject(activeXControlId);
+ installedVersion = plugin.getMasterVersion();
+ } catch (e) {}
+ }
+
+ updaterMimeType = installedVersion !== -1 ?
+ unversionedMimeType + ',version=' + installedVersion :
+ null;
+ };
+
+ var getInstallerMimeType = function getInstallerMimeType() {
+ if (updaterMimeType === void 0) {
+ findMimeTypeAndVersion();
+ }
+
+ return updaterMimeType;
+ };
+
+ var getInstalledVersion = function getInstalledVersion() {
+ if (installedVersion === void 0) {
+ findMimeTypeAndVersion();
+ }
+
+ return installedVersion;
+ };
+
+ // Version 0.4.0.4 autoupdate was broken. We want to prompt
+ // for install on 0.4.0.4 or earlier. We're also including
+ // earlier versions just in case. Version 0.4.0.10 also
+ // had a broken updater, we'll treat that version the same
+ // way.
+ var hasBrokenUpdater = function() {
+ var _broken = getInstalledVersion() === '0.4.0.9' ||
+ !versionGreaterThan(getInstalledVersion(), '0.4.0.4');
+
+ hasBrokenUpdater = function() { return _broken; };
+ return _broken;
+ };
+
+ AutoUpdater = function() {
+ var plugin;
+
+ var getControllerCurry = function getControllerFirstCurry(fn) {
+ return function() {
+ if (plugin) {
+ return fn(void 0, arguments);
+ }
+
+ PluginProxies.create({
+ mimeType: getInstallerMimeType(),
+ isVisible: false,
+ windowless: false
+ }, function(err, p) {
+ plugin = p;
+
+ if (err) {
+ OTPlugin.error('Error while loading the AutoUpdater: ' + err);
+ return;
+ }
+
+ return fn.apply(void 0, arguments);
+ });
+ };
+ };
+
+ // Returns true if the version of the plugin installed on this computer
+ // does not match the one expected by this version of OTPlugin.
+ this.isOutOfDate = function() {
+ return versionGreaterThan(OTPlugin.meta.version, getInstalledVersion());
+ };
+
+ this.autoUpdate = getControllerCurry(function() {
+ var modal = OT.Dialogs.Plugin.updateInProgress(),
+ analytics = new OT.Analytics(),
+ payload = {
+ ieVersion: $.env.version,
+ pluginOldVersion: OTPlugin.installedVersion(),
+ pluginNewVersion: OTPlugin.version()
+ };
+
+ var success = curryCallAsync(function() {
+ analytics.logEvent({
+ action: 'OTPluginAutoUpdate',
+ variation: 'Success',
+ partnerId: OT.APIKEY,
+ payload: JSON.stringify(payload)
+ });
+
+ plugin.destroy();
+
+ modal.close();
+ OT.Dialogs.Plugin.updateComplete().on({
+ reload: function() {
+ window.location.reload();
+ }
+ });
+ }),
+
+ error = curryCallAsync(function(errorCode, errorMessage, systemErrorCode) {
+ payload.errorCode = errorCode;
+ payload.systemErrorCode = systemErrorCode;
+
+ analytics.logEvent({
+ action: 'OTPluginAutoUpdate',
+ variation: 'Failure',
+ partnerId: OT.APIKEY,
+ payload: JSON.stringify(payload)
+ });
+
+ plugin.destroy();
+
+ modal.close();
+ var updateMessage = errorMessage + ' (' + errorCode +
+ '). Please restart your browser and try again.';
+
+ modal = OT.Dialogs.Plugin.updateComplete(updateMessage).on({
+ reload: function() {
+ modal.close();
+ }
+ });
+
+ OTPlugin.error('autoUpdate failed: ' + errorMessage + ' (' + errorCode +
+ '). Please restart your browser and try again.');
+ // TODO log client event
+ }),
+
+ progress = curryCallAsync(function(progress) {
+ modal.setUpdateProgress(progress.toFixed());
+ // modalBody.innerHTML = 'Updating...' + progress.toFixed() + '%';
+ });
+
+ plugin._.updatePlugin(OTPlugin.pathToInstaller(), success, error, progress);
+ });
+
+ this.destroy = function() {
+ if (plugin) plugin.destroy();
+ };
+
+ // Refresh the plugin list so that we'll hopefully detect newer versions
+ if (navigator.plugins) {
+ navigator.plugins.refresh(false);
+ }
+ };
+
+ AutoUpdater.get = function(completion) {
+ if (!autoUpdaterController) {
+ if (!this.isinstalled()) {
+ completion.call(null, 'Plugin was not installed');
+ return;
+ }
+
+ autoUpdaterController = new AutoUpdater();
+ }
+
+ completion.call(null, void 0, autoUpdaterController);
+ };
+
+ AutoUpdater.isinstalled = function() {
+ return getInstallerMimeType() !== null && !hasBrokenUpdater();
+ };
+
+ AutoUpdater.installedVersion = function() {
+ return getInstalledVersion();
+ };
+
+})();
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+// tb_require('./proxy.js')
+// tb_require('./auto_updater.js')
+
+/* global PluginProxies */
+/* exported MediaDevices */
+
+// Exposes a enumerateDevices method and emits a devicechange event
+//
+// http://w3c.github.io/mediacapture-main/#idl-def-MediaDevices
+//
+var MediaDevices = function() {
+ var Proto = function MediaDevices() {},
+ api = new Proto();
+
+ api.enumerateDevices = function enumerateDevices(completion) {
+ OTPlugin.ready(function(error) {
+ if (error) {
+ completion(error);
+ } else {
+ PluginProxies.mediaCapturer.enumerateDevices(completion);
+ }
+ });
+ };
+
+ api.addListener = function addListener(fn, context) {
+ OTPlugin.ready(function(error) {
+ if (error) {
+ // No error message here, ready failing would have
+ // created a bunch elsewhere
+ return;
+ }
+
+ PluginProxies.mediaCapturer.on('devicesChanged', fn, context);
+ });
+ };
+
+ api.removeListener = function removeListener(fn, context) {
+ if (OTPlugin.isReady()) {
+ PluginProxies.mediaCapturer.off('devicesChanged', fn, context);
+ }
+ };
+
+ return api;
+};
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
// tb_require('./proxy.js')
// tb_require('./auto_updater.js')
// tb_require('./media_constraints.js')
// tb_require('./peer_connection.js')
// tb_require('./media_stream.js')
// tb_require('./video_container.js')
// tb_require('./rumor.js')
+
+/* global scope, shim, pluginIsReady:true, PluginProxies, AutoUpdater */
+/* export registerReadyListener, notifyReadyListeners, onDomReady */
+
+var readyCallbacks = [],
+
+ // This stores the error from the load attempt. We use
+ // this if registerReadyListener gets called after a load
+ // attempt fails.
+ loadError;
+
+var // jshint -W098
+ destroy = function destroy() {
+ PluginProxies.removeAll();
+ },
+
+ registerReadyListener = function registerReadyListener(callback) {
+ if (!$.isFunction(callback)) {
+ OTPlugin.warn('registerReadyListener was called with something that was not a function.');
+ return;
+ }
+
+ if (OTPlugin.isReady()) {
+ callback.call(OTPlugin, loadError);
+ } else {
+ readyCallbacks.push(callback);
+ }
+ },
+
+ notifyReadyListeners = function notifyReadyListeners() {
+ var callback;
+
+ while ((callback = readyCallbacks.pop()) && $.isFunction(callback)) {
+ callback.call(OTPlugin, loadError);
+ }
+ },
+
+ onDomReady = function onDomReady() {
+ AutoUpdater.get(function(err, updater) {
+ if (err) {
+ loadError = 'Error while loading the AutoUpdater: ' + err;
+ notifyReadyListeners();
+ return;
+ }
+
+ // If the plugin is out of date then we kick off the
+ // auto update process and then bail out.
+ if (updater.isOutOfDate()) {
+ updater.autoUpdate();
+ return;
+ }
+
+ // Inject the controller object into the page, wait for it to load or timeout...
+ PluginProxies.createMediaCapturer(function(err) {
+ loadError = err;
+
+ if (!loadError && (!PluginProxies.mediaCapturer ||
+ !PluginProxies.mediaCapturer.isValid())) {
+ loadError = 'The TB Plugin failed to load properly';
+ }
+
+ pluginIsReady = true;
+ notifyReadyListeners();
+
+ $.onDOMUnload(destroy);
+ });
+ });
+ };
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+// tb_require('./proxy.js')
+// tb_require('./auto_updater.js')
+// tb_require('./media_constraints.js')
+// tb_require('./peer_connection.js')
+// tb_require('./media_stream.js')
+// tb_require('./media_devices.js')
+// tb_require('./video_container.js')
+// tb_require('./rumor.js')
// tb_require('./lifecycle.js')
/* global AutoUpdater,
RumorSocket,
MediaConstraints, PeerConnection, MediaStream,
registerReadyListener,
- PluginProxies */
-
-OTPlugin.isInstalled = function isInstalled () {
+ PluginProxies,
+ MediaDevices */
+
+OTPlugin.isInstalled = function isInstalled() {
if (!this.isSupported()) return false;
return AutoUpdater.isinstalled();
};
-OTPlugin.version = function version () {
+OTPlugin.version = function version() {
return OTPlugin.meta.version;
};
-OTPlugin.installedVersion = function installedVersion () {
+OTPlugin.installedVersion = function installedVersion() {
return AutoUpdater.installedVersion();
};
// Returns a URI to the OTPlugin installer that is paired with
// this version of OTPlugin.js.
-OTPlugin.pathToInstaller = function pathToInstaller () {
+OTPlugin.pathToInstaller = function pathToInstaller() {
return 'https://s3.amazonaws.com/otplugin.tokbox.com/v' +
- OTPlugin.meta.version + '/otiePluginMain.msi';
+ OTPlugin.meta.version + '/OpenTokPluginMain.msi';
};
// Trigger +callback+ when the plugin is ready
//
// Most of the public API cannot be called until
// the plugin is ready.
//
-OTPlugin.ready = function ready (callback) {
- if (OTPlugin.isReady()) {
- var err;
-
- if (!PluginProxies.mediaCapturer || !PluginProxies.mediaCapturer.isValid()) {
- err = 'The TB Plugin failed to load properly';
- }
-
- callback.call(OTPlugin, err);
- }
- else {
- registerReadyListener(callback);
- }
+OTPlugin.ready = function ready(callback) {
+ registerReadyListener(callback);
};
// Helper function for OTPlugin.getUserMedia
var _getUserMedia = function _getUserMedia(mediaConstraints, success, error) {
PluginProxies.createMediaPeer(function(err, plugin) {
if (err) {
error.call(OTPlugin, err);
return;
@@ -7914,23 +8024,22 @@ var _getUserMedia = function _getUserMed
success.call(OTPlugin, MediaStream.fromJson(streamJson, plugin));
}, error);
});
};
// Equivalent to: window.getUserMedia(constraints, success, error);
//
// Except that the constraints won't be identical
-OTPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) {
+OTPlugin.getUserMedia = function getUserMedia(userConstraints, success, error) {
var constraints = new MediaConstraints(userConstraints);
if (constraints.screenSharing) {
_getUserMedia(constraints, success, error);
- }
- else {
+ } else {
var sources = [];
if (constraints.hasVideo) sources.push('video');
if (constraints.hasAudio) sources.push('audio');
PluginProxies.mediaCapturer.selectSources(sources, function(captureDevices) {
for (var key in captureDevices) {
if (captureDevices.hasOwnProperty(key)) {
OTPlugin.debug(key + ' Capture Device: ' + captureDevices[key]);
@@ -7941,34 +8050,43 @@ OTPlugin.getUserMedia = function getUser
constraints.setVideoSource(captureDevices.video);
constraints.setAudioSource(captureDevices.audio);
_getUserMedia(constraints, success, error);
}, error);
}
};
+OTPlugin.enumerateDevices = function(completion) {
+ OTPlugin.ready(function(error) {
+ if (error) {
+ completion(error);
+ } else {
+ PluginProxies.mediaCapturer.enumerateDevices(completion);
+ }
+ });
+};
+
OTPlugin.initRumorSocket = function(messagingURL, completion) {
OTPlugin.ready(function(error) {
- if(error) {
+ if (error) {
completion(error);
} else {
completion(null, new RumorSocket(PluginProxies.mediaCapturer, messagingURL));
}
});
};
-
// Equivalent to: var pc = new window.RTCPeerConnection(iceServers, options);
//
// Except that it is async and takes a completion handler
-OTPlugin.initPeerConnection = function initPeerConnection (iceServers,
- options,
- localStream,
- completion) {
+OTPlugin.initPeerConnection = function initPeerConnection(iceServers,
+ options,
+ localStream,
+ completion) {
var gotPeerObject = function(err, plugin) {
if (err) {
completion.call(OTPlugin, err);
return;
}
OTPlugin.debug('Got PeerConnection for ' + plugin.id);
@@ -7986,49 +8104,117 @@ OTPlugin.initPeerConnection = function i
// @fixme this is nasty and brittle. We need some way to use the same Object
// for the PeerConnection that was used for the getUserMedia call (in the case
// of publishers). We don't really have a way of implicitly associating them though.
// Hence, publishers will have to pass through their localStream (if they have one)
// and we will look up the original Object and use that. Otherwise we generate
// a new one.
if (localStream && localStream._.plugin) {
gotPeerObject(null, localStream._.plugin);
- }
- else {
+ } else {
PluginProxies.createMediaPeer(gotPeerObject);
}
};
// A RTCSessionDescription like object exposed for native WebRTC compatability
-OTPlugin.RTCSessionDescription = function RTCSessionDescription (options) {
+OTPlugin.RTCSessionDescription = function RTCSessionDescription(options) {
this.type = options.type;
this.sdp = options.sdp;
};
// A RTCIceCandidate like object exposed for native WebRTC compatability
-OTPlugin.RTCIceCandidate = function RTCIceCandidate (options) {
+OTPlugin.RTCIceCandidate = function RTCIceCandidate(options) {
this.sdpMid = options.sdpMid;
this.sdpMLineIndex = parseInt(options.sdpMLineIndex, 10);
this.candidate = options.candidate;
};
+OTPlugin.mediaDevices = new MediaDevices();
+
+
// tb_require('./api.js')
/* global shim, onDomReady */
shim();
$.onDOMLoad(onDomReady);
/* jshint ignore:start */
})(this);
/* jshint ignore:end */
+/* exported _setCertificates */
+
+var _setCertificates = function(pcConfig, completion) {
+ if (
+ OT.$.env.name === 'Firefox' &&
+ window.mozRTCPeerConnection &&
+ window.mozRTCPeerConnection.generateCertificate
+ ) {
+ window.mozRTCPeerConnection.generateCertificate({
+ name: 'RSASSA-PKCS1-v1_5',
+ hash: 'SHA-256',
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1])
+ }).catch(function(err) {
+ completion(err);
+ }).then(function(cert) {
+ pcConfig.certificates = [cert];
+ completion(undefined, pcConfig);
+ });
+ } else {
+ OT.$.callAsync(function() {
+ completion(undefined, pcConfig);
+ });
+ }
+};
+
+/* exported videoContentResizesMixin */
+
+var videoContentResizesMixin = function(self, domElement) {
+
+ var width = domElement.videoWidth,
+ height = domElement.videoHeight,
+ stopped = true;
+
+ function actor() {
+ if (stopped) {
+ return;
+ }
+ if (width !== domElement.videoWidth || height !== domElement.videoHeight) {
+ self.trigger('videoDimensionsChanged',
+ { width: width, height: height },
+ { width: domElement.videoWidth, height: domElement.videoHeight }
+ );
+ width = domElement.videoWidth;
+ height = domElement.videoHeight;
+ }
+ waiter();
+ }
+
+ function waiter() {
+ self.whenTimeIncrements(function() {
+ window.requestAnimationFrame(actor);
+ });
+ }
+
+ self.startObservingSize = function() {
+ stopped = false;
+ waiter();
+ };
+
+ self.stopObservingSize = function() {
+ stopped = true;
+ };
+
+};
+
/* jshint ignore:start */
!(function(window, OT) {
/* jshint ignore:end */
// tb_require('./header.js')
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
trailing: true, browser: true, smarttabs:true */
@@ -8040,38 +8226,37 @@ if (location.protocol === 'file:') {
/*global alert*/
alert('You cannot test a page using WebRTC through the file system due to browser ' +
'permissions. You must run it over a web server.');
}
var OT = window.OT || {};
// Define the APIKEY this is a global parameter which should not change
-OT.APIKEY = (function(){
+OT.APIKEY = (function() {
// Script embed
- var scriptSrc = (function(){
+ var scriptSrc = (function() {
var s = document.getElementsByTagName('script');
s = s[s.length - 1];
s = s.getAttribute('src') || s.src;
return s;
})();
var m = scriptSrc.match(/[\?\&]apikey=([^&]+)/i);
return m ? m[1] : '';
})();
-
if (!window.OT) window.OT = OT;
if (!window.TB) window.TB = OT;
// tb_require('../js/ot.js')
OT.properties = {
- version: 'v2.5.2', // The current version (eg. v2.0.4) (This is replaced by gradle)
- build: 'f4508e1', // The current build hash (This is replaced by gradle)
+ version: 'v2.6.8', // The current version (eg. v2.0.4) (This is replaced by gradle)
+ build: 'fae7901', // The current build hash (This is replaced by gradle)
// Whether or not to turn on debug logging by default
debug: 'false',
// The URL of the tokbox website
websiteURL: 'http://www.tokbox.com',
// The URL of the CDN
cdnURL: 'http://static.opentok.com',
@@ -8092,45 +8277,43 @@ OT.properties = {
cdnURLSSL: 'https://static.opentok.com',
// The URL to use for logging
loggingURLSSL: 'https://hlg.tokbox.com/prod',
// The anvil API URL to use if we're using SSL
apiURLSSL: 'https://anvil.opentok.com',
minimumVersion: {
- firefox: parseFloat('29'),
- chrome: parseFloat('34')
- }
-};
-
+ firefox: parseFloat('37'),
+ chrome: parseFloat('39')
+ }
+};
// tb_require('../ot.js')
// tb_require('../../conf/properties.js');
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
// Mount OTHelpers on OT.$
OT.$ = window.OTHelpers;
// Allow events to be bound on OT
OT.$.eventing(OT);
// REMOVE THIS POST IE MERGE
OT.$.defineGetters = function(self, getters, enumerable) {
var propsDefinition = {};
if (enumerable === void 0) enumerable = false;
for (var key in getters) {
- if(!getters.hasOwnProperty(key)) {
+ if (!getters.hasOwnProperty(key)) {
continue;
}
propsDefinition[key] = {
get: getters[key],
enumerable: enumerable
};
}
@@ -8164,26 +8347,24 @@ OT.setLogLevel = function(level) {
}
OT.debug('OT.setLogLevel(' + retVal + ')');
return retVal;
};
var debugTrue = OT.properties.debug === 'true' || OT.properties.debug === true;
OT.setLogLevel(debugTrue ? OT.DEBUG : OT.ERROR);
-
// Patch the userAgent to ref OTPlugin, if it's installed.
if (OTPlugin && OTPlugin.isInstalled()) {
OT.$.env.userAgent += '; OTPlugin ' + OTPlugin.version();
}
// @todo remove this
OT.$.userAgent = function() { return OT.$.env.userAgent; };
-
/**
* Sets the API log level.
* <p>
* Calling <code>OT.setLogLevel()</code> sets the log level for runtime log messages that
* are the OpenTok library generates. The default value for the log level is <code>OT.ERROR</code>.
* </p>
* <p>
* The OpenTok JavaScript library displays log messages in the debugger console (such as
@@ -8248,16 +8429,132 @@ OT.$.userAgent = function() { return OT.
* @name OT.log
* @memberof OT
* @function
* @see <a href="#setLogLevel">OT.setLogLevel()</a>
*/
// tb_require('../../../helpers/helpers.js')
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+/* global OT */
+
+// Rumor Messaging for JS
+//
+// https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork
+//
+// @todo Rumor {
+// Add error codes for all the error cases
+// Add Dependability commands
+// }
+
+OT.Rumor = {
+ MessageType: {
+ // This is used to subscribe to address/addresses. The address/addresses the
+ // client specifies here is registered on the server. Once any message is sent to
+ // that address/addresses, the client receives that message.
+ SUBSCRIBE: 0,
+
+ // This is used to unsubscribe to address / addresses. Once the client unsubscribe
+ // to an address, it will stop getting messages sent to that address.
+ UNSUBSCRIBE: 1,
+
+ // This is used to send messages to arbitrary address/ addresses. Messages can be
+ // anything and Rumor will not care about what is included.
+ MESSAGE: 2,
+
+ // This will be the first message that the client sends to the server. It includes
+ // the uniqueId for that client connection and a disconnect_notify address that will
+ // be notified once the client disconnects.
+ CONNECT: 3,
+
+ // This will be the message used by the server to notify an address that a
+ // client disconnected.
+ DISCONNECT: 4,
+
+ //Enhancements to support Keepalives
+ PING: 7,
+ PONG: 8,
+ STATUS: 9
+ }
+};
+
+// tb_require('../../../helpers/helpers.js')
+// tb_require('./rumor.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+/* global OT, OTPlugin */
+
+!(function() {
+
+ OT.Rumor.PluginSocket = function(messagingURL, events) {
+
+ var webSocket,
+ state = 'initializing';
+
+ OTPlugin.initRumorSocket(messagingURL, OT.$.bind(function(err, rumorSocket) {
+ if (err) {
+ state = 'closed';
+ events.onClose({ code: 4999 });
+ } else if (state === 'initializing') {
+ webSocket = rumorSocket;
+
+ webSocket.onOpen(function() {
+ state = 'open';
+ events.onOpen();
+ });
+ webSocket.onClose(function(error) {
+ state = 'closed'; /* CLOSED */
+ events.onClose({ code: error });
+ });
+ webSocket.onError(function(error) {
+ state = 'closed'; /* CLOSED */
+ events.onError(error);
+ /* native websockets seem to do this, so should we */
+ events.onClose({ code: error });
+ });
+
+ webSocket.onMessage(function(type, addresses, headers, payload) {
+ var msg = new OT.Rumor.Message(type, addresses, headers, payload);
+ events.onMessage(msg);
+ });
+
+ webSocket.open();
+ } else {
+ this.close();
+ }
+ }, this));
+
+ this.close = function() {
+ if (state === 'initializing' || state === 'closed') {
+ state = 'closed';
+ return;
+ }
+
+ webSocket.close(1000, '');
+ };
+
+ this.send = function(msg) {
+ if (state === 'open') {
+ webSocket.send(msg);
+ }
+ };
+
+ this.isClosed = function() {
+ return state === 'closed';
+ };
+
+ };
+
+}(this));
+
+// tb_require('../../../helpers/helpers.js')
+
// https://code.google.com/p/stringencoding/
// An implementation of http://encoding.spec.whatwg.org/#api
// Modified by TokBox to remove all encoding support except for utf-8
/**
* @license Copyright 2014 Joshua Bell
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -8274,21 +8571,21 @@ OT.$.userAgent = function() { return OT.
*
* Original source: https://github.com/inexorabletash/text-encoding
***/
/*jshint unused:false*/
(function(global) {
'use strict';
- if(OT.$.env && OT.$.env.name === 'IE' && OT.$.env.version < 10) {
+ if (OT.$.env && OT.$.env.name === 'IE' && OT.$.env.version < 10) {
return; // IE 8 doesn't do websockets. No websockets, no encoding.
}
- if ( (global.TextEncoder !== void 0) && (global.TextDecoder !== void 0)) {
+ if ((global.TextEncoder !== void 0) && (global.TextDecoder !== void 0)) {
// defer to the native ones
return;
}
/* jshint ignore:start */
//
// Utilities
@@ -8308,17 +8605,16 @@ OT.$.userAgent = function() { return OT.
* @param {number} n The numerator.
* @param {number} d The denominator.
* @return {number} The result of the integer division of n by d.
*/
function div(n, d) {
return Math.floor(n / d);
}
-
//
// Implementation of Encoding specification
// http://dvcs.w3.org/hg/encoding/raw-file/tip/Overview.html
//
//
// 3. Terminology
//
@@ -9304,157 +9600,41 @@ O