Base for multi-host support

Must do further testing; it's too late for me to continue now.
This commit is contained in:
unkelpehr 2014-09-22 01:04:52 +02:00
parent 482d352f30
commit f90869e370
9 changed files with 372 additions and 127 deletions

View File

@ -24,6 +24,12 @@ body, html {
color: #00DD00; color: #00DD00;
} }
.good { color: #99FF99; }
.bad { color: #ff7777; }
.info { color:#99FFFF; }
.neutral { color: #eeeeee; }
/*------------------------------------*\ /*------------------------------------*\
CHAT CHAT
\*------------------------------------*/ \*------------------------------------*/
@ -58,7 +64,7 @@ body, html {
#chat i.fatal { color: #ff7777; } #chat i.fatal { color: #ff7777; }
/*------------------------------------*\ /*------------------------------------*\
INPUT INPUT & LOADER
\*------------------------------------*/ \*------------------------------------*/
#input_wrapper { #input_wrapper {
right:0; right:0;
@ -69,7 +75,8 @@ body, html {
height:30px; height:30px;
} }
#input { #input,
#loader {
top: 0; top: 0;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
@ -78,9 +85,28 @@ body, html {
border: 0; border: 0;
outline: 0; outline: 0;
padding: 5px 5px 5px 15px; padding: 5px 5px 5px 15px;
color: #FFFFFF; color: #FFFFFF;
background-color:#141414; background-color:#141414;
height:30px; height:30px;
}
#input { z-index: 1; }
#loader { z-index: 0; line-height: 20px; font-size: 14px; font-weight: 100; font-family: tahoma;}
/*------------------------------------*\
SPINNER
\*------------------------------------*/
.loading #loader { z-index: 2; }
.loading #loader span {
margin-left:-2px;
-webkit-animation: rotation 1s infinite linear;
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(359deg);}
} }

View File

@ -15,10 +15,11 @@
</ul> </ul>
<!-- Message input --> <!-- Message input -->
<div id="input_wrapper"> <div id="input_wrapper" class="loading">
<div id="loader"><span>|</span></div>
<input type="text" id="input" /> <input type="text" id="input" />
</div> </div>
<!-- Only include the script needed for loading the app --> <!-- Only include the script needed for loading the app -->
<script src="js/vendor/fandango.v20140921.min.js"></script> <script src="js/vendor/fandango.v20140921.min.js"></script>
<script src="js/bootstrap.js"></script> <script src="js/bootstrap.js"></script>

View File

@ -5,6 +5,9 @@ fandango.defaults({
baseUrl: 'js/cryptalk_modules/', baseUrl: 'js/cryptalk_modules/',
paths: { paths: {
websocket: 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js', websocket: 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js',
// Newer version:
// We'll have to fix the Access Control issue first though (https://github.com/Automattic/socket.io-client/issues/641).
// websocket: 'https://cdn.socket.io/socket.io-1.1.0.js',
aes: 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js', aes: 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js',
domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min.js' domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min.js'
}, },

View File

@ -9,7 +9,16 @@ define(['fandango', 'websocket', 'aes'], function (fandango, websocket, aes) {
utils = exports.utilities, utils = exports.utilities,
proto = exports.prototype, proto = exports.prototype,
each = fandango.each; each = fandango.each,
/**
* Regex for matching NaN.
*
* @property reNaN
* @type {Regex}
* @private
*/
reDigits = /^\d+$/;
// The DOM selector engine // The DOM selector engine
exports.selector = function (selector) { exports.selector = function (selector) {
@ -61,6 +70,20 @@ define(['fandango', 'websocket', 'aes'], function (fandango, websocket, aes) {
try { return document.activeElement; } catch (e) {} try { return document.activeElement; } catch (e) {}
} }
/**
* Removes all characters but 0 - 9 from given string.
*
* @method digits
* @param {String} str The string to sanitize
* @return {String} The sanitized string
* @example
* $.digits('foo8bar'); // `8`
* $.digits('->#5*duckM4N!!!111'); // `54111`
*/
utils.isDigits = function(value) {
return reDigits.test(value);
};
/** /**
* A very simple templating function. * A very simple templating function.
* @param {} str [description] * @param {} str [description]
@ -73,6 +96,28 @@ define(['fandango', 'websocket', 'aes'], function (fandango, websocket, aes) {
}); });
}; };
utils.getJSON = function (path, onSuccess, onError) {
var data, request = new XMLHttpRequest();
request.open('GET', path, true);
request.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 400) {
try {
onSuccess && onSuccess(JSON.parse(this.responseText));
} catch (e) {
onError && onError();
}
} else {
onError && onError();
}
}
};
request.send();
request = null;
};
// Part of this is originating from mustasche.js // Part of this is originating from mustasche.js
// Code: https://github.com/janl/mustache.js/blob/master/mustache.js#L43 // Code: https://github.com/janl/mustache.js/blob/master/mustache.js#L43
// License: https://github.com/janl/mustache.js/blob/master/LICENSE // License: https://github.com/janl/mustache.js/blob/master/LICENSE

View File

@ -1,7 +1,7 @@
// Main cryptalk module // Main cryptalk module
define({ define({
compiles: ['$'], compiles: ['$'],
requires: ['settings', 'templates', 'sound', 'fandango'] requires: ['hosts', 'templates', 'sound', 'fandango']
}, function ($, requires, data) { }, function ($, requires, data) {
var socket, var socket,
key, key,
@ -9,8 +9,9 @@ define({
room, room,
hash, hash,
nick, nick,
mute,
mute = false, settings = {},
history = [], history = [],
history_pos = -1, history_pos = -1,
@ -20,15 +21,27 @@ define({
// Collection of DOM components // Collection of DOM components
components = { components = {
chat: $('#chat'), chat: $('#chat'),
input: $('#input') input: $('#input'),
inputWrapper: $('#input_wrapper')
}, },
// Shortcut // Shortcut
settings = requires.settings; hosts = requires.hosts.hosts;
fandango = requires.fandango; fandango = requires.fandango;
templates = requires.templates; templates = requires.templates;
sound = requires.sound; sound = requires.sound;
lockInput = function () {
components.input[0].setAttribute('disabled', 'disabled');
components.inputWrapper[0].className = 'loading';
},
unlockInput = function () {
components.input[0].removeAttribute('disabled');
components.inputWrapper[0].className = '';
components.input.focus();
},
// Adds a new message to the DOM // Adds a new message to the DOM
post = function (type, text, clearChat, clearBuffer, nick) { post = function (type, text, clearChat, clearBuffer, nick) {
var tpl = templates.post[type], var tpl = templates.post[type],
@ -53,8 +66,205 @@ define({
// Chat related commands // Chat related commands
commands = { commands = {
help: function () { help: function (payload, done) {
post('motd', templates.help); post('motd', templates.help);
done();
},
hosts: function (force, done) {
var i = 0,
left = hosts.length,
host,
strhosts = '\n',
callback = function (host, index, isUp) {
return function (hostSettings) {
host.settings = (isUp ? hostSettings : 0);
strhosts += $.template(templates.messages[(isUp ? 'host_available' : 'host_unavailable')], {
name: host.name,
path: host.path,
index: index
});
if (!--left) {
post('info', strhosts);
done();
}
}
};
//
force = (force && force.toLowerCase() === 'force');
// Loop through all the hosts
while (host = hosts[i]) {
if (!force && host.settings !== undefined) {
if (host.settings) {
callback(host, i, 1)();
} else {
callback(host, i, 0)();
}
} else {
require([host.path], callback(host, i, 1), callback(host, i, 0));
}
i++;
}
},
connect: function (toHost, done) {
var request;
if (host && host.connected) {
done();
return post('error', $.template(templates.messages.already_connected, {
host: host.name || 'localhost'
}));
}
if ($.isDigits(toHost)) {
if (host = hosts[+toHost]) {
if (host.settings) {
settings = host.settings;
} else {
request = host.path;
}
} else {
return post('error', 'Undefined host index: ' + toHost);
}
} else if (fandango.is(toHost, 'untyped')) {
settings = toHost.settings;
} else { // Assume string
request = toHost;
}
if (request) {
return require([request], function (settings) {
host.settings = settings;
commands.connect(toHost, done);
}, function () {
return post('error', 'Could not fetch host settings: ' + request);
});
}
// Push 'Connecting...' message
post('info', $.template(templates.messages.connecting, {
host: host.name || 'localhost'
}));
// The one and only socket
socket = $.Websocket.connect(host.host, {
forceNew: true,
'force new connection': true
});
// Bind socket events
socket
.on('room:generated', function (data) {
var sanitized = $.escapeHtml(data);
post('server', $.template(templates.server.room_generated, { payload: sanitized }));
socket.emit('room:join', sanitized);
})
.on('room:joined', function (data) {
room = data;
post('info', $.template(templates.messages.joined_room, { roomName: room }));
// Automatically count persons on join
socket.emit('room:count');
})
.on('room:left', function () {
post('info', $.template(templates.messages.left_room, { roomName: room }));
// Clear history on leaving room
clearHistory();
room = false;
})
.on('message:send', function (data) {
var decrypted = $.AES.decrypt(data.msg, room + key),
sanitized = $.escapeHtml(decrypted),
nick = (data.nick == undefined || !data.nick ) ? templates.default_nick : $.escapeHtml($.AES.decrypt(data.nick, room + key));
if (!decrypted) {
post('error', templates.messages.unable_to_decrypt);
} else {
post('message', sanitized, false, false, nick);
if( !mute ) sound.playTones(sound.messages.message);
}
})
.on('message:server', function (data) {
if( data.msg ) {
var sanitized = $.escapeHtml(data.msg);
if( templates.server[sanitized] ) {
if( data.payload !== undefined ) {
var sanitized_payload = $.escapeHtml(data.payload);
post('server', $.template(templates.server[sanitized], { payload: sanitized_payload }));
} else {
post('server', templates.server[sanitized]);
}
// Play sound
if (sound.messages[sanitized] !== undefined && !mute ) sound.playTones(sound.messages[sanitized]);
} else {
post('error', templates.server.bogus);
}
} else {
post('error', templates.server.bogus);
}
})
.on('connect', function () {
// Tell the user that the chat is ready to interact with
post('info', $.template(templates.messages.connected, {
host: host.name || 'localhost'
}));
host.connected = 1;
done();
})
.on('disconnect', function () {
room = 0;
key = 0;
host.connected = 0;
// Tell the user that the chat is ready to interact with
post('info', $.template(templates.messages.disconnected, {
host: host.name || 'localhost'
}));
})
.on('error', function () {
room = 0;
key = 0;
host.connected = 0;
post('error', templates.messages.socket_error);
done();
});
},
reconnect: function (foo, done) {
if (host) {
if (host.connected) {
commands.disconnect()
commands.connect(host, done);
} else {
commands.connect(host, done);
}
} else {
done();
return post('error', templates.messages.reconnect_no_host);
}
},
disconnect: function () {
socket.disconnect();
}, },
clear: function () { clear: function () {
@ -81,6 +291,10 @@ define({
}, },
key: function (payload) { key: function (payload) {
if (!host) {
return post('error', templates.messages.key_no_host);
}
// Make sure the key meets the length requirements // Make sure the key meets the length requirements
if (payload.length > settings.key_maxLen) { if (payload.length > settings.key_maxLen) {
return post('error', templates.messages.key_to_long); return post('error', templates.messages.key_to_long);
@ -119,6 +333,10 @@ define({
}, },
join: function (payload) { join: function (payload) {
if (!host) {
return post('error', templates.messages.join_no_host);
}
return ( return (
room room
? post('error', templates.messages.already_in_room) ? post('error', templates.messages.already_in_room)
@ -169,7 +387,7 @@ define({
// The Document object is bound to this element. // The Document object is bound to this element.
// If the active element is not the input, focus on it and exit the function. // If the active element is not the input, focus on it and exit the function.
// Ignore this when ctrl and/or alt is pressed! // Ignore this when ctrl and/or alt is pressed!
if (components.input[0] !== $.activeElement() && !e.ctrlKey && !e.altKey) { if (!e.ctrlKey && !e.altKey && components.input[0] !== $.activeElement()) {
return components.input.focus(); return components.input.focus();
} }
@ -178,7 +396,7 @@ define({
history_timer = setTimeout(clearHistory, 60000); history_timer = setTimeout(clearHistory, 60000);
// Check for escape key, this does nothing but clear the input buffer and reset history position // Check for escape key, this does nothing but clear the input buffer and reset history position
if ( e.keyCode == 27 ) { if (e.keyCode == 27) {
history_pos = -1; history_pos = -1;
clearInput(); clearInput();
@ -186,7 +404,7 @@ define({
} }
// Check for up or down-keys, they handle the history position // Check for up or down-keys, they handle the history position
if( e.keyCode == 38 || e.keyCode == 40) { if (e.keyCode == 38 || e.keyCode == 40) {
if (e.keyCode == 38 ) { history_pos = (history_pos > history.length - 2) ? -1 : history_pos = history_pos + 1; } if (e.keyCode == 38 ) { history_pos = (history_pos > history.length - 2) ? -1 : history_pos = history_pos + 1; }
else { history_pos = (history_pos <= 0) ? -1 : history_pos = history_pos - 1; } else { history_pos = (history_pos <= 0) ? -1 : history_pos = history_pos - 1; }
@ -220,8 +438,18 @@ define({
return post('error', $.template(templates.messages.unrecognized_command, { commandName: command })); return post('error', $.template(templates.messages.unrecognized_command, { commandName: command }));
} }
// Execute command handler // Some commands are asynchrounous;
commands[command](payload); // If the command expects more than one argument, the second argument is a callback that is called when the command is done.
if (commands[command].length > 1) {
// Lock the input from further interaction
lockInput();
// Execute command handler with callback function.
commands[command](payload, unlockInput);
} else {
// Execute normally.
commands[command](payload);
}
// Clear input field // Clear input field
clearInput(); clearInput();
@ -257,103 +485,24 @@ define({
} }
}; };
host = settings.host; // Bind the necessary DOM events
$(document).on('keydown', onKeyDown);
// Put focus on the message input
components.input.focus();
// Post the help/welcome message // Post the help/welcome message
post('motd', templates.motd, true); post('motd', templates.motd, true);
// Push 'Connecting...' message unlockInput();
post('info', $.template(templates.messages.connecting, {
host: host || 'localhost'
}));
// The one and only socket // It's possible to provide room and key using the hashtag.
socket = $.Websocket.connect(host); // The room and key is then seperated by semicolon (room:key).
// If there is no semicolon present, the complete hash will be treated as the room name and the key has to be set manually.
if (host && (hash = window.location.hash)) {
parts = hash.slice(1).split(':');
// Bind socket events parts[0] && commands.join(parts[0]);
socket parts[1] && commands.key(parts[1]);
.on('room:generated', function (data) { }
var sanitized = $.escapeHtml(data);
post('server', $.template(templates.server.room_generated, { payload: sanitized }));
socket.emit('room:join', sanitized);
})
.on('room:joined', function (data) {
room = data;
post('info', $.template(templates.messages.joined_room, { roomName: room }));
// Automatically count persons on join
socket.emit('room:count');
})
.on('room:left', function () {
post('info', $.template(templates.messages.left_room, { roomName: room }));
// Clear history on leaving room
clearHistory();
room = false;
})
.on('message:send', function (data) {
var decrypted = $.AES.decrypt(data.msg, room + key),
sanitized = $.escapeHtml(decrypted),
nick = (data.nick == undefined || !data.nick ) ? templates.default_nick : $.escapeHtml($.AES.decrypt(data.nick, room + key));
if (!decrypted) {
post('error', templates.messages.unable_to_decrypt);
} else {
post('message', sanitized, false, false, nick);
if( !mute ) sound.playTones(sound.messages.message);
}
})
.on('message:server', function (data) {
if( data.msg ) {
var sanitized = $.escapeHtml(data.msg);
if( templates.server[sanitized] ) {
if( data.payload !== undefined ) {
var sanitized_payload = $.escapeHtml(data.payload);
post('server', $.template(templates.server[sanitized], { payload: sanitized_payload }));
} else {
post('server', templates.server[sanitized]);
}
// Play sound
if (sound.messages[sanitized] !== undefined && !mute ) sound.playTones(sound.messages[sanitized]);
} else {
post('error', templates.server.bogus);
}
} else {
post('error', templates.server.bogus);
}
})
.on('connect', function () {
// Bind the necessary DOM events
$(document).on('keydown', onKeyDown);
// Put focus on the message input
components.input.focus();
// Tell the user that the chat is ready to interact with
post('info', $.template(templates.messages.connected, {
host: host || 'localhost'
}));
// It's possible to provide room and key using the hashtag.
// The room and key is then seperated by semicolon (room:key).
// If there is no semicolon present, the complete hash will be treated as the room name and the key has to be set manually.
if (hash = window.location.hash) {
parts = hash.slice(1).split(':');
parts[0] && commands.join(parts[0]);
parts[1] && commands.key(parts[1]);
}
})
.on('error', function () {
post('error', templates.messages.socket_error);
});
}); });

View File

@ -0,0 +1,15 @@
define({
// Used to autoconnect to specific host.
// Points to a specific index in the 'hosts' array.
// Use -1 to not autoconnect.
autoconnect: 0,
// A collection of hosts to choose from
hosts: [
{
name: 'localhost',
host: 'http://localhost:8080',
path: 'http://localhost:8080/js/cryptalk_modules/settings.js'
}
]
});

View File

@ -1,7 +1,4 @@
define({ define({
// If no host is given it will default to localhost.
host: '',
nick_maxLen: 20, nick_maxLen: 20,
nick_minLen: 3, nick_minLen: 3,

View File

@ -65,6 +65,9 @@ define({
key_to_long: 'Man that\'s a long key. Make it a tad short, \'kay?', key_to_long: 'Man that\'s a long key. Make it a tad short, \'kay?',
key_ok_ready: 'Key set, you can now start communicating.', key_ok_ready: 'Key set, you can now start communicating.',
key_ok_but_no_room: 'Key set, you can now join a room and start communicating.', key_ok_but_no_room: 'Key set, you can now join a room and start communicating.',
key_no_host: 'You have to connect to a host before setting the key.',
join_no_host: 'You have to connect to a host before joining a room.',
nick_to_short: 'Nickname is too short, it has to be at least {nick_minLen} characters long. Try again.', nick_to_short: 'Nickname is too short, it has to be at least {nick_minLen} characters long. Try again.',
nick_to_long: 'Nickname is too long, it can be at most {nick_maxLen} characters long. Try again.', nick_to_long: 'Nickname is too long, it can be at most {nick_maxLen} characters long. Try again.',
@ -90,7 +93,13 @@ define({
socket_error: 'A network error has occurred. A restart may be required to bring back full functionality.<br>Examine the logs for more details.', socket_error: 'A network error has occurred. A restart may be required to bring back full functionality.<br>Examine the logs for more details.',
connecting: 'Connecting to host {host}...', connecting: 'Connecting to host {host}...',
connected: 'A connection to the server has been established. Happy chatting!' connected: 'A connection to the server has been established. Happy chatting!',
disconnected: 'Disconnected from host {host}.',
already_connected: 'You have to disconnect from {host} before joining another.',
reconnect_no_host: 'There is no host to reconnect with.',
host_available: '<span class="info">{index}</span> <span class="good">[AVAILABLE]</span> <span class="neutral">{name}</span>\n',
host_unavailable: '<span class="info">{index}</span> <span class="bad">[UNAVAILABLE]</span> <span class="neutral">{name}</span>\n'
}, },
server: { server: {

View File

@ -1,20 +1,20 @@
(function(r){function A(f){this.name="TimeoutError";this.message=f||""}function B(f){this.name="RejectedError";this.message=f||""}function F(f,c,e,g){var d,a,b=[],m,p,k,h=function(a){f.length===b.push(l[a].exports)&&(g&&clearTimeout(m),c(b))};g=g||v.timeout;for(k=0;p=f[k++];)if((d=l[p])&&1!==d.state)if(2===d.state)if(a=new B('Could resolve all dependencies; dependency "'+p+'" has been rejected.'),e)e(a);else throw a;else 3===d.state&&h(p);else(u[p]=u[p]||[]).push(h);g&&(m=setTimeout(function(){for(var c, (function(s){function A(f){this.name="TimeoutError";this.message=f||""}function B(f){this.name="RejectedError";this.message=f||""}function F(f,c,e,g){var d,a,b=[],n,m,k,h=function(a){f.length===b.push(l[a].exports)&&(g&&clearTimeout(n),c(b))};g=g||v.timeout;for(k=0;m=f[k++];)if((d=l[m])&&1!==d.state)if(2===d.state)if(a=new B('Could resolve all dependencies; dependency "'+m+'" has been rejected.'),e)e(a);else throw a;else 3===d.state&&h(m);else(u[m]=u[m]||[]).push(h);g&&(n=setTimeout(function(){for(var d,
d,m=0,p=0;c=f[m++];)for(p=0;d=b[p++];)c===d&&(b.splice(--p,1),f.splice(--m,1));a=new A("Load timeout of "+g+'ms exceeded for module(s) "'+f.join('", "')+'".');if(e)e(a);else throw a;},g))}function y(){var f=!1,c,e,g,d,a,b,m;if(arguments[0]!==C){for(c=0;(e=arguments[c++])&&(_type=(typeof e)[0]);)if("s"===_type?d?g=1:d=e:"f"===_type?b?m?g=1:m=e:b=e:"o"===_type&&(h.is(e,"array")?(a={requires:e},f=!0):a?b=e:a=e),g)throw new TypeError("define called with unrecognized signature; `"+Array.prototype.join.call(arguments, c,n=0,m=0;d=f[n++];)for(m=0;c=b[m++];)d===c&&(b.splice(--m,1),f.splice(--n,1));a=new A("Load timeout of "+g+'ms exceeded for module(s) "'+f.join('", "')+'".');if(e)e(a);else throw a;},g))}function y(){var f=!1,c,e,g,d,a,b,n;if(arguments[0]!==C){for(c=0;(e=arguments[c++])&&(_type=(typeof e)[0]);)if("s"===_type?d?g=1:d=e:"f"===_type?b?n?g=1:n=e:b=e:"o"===_type&&(h.is(e,"array")?(a={requires:e},f=!0):a?b=e:a=e),g)throw new TypeError("define called with unrecognized signature; `"+Array.prototype.join.call(arguments,
", ")+"`.");a=a||{};d=d||a.UID;b=b||a.factory;m=m||a.onRejected}else if(c=D.pop())d=arguments[1],a=c[0],b=c[1],m=c[2],f=c[3];else throw Error("Inconsistent naming queue");if(!b)if(a)b=a,a={};else throw Error('Missing factory for module "'+d+'"');if(l[d])throw Error('Duplicate entry for module "'+d+'"');d?(c=l[d]=a,c.UID=d,c.amdStyle=f,c.factory=b,c.state=1,c.requires=h.is(c.requires,"array")&&c.requires,c.inherits=h.is(c.inherits,"array")&&c.inherits,c.compiles=h.is(c.compiles,"array")&&c.compiles, ", ")+"`.");a=a||{};d=d||a.UID;b=b||a.factory;n=n||a.onRejected}else if(c=D.pop())d=arguments[1],a=c[0],b=c[1],n=c[2],f=c[3];else throw Error("Inconsistent naming queue");if(!b)if(a)b=a,a={};else throw Error('Missing factory for module "'+d+'"');if(l[d])throw Error('Duplicate entry for module "'+d+'"');d?(c=l[d]=a,c.UID=d,c.amdStyle=f,c.factory=b,c.state=1,c.requires=h.is(c.requires,"array")&&c.requires,c.inherits=h.is(c.inherits,"array")&&c.inherits,c.compiles=h.is(c.compiles,"array")&&c.compiles,
c.instances||(c.instances={instance1:{}})):D.unshift([a,b,m,f])}var l={},v={deepCopy:!0,baseUrl:"",namespace:"default",timeout:1E3,paths:{},shim:{}},h={},C={a:1},u={},D=[],z=Array.prototype.push;A.prototype=Error.prototype;B.prototype=Error.prototype;h.is=function(){var f=Object.prototype.hasOwnProperty,c=Object.prototype.toString,e={array:Array.isArray||function(a){return"[object Array]"==c.call(a)},arraylike:function(a){if(!a||!a.length&&0!==a.length||e.window(a))return!1;var b=a.length;return 1=== c.instances||(c.instances={instance1:{}})):D.unshift([a,b,n,f])}var l={},v={deepCopy:!0,baseUrl:"",namespace:"default",timeout:1E3,paths:{},shim:{}},h={},C={a:1},u={},D=[],z=Array.prototype.push;A.prototype=Error.prototype;B.prototype=Error.prototype;h.is=function(){var f=Object.prototype.hasOwnProperty,c=Object.prototype.toString,e={array:Array.isArray||function(a){return"[object Array]"==c.call(a)},arraylike:function(a){if(!a||!a.length&&0!==a.length||e.window(a))return!1;var b=a.length;return 1===
a.nodeType||e.array(a)||!e["function"](a)&&(0===b||"number"===typeof b&&0<b&&!h.is(a,"primitive")&&b-1 in a)},"boolean":function(a){return!0===a||!1===a},element:function(a){return!(!a||1!==a.nodeType)},finite:function(a){return isFinite(a)&&!isNaN(parseFloat(a))},integer:Number.isInteger||function(a){return"number"===typeof a&&e.finite(a)&&-9007199254740992<a&&9007199254740992>a&&Math.floor(a)===a},iterable:function(a){try{1 in obj}catch(b){return!1}return!0},nan:function(a){return e.number(a)&& a.nodeType||e.array(a)||!e["function"](a)&&(0===b||"number"===typeof b&&0<b&&!h.is(a,"primitive")&&b-1 in a)},"boolean":function(a){return!0===a||!1===a},element:function(a){return!(!a||1!==a.nodeType)},finite:function(a){return isFinite(a)&&!isNaN(parseFloat(a))},integer:Number.isInteger||function(a){return"number"===typeof a&&e.finite(a)&&-9007199254740992<a&&9007199254740992>a&&Math.floor(a)===a},iterable:function(a){try{1 in obj}catch(b){return!1}return!0},nan:function(a){return e.number(a)&&
a!=+a},number:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},object:function(a){return a===Object(a)},primitive:function(a){return!0===a||!1===a||null==a||!!{string:1,number:1}[typeof a]},string:function(a){return"string"==typeof a||a instanceof String},undefined:function(a){return void 0===a},untyped:function(a){if(!a||a.nodeType||"[object Object]"!==c.call(a)||e.window(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}for(var d in a); a!=+a},number:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},object:function(a){return a===Object(a)},primitive:function(a){return!0===a||!1===a||null==a||!!{string:1,number:1}[typeof a]},string:function(a){return"string"==typeof a||a instanceof String},undefined:function(a){return void 0===a},untyped:function(a){if(!a||a.nodeType||"[object Object]"!==c.call(a)||e.window(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}for(var d in a);
return void 0===d||f.call(a,d)},window:function(a){return null!=a&&a==a.window},empty:function(a){if(a){if(h.is(a,"array"))return 0===a.length;for(var b in a)if(f.call(a,b))return!1}return!0}},g=0,d=["Arguments","Date","Function","RegExp"];for(;4>g;g++)e[d[g].toLowerCase()]=function(a){a="[object "+a+"]";return function(b){return c.call(b)==a}}(d[g]);e.args=e.arguments;e.bool=e["boolean"];e.plain=e.untyped;return function(a,b){if(e["function"](b))return a===b(a);if(!e.string(b))return a===b;if((b= return void 0===d||f.call(a,d)},window:function(a){return null!=a&&a==a.window},empty:function(a){if(a){if(h.is(a,"array"))return 0===a.length;for(var b in a)if(f.call(a,b))return!1}return!0}},g=0,d=["Arguments","Date","Function","RegExp"];for(;4>g;g++)e[d[g].toLowerCase()]=function(a){a="[object "+a+"]";return function(b){return c.call(b)==a}}(d[g]);e.args=e.arguments;e.bool=e["boolean"];e.plain=e.untyped;return function(a,b){if(e["function"](b))return a===b(a);if(!e.string(b))return a===b;if((b=
b.toLowerCase())&&e[b])return e[b](a);throw'Unknown type "'+b+'"';}}();h.each=function(){var f=Array.prototype.some;return function(c,e,g){var d,a;if(void 0===c)return obj;g=g||h;if(f&&c.some===f)return c.some(e,g),c;if(h.is(c,"array")||h.is(c,"arraylike")){d=0;for(a=c.length;d<a&&(void 0===c[d]||!e.call(g,c[d],d,c));d++);return c}for(d in c)if(c.hasOwnProperty(d)&&e.call(g,c[d],d,c))break;return c}}();h.merge=function(f){var c,e,g,d,a;c=arguments.length;var b=!1;f=arguments[0];var m=1,p,k,l=0;if(!0=== b.toLowerCase())&&e[b])return e[b](a);throw'Unknown type "'+b+'"';}}();h.each=function(){var f=Array.prototype.some;return function(c,e,g){var d,a;if(void 0===c)return obj;g=g||h;if(f&&c.some===f)return c.some(e,g),c;if(h.is(c,"array")||h.is(c,"arraylike")){d=0;for(a=c.length;d<a&&(void 0===c[d]||!e.call(g,c[d],d,c));d++);return c}for(d in c)if(c.hasOwnProperty(d)&&e.call(g,c[d],d,c))break;return c}}();h.merge=function(f){var c,e,g,d,a,b=arguments.length,n=!1;f=arguments[0];var m=1,k,l,q=0;if(!0===
f||!1===f)b=f,f=arguments[m],m++;h.is(arguments[c-1],"function")&&(a=arguments[c-1],c--);if(void 0===f)f={};else if(h.is(f,"primitive"))throw new TypeError("Cannot merge primitive data types: merge("+Array.prototype.join.call(arguments,", ")+")");k=h.is(f,"array");if(m<c)for(;d=arguments[m++];){k=k&&h.is(d,"array");for(g in d)e=f[g],c=d[g],void 0===c||c===f?k&&l--:(b&&c&&((p=h.is(c,"array"))||h.is(c,"untyped"))&&(c=h.merge(b,e&&(p?h.is(e,"array")?e:[]:h.is(e,"untyped")?e:{}),c,a)),a&&a(g,f,d)||(l&& f||!1===f)n=f,f=arguments[m],m++;h.is(arguments[b-1],"function")&&(a=arguments[b-1],b--);if(void 0===f)f={};else if(h.is(f,"primitive"))throw new TypeError("Cannot merge primitive data types: merge("+Array.prototype.join.call(arguments,", ")+")");l=h.is(f,"array");if(m<b)for(;m<=b;)if(d=arguments[m++]){l=l&&h.is(d,"array");for(g in d)e=f[g],c=d[g],void 0===c||c===f?l&&q--:(n&&c&&((k=h.is(c,"array"))||h.is(c,"untyped"))&&(c=h.merge(n,e||(k?h.is(e,"array")?e:[]:h.is(e,"untyped")?e:{}),c,a)),a&&a(g,
(g=+g+l),f[g]=c));l=0}return f};h.subordinate=function(){function f(a){if(!a||a.source===window&&a.data===g){var c=d.shift();a=c[0];var c=c[1],f=c.length;f?1===f?a(c[0]):2===f?a(c[0],c[1]):3===f?a(c[0],c[1],c[2]):4===f?a(c[0],c[1],c[2],c[3]):a.apply(0,c):a()}}var c=Array.prototype.slice,e=window.postMessage,g="__fandango@"+Math.random().toString(36),d=[],a=function(){return e?function(){e(g,"*")}:function(){setTimeout(f)}}();e&&window.addEventListener("message",f,!1);return function(b){a(d.push([arguments[arguments.length- f,d)||(q&&(g=+g+q),f[g]=c));q=0}return f};h.subordinate=function(){function f(a){if(!a||a.source===window&&a.data===g){var c=d.shift();a=c[0];var c=c[1],f=c.length;f?1===f?a(c[0]):2===f?a(c[0],c[1]):3===f?a(c[0],c[1],c[2]):4===f?a(c[0],c[1],c[2],c[3]):a.apply(0,c):a()}}var c=Array.prototype.slice,e=window.postMessage,g="__fandango@"+Math.random().toString(36),d=[],a=function(){return e?function(){e(g,"*")}:function(){setTimeout(f)}}();e&&window.addEventListener("message",f,!1);return function(b){a(d.push([arguments[arguments.length-
1],c.call(arguments).slice(0,-1)])-1)}}();var G=function(){var f={},c=[].slice,e=function(){return[]};return function(g,d){function a(a,c){return new b(a,c)}function b(a,b){var c=0,d=m(a,b),f=d.length;this.context=b||document;this.selector=a;for(this.length=f;c<f;c++)this[c]=d[c];return this}var m,p,k,h;if(f[g])return f[g];for(h=d.length;p=d[--h];){p.selector&&(m=p.selector);if(source=p.prototype)if("function"===typeof source)source.call(b.prototype);else if(b.prototype.prototype)for(k in source)source.hasOwnProperty(k)&& 1],c.call(arguments).slice(0,-1)])-1)}}();var G=function(){var f={},c=[].slice,e=function(){return[]};return function(g,d){function a(a,c){return new b(a,c)}function b(a,b){var c=0,d=n(a,b),f=d.length;this.context=b||document;this.selector=a;for(this.length=f;c<f;c++)this[c]=d[c];return this}var n,m,k,h;if(f[g])return f[g];for(h=d.length;m=d[--h];){m.selector&&(n=m.selector);if(source=m.prototype)if("function"===typeof source)source.call(b.prototype);else if(b.prototype.prototype)for(k in source)source.hasOwnProperty(k)&&
(b.prototype[k]=source[k]);else b.prototype=source;if(source=p.utilities)if("function"===typeof source)source.utilities.call(a);else for(k in source)source.hasOwnProperty(k)&&(a[k]=source[k])}b.prototype.length=0;b.prototype.selector="";b.prototype.splice=c;b.prototype.fandango=1;m||(m=e);return f[g]=a}}(),E=function(){function f(c,f,g,d){var a,b=l[c],m=h.is(b.factory,"function"),p=m?b.factory.length:0,k=b.amdStyle,t,s;d&&(a=G(b.compiles+"",d));for(s in b.instances)d=b.instances[s],d.context=void 0=== (b.prototype[k]=source[k]);else b.prototype=source;if(source=m.utilities)if("function"===typeof source)source.utilities.call(a);else for(k in source)source.hasOwnProperty(k)&&(a[k]=source[k])}b.prototype.length=0;b.prototype.selector="";b.prototype.splice=c;b.prototype.fandango=1;n||(n=e);return f[g]=a}}(),E=function(){function f(c,f,g,d){var a,b=l[c],n=h.is(b.factory,"function"),m=n?b.factory.length:0,k=b.amdStyle,t,q;d&&(a=G(b.compiles+"",d));for(q in b.instances)d=b.instances[q],d.context=void 0===
d.context?b.context:d.context,d.deepCopy=void 0===d.deepCopy?b.deepCopy:d.deepCopy,d.namespace=void 0===d.namespace?b.namespace:d.namespace,t=[],k?t=f||[]:(a&&t.push(a),f&&t.push(f),p>t.length&&t.push(d.data?h.merge(d.deepCopy,{},d.data,b.data):b.data),p>t.length&&t.push(h.merge(d.deepCopy,{},d,b))),d.exports=m?b.factory.apply(d.context,t)||{}:b.factory,void 0===b.exports&&(b.exports=g?d.exports?h.merge(!0,{},g,d.exports):g:d.exports);b.state=3;if(u[c])for(;u[c].length;)u[c].pop()(c)}return function e(g, d.context?b.context:d.context,d.deepCopy=void 0===d.deepCopy?b.deepCopy:d.deepCopy,d.namespace=void 0===d.namespace?b.namespace:d.namespace,t=[],k?t=f||[]:(a&&t.push(a),f&&t.push(f),m>t.length&&t.push(d.data?h.merge(d.deepCopy,{},d.data,b.data):b.data),m>t.length&&t.push(h.merge(d.deepCopy,{},d,b))),d.exports=n?b.factory.apply(d.context,t)||{}:b.factory,void 0===b.exports&&(b.exports=g?d.exports?h.merge(!0,{},g,d.exports):g:d.exports);b.state=3;if(u[c])for(;u[c].length;)u[c].pop()(c)}return function e(g,
d){var a,b=l[g],m=h.is(b.factory,"function")?b.factory.length:0,p=b.amdStyle,k=[],t,s,r;a=[];var q,n;if(!d&&(b.requires||b.compiles||b.inherits)){b.requires&&z.apply(a,b.requires);b.inherits&&z.apply(a,b.inherits);b.compiles&&z.apply(a,b.compiles);for(q=0;n=a[q++];)if(l[n])if(3===l[n].state)a.splice(--q,1);else if(2===l[n].state)throw Error('Could not instantiate "'+UID+'"; dependency "'+dependency+'" has been rejected.');if(0<a.length){h.subordinate(a,function(){e(g,!0)},x);return}}void 0===(a=b.deepCopy)&& d){var a,b=l[g],n=h.is(b.factory,"function")?b.factory.length:0,m=b.amdStyle,k=[],t,q,s;a=[];var r,p;if(!d&&(b.requires||b.compiles||b.inherits)){b.requires&&z.apply(a,b.requires);b.inherits&&z.apply(a,b.inherits);b.compiles&&z.apply(a,b.compiles);for(r=0;p=a[r++];)if(l[p])if(3===l[p].state)a.splice(--r,1);else if(2===l[p].state)throw Error('Could not instantiate "'+UID+'"; dependency "'+dependency+'" has been rejected.');if(0<a.length){h.subordinate(a,function(){e(g,!0)},x);return}}void 0===(a=b.deepCopy)&&
(a=v.deepCopy);b=l[g]=h.merge(a,{},v,b);if(m&&b.requires)for(s=b.requires&&(p?[]:{}),q=0;n=b.requires[q++];)l[n]?p?s.push(l[n].exports):h.is(l[n].exports,"function")?s[n]=l[n].exports:h.merge(a,s[n]={},l[n].exports):k.push(n);if(b.inherits)for(r=b.inherits&&{},q=0;n=b.inherits[q++];)l[n]?h.merge(a,r,l[n].exports):k.push(n);if(m&&b.compiles)for(t=b.compiles&&[],q=0;n=b.compiles[q++];)l[n]?t.push(l[n].exports):k.push(n);k.length?x(k,function(){},onError):f(g,s,r,t)}}();y.amd={};var x=function(){var f= (a=v.deepCopy);b=l[g]=h.merge(a,{},v,b);if(n&&b.requires)for(q=b.requires&&(m?[]:{}),r=0;p=b.requires[r++];)l[p]?m?q.push(l[p].exports):h.is(l[p].exports,"function")?q[p]=l[p].exports:h.merge(a,q[p]={},l[p].exports):k.push(p);if(b.inherits)for(s=b.inherits&&{},r=0;p=b.inherits[r++];)l[p]?h.merge(a,s,l[p].exports):k.push(p);if(n&&b.compiles)for(t=b.compiles&&[],r=0;p=b.compiles[r++];)l[p]?t.push(l[p].exports):k.push(p);k.length?x(k,function(){},onError):f(g,q,s,t)}}();y.amd={};var x=function(){var f=
{},c=document.getElementsByTagName("head")[0],e="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),g=function(a){var b=document.createElement("script");b.setAttribute("id",a);b.type="text/javascript";b.charset="utf-8";b.async=!0;return b},d=!(!((d=g())&&d.attachEvent&&d.attachEvent.toString&&0<d.attachEvent.toString().indexOf("[native code"))||e);buildUrl=function(){var a={"http://":1,"https://":1,"file://":1};return function(b){var c=b.toLowerCase(),d=c[0];"/"===d||"\\"===d||"h"===d&& {},c=document.getElementsByTagName("head")[0],e="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),g=function(a){var b=document.createElement("script");b.setAttribute("id",a);b.type="text/javascript";b.charset="utf-8";b.async=!0;return b},d=!(!((d=g())&&d.attachEvent&&d.attachEvent.toString&&0<d.attachEvent.toString().indexOf("[native code"))||e);buildUrl=function(){var a={"http://":1,"https://":1,"file://":1};return function(b){var c=b.toLowerCase(),d=c[0];"/"===d||"\\"===d||"h"===d&&
(a[c.slice(0,7)]||a[c.slice(0,8)])||"f"===d&&a[c.slice(0,7)]||(b=v.baseUrl+b);".js"!==b.slice(-3)&&(b+=".js");return b}}();onErrorEvent=function(a,b,c,d){return function(e){f[b]=0;a.error=e||!0;d.parentElement.removeChild(d);if(a.onError)a.onError(e,c,b)}};onLoadEvent=function(a,b,c,d,e){return function(){if(2===f[b]){f[b]=3;if(a.onPartial)a.onPartial(b,c,d);e?d.onreadystatechange=null:(d.removeEventListener("load",a.loadListener),d.removeEventListener("error",a.errorListener));if(a.loaded.push(b)=== (a[c.slice(0,7)]||a[c.slice(0,8)])||"f"===d&&a[c.slice(0,7)]||(b=v.baseUrl+b);".js"!==b.slice(-3)&&(b+=".js");return b}}();onErrorEvent=function(a,b,c,d){return function(e){f[b]=0;a.error=e||!0;d.parentElement.removeChild(d);if(a.onError)a.onError(e,c,b)}};onLoadEvent=function(a,b,c,d,e){return function(){if(2===f[b]){f[b]=3;if(a.onPartial)a.onPartial(b,c,d);e?d.onreadystatechange=null:(d.removeEventListener("load",a.loadListener),d.removeEventListener("error",a.errorListener));if(a.loaded.push(b)===
a.paths.length&&a.onDone)a.onDone(a.load)}}};get=function(a){for(var b=0,e,h,k;!a.error&&(e=a.paths[b++]);)h=a.names[b-1],f[e]=2,k=g(e),d?k.attachEvent("onreadystatechange",onLoadEvent(a,e,h,k,!0)):(a.loadListener=onLoadEvent(a,e,h,k),a.errorListener=onErrorEvent(a,e,h,k),k.addEventListener("load",a.loadListener,!1),k.addEventListener("error",a.errorListener,!1)),k.src=e,c.appendChild(k)};return function(a,b,c,d){var e=[],g=[],s={paths:[],names:[],loaded:[],error:null,loadListener:null,errorListener:null}, a.paths.length&&a.onDone)a.onDone(a.load)}}};get=function(a){for(var b=0,e,h,k;!a.error&&(e=a.paths[b++]);)h=a.names[b-1],f[e]=2,k=g(e),d?k.attachEvent("onreadystatechange",onLoadEvent(a,e,h,k,!0)):(a.loadListener=onLoadEvent(a,e,h,k),a.errorListener=onErrorEvent(a,e,h,k),k.addEventListener("load",a.loadListener,!1),k.addEventListener("error",a.errorListener,!1)),k.src=e,c.appendChild(k)};return function(a,b,c,d){var e=[],g=[],q={paths:[],names:[],loaded:[],error:null,loadListener:null,errorListener:null},
u,q,n,w,x;for(x=0;q=a[x++];)if(l[q])if(3===l[q].state)g.push(l[q].exports);else{if(2===l[q].state)throw Error("require(): Rejected dependency. Cannot continue.");E(q);e.push(q)}else f[u]||(u=buildUrl(v.paths[q]||q),s.paths.push(u),s.names.push(q)),e.push(q);b&&!e.length?b.apply(null,g):(s.paths&&(s.onError=function(a,b,d){a instanceof Event&&(a=Error('Could require "'+b+'"; an error occurred while trying to load "'+d+'".'));if(c)c(a);else throw a;},s.onPartial=function(a,b,c){l[b]||((n=v.shim[b])? u,r,p,w,x;for(x=0;r=a[x++];)if(l[r])if(3===l[r].state)g.push(l[r].exports);else{if(2===l[r].state)throw Error("require(): Rejected dependency. Cannot continue.");E(r);e.push(r)}else f[u]||(u=buildUrl(v.paths[r]||r),q.paths.push(u),q.names.push(r)),e.push(r);b&&!e.length?b.apply(null,g):(q.paths&&(q.onError=function(a,b,d){a instanceof Event&&(a=Error('Could require "'+b+'"; an error occurred while trying to load "'+d+'".'));if(c)c(a);else throw a;},q.onPartial=function(a,b,c){l[b]||((p=v.shim[b])?
(w=h.is(n.exports,"function")?n.exports:function(){return r[n.exports]},y(b,w)):y(C,b));E(b);d&&d(a,c)},get(s)),F(e,function(a){b.apply(null,a)},s.onError))}}();y("fandango",h);var H=r.fandango,I=r.define,J=r.require,w={fn:h,define:y,require:x,noConflict:function(f){r.define===y&&(r.define=I);r.require===x&&(r.require=J);f&&r.fandango===w&&(r.fandango=H);return w},defaults:function(f){return f?h.merge(v,f):v}};r.define||(r.define=w.define);r.require||(r.require=w.require);r.fandango=w})(this); (w=h.is(p.exports,"function")?p.exports:function(){return s[p.exports]},y(b,w)):y(C,b));E(b);d&&d(a,c)},get(q)),F(e,function(a){b.apply(null,a)},q.onError))}}();y("fandango",h);var H=s.fandango,I=s.define,J=s.require,w={fn:h,define:y,require:x,noConflict:function(f){s.define===y&&(s.define=I);s.require===x&&(s.require=J);f&&s.fandango===w&&(s.fandango=H);return w},defaults:function(f){return f?h.merge(v,f):v}};s.define||(s.define=w.define);s.require||(s.require=w.require);s.fandango=w})(this);