Commit be524c1f authored by Dele Olajide's avatar Dele Olajide Committed by dele

Implemented OF-703 (Auto-creation of shared groups in userservice plugin)

Demo application for Rayo plugin

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13752 b35dd754-fafc-0310-a699-88a17e54d16e
parent b7ac7a29
/*global io MediaServices Phono*/
(function () {
// Utils and references
var root = this,
att = {};
// global utils
var _ = att.util = {
_uuidCounter: 0,
uuid: function () {
return Math.random().toString(16).substring(2) + (_._uuidCounter++).toString(16);
},
slice: Array.prototype.slice,
isFunc: function (obj) {
return Object.prototype.toString.call(obj) == '[object Function]';
},
extend: function (obj) {
this.slice.call(arguments, 1).forEach(function (source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
},
each: function (obj, func) {
if (!obj) return;
if (obj instanceof Array) {
obj.forEach(func);
} else {
for (var key in obj) {
func(key, obj[key]);
}
}
},
getQueryParam: function (name) {
// query string parser
var cleaned = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"),
regexS = "[\\?&]" + cleaned + "=([^&#]*)",
regex = new RegExp(regexS),
results = regex.exec(window.location.search);
return (results) ? decodeURIComponent(results[1].replace(/\+/g, " ")) : undefined;
},
// used to try to determine whether they're using the ericsson leif browser
// this is not an ideal way to check, but I'm not sure how to do it since
// leif if pretty much just stock chromium.
h2sSupport: function () {
return !!window.webkitPeerConnection00 && window.navigator.userAgent.indexOf('Chrome/24') !== -1;
}
};
var phoneNumber = {};
phoneNumber.stringify = function (text) {
// strip all non numbers
var cleaned = phoneNumber.parse(text),
len = cleaned.length,
countryCode = (cleaned.charAt(0) === '1'),
arr = cleaned.split(''),
diff;
// if it's long just return it unformatted
if (len > (countryCode ? 11 : 10)) return cleaned;
// if it's too short to tell
if (!countryCode && len < 4) return cleaned;
// remove country code if we have it
if (countryCode) arr.splice(0, 1);
// the rules are different enough when we have
// country codes so we just split it out
if (countryCode) {
if (len > 1) {
diff = 4 - len;
diff = (diff > 0) ? diff : 0;
arr.splice(0, 0, " (");
// back fill with spaces
arr.splice(4, 0, (new Array(diff + 1).join(' ') + ") "));
if (len > 7) {
arr.splice(8, 0, '-');
}
}
} else {
if (len > 7) {
arr.splice(0, 0, "(");
arr.splice(4, 0, ") ");
arr.splice(8, 0, "-");
} else if (len > 3) {
arr.splice(3, 0, "-");
}
}
// join it back when we're done with the CC if it's there
return (countryCode ? '1' : '') + arr.join('');
};
phoneNumber.parse = function (input) {
return String(input)
.toUpperCase()
.replace(/[A-Z]/g, function (l) {
return (l.charCodeAt(0) - 65) / 3 + 2 - ("SVYZ".indexOf(l) > -1) | 0;
})
.replace(/\D/g, '');
};
phoneNumber.getCallable = function (input, countryAbr) {
var country = countryAbr || 'us',
cleaned = phoneNumber.parse(input);
if (cleaned.length === 10) {
if (country == 'us') {
return '1' + cleaned;
}
} else if (country == 'us' && cleaned.length === 11 && cleaned.charAt(0) === '1') {
return cleaned;
} else {
return false;
}
};
att.phoneNumber = phoneNumber;
// attach to window or export with commonJS
if (typeof exports !== 'undefined') {
module.exports = att;
} else {
// make sure we've got an "att" global
root.ATT || (root.ATT = {});
_.extend(root.ATT, att);
}
}).call(this);
// This code was written by Tyler Akins and has been placed in the
// public domain. It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com
var Base64 = (function () {
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var obj = {
/**
* Encodes a string in base64
* @param {String} input The string to encode in base64.
*/
encode: function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
keyStr.charAt(enc3) + keyStr.charAt(enc4);
} while (i < input.length);
return output;
},
/**
* Decodes a base64 string.
* @param {String} input The string to decode.
*/
decode: function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
enc1 = keyStr.indexOf(input.charAt(i++));
enc2 = keyStr.indexOf(input.charAt(i++));
enc3 = keyStr.indexOf(input.charAt(i++));
enc4 = keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
} while (i < input.length);
return output;
}
};
return obj;
})();
html,body{margin:0;padding:0;}
h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;}
table{border-collapse:collapse;border-spacing:0;}
ol,ul{list-style:none;}
q:before,q:after,blockquote:before,blockquote:after{content:"";}
html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
a:focus{outline:thin dotted;}
a:hover,a:active{outline:0;}
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
audio:not([controls]){display:none;}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
sup{top:-0.5em;}
sub{bottom:-0.25em;}
img{border:0;-ms-interpolation-mode:bicubic;}
button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;}
button,input{line-height:normal;*overflow:visible;}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
textarea{overflow:auto;vertical-align:top;}
body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;}
.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;}
.container:after{clear:both;}
.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;}
.container-fluid:after{clear:both;}
.container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;}
.container-fluid>.content{margin-left:240px;}
a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;}
.pull-right{float:right;}
.pull-left{float:left;}
.hide{display:none;}
.show{display:block;}
.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;}
.row:after{clear:both;}
.row>[class*="span"]{display:inline;float:left;margin-left:20px;}
.span1{width:40px;}
.span2{width:100px;}
.span3{width:160px;}
.span4{width:220px;}
.span5{width:280px;}
.span6{width:340px;}
.span7{width:400px;}
.span8{width:460px;}
.span9{width:520px;}
.span10{width:580px;}
.span11{width:640px;}
.span12{width:700px;}
.span13{width:760px;}
.span14{width:820px;}
.span15{width:880px;}
.span16{width:940px;}
.span17{width:1000px;}
.span18{width:1060px;}
.span19{width:1120px;}
.span20{width:1180px;}
.span21{width:1240px;}
.span22{width:1300px;}
.span23{width:1360px;}
.span24{width:1420px;}
.row>.offset1{margin-left:80px;}
.row>.offset2{margin-left:140px;}
.row>.offset3{margin-left:200px;}
.row>.offset4{margin-left:260px;}
.row>.offset5{margin-left:320px;}
.row>.offset6{margin-left:380px;}
.row>.offset7{margin-left:440px;}
.row>.offset8{margin-left:500px;}
.row>.offset9{margin-left:560px;}
.row>.offset10{margin-left:620px;}
.row>.offset11{margin-left:680px;}
.row>.offset12{margin-left:740px;}
.span-one-third{width:300px;}
.span-two-thirds{width:620px;}
.row>.offset-one-third{margin-left:340px;}
.row>.offset-two-thirds{margin-left:660px;}
p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;}
h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;}
h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;}
h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;}
h3,h4,h5,h6{line-height:36px;}
h3{font-size:18px;}h3 small{font-size:14px;}
h4{font-size:16px;}h4 small{font-size:12px;}
h5{font-size:14px;}
h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;}
ul,ol{margin:0 0 18px 25px;}
ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
ul{list-style:disc;}
ol{list-style:decimal;}
li{line-height:18px;color:#808080;}
ul.unstyled{list-style:none;margin-left:0;}
dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;}
dl dt{font-weight:bold;}
dl dd{margin-left:9px;}
hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;}
strong{font-style:inherit;font-weight:bold;}
em{font-style:italic;font-weight:inherit;line-height:inherit;}
.muted{color:#bfbfbf;}
blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;}
blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';}
address{display:block;line-height:18px;margin-bottom:18px;}
code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;}
pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;}
form{margin-bottom:18px;}
fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;}
form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;}
form .clearfix:after{clear:both;}
label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;}
label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;}
form .input{margin-left:150px;}
input[type=checkbox],input[type=radio]{cursor:pointer;}
input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
select{padding:initial;}
input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;}
input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;}
select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;}
select[multiple]{height:inherit;background-color:#ffffff;}
textarea{height:auto;}
.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
:-moz-placeholder{color:#bfbfbf;}
::-webkit-input-placeholder{color:#bfbfbf;}
input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);}
input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);}
input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;}
form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;}
form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;}
form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;}
form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;}
form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;}
form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;}
form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;}
form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;}
.input-mini,input.mini,textarea.mini,select.mini{width:60px;}
.input-small,input.small,textarea.small,select.small{width:90px;}
.input-medium,input.medium,textarea.medium,select.medium{width:150px;}
.input-large,input.large,textarea.large,select.large{width:210px;}
.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;}
.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;}
textarea.xxlarge{overflow-y:auto;}
input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;}
input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;}
input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;}
input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;}
input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;}
input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;}
input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;}
input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;}
input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;}
input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;}
input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;}
input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;}
input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;}
input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;}
input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;}
input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;}
input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;}
.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;}
.help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;}
.help-inline{padding-left:5px;*position:relative;*top:-5px;}
.help-block{display:block;max-width:600px;}
.inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;}
.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;}
.input-prepend .add-on{*margin-top:1px;}
.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;}
.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;}
.inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;}
.inputs-list label small{font-size:11px;font-weight:normal;}
.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;}
.inputs-list:first-child{padding-top:6px;}
.inputs-list li+li{padding-top:2px;}
.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;}
.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;}
.form-stacked legend{padding-left:0;}
.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;}
.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;}
.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;}
.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;}
.form-stacked .actions{margin-left:-20px;padding-left:20px;}
table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;}
table th{padding-top:9px;font-weight:bold;vertical-align:middle;}
table td{vertical-align:top;border-top:1px solid #ddd;}
table tbody th{border-top:1px solid #ddd;vertical-align:top;}
.condensed-table th,.condensed-table td{padding:5px 5px 4px;}
.bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;}
.bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
.bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
.bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
.bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
table .span1{width:20px;}
table .span2{width:60px;}
table .span3{width:100px;}
table .span4{width:140px;}
table .span5{width:180px;}
table .span6{width:220px;}
table .span7{width:260px;}
table .span8{width:300px;}
table .span9{width:340px;}
table .span10{width:380px;}
table .span11{width:420px;}
table .span12{width:460px;}
table .span13{width:500px;}
table .span14{width:540px;}
table .span15{width:580px;}
table .span16{width:620px;}
.zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
.zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;}
table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;}
table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);}
table .header:hover:after{visibility:visible;}
table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
table .blue{color:#049cdb;border-bottom-color:#049cdb;}
table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;}
table .green{color:#46a546;border-bottom-color:#46a546;}
table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;}
table .red{color:#9d261d;border-bottom-color:#9d261d;}
table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;}
table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;}
table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;}
table .orange{color:#f89406;border-bottom-color:#f89406;}
table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;}
table .purple{color:#7a43b6;border-bottom-color:#7a43b6;}
table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;}
.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
.topbar h3 a:hover,.topbar .brand:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;}
.topbar h3{position:relative;}
.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;}
.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;}
.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;}
.topbar form.pull-right{float:right;}
.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;}
.topbar input::-webkit-input-placeholder{color:#e6e6e6;}
.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;}
.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);}
.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;}
.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;}
.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);}
.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;}
.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);}
.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);}
.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;}
.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;}
.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;}
.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;}
li.menu,.dropdown{position:relative;}
a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"&darr;";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;}
.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;}
.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;}
.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover,.topbar .dropdown-menu a.hover,.dropdown-menu a.hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);}
.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;}
.tabs,.pills{margin:0 0 18px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;}
.tabs:after,.pills:after{clear:both;}
.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;}
.tabs{border-color:#ddd;border-style:solid;border-width:0 0 1px;}.tabs>li{position:relative;margin-bottom:-1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:34px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;}
.tabs .active>a,.tabs .active>a:hover{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;}
.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;}
.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;}
.pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;}
.pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;}
.pills-vertical>li{float:none;}
.tab-content>.tab-pane,.pill-content>.pill-pane,.tab-content>div,.pill-content>div{display:none;}
.tab-content>.active,.pill-content>.active{display:block;}
.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;}
.breadcrumb .divider{padding:0 5px;color:#bfbfbf;}
.breadcrumb .active a{color:#404040;}
.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;}
.hero-unit p{font-size:18px;font-weight:200;line-height:27px;}
footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;}
.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;}
.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;}
.btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;}
.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;}
.btn:focus{outline:1px dotted #666;}
.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
.btn.active,.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);}
.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
.btn.small{padding:7px 9px 7px;font-size:11px;}
:root .alert-message,:root .btn{border-radius:0 \0;}
button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;}
.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;}
.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{margin-top:1px;*margin-top:0;}
.alert-message a{font-weight:bold;color:#404040;}
.alert-message.danger p a,.alert-message.error p a,.alert-message.success p a,.alert-message.info p a{color:#ffffff;}
.alert-message h5{line-height:18px;}
.alert-message p{margin-bottom:0;}
.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;}
.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);}
.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;}
.alert-message.block-message ul{margin-bottom:0;}
.alert-message.block-message li{color:#404040;}
.alert-message.block-message .alert-actions{margin-top:5px;}
.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;}
.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;}
.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;}
.alert-message.block-message.danger p a,.alert-message.block-message.error p a,.alert-message.block-message.success p a,.alert-message.block-message.info p a{color:#404040;}
.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
.pagination li{display:inline;}
.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;}
.pagination a:hover,.pagination .active a{background-color:#c7eefe;}
.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;}
.pagination .next a{border:0;}
.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;}
.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;}
.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
.modal.fade.in{top:50%;}
.modal-header{border-bottom:1px solid #eee;padding:5px 15px;}
.modal-body{padding:15px;}
.modal-body form{margin-bottom:0;}
.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;}
.modal-footer:after{clear:both;}
.modal-footer .btn{float:right;margin-left:5px;}
.modal .popover,.modal .twipsy{z-index:12000;}
.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.twipsy-arrow{position:absolute;width:0;height:0;}
.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.popover .arrow{position:absolute;width:0;height:0;}
.popover .inner{background:#000000;background:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;}
.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;}
.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
.label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;}
.label.warning{background-color:#f89406;}
.label.success{background-color:#46a546;}
.label.notice{background-color:#62cffc;}
.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;}
.media-grid:after{clear:both;}
.media-grid li{display:inline;}
.media-grid a{float:left;padding:4px;margin:0 0 18px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;}
.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
\ No newline at end of file
body {
position: relative;
-webkit-transition: top 1s; }
body.candybarVisible {
top: 100px; }
#callStatus {
position: fixed;
top: -120px;
left: 0px;
-webkit-transition: background-color 1s;
-webkit-transition: top 1s;
width: 100%;
height: 80px;
padding: 10px;
z-index: 1000; }
#callStatus.visible {
top: 0px; }
#callStatus.havatar .callActions {
left: 100px; }
#callStatus.havatar .caller {
margin-left: 90px; }
#callStatus.incoming {
background-color: #41ade0;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #63cfff;
border-bottom: 2px solid #00699c;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.incoming .callTime {
display: none; }
#callStatus.incoming .callerName:before {
content: "Incoming: "; }
#callStatus.waiting {
background-color: #f47820;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #ff9a42;
border-bottom: 2px solid #b03400;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.waiting .spinner div {
background-color: white; }
#callStatus.calling {
background-color: #41ade0;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #63cfff;
border-bottom: 2px solid #00699c;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.calling .callTime {
display: none; }
#callStatus.calling .callerName:before {
content: "Calling: "; }
#callStatus.active {
background-color: #77a803;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #99ca25;
border-bottom: 2px solid #336400;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.active .callerName:before {
content: "On Call: "; }
#callStatus.inactive {
background-color: white;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid white;
border-bottom: 2px solid #bbbbbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.remote {
background-color: #74e0ff;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #96ffff;
border-bottom: 2px solid #309cbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.remote .callTime {
display: none; }
#callStatus.ending {
background-color: #bbbbbb;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #dddddd;
border-bottom: 2px solid #777777;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.ending .callerName:before {
content: "Call ending with: "; }
#callStatus .callActions {
position: absolute;
left: 10px;
top: 50px;
display: block;
width: 100%; }
#callStatus nav {
float: left; }
#callStatus button {
min-width: auto;
background-color: rgba(255, 255, 255, 0.3);
background-image: rgba(255, 255, 255, 0.5);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.5)), color-stop(1, rgba(0, 0, 0, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 1px solid rgba(255, 255, 255, 0.6);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
border-left: 1px solid rgba(255, 255, 255, 0.2);
border-right: 1px solid rgba(255, 255, 255, 0.2);
width: 100px;
margin-right: 10px;
font-size: 16px;
color: rgba(0, 0, 0, 0.75);
text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(255, 255, 255, 0.5),Direction=135,Strength=0);
float: left;
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.3),Direction=135,Strength=3); }
#callStatus button:hover {
background-color: rgba(255, 255, 255, 0.4); }
#callStatus button:active {
box-shadow: inset rgba(0, 0, 0, 0.2) 0 1px 3px;
-moz-box-shadow: inset rgba(0, 0, 0, 0.2) 0 1px 3px;
-webkit-box-shadow: inset rgba(0, 0, 0, 0.2) 0 1px 3px;
padding-top: 11px;
padding-bottom: 9px;
border-bottom: 1px solid white;
border-top: 1px solid rgba(0, 0, 0, 0.2); }
#callStatus .callerAvatar {
float: left;
width: 65px;
height: 65px;
border: 5px solid #eeeeee;
margin-right: 10px; }
#callStatus .callerName, #callStatus .callTime {
font-weight: bold;
color: white;
text-shadow: rgba(0, 0, 0, 0.7) 0 1px 0px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.7),Direction=135,Strength=0);
line-height: 1; }
#callStatus .caller {
margin-top: 0px;
margin-right: 30px;
margin-left: 0px;
font-size: 20px;
padding-bottom: 0px;
border-bottom: 2px groove rgba(255, 255, 255, 0.4); }
#callStatus .callerName {
display: inline; }
#callStatus .callerNumber {
display: inline;
margin-left: 10px; }
#callStatus .callTime {
position: absolute;
top: 12px;
right: 40px;
font-size: 20px;
margin: 0; }
/* global */
(function (window) {
var jade=function(exports){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==Object.prototype.toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(key);return arr}),exports.merge=function merge(a,b){var ac=a["class"],bc=b["class"];if(ac||bc)ac=ac||[],bc=bc||[],Array.isArray(ac)||(ac=[ac]),Array.isArray(bc)||(bc=[bc]),ac=ac.filter(nulls),bc=bc.filter(nulls),a["class"]=ac.concat(bc).join(" ");for(var key in b)key!="class"&&(a[key]=b[key]);return a};function nulls(val){return val!=null}return exports.attrs=function attrs(obj,escaped){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];"boolean"==typeof val||null==val?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):0==key.indexOf("data")&&"string"!=typeof val?buf.push(key+"='"+JSON.stringify(val)+"'"):"class"==key&&Array.isArray(val)?buf.push(key+'="'+exports.escape(val.join(" "))+'"'):escaped&&escaped[key]?buf.push(key+'="'+exports.escape(val)+'"'):buf.push(key+'="'+val+'"')}}return buf.join(" ")},exports.escape=function escape(html){return String(html).replace(/&(?!(\w+|\#\d+);)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err},exports}({});
var template = function anonymous(locals, attrs, escape, rethrow, merge) {
attrs = attrs || jade.attrs;
escape = escape || jade.escape;
rethrow = rethrow || jade.rethrow;
merge = merge || jade.merge;
var buf = [];
with (locals || {}) {
var interp;
var __indent = [];
buf.push('\n<div id="callStatus"><img');
buf.push(attrs({
src: locals.picUrl,
"class": "callerAvatar"
}, {
src: true
}));
buf.push('/>\n <h1 class="caller"><span class="callerName">');
var __val__ = locals.caller;
buf.push(escape(null == __val__ ? "" : __val__));
buf.push('</span><span class="callerNumber">');
var __val__ = locals.caller;
buf.push(escape(null == __val__ ? "" : __val__));
buf.push('</span></h1>\n <h2 class="callTime">0:00:00</h2>\n <div class="callActions"></div>\n</div>');
}
return buf.join("");
}
var phoney = window.ATT && window.ATT.phoneNumber || window.phoney;
var CandyBar = function (options) {
var spec = options || {};
this.states = {
incoming: {
buttons: [
{
cls: 'answer',
label: 'Answer'
},
{
cls: 'ignore',
label: 'Ignore'
}
]
},
calling: {
buttons: [{
cls: 'cancel',
label: 'Cancel'
}]
},
active: {
buttons: [{
cls: 'end',
label: 'End Call'
}],
timer: true
},
inactive: {
buttons: [],
clearUser: true,
hidden: true
},
ending: {
buttons: []
},
waiting: {
buttons: []
}
};
this.config = {
defaultName: '',
defaultNumber: 'Unknown Number'
};
this.registerPhoneHandlers();
};
CandyBar.prototype.render = function () {
if (!this.dom) {
this.dom = this.domify(template(this));
this.addButtonHandlers();
document.body.insertBefore(this.dom, document.body.firstChild);
} else {
this.dom.innerHTML = this.domify(template(this)).innerHTML;
}
this.setState('inactive');
return this.dom;
};
CandyBar.prototype.addButtonHandlers = function () {
var self = this;
this.dom.addEventListener('click', function (e) {
var target = e.target;
if (target.tagName === 'BUTTON') {
if (self[target.className]) {
self[target.className]();
}
return false;
}
}, true);
};
CandyBar.prototype.getStates = function () {
return Object.keys(this.states);
};
CandyBar.prototype.setState = function (state) {
if (!this.dom) return this;
var buttons = this.dom.querySelectorAll('button'),
callActionsEl = this.dom.querySelector('.callActions'),
self = this,
stateDef = this.states[state],
forEach = Array.prototype.forEach;
if (stateDef) {
// set proper class on bar itself
this.getStates().forEach(function (cls) {
self.dom.classList.remove(cls);
});
self.dom.classList.add(state);
// set/remove 'hidden' class on bar itself
if (stateDef.hidden) {
self.dom.classList.remove('visible');
document.body.classList.remove('candybarVisible');
} else {
self.dom.classList.add('visible');
document.body.classList.add('candybarVisible');
}
// remove all the buttons
forEach.call(buttons, function (button) {
button.parentElement.removeChild(button);
});
// add buttons
stateDef.buttons.forEach(function (button) {
callActionsEl.appendChild(self.domify('<button class="' + button.cls + '">' + button.label + '</button>'));
});
// start/stop timer
if (stateDef.timer) {
if (this.timerStopped) {
this.startTimer();
}
} else {
this.resetTimer();
}
// reset user if relevant
if (stateDef.clearUser) {
this.clearUser();
}
} else {
throw new Error('Invalid value for CandyBar state. Valid values are: ' + this.getStates().join(', '));
}
return this;
};
CandyBar.prototype.endGently = function (delay) {
var self = this;
this.setState('ending');
setTimeout(function () {
self.dom.classList.remove('visible');
setTimeout(function () {
self.setState('inactive');
self.clearUser();
}, 1000);
}, 1000);
return this;
};
CandyBar.prototype.setImageUrl = function (url) {
this.attachImageDom(!!url);
this.imageDom.src = url;
this.dom.classList[!!url ? 'add' : 'remove']('havatar');
};
CandyBar.prototype.attachImageDom = function (bool) {
if (!this.imageDom) {
this.imageDom = this.dom.querySelector('.callerAvatar');
}
if (bool && !this.imageDom.parentElement) {
this.dom.insertBefore(this.imageDom, this.dom.firstChild);
} else if (this.imageDom.parentElement) {
this.imageDom.parentElement.removeChild(this.imageDom);
}
return this.imageDom;
};
CandyBar.prototype.registerPhoneHandlers = function () {
var self = this;
self.end = function () {
if (self.call) {
self.call.hangup && self.call.hangup();
self.call && self.call.ended && self.call.ended();
delete self.call;
}
};
self.answer = function () {
if (self.call) {
self.call.answer();
}
};
self.cancel = function () {
if (self.call) {
self.call.hangup();
}
};
};
CandyBar.prototype.getUser = function () {
var user = this.user || {},
self = this;
return {
picUrl: user.picUrl,
name: (user.name && user.name) || this.config.defaultName,
number: function () {
if (user.number && user.number !== self.config.defaultNumber) {
if (phoney) {
return phoney.stringify(user.number);
} else {
return escape(user.number);
}
} else {
return self.config.defaultNumber;
}
}()
};
};
CandyBar.prototype.setUser = function (details) {
this.user = details;
if (!this.dom) return;
var user = this.getUser();
this.dom.querySelector('.callerNumber').innerHTML = user.number;
this.dom.querySelector('.callerName').innerHTML = user.name;
this.setImageUrl(user.picUrl);
return this;
};
CandyBar.prototype.clearUser = function () {
this.setUser({
picUrl: '',
name: '',
number: ''
});
return this;
};
CandyBar.prototype.domify = function (str) {
var div = document.createElement('div');
div.innerHTML = str;
return div.firstElementChild;
};
CandyBar.prototype.startTimer = function () {
this.timerStartTime = Date.now();
this.timerStopped = false;
this.updateTimer();
return this;
};
CandyBar.prototype.stopTimer = function () {
this.timerStopped = true;
return this;
};
CandyBar.prototype.resetTimer = function () {
this.timerStopped = true;
this.setTimeInDom('0:00:00');
return this;
};
CandyBar.prototype.updateTimer = function () {
if (this.timerStopped) return;
var diff = Date.now() - this.timerStartTime,
s = Math.floor(diff / 1000) % 60,
min = Math.floor((diff / 1000) / 60) % 60,
hr = Math.floor(((diff / 1000) / 60) / 60) % 60,
time = [hr, this.zeroPad(min), this.zeroPad(s)].join(':');
if (this.time !== time) {
this.time = time;
this.setTimeInDom(time);
}
setTimeout(this.updateTimer.bind(this), 100);
};
CandyBar.prototype.setTimeInDom = function (timeString) {
if (!this.dom) return;
this.dom.querySelector('.callTime').innerHTML = timeString;
};
CandyBar.prototype.zeroPad = function (num) {
return ((num + '').length === 1) ? '0' + num : num;
};
window.CandyBar = CandyBar;
})(window);
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section {
display: block; }
audio, canvas, video {
display: inline-block;
*display: inline;
*zoom: 1; }
audio:not([controls]) {
display: none; }
[hidden] {
display: none; }
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%; }
html, button, input, select, textarea {
font-family: sans-serif;
color: #222222; }
body {
margin: 0;
font-size: 1em;
line-height: 1.4; }
a {
color: #0000ee; }
a:visited {
color: #551a8b; }
a:hover {
color: #0066ee; }
a:focus {
outline: thin dotted; }
a:hover, a:active {
outline: 0; }
abbr[title] {
border-bottom: 1px dotted; }
b, strong {
font-weight: bold; }
blockquote {
margin: 1em 40px; }
dfn {
font-style: italic; }
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #cccccc;
margin: 1em 0;
padding: 0; }
ins {
background: #ffff99;
color: black;
text-decoration: none; }
mark {
background: yellow;
color: black;
font-style: italic;
font-weight: bold; }
pre, code, kbd, samp {
font-family: monospace, serif;
_font-family: "courier new", monospace;
font-size: 1em; }
pre {
white-space: pre-wrap;
word-wrap: break-word; }
q {
quotes: none; }
q:before, q:after {
content: none; }
small {
font-size: 85%; }
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline; }
sup {
top: -0.5em; }
sub {
bottom: -0.25em; }
ul, ol {
margin: 1em 0;
padding: 0 0 0 40px; }
dd {
margin: 0 0 0 40px; }
nav ul, nav ol {
list-style: none;
list-style-image: none;
margin: 0;
padding: 0; }
img {
border: 0;
-ms-interpolation-mode: bicubic;
vertical-align: middle; }
svg:not(:root) {
overflow: hidden; }
figure {
margin: 0; }
form {
margin: 0; }
fieldset {
border: 0;
margin: 0;
padding: 0; }
label {
cursor: pointer; }
legend {
border: 0;
*margin-left: -7px;
padding: 0;
white-space: normal; }
button, input, select, textarea {
font-size: 100%;
margin: 0;
vertical-align: baseline;
*vertical-align: middle; }
button, input {
line-height: normal; }
button, input[type="button"], input[type="reset"], input[type="submit"] {
cursor: pointer;
-webkit-appearance: button;
*overflow: visible; }
button[disabled], input[disabled] {
cursor: default; }
input[type="checkbox"], input[type="radio"] {
box-sizing: border-box;
padding: 0;
*width: 13px;
*height: 13px; }
input[type="search"] {
-webkit-appearance: textfield;
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner {
border: 0;
padding: 0; }
textarea {
overflow: auto;
vertical-align: top;
resize: vertical; }
input:invalid, textarea:invalid {
background-color: #f0dddd; }
table {
border-collapse: collapse;
border-spacing: 0; }
td {
vertical-align: top; }
.clearfix:before, .clearfix:after {
content: "\0020";
display: block;
height: 0;
visibility: hidden; }
.clearfix:after {
clear: both; }
.clearfix {
zoom: 1; }
body {
font-family: sans-serif; }
.dialerwrapper {
width: 280px;
height: 350px;
background-color: #cccccc; }
.dialerwrapper .numberEntry {
width: 100%;
height: 15%;
text-align: center;
text-shadow: 1px 1px 0 #555555;
font-size: 2em;
color: white;
outline: 0;
border: 0;
background: #959595;
background: -moz-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(50%, #959595), color-stop(50%, #767676), color-stop(100%, #565656));
background: -webkit-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: -o-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: -ms-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
padding: 0;
margin: 0;
border-top: 1px solid #777777; }
.dialerwrapper .numberEntry:focus {
outline: 0; }
.dialerwrapper #dialpad {
width: 100%;
height: 100%;
list-style: none;
margin: 0;
padding: 0; }
.dialerwrapper #dialpad li {
width: inherit;
height: 21.25%;
white-space: nowrap;
font-size: 0;
margin: 0;
padding: 0; }
.dialerwrapper #dialpad button {
display: inline-block;
vertical-align: top;
width: 33.3%;
height: 100%;
background: #eeeeee;
font-size: 20px;
text-align: center;
outline: 0;
border-top: 1px solid white;
border-right: 1px solid white;
border-bottom: 1px solid #c7c7c7;
border-left: 1px solid #c7c7c7;
border-radius: 0;
margin: 0;
padding: 0;
color: #555555;
vertical-align: middle; }
.dialerwrapper #dialpad button:hover {
background-color: #cecece; }
.dialerwrapper #dialpad button p {
width: 100%;
display: inline-table;
font-size: 1.2em;
font-weight: 700;
margin: 0; }
.dialerwrapper #dialpad button div {
text-transform: uppercase;
font-size: 0.6em; }
.dialerwrapper #actions nav {
position: relative; }
.dialerwrapper #actions a {
width: 33.3%; }
.close_dialer {
background-color: #555555;
height: 25px;
width: 100%;
display: block;
position: relative; }
.cancel {
position: absolute;
top: 4px;
right: 10px;
padding: 3px;
line-height: 9px;
display: block;
color: white;
border: 1px solid white;
background-color: #666666; }
.cancel:hover {
color: white;
background-color: #bb0000;
cursor: pointer; }
.call {
cursor: hand;
}
#screen {
overflow: hidden;
padding: 15px 15px 0 15px;
z-index: 99; }
#screen header {
background-color: #555555; }
#screen header, #screen footer {
height: 40px;
width: 100%;
padding: 10px 0;
display: block; }
#screen header nav, #screen footer nav {
width: 100%;
display: table;
margin: 0 auto;
border: 1px solid #222222;
border-radius: 10px; }
#screen header nav a, #screen footer nav a {
display: table-cell;
width: 50%;
color: #eeeeee;
font-weight: 900;
text-decoration: none;
text-align: center;
text-shadow: 1px 1px 0 #222222;
letter-spacing: 0.3px;
padding: 10px 0;
border-left: 1px solid #222222;
border-right: 1px solid #777777;
background: #888888;
background: -moz-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #888888) repeat-x, color-stop(100%, #555555)), #888888;
background: -webkit-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: -o-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: -ms-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
-moz-transition: background 1s linear;
-webkit-transition: all 0.3s linear 0;
-moz-transition-property: all;
-moz-transition-duration: 1s;
-moz-transition-timing-function: linear;
-moz-transition-delay: linear; }
#screen header nav a:first-child, #screen footer nav a:first-child {
border-left: 0;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px; }
#screen header nav a:last-child, #screen footer nav a:last-child {
margin: 0;
border-right: 0;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px; }
#screen header nav a:hover, #screen footer nav a:hover {
background-position: 0 15px;
cursor: pointer; }
#login #screen {
padding: 0;
margin: 0 10px 10px 10px;
position: relative; }
#login #screen:after {
content: "";
width: 280px;
height: 1px;
display: block;
position: absolute;
left: 1px;
bottom: 0px;
background: #cccccc;
z-index: 200000000; }
#login footer {
display: none; }
#login .dialerwrapper {
background-color: white;
border-right: 1px solid #cccccc; }
#login .numberEntry {
border-left: 0px solid #dddddd;
margin-left: 1px;
margin-right: 2px;
padding-top: 3px;
border-top: 1px solid #dddddd;
background-image: #eeeeee;
background-image: -moz-linear-gradient(top, #eeeeee, #dddddd);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eeeeee), color-stop(1, #dddddd));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
color: #555555;
text-shadow: white 0 1px 0px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=white,Direction=135,Strength=0); }
/* global */
(function () {
/*
WildEmitter.js is a slim little event emitter largely based on @visionmedia's Emitter from UI Kit.
I wanted it standalone.
I also wanted support for wildcard emitters. Like:
emitter.on('*', function (eventName, other, event, payloads) {
});
emitter.on('somenamespace*', function (eventName, payloads) {
});
Functions triggered by wildcard registered events also get the event name as the first argument.
*/
function WildEmitter() {
this.callbacks = {};
}
// Listen on the given `event` with `fn`. Store a group name if present.
WildEmitter.prototype.on = function (event, groupName, fn) {
var hasGroup = (arguments.length === 3),
group = hasGroup ? arguments[1] : undefined,
func = hasGroup ? arguments[2] : arguments[1];
func._groupName = group;
(this.callbacks[event] = this.callbacks[event] || []).push(func);
return this;
};
// Adds an `event` listener that will be invoked a single
// time then automatically removed.
WildEmitter.prototype.once = function (event, fn) {
var self = this;
function on() {
self.off(event, on);
fn.apply(this, arguments);
}
this.on(event, on);
return this;
};
// Unbinds an entire group
WildEmitter.prototype.releaseGroup = function (groupName) {
var item, i, len, handlers;
for (item in this.callbacks) {
handlers = this.callbacks[item];
for (i = 0, len = handlers.length; i < len; i++) {
if (handlers[i]._groupName === groupName) {
//console.log('removing');
// remove it and shorten the array we're looping through
handlers.splice(i, 1);
i--;
len--;
}
}
}
return this;
};
// Remove the given callback for `event` or all
// registered callbacks.
WildEmitter.prototype.off = function (event, fn) {
var callbacks = this.callbacks[event],
i;
if (!callbacks) return this;
// remove all handlers
if (arguments.length === 1) {
delete this.callbacks[event];
return this;
}
// remove specific handler
i = callbacks.indexOf(fn);
callbacks.splice(i, 1);
return this;
};
// Emit `event` with the given args.
// also calls any `*` handlers
WildEmitter.prototype.emit = function (event) {
var args = [].slice.call(arguments, 1),
callbacks = this.callbacks[event],
specialCallbacks = this.getWildcardCallbacks(event),
i,
len,
item;
if (callbacks) {
for (i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
if (specialCallbacks) {
for (i = 0, len = specialCallbacks.length; i < len; ++i) {
specialCallbacks[i].apply(this, [event].concat(args));
}
}
return this;
};
// Helper for for finding special wildcard event handlers that match the event
WildEmitter.prototype.getWildcardCallbacks = function (eventName) {
var item,
split,
result = [];
for (item in this.callbacks) {
split = item.split('*');
if (item === '*' || (split.length === 2 && eventName.slice(0, split[1].length) === split[1])) {
result = result.concat(this.callbacks[item]);
}
}
return result;
};
var jade=function(exports){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==Object.prototype.toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(key);return arr}),exports.merge=function merge(a,b){var ac=a["class"],bc=b["class"];if(ac||bc)ac=ac||[],bc=bc||[],Array.isArray(ac)||(ac=[ac]),Array.isArray(bc)||(bc=[bc]),ac=ac.filter(nulls),bc=bc.filter(nulls),a["class"]=ac.concat(bc).join(" ");for(var key in b)key!="class"&&(a[key]=b[key]);return a};function nulls(val){return val!=null}return exports.attrs=function attrs(obj,escaped){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i<len;++i){var key=keys[i],val=obj[key];"boolean"==typeof val||null==val?val&&(terse?buf.push(key):buf.push(key+'="'+key+'"')):0==key.indexOf("data")&&"string"!=typeof val?buf.push(key+"='"+JSON.stringify(val)+"'"):"class"==key&&Array.isArray(val)?buf.push(key+'="'+exports.escape(val.join(" "))+'"'):escaped&&escaped[key]?buf.push(key+'="'+exports.escape(val)+'"'):buf.push(key+'="'+val+'"')}}return buf.join(" ")},exports.escape=function escape(html){return String(html).replace(/&(?!(\w+|\#\d+);)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err},exports}({});
var template = function anonymous(locals, attrs, escape, rethrow, merge) {
attrs = attrs || jade.attrs;
escape = escape || jade.escape;
rethrow = rethrow || jade.rethrow;
merge = merge || jade.merge;
var buf = [];
with (locals || {}) {
var interp;
var __indent = [];
buf.push('\n<div id="screen">\n <div class="dialerwrapper">\n <div class="numberEntry"></div>\n <ul id="dialpad">\n <li>\n <button data-value="1">\n <p>1</p>\n <div>&nbsp;</div>\n </button>\n <button data-value="2">\n <p>2</p>\n <div>abc</div>\n </button>\n <button data-value="3">\n <p>3</p>\n <div>def</div>\n </button>\n </li>\n <li>\n <button data-value="4">\n <p>4</p>\n <div>ghi</div>\n </button>\n <button data-value="5">\n <p>5</p>\n <div>jki</div>\n </button>\n <button data-value="6">\n <p>6</p>\n <div>mno</div>\n </button>\n </li>\n <li>\n <button data-value="7">\n <p>7</p>\n <div>pqrs</div>\n </button>\n <button data-value="8">\n <p>8</p>\n <div>tuv</div>\n </button>\n <button data-value="9">\n <p>9</p>\n <div>wxyz</div>\n </button>\n </li>\n <li>\n <button data-value="#">\n <p>#</p>\n <div>&nbsp;</div>\n </button>\n <button data-value="0">\n <p>0</p>\n <div>abc</div>\n </button>\n <button data-value="*">\n <p>*</p>\n <div></div>\n </button>\n </li>\n </ul>\n </div>');
if (locals.footer) {
buf.push('\n <footer>\n <nav id="actions"><a id="call_button" class="call">Call</a></nav>\n </footer>');
}
buf.push("\n</div>");
}
return buf.join("");
}
var phoney = window.ATT && window.ATT.phoneNumber || window.phoney;
var Dialpad = function (spec) {
var config = spec || {},
availableCallbacks = {
'onHide': 'hide',
'onPress': 'press',
'onCallableNumber': 'callableNumber',
'onCall': 'call'
};
// inherit wildemitter properties
WildEmitter.call(this);
this.number = '';
this.footer = true;
// register handlers passed in on init
for (var item in availableCallbacks) {
if (config[item]) this.on(availableCallbacks[item], config[item]);
}
};
Dialpad.prototype = new WildEmitter();
Dialpad.prototype.render = function (container) {
this.dom = this.domify(template(this));
this.addClickHandlers();
this.numberField = this.dom.querySelector('.numberEntry');
this.clear();
this.addDocListener();
return this.dom;
};
Dialpad.prototype.hide = function () {
this.removeDocListener();
this.dom.parentElement.removeChild(this.dom);
this.emit('hide');
};
Dialpad.prototype.addDocListener = function () {
var self = this;
this.boundKeyHandler = function () {
self.handleKeyDown.apply(self, arguments);
};
document.addEventListener('keydown', this.boundKeyHandler, true);
};
Dialpad.prototype.removeDocListener = function () {
document.removeEventListener('keydown', this.boundKeyHandler, true);
};
Dialpad.prototype.addClickHandlers = function () {
var self = this,
buttons = this.dom.querySelectorAll('button'),
callButton = this.dom.querySelector('.call');
// for button handlers
Array.prototype.forEach.call(buttons, function (button) {
button.addEventListener('click', function (e) {
var data = this.attributes['data-value'],
value = data && data.nodeValue;
if (value == 'del') {
self.removeLastNumber();
} else {
self.addNumber(value);
}
return false;
}, true);
});
if (callButton) {
callButton.addEventListener('click', function () {
self.handleCallClick.apply(self, arguments);
}, false);
}
};
Dialpad.prototype.handleKeyDown = function (e) {
var number,
keyCode = e.which;
// only handle if Dialpad is showing
if (keyCode >= 48 && keyCode <= 57) {
number = keyCode - 48;
this.addNumber(number + '');
}
if (keyCode === 8) {
this.removeLastNumber();
e.preventDefault();
}
if (keyCode === 13) {
this.handleCallClick(e);
}
};
Dialpad.prototype.getNumber = function () {
return this.number;
};
Dialpad.prototype.setCallLabel = function (label) {
var callButton = this.dom.querySelector('.call');
if (callButton) {
callButton.innerHTML = label;
}
};
Dialpad.prototype.getCallLabel = function () {
var callButton = this.dom.querySelector('.call');
return callButton.innerHTML;
};
Dialpad.prototype.setNumber = function (number) {
var newNumber = phoney.parse(number),
oldNumber = this.number,
callable = phoney.getCallable(newNumber);
this.number = newNumber;
this.numberField.innerHTML = phoney.stringify(this.number);
if (callable) {
this.emit('callableNumber', callable);
}
};
Dialpad.prototype.addNumber = function (number) {
var newNumber = (this.getNumber() + '') + number;
this.emit('press', number);
this.setNumber(newNumber);
};
Dialpad.prototype.removeLastNumber = function () {
this.setNumber(this.getNumber().slice(0, -1));
this.emit('press', 'del');
};
Dialpad.prototype.clear = function () {
this.setNumber('');
};
Dialpad.prototype.domify = function (str) {
var div = document.createElement('div');
div.innerHTML = str;
return div.firstElementChild;
};
Dialpad.prototype.handleCallClick = function (e) {
e.preventDefault();
this.emit('call', this.number, !!phoney.getCallable(this.number));
return false;
};
// attach to window or export with commonJS
if (typeof module !== "undefined") {
module.exports = Dialpad;
} else if (typeof define === "function" && define.amd) {
define(Dialpad);
} else {
window.Dialpad = Dialpad;
}
})(window);
<html>
<head>
<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="dialpad.css">
<link rel="stylesheet" href="candybar.css">
<script src="jquery_1_4_2.js"></script>
<script src="md5.js"></script>
<script src="base64.js"></script>
<script src="strophe.js"></script>
<script src="strophe-openfire.js"></script>
<script src="rayo-plugin.js"></script>
<script src="candybar.js"></script>
<script src="att.phonenumber.js"></script>
<script src="dialpad.js"></script>
<script>
var avatar = 'unknown.jpg';
var callerId = "unknown";
var domain = "81.201.82.25";
var prefix = "sip:";
var handsetOffhook = false;
var ringtone;
window.dialer = new Dialpad({
onPress: function (key) {
console.log('a key was pressed', key);
if (window.candybar.call) window.candybar.call.digit(key);
},
onCallableNumber: function (number) {
console.log('we have a number that seems callable', number);
//makeCall(number);
},
onHide: function () {
console.log('removed it');
},
onCall: function (number) {
if (window.dialer.getCallLabel() == "Call") {
console.log('The call button was pressed', number);
makeCall(number);
} else if (window.dialer.getCallLabel() == "Hangup") {
window.candybar.call.hangup();
} else {
window.candybar.call.answer();
}
}
});
window.candybar = new CandyBar();
$(document).ready(function()
{
document.getElementById("dialpadDiv").insertBefore(window.dialer.render());
window.candybar.render();
window.candybar.call = null;
if (urlParam("prefix")) prefix = urlParam("prefix");
if (urlParam("domain")) domain = urlParam("domain");
if (domain == "81.201.82.25" && prefix == "sip:") prefix = "sip:883510";
if (prefix == "tel:") domain = "";
var iNum = urlParam("inum");
if (!iNum)
{
iNum = Math.random().toString(36).substr(2,9);
}
window.connection = new Openfire.Connection(window.location.protocol + '//' + window.location.host + '/http-bind/');
window.connection.resource = iNum;
window.connection.addHandler(handlePresence, null,"presence", null, null, null);
window.connection.connect(window.location.hostname, null, function (status)
{
//console.log("XMPPConnection.connect");
//console.log(status);
if (status === Strophe.Status.CONNECTED)
{
$("#status").html("Ready");
setPresence();
$(window).unload( function() {
onhook();
window.connection.disconnect();
});
setPresence();
getContacts();
offhook();
}
});
var destination = urlParam("destination");
if (destination)
{
makeCall(destination);
}
})
function setPresence(chat)
{
//console.log("setPresence");
if (window.connection)
{
var presence = $pres();
if (chat) presence.c('show').t(chat).up();
window.connection.send(presence);
}
}
function offhook()
{
console.log("offhook()");
if (window.connection)
{
window.connection.rayo.offhook(
{
codec: "OPUS",
stereo: "0",
onReady: function() {
console.log('Handset is off hook');
$("#status").html("Off Hook");
handsetOffhook = true;
},
onUnready: function() {
console.log('Handset is on hook');
$("#status").html("On Hook");
handsetOffhook = false;
},
onEnd: function() {
console.log('Handset is disconnected');
$("#status").html("On Hook");
handsetOffhook = false;
},
onError: function(e) {
console.error(e);
}
});
}
}
function toggleHook()
{
console.log("onhook()");
if (window.connection)
{
if (handsetOffhook)
window.connection.rayo.onhook();
else
offhook()
}
}
function handlePresence(presence)
{
//console.log("handlePresence");
//console.log(presence);
var from = $(presence).attr('from');
var iNum = Strophe.getResourceFromJid(from);
var xquery = presence.getElementsByTagName("x");
if (xquery.length == 0)
{
var type = $(presence).attr('type');
if (type == "unavailable")
{
} else {
//var status = $(presence).find('status').text();
}
}
return true;
};
function getContacts ()
{
//console.log("getContacts ");
};
function makeCall(destination)
{
console.log("makeCall " + destination);
var sipUri = prefix + destination + "@" + domain
if (prefix == "tel:") sipUri = prefix + destination
window.candybar.call = window.connection.rayo.dial(
{
from: 'unknown',
to: sipUri,
onEnd: function() {
//console.log('ended...............');
window.candybar.endGently();
window.candybar.call = null;
window.dialer.setCallLabel('Call');
},
onAnswer: function() {
//console.log('answered...............');
window.candybar.setState('active');
window.dialer.setCallLabel('Hangup');
stopTone();
},
onRing: function() {
//console.log('ringing...............');
window.candybar.setState('calling');
startTone("ringback-uk");
},
onError: function(e) {
//console.log('dial error ' + e);
window.candybar.endGently();
window.candybar.call = null;
}
});
window.candybar.setUser({
name: callerId,
number: destination,
picUrl: 'unknown.jpg'
});
}
function onIncomingCall(event)
{
console.log(' call from ' + event.call.initiator);
if (window.candybar.call == null) // ignore when user has active call
{
var destination = Strophe.getNodeFromJid(event.call.initiator);
window.candybar.setUser({
name: destination,
number: destination,
picUrl: 'http://placekitten.com/100/100'
});
window.candybar.call = event.call;
window.candybar.setState('incoming');
window.dialer.setCallLabel('Answer');
/*
window.candybar.call.bind(
{
onHangup: function(event)
{
window.candybar.endGently();
//window.candybar.call = null;
window.dialer.setCallLabel('Call');
},
onAnswer: function(event)
{
window.candybar.setState('active');
window.dialer.setCallLabel('Hangup');
},
onError: function(event)
{
console.log('call error ' + event.reason);
}
});
*/
}
}
function urlParam(name)
{
var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (!results) { return undefined; }
return decodeURIComponent(results[1]) || undefined;
}
function startTone(name)
{
ringtone = new Audio();
ringtone.loop = true;
ringtone.src = "ringtones/" + name + ".mp3";
ringtone.play();
}
function stopTone()
{
ringtone.pause();
ringtone = null;
}
</script>
</head>
<body>
<div id="dialpadDiv" style="position: absolute; width: 500px: height: 300px;"/>
<span id="status" onClick="toggleHook()">Loading...</span>
</body>
</html>
\ No newline at end of file
/*!
* jQuery JavaScript Library v1.4.2
* http://jquery.com/
*
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2010, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Sat Feb 13 22:33:48 2010 -0500
*/
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
\ No newline at end of file
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
var MD5 = (function () {
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
var safe_add = function (x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
};
/*
* Bitwise rotate a 32-bit number to the left.
*/
var bit_rol = function (num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
};
/*
* Convert a string to an array of little-endian words
* If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
var str2binl = function (str) {
var bin = [];
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
{
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
}
return bin;
};
/*
* Convert an array of little-endian words to a string
*/
var binl2str = function (bin) {
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
{
str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
}
return str;
};
/*
* Convert an array of little-endian words to a hex string.
*/
var binl2hex = function (binarray) {
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
}
return str;
};
/*
* Convert an array of little-endian words to a base-64 string
*/
var binl2b64 = function (binarray) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
var triplet, j;
for(var i = 0; i < binarray.length * 4; i += 3)
{
triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) |
(((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) |
((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
for(j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); }
}
}
return str;
};
/*
* These functions implement the four basic operations the algorithm uses.
*/
var md5_cmn = function (q, a, b, x, s, t) {
return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b);
};
var md5_ff = function (a, b, c, d, x, s, t) {
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
};
var md5_gg = function (a, b, c, d, x, s, t) {
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
};
var md5_hh = function (a, b, c, d, x, s, t) {
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
};
var md5_ii = function (a, b, c, d, x, s, t) {
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
};
/*
* Calculate the MD5 of an array of little-endian words, and a bit length
*/
var core_md5 = function (x, len) {
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var olda, oldb, oldc, oldd;
for (var i = 0; i < x.length; i += 16)
{
olda = a;
oldb = b;
oldc = c;
oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return [a, b, c, d];
};
/*
* Calculate the HMAC-MD5, of a key and some data
*/
var core_hmac_md5 = function (key, data) {
var bkey = str2binl(key);
if(bkey.length > 16) { bkey = core_md5(bkey, key.length * chrsz); }
var ipad = new Array(16), opad = new Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
return core_md5(opad.concat(hash), 512 + 128);
};
var obj = {
/*
* These are the functions you'll usually want to call.
* They take string arguments and return either hex or base-64 encoded
* strings.
*/
hexdigest: function (s) {
return binl2hex(core_md5(str2binl(s), s.length * chrsz));
},
b64digest: function (s) {
return binl2b64(core_md5(str2binl(s), s.length * chrsz));
},
hash: function (s) {
return binl2str(core_md5(str2binl(s), s.length * chrsz));
},
hmac_hexdigest: function (key, data) {
return binl2hex(core_hmac_md5(key, data));
},
hmac_b64digest: function (key, data) {
return binl2b64(core_hmac_md5(key, data));
},
hmac_hash: function (key, data) {
return binl2str(core_hmac_md5(key, data));
},
/*
* Perform a simple self-test to see if the VM is working
*/
test: function () {
return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72";
}
};
return obj;
})();
/**
* RAYO : XMPP -0327 plugin for Strophe
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Strophe.addConnectionPlugin('rayo',
{
_connection: null,
init: function(conn)
{
this._connection = conn;
this.calls = {};
Strophe.addNamespace('RAYO_CORE', "urn:xmpp:rayo:1");
Strophe.addNamespace('RAYO_CALL', "urn:xmpp:rayo:call:1");
Strophe.addNamespace('RAYO_MIXER', "urn:xmpp:rayo:mixer:1");
Strophe.addNamespace('RAYO_EXT', "urn:xmpp:rayo:ext:1");
Strophe.addNamespace('RAYO_EXT_COMPLETE', "urn:xmpp:rayo:ext:complete:1");
Strophe.addNamespace('RAYO_INPUT', "urn:xmpp:rayo:input:1");
Strophe.addNamespace('RAYO_INPUT_COMPLETE', "urn:xmpp:rayo:input:complete:1");
Strophe.addNamespace('RAYO_OUTPUT', "urn:xmpp:rayo:output:1");
Strophe.addNamespace('RAYO_OUTPUT_COMPLETE', "urn:xmpp:rayo:output:complete:1");
Strophe.addNamespace('RAYO_PROMPT', "urn:xmpp:rayo:prompt:1");
Strophe.addNamespace('RAYO_RECORD', "urn:xmpp:rayo:record:1");
Strophe.addNamespace('RAYO_RECORD_COMPLETE', "urn:xmpp:rayo:record:complete:1");
Strophe.addNamespace('RAYO_SAY', "urn:xmpp:tropo:say:1");
Strophe.addNamespace('RAYO_SAY_COMPLETE', "urn:xmpp:tropo:say:complete:1");
Strophe.addNamespace('RAYO_HANDSET', "urn:xmpp:rayo:handset:1");
Strophe.addNamespace('RAYO_HANDSET_COMPLETE', "urn:xmpp:rayo:handset:complete:1");
this._connection.addHandler(this.handlePresence.bind(this), null,"presence", null, null, null);
console.log('Rayo plugin initialised');
},
offhook: function(handset)
{
//console.log('Rayo plugin offhook');
if (this.handset && this.handset.mixer) // reuse mixer
{
handset.mixer = this.handset.mixer;
}
if (!handset.mixer) handset.mixer = "rayo-mixer-" + Math.random().toString(36).substr(2,9);
this.handset = handset;
var that = this;
navigator.webkitGetUserMedia({audio:true, video:false}, function(stream)
{
that.localStream = stream;
that._offhook1();
}, function(error) {
if (that.handset && that.handset.onError) that.handset.onError(error);
});
},
_offhook1: function()
{
//console.log('Rayo plugin _offhook1 ');
var that = this;
that.pc1 = new webkitRTCPeerConnection(null);
that.pc1.addStream(that.localStream);
that.pc1.createOffer(function(desc)
{
//console.log(desc.sdp);
that.pc1.setLocalDescription(desc);
var sdpObj1 = WebrtcSDP.parseSDP(desc.sdp);
sdpObj1.contents[0].codecs = [{clockrate: "48000", id: "111", name: "opus", channels: 2}];
var sdp = WebrtcSDP.buildSDP(sdpObj1);
//console.log(sdp);
that.cryptoSuite = sdpObj1.contents[0].crypto['crypto-suite'];
that.remoteCrypto = sdpObj1.contents[0].crypto['key-params'].substring(7);
that.pc2.setRemoteDescription(new RTCSessionDescription({type: "offer", sdp : sdp}));
that.pc2.createAnswer(function(desc)
{
that.pc2.setLocalDescription(desc);
var sdpObj2 = WebrtcSDP.parseSDP(desc.sdp);
that.localCrypto = sdpObj2.contents[0].crypto['key-params'].substring(7);
var sdp = WebrtcSDP.buildSDP(sdpObj2);
//console.log(sdp);
that.pc1.setRemoteDescription(new RTCSessionDescription({type: "answer", sdp : sdp}));
that._offhook2();
});
});
that.pc2 = new webkitRTCPeerConnection(null);
that.pc2.onaddstream = function(e)
{
that.audio = new Audio();
that.audio.autoplay = true;
that.audio.src = webkitURL.createObjectURL(e.stream)
};
},
_offhook2: function()
{
//console.log('Rayo plugin _offhook2 ' + this.cryptoSuite + " " + this.localCrypto + " " + this.remoteCrypto);
var that = this;
var iq = $iq({to: "rayo." + that._connection.domain, from: that._connection.jid, type: "get"}).c("offhook", {xmlns: Strophe.NS.RAYO_HANDSET, cryptoSuite: that.cryptoSuite, localCrypto: that.localCrypto, remoteCrypto: that.remoteCrypto, codec: that.handset.codec, stereo: that.handset.stereo, mixer: that.handset.mixer});
//console.log(iq.toString())
that._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') == "result")
{
$('ref', response).each(function()
{
that.handsetId = $(this).attr('id');
that.handsetUri = $(this).attr('uri');
that.relayHost = $(this).attr('host');
that.relayLocalPort = $(this).attr('localport');
that.relayRemotePort = $(this).attr('remoteport');
that.pc2.addIceCandidate(new RTCIceCandidate({sdpMLineIndex: "0", candidate: "a=candidate:3707591233 1 udp 2113937151 " + that.relayHost + " " + that.relayRemotePort + " typ host generation 0"}));
that.pc1.addIceCandidate(new RTCIceCandidate({sdpMLineIndex: "0", candidate: "a=candidate:3707591233 1 udp 2113937151 " + that.relayHost + " " + that.relayLocalPort + " typ host generation 0"}));
});
} else {
if (handset.onError) handset.onError("offhook failure");
}
});
},
onhook: function()
{
//console.log('Rayo plugin onhook ' + this.handsetId);
that = this;
var server = this.handsetId + "@rayo." + this._connection.domain;
this._connection.sendIQ($iq({to: server, from: this._connection.jid, type: "get"}).c('onhook', {xmlns: Strophe.NS.RAYO_HANDSET}), function(response)
{
that.localStream.stop();
that.localStream = null;
that.pc1.close();
that.pc2.close();
that.pc1 = null;
that.pc2 = null;
});
},
dial: function(config)
{
//console.log('Rayo plugin dial');
//console.log(config)
var callId = "rayo-call-" + Math.random().toString(36).substr(2,9);
var that = this;
var iq = $iq({to: "rayo." + this._connection.domain, from: this._connection.jid, type: "get"}).c("dial", {xmlns: Strophe.NS.RAYO_CORE, to: config.to, from: config.from});
iq.c("join", {xmlns: Strophe.NS.RAYO_CORE, 'mixer-name': this.handset.mixer}).up();
iq.c("header", {name: "call-id", value: callId}).up();
iq.c("header", {name: "handset", value: that.handsetId}).up();
if (config.headers)
{
for (var i=0; i<headers.length; i++)
{
iq.c("header", {name: config.headers[i].name, value: config.headers[i].value}).up();
}
}
//console.log(iq.toString());
this._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') != "result")
{
if (config.onError) config.onError("dial failure");
}
});
this.calls[callId] =
{
callId: callId,
onRing: config.onRing,
onAnswer: config.onAnswer,
onError: config.onError,
onEnd: config.onEnd,
from: config.from,
to: config.to,
hangup: function()
{
//console.log("hangup " + this.callId);
var iq = $iq({to: this.callId + "@rayo." + that._connection.domain, from: that._connection.jid, type: "get"}).c("hangup", {xmlns: Strophe.NS.RAYO_CORE});
that._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') != "result")
{
if (config.onError) config.onError("hangup failure");
}
});
},
answer: function()
{
},
digit: function(key)
{
//console.log("digit " + this.callId + " " + key);
var iq = $iq({to: this.callId + "@rayo." + that._connection.domain, from: that._connection.jid, type: "get"}).c("dtmf", {xmlns: Strophe.NS.RAYO_CORE, tones: key});
//console.log(iq.toString());
that._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') != "result")
{
if (config.onError) config.onError("dtmf failure");
}
});
}
};
return this.calls[callId];
},
handlePresence: function(presence)
{
//console.log('Rayo plugin handlePresence');
//console.log(presence);
var that = this;
var from = $(presence).attr('from');
$(presence).find('complete').each(function()
{
$(this).find('success').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_HANDSET_COMPLETE)
{
that.onhook();
if (that.handset && that.handset.onEnd) that.handset.onEnd();
}
});
});
$(presence).find('joined').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
if (that.handsetId == Strophe.getNodeFromJid(from))
{
if (that.handset && that.handset.onReady) that.handset.onReady();
}
}
});
$(presence).find('unjoined').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
if (that.handsetId == Strophe.getNodeFromJid(from))
{
if (that.handset && that.handset.onUnready) that.handset.onUnready();
}
}
});
$(presence).find('ringing').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
var callId = Strophe.getNodeFromJid(from);
var call = that.calls[callId];
if (call && call.onRing) call.onRing(call);
}
});
$(presence).find('answered').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
var callId = Strophe.getNodeFromJid(from);
var call = that.calls[callId];
if (call && call.onAnswer) call.onAnswer(call);
}
});
$(presence).find('end').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
var callId = Strophe.getNodeFromJid(from);
var call = that.calls[callId];
if (call && call.onEnd) call.onEnd(call);
that.calls[callId] = null;
}
});
return true;
}
});
;(function() {
// Helper library to translate to and from SDP and an intermediate javascript object
// representation of candidates, offers and answers
_parseLine = function(line) {
var s1 = line.split("=");
return {
type: s1[0],
contents: s1[1]
}
}
_parseA = function(attribute) {
var s1 = attribute.split(":");
return {
key: s1[0],
params: attribute.substring(attribute.indexOf(":")+1).split(" ")
}
}
_parseM = function(media) {
var s1 = media.split(" ");
return {
type:s1[0],
port:s1[1],
proto:s1[2],
pts:media.substring((s1[0]+s1[1]+s1[2]).length+3).split(" ")
}
}
_parseO = function(media) {
var s1 = media.split(" ");
return {
username:s1[0],
id:s1[1],
ver:s1[2],
nettype:s1[3],
addrtype:s1[4],
address:s1[5]
}
}
_parseC = function(media) {
var s1 = media.split(" ");
return {
nettype:s1[0],
addrtype:s1[1],
address:s1[2]
}
}
_parseCandidate = function (params) {
var candidate = {
foundation:params[0],
component:params[1],
protocol:params[2],
priority:params[3],
ip:params[4],
port:params[5]
};
var index = 6;
while (index + 1 <= params.length) {
if (params[index] == "typ") candidate["type"] = params[index+1];
if (params[index] == "generation") candidate["generation"] = params[index+1];
if (params[index] == "username") candidate["username"] = params[index+1];
if (params[index] == "password") candidate["password"] = params[index+1];
index += 2;
}
return candidate;
}
//a=rtcp:1 IN IP4 0.0.0.0
_parseRtcp = function (params) {
var rtcp = {
port:params[0]
};
if (params.length > 1) {
rtcp['nettype'] = params[1];
rtcp['addrtype'] = params[2];
rtcp['address'] = params[3];
}
return rtcp;
}
//a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:zvrxmXFpomTqz7CJYhN5G7JM3dVVxG/fZ0Il6DDo
_parseCrypto = function(params) {
var crypto = {
'tag':params[0],
'crypto-suite':params[1],
'key-params':params[2]
}
return crypto;
}
_parseFingerprint = function(params) {
var finger = {
'hash':params[0],
'print':params[1],
'required':'1'
}
return finger;
}
//a=rtpmap:101 telephone-event/8000"
_parseRtpmap = function(params) {
var bits = params[1].split("/");
var codec = {
id: params[0],
name: bits[0],
clockrate: bits[1]
}
if (bits.length >2){
codec.channels = bits[2];
}
return codec;
}
_parseSsrc = function(params, ssrc) {
var ssrcObj = {};
if (ssrc != undefined) ssrcObj = ssrc;
ssrcObj.ssrc = params[0];
var value = params[1];
ssrcObj[value.split(":")[0]] = value.split(":")[1];
return ssrcObj;
}
_parseGroup = function(params) {
var group = {
type: params[0]
}
group.contents = [];
var index = 1;
while (index + 1 <= params.length) {
group.contents.push(params[index]);
index = index + 1;
}
return group;
}
_parseMid = function(params) {
var mid = params[0];
return mid;
}
// Object -> SDP
_buildCandidate = function(candidateObj, iceObj) {
var c = candidateObj;
var sdp = "a=candidate:" + c.foundation + " " +
c.component + " " +
c.protocol.toUpperCase() + " " +
c.priority + " " +
c.ip + " " +
c.port;
if (c.type) sdp = sdp + " typ host"; //+ c.type;
if (c.component == 1) sdp = sdp + " name rtp";
if (c.component == 2) sdp = sdp + " name rtcp";
sdp = sdp + " network_name en0";
if (c.username && c.password ){
sdp = sdp + " username "+c.username;
sdp = sdp + " password "+c.password;
if (!iceObj.ufrag) iceObj.ufrag = c.username;
if (!iceObj.pwd) iceObj.pwd=c.username;;
} else if (iceObj) {
if (iceObj.ufrag) sdp = sdp + " username " + iceObj.ufrag;
if (iceObj.pwd) sdp = sdp + " password " + iceObj.pwd;
} else {
sdp = sdp+ " username root password mysecret";// I know a secret
}
if (c.generation) sdp = sdp + " generation " + c.generation;
sdp = sdp + "\r\n";
return sdp;
}
_buildCodec = function(codecObj) {
var sdp = "a=rtpmap:" + codecObj.id + " " + codecObj.name + "/" + codecObj.clockrate
if (codecObj.channels){
sdp+="/"+codecObj.channels;
}
sdp += "\r\n";
if (codecObj.ptime){
sdp+="a=ptime:"+codecObj.ptime;
sdp += "\r\n";
} else if (codecObj.name.toLowerCase().indexOf("opus")==0){
sdp+="a=ptime:20\r\n";
sdp+="a=fmtp:"+codecObj.id+" minptime=20 stereo=1\r\n";
}
if (codecObj.name.toLowerCase().indexOf("telephone-event")==0){
sdp+="a=fmtp:"+codecObj.id+" 0-15\r\n";
}
return sdp;
}
_buildCrypto = function(cryptoObj) {
var sdp = "a=crypto:" + cryptoObj.tag + " " + cryptoObj['crypto-suite'] + " " +
cryptoObj["key-params"] + "\r\n";
return sdp;
}
_buildFingerprint = function(fingerObj) {
var sdp = "a=fingerprint:" + fingerObj.hash + " " + fingerObj.print + "\r\n";
return sdp;
}
_buildIce= function(ice) {
var sdp="";
if (ice.ufrag) {
if (!ice.filterLines) {
sdp = sdp + "a=ice-ufrag:" + ice.ufrag + "\r\n";
sdp = sdp + "a=ice-pwd:" + ice.pwd + "\r\n";
}
if (ice.options) {
sdp = sdp + "a=ice-options:" + ice.options + "\r\n";
}
}
return sdp;
}
_buildSessProps = function(sdpObj) {
var sdp ="";
if (sdpObj.fingerprint) {
sdp = sdp + _buildFingerprint(sdpObj.fingerprint);
}
if (sdpObj.ice) {
sdp= sdp + _buildIce(sdpObj.ice);
}
return sdp;
}
_buildMedia =function(sdpObj) {
var sdp ="";
sdp += "m=" + sdpObj.media.type + " " + sdpObj.media.port + " " + sdpObj.media.proto;
var mi = 0;
while (mi + 1 <= sdpObj.media.pts.length) {
sdp = sdp + " " + sdpObj.media.pts[mi];
mi = mi + 1;
}
sdp = sdp + "\r\n";
if (sdpObj.connection) {
sdp = sdp + "c=" + sdpObj.connection.nettype + " " + sdpObj.connection.addrtype + " " +
sdpObj.connection.address + "\r\n";
}
if (sdpObj.mid) {
sdp = sdp + "a=mid:" + sdpObj.mid + "\r\n";
}
if (sdpObj.rtcp) {
sdp = sdp + "a=rtcp:" + sdpObj.rtcp.port + " " + sdpObj.rtcp.nettype + " " +
sdpObj.rtcp.addrtype + " " +
sdpObj.rtcp.address + "\r\n";
}
if (sdpObj.ice) {
sdp= sdp + _buildIce(sdpObj.ice);
}
var ci = 0;
while (ci + 1 <= sdpObj.candidates.length) {
sdp = sdp + _buildCandidate(sdpObj.candidates[ci], sdpObj.ice);
ci = ci + 1;
}
if (sdpObj.direction) {
if (sdpObj.direction == "recvonly") {
sdp = sdp + "a=recvonly\r\n";
} else if (sdpObj.direction == "sendonly") {
sdp = sdp + "a=sendonly\r\n";
} else if (sdpObj.direction == "none") {
sdp = sdp;
} else {
sdp = sdp + "a=sendrecv\r\n";
}
} else {
sdp = sdp + "a=sendrecv\r\n";
}
if (sdpObj['rtcp-mux']) {
sdp = sdp + "a=rtcp-mux" + "\r\n";
}
if (sdpObj.crypto) {
sdp = sdp + _buildCrypto(sdpObj.crypto);
}
if (sdpObj.fingerprint) {
sdp = sdp + _buildFingerprint(sdpObj.fingerprint);
}
var cdi = 0;
while (cdi + 1 <= sdpObj.codecs.length) {
sdp = sdp + _buildCodec(sdpObj.codecs[cdi]);
cdi = cdi + 1;
}
if (sdpObj.ssrc) {
var ssrc = sdpObj.ssrc;
if (ssrc.cname) sdp = sdp + "a=ssrc:" + ssrc.ssrc + " " + "cname:" + ssrc.cname + "\r\n";
if (ssrc.mslabel) sdp = sdp + "a=ssrc:" + ssrc.ssrc + " " + "mslabel:" + ssrc.mslabel + "\r\n";
if (ssrc.label) sdp = sdp + "a=ssrc:" + ssrc.ssrc + " " + "label:" + ssrc.label + "\r\n";
}
return sdp;
}
WebrtcSDP = {
getAttributes: function(element)
{
var res = {},
attr;
for(var i = 0, len = element.attributes.length; i < len; i++) {
if(element.attributes.hasOwnProperty(i)) {
attr = element.attributes[i];
res[attr.name] = attr.value;
}
}
return res;
},
each: function( object, callback, args )
{
var name, i = 0,
length = object.length,
isObj = length === undefined || $.isFunction(object);
if ( args ) {
if ( isObj ) {
for ( name in object ) {
if ( callback.apply( object[ name ], args ) === false ) {
break;
}
}
} else {
for ( ; i < length; ) {
if ( callback.apply( object[ i++ ], args ) === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isObj ) {
for ( name in object ) {
if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
break;
}
}
} else {
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
}
}
return object;
},
buildJingle: function(jingle, blob) {
var description = "urn:xmpp:jingle:apps:rtp:1";
var c = jingle;
if (blob.group) {
var bundle = "";
c.c('group', {type:blob.group.type,
contents:blob.group.contents.join(",")}).up();
}
WebrtcSDP.util.each(blob.contents, function () {
var sdpObj = this;
var desc = {xmlns:description,
media:sdpObj.media.type};
if (sdpObj.ssrc) {
desc.ssrc = sdpObj.ssrc.ssrc,
desc.cname = sdpObj.ssrc.cname,
desc.mslabel = sdpObj.ssrc.mslabel,
desc.label = sdpObj.ssrc.label
}
if (sdpObj.mid) {
desc.mid = sdpObj.mid
}
if (sdpObj['rtcp-mux']) {
desc['rtcp-mux'] = sdpObj['rtcp-mux'];
}
c = c.c('content', {creator:"initiator"})
.c('description', desc);
WebrtcSDP.util.each(sdpObj.codecs, function() {
c = c.c('payload-type', this).up();
});
if (sdpObj.crypto) {
c = c.c('encryption', {required: '1'}).c('crypto', sdpObj.crypto).up();
c = c.up();
}
// Raw candidates
c = c.up().c('transport',{xmlns:"urn:xmpp:jingle:transports:raw-udp:1"});
c = c.c('candidate', {component:'1',
ip: sdpObj.connection.address,
port: sdpObj.media.port}).up();
if(sdpObj.rtcp) {
c = c.c('candidate', {component:'2',
ip: sdpObj.rtcp.address,
port: sdpObj.rtcp.port}).up();
}
c = c.up();
// 3 places we might find ice creds - in order of priority:
// candidate username
// media level icefrag
// session level icefrag
var iceObj = {};
if (sdpObj.candidates[0].username ){
iceObj = {ufrag:sdpObj.candidates[0].username,pwd:sdpObj.candidates[0].password};
} else if ((sdpObj.ice) && (sdpObj.ice.ufrag)){
iceObj = sdpObj.ice;
} else if ((blob.session.ice) && (blob.session.ice.ufrag)){
iceObj = blob.session.ice;
}
// Ice candidates
var transp = {xmlns:"urn:xmpp:jingle:transports:ice-udp:1",
pwd: iceObj.pwd,
ufrag: iceObj.ufrag};
if (iceObj.options) {
transp.options = iceObj.options;
}
c = c.c('transport',transp);
WebrtcSDP.util.each(sdpObj.candidates, function() {
c = c.c('candidate', this).up();
});
// two places to find the fingerprint
// media
// session
var fp = null;
if (sdpObj.fingerprint) {
fp= sdpObj.fingerprint;
}else if(blob.session.fingerprint){
fp = blob.session.fingerprint;
}
if (fp){
c = c.c('fingerprint',{xmlns:"urn:xmpp:tmp:jingle:apps:dtls:0",
hash:fp.hash,
required:fp.required});
c.t(fp.print);
c.up();
}
c = c.up().up();
});
return c;
},
// jingle: Some Jingle to parse
// Returns a js object representing the SDP
parseJingle: function(jingle) {
var blobObj = {};
jingle.find('group').each(function () {
blobObj.group = {};
blobObj.group.type = $(this).attr('type');
blobObj.group.contents = $(this).attr('contents').split(",");
});
blobObj.contents = [];
jingle.find('content').each(function () {
var sdpObj = {};
var mediaObj = {};
mediaObj.pts = [];
blobObj.contents.push(sdpObj);
sdpObj.candidates = [];
sdpObj.codecs = [];
$(this).find('description').each(function () {
if($(this).attr('xmlns') == "urn:xmpp:jingle:apps:rtp:1"){
var mediaType = $(this).attr('media');
mediaObj.type = mediaType;
mediaObj.proto = "RTP/SAVPF"; // HACK
mediaObj.port = 1000;
var ssrcObj = {};
if ($(this).attr('ssrc')) {
ssrcObj.ssrc = $(this).attr('ssrc');
if ($(this).attr('cname')) ssrcObj.cname = $(this).attr('cname');
if ($(this).attr('mslabel')) ssrcObj.mslabel = $(this).attr('mslabel');
if ($(this).attr('label')) ssrcObj.label = $(this).attr('label');
sdpObj.ssrc = ssrcObj;
}
if ($(this).attr('rtcp-mux')) {
sdpObj['rtcp-mux'] = $(this).attr('rtcp-mux');
}
if ($(this).attr('mid')) {
sdpObj['mid'] = $(this).attr('mid');
}
sdpObj.media = mediaObj;
$(this).find('payload-type').each(function () {
var codec = WebrtcSDP.util.getAttributes(this);
//console.log("codec: "+JSON.stringify(codec,null," "));
sdpObj.codecs.push(codec);
mediaObj.pts.push(codec.id);
});
} else {
console.log("skip description with wrong xmlns: "+$(this).attr('xmlns'));
}
});
$(this).find('crypto').each(function () {
var crypto = WebrtcSDP.util.getAttributes(this);
//console.log("crypto: "+JSON.stringify(crypto,null," "));
sdpObj.crypto = crypto;
});
$(this).find('fingerprint').each(function () {
var fingerprint = WebrtcSDP.util.getAttributes(this);
fingerprint.print = Strophe.getText(this);
//console.log("fingerprint: "+JSON.stringify(fingerprint,null," "));
sdpObj.fingerprint = fingerprint;
});
sdpObj.ice = {};
$(this).find('transport').each(function () {
if ($(this).attr('xmlns') == "urn:xmpp:jingle:transports:raw-udp:1") {
$(this).find('candidate').each(function () {
var candidate = WebrtcSDP.util.getAttributes(this);
//console.log("candidate: "+JSON.stringify(candidate,null," "));
if (candidate.component == "1") {
sdpObj.media.port = candidate.port;
sdpObj.connection = {};
sdpObj.connection.address = candidate.ip;
sdpObj.connection.addrtype = "IP4";
sdpObj.connection.nettype = "IN";
}
if (candidate.component == "2") {
sdpObj.rtcp = {};
sdpObj.rtcp.port = candidate.port;
sdpObj.rtcp.address = candidate.ip;
sdpObj.rtcp.addrtype = "IP4";
sdpObj.rtcp.nettype = "IN";
}
});
}
if ($(this).attr('xmlns') == "urn:xmpp:jingle:transports:ice-udp:1") {
sdpObj.ice.pwd = $(this).attr('pwd');
sdpObj.ice.ufrag = $(this).attr('ufrag');
if ($(this).attr('options')) {
sdpObj.ice.options = $(this).attr('options');
}
$(this).find('candidate').each(function () {
var candidate = WebrtcSDP.util.getAttributes(this);
//console.log("candidate: "+JSON.stringify(candidate,null," "));
sdpObj.candidates.push(candidate);
});
}
});
});
return blobObj;
},
dumpSDP: function(sdpString) {
var sdpLines = sdpString.split("\r\n");
for (var sdpLine in sdpLines) {
//console.log(sdpLines[sdpLine]);
}
},
// sdp: an SDP text string representing an offer or answer, missing candidates
// Return an object representing the SDP in Jingle like constructs
parseSDP: function(sdpString) {
var contentsObj = {};
contentsObj.contents = [];
var sdpObj = null;
// Iterate the lines
var sdpLines = sdpString.split("\r\n");
for (var sdpLine in sdpLines) {
//console.log(sdpLines[sdpLine]);
var line = _parseLine(sdpLines[sdpLine]);
if (line.type == "o") {
contentsObj.session = _parseO(line.contents);
contentsObj.session.ice = {};
sdpObj = contentsObj.session;
}
if (line.type == "m") {
// New m-line,
// create a new content
var media = _parseM(line.contents);
sdpObj = {};
sdpObj.candidates = [];
sdpObj.codecs = [];
sdpObj.ice = {};
if (contentsObj.session.fingerprint != null){
sdpObj.fingerprint = contentsObj.session.fingerprint;
}
sdpObj.media = media;
contentsObj.contents.push(sdpObj);
}
if (line.type == "c") {
if (sdpObj != null) {
sdpObj.connection = _parseC(line.contents);
} else {
contentsObj.connection = _parseC(line.contents);
}
}
if (line.type == "a") {
var a = _parseA(line.contents);
switch (a.key) {
case "candidate":
var candidate = _parseCandidate(a.params);
sdpObj.candidates.push(candidate);
break;
case "group":
var group = _parseGroup(a.params);
contentsObj.group = group;
break;
case "mid":
var mid = _parseMid(a.params);
sdpObj.mid = mid;
break;
case "rtcp":
var rtcp = _parseRtcp(a.params);
sdpObj.rtcp = rtcp;
break;
case "rtcp-mux":
sdpObj['rtcp-mux'] = true;
break;
case "rtpmap":
var codec = _parseRtpmap(a.params);
if (codec) sdpObj.codecs.push(codec);
break;
case "sendrecv":
sdpObj.direction = "sendrecv";
break;
case "sendonly":
sdpObj.direction = "sendonly";
break;
case "recvonly":
sdpObj.recvonly = "recvonly";
break;
case "ssrc":
sdpObj.ssrc = _parseSsrc(a.params, sdpObj.ssrc);
break;
case "fingerprint":
var print = _parseFingerprint(a.params);
sdpObj.fingerprint = print;
break;
case "crypto":
var crypto = _parseCrypto(a.params);
sdpObj.crypto = crypto;
break;
case "ice-ufrag":
sdpObj.ice.ufrag = a.params[0];
break;
case "ice-pwd":
sdpObj.ice.pwd = a.params[0];
break;
case "ice-options":
sdpObj.ice.options = a.params[0];
break;
}
}
}
return contentsObj;
},
// sdp: an object representing the body
// Return a text string in SDP format
buildSDP: function(contentsObj) {
// Write some constant stuff
var session = contentsObj.session;
var sdp =
"v=0\r\n";
if (contentsObj.session) {
var session = contentsObj.session;
sdp = sdp + "o=" + session.username + " " + session.id + " " + session.ver + " " +
session.nettype + " " + session.addrtype + " " + session.address + "\r\n";
} else {
var id = new Date().getTime();
var ver = 2;
sdp = sdp + "o=-" + " 3" + id + " " + ver + " IN IP4 192.67.4.14" + "\r\n"; // does the IP here matter ?!?
}
sdp = sdp + "s=-\r\n" +
"t=0 0\r\n";
if (contentsObj.connection) {
var connection = contentsObj.connection;
sdp = sdp + "c=" + connection.nettype + " " + connection.addrtype +
" " + connection.address + "\r\n";
}
if (contentsObj.group) {
var group = contentsObj.group;
sdp = sdp + "a=group:" + group.type;
var ig = 0;
while (ig + 1 <= group.contents.length) {
sdp = sdp + " " + group.contents[ig];
ig = ig + 1;
}
sdp = sdp + "\r\n";
}
if (contentsObj.session){
sdp = sdp + _buildSessProps(contentsObj.session);
}
var contents = contentsObj.contents;
var ic = 0;
while (ic + 1 <= contents.length) {
var sdpObj = contents[ic];
sdp = sdp + _buildMedia(sdpObj);
ic = ic + 1;
}
return sdp;
},
// candidate: an SDP text string representing a cadidate
// Return: an object representing the candidate in Jingle like constructs
parseCandidate: function(candidateSDP) {
var line = _parseLine(candidateSDP);
return _parseCandidate(line.contents);
},
// candidate: an object representing the body
// Return a text string in SDP format
buildCandidate: function(candidateObj) {
return _buildCandidate(candidateObj);
}
};
}());
var Openfire = {};
/** Class: Openfire.Connection
* WebSockets Connection Manager for Openfire
*
* Thie class manages an WebSockets connection
* to an Openfire XMPP server through the WebSockets plugin and dispatches events to the user callbacks as
* data arrives. It uses the server side Openfire authentication
*
* After creating a Openfire object, the user will typically
* call connect() with a user supplied callback to handle connection level
* events like authentication failure, disconnection, or connection
* complete.
*
* To send data to the connection, use send(doc) or sendRaw(text)
*
* Use xmlInput(doc) and RawInput(text) overrideable function to receive XML data coming into the
* connection.
*
* The user will also have several event handlers defined by using
* addHandler() and addTimedHandler(). These will allow the user code to
* respond to interesting stanzas or do something periodically with the
* connection. These handlers will be active once authentication is
* finished.
*
* Create and initialize a Openfire object.
*
*
* Returns:
* A new Openfire object.
*/
Openfire.Connection = function(url)
{
if (!window.WebSocket)
{
window.WebSocket=window.MozWebSocket;
if (!window.WebSocket)
{
var msg = "WebSocket not supported by this browser";
alert(msg);
throw Error(msg);
}
}
this.host = url.indexOf("/") < 0 ? url : url.split("/")[2];
this.protocol = url.indexOf("/") < 0 ? "wss:" : (url.split("/")[0] == "http:") ? "ws:" : "wss:";
this.jid = "";
this.resource = "ofchat";
this.streamId = null;
// handler lists
this.timedHandlers = [];
this.handlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
this._idleTimeout = null;
this.authenticated = false;
this.disconnecting = false;
this.connected = false;
this.errors = 0;
this._uniqueId = Math.round(Math.random() * 10000);
// setup onIdle callback every 1/10th of a second
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
// initialize plugins
for (var k in Strophe._connectionPlugins)
{
if (Strophe._connectionPlugins.hasOwnProperty(k)) {
var ptype = Strophe._connectionPlugins[k];
// jslint complaints about the below line, but this is fine
var F = function () {};
F.prototype = ptype;
this[k] = new F();
this[k].init(this);
}
}
}
Openfire.Connection.prototype = {
/** Function: reset
* Reset the connection.
*
* This function should be called after a connection is disconnected
* before that connection is reused.
*/
reset: function ()
{
this.streamId = null;
this.timedHandlers = [];
this.handlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
this.authenticated = false;
this.disconnecting = false;
this.connected = false;
this.errors = 0;
},
/** Function: pause
* UNUSED with websockets
*/
pause: function ()
{
return;
},
/** Function: resume
* UNUSED with websockets
*/
resume: function ()
{
return;
},
/** Function: getUniqueId
* Generate a unique ID for use in <iq/> elements.
*
* All <iq/> stanzas are required to have unique id attributes. This
* function makes creating these easy. Each connection instance has
* a counter which starts from zero, and the value of this counter
* plus a colon followed by the suffix becomes the unique id. If no
* suffix is supplied, the counter is used as the unique id.
*
* Suffixes are used to make debugging easier when reading the stream
* data, and their use is recommended. The counter resets to 0 for
* every new connection for the same reason. For connections to the
* same server that authenticate the same way, all the ids should be
* the same, which makes it easy to see changes. This is useful for
* automated testing as well.
*
* Parameters:
* (String) suffix - A optional suffix to append to the id.
*
* Returns:
* A unique string to be used for the id attribute.
*/
getUniqueId: function (suffix)
{
if (typeof(suffix) == "string" || typeof(suffix) == "number") {
return ++this._uniqueId + ":" + suffix;
} else {
return ++this._uniqueId + "";
}
},
/** Function: connect
* Starts the connection process.
*
*
* Parameters:
* (String) username - The Openfire username.
* (String) pass - The user's password.
* (String) resource - The user resource for this connection.
* (Function) callback The connect callback function.
*/
connect: function (jid, pass, callback, wait, hold, route)
{
this.jid = jid.indexOf("/") > -1 ? jid : jid + '/' + this.resource;
this.username = jid.indexOf("@") < 0 ? null : jid.split("@")[0];
this.pass = pass == "" ? null : pass;
this.connect_callback = callback;
this.disconnecting = false;
this.connected = false;
this.authenticated = false;
this.errors = 0;
this._changeConnectStatus(Strophe.Status.CONNECTING, null);
this.url = this.protocol + "//" + this.host + "/ws/server?username=" + this.username + "&password=" + this.pass + "&resource=" + this.resource;
this._ws = new WebSocket(this.url, "xmpp");
this._ws.onopen = this._onopen.bind(this);
this._ws.onmessage = this._onmessage.bind(this);
this._ws.onclose = this._onclose.bind(this);
window.openfireWebSocket = this;
this.jid = this.jid.indexOf("@") < 0 ? this.resource + "@" + this.jid : this.jid;
this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
},
/**
*
* Private Function: _onopen websocket event handler
*
*/
_onopen: function()
{
this.connected = true;
this.authenticated = true;
this.resource = Strophe.getResourceFromJid(this.jid);
this.domain = Strophe.getDomainFromJid(this.jid);
try {
this._changeConnectStatus(Strophe.Status.CONNECTED, null);
} catch (e) {
throw Error("User connection callback caused an exception: " + e);
}
this.interval = setInterval (function() {window.openfireWebSocket.sendRaw(" ")}, 10000 );
},
/** Function: attach
* UNUSED, use connect again
*/
attach: function()
{
return
},
/** Function: xmlInput
* User overrideable function that receives XML data coming into the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.xmlInput = function (elem) {
* > (user code)
* > };
*
* Parameters:
* (XMLElement) elem - The XML data received by the connection.
*/
xmlInput: function (elem)
{
return;
},
/** Function: xmlOutput
* User overrideable function that receives XML data sent to the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.xmlOutput = function (elem) {
* > (user code)
* > };
*
* Parameters:
* (XMLElement) elem - The XMLdata sent by the connection.
*/
xmlOutput: function (elem)
{
return;
},
/** Function: rawInput
* User overrideable function that receives raw data coming into the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.rawInput = function (data) {
* > (user code)
* > };
*
* Parameters:
* (String) data - The data received by the connection.
*/
rawInput: function (data)
{
return;
},
/** Function: rawOutput
* User overrideable function that receives raw data sent to the
* connection.
*
* The default function does nothing. User code can override this with
* > Openfire.rawOutput = function (data) {
* > (user code)
* > };
*
* Parameters:
* (String) data - The data sent by the connection.
*/
rawOutput: function (data)
{
return;
},
/** Function: sendRaw
* Send a stanza in raw XML text.
*
* This function is called to push data onto the send queue to
* go out over the wire. Whenever a request is sent to the BOSH
* server, all pending data is sent and the queue is flushed.
*
* Parameters:
* xml - The stanza text XML to send.
*/
sendRaw: function(xml) {
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
if (xml != " ")
{
this.xmlOutput(this._textToXML(xml));
this.rawOutput(xml);
}
this._ws.send(xml);
},
/** Function: send
* Send a stanza.
*
* This function is called to push data onto the send queue to
* go out over the wire. Whenever a request is sent to the BOSH
* server, all pending data is sent and the queue is flushed.
*
* Parameters:
* (XMLElement |
* [XMLElement] |
* Strophe.Builder) elem - The stanza to send.
*/
send: function(elem)
{
if(!this.connected || this._ws == null) {
throw Error("Not connected, cannot send packets.");
}
var toSend = "";
if (elem === null) { return ; }
if (typeof(elem.sort) === "function")
{
for (var i = 0; i < elem.length; i++)
{
toSend += Strophe.serialize(elem[i]);
this.xmlOutput(elem[i]);
}
} else if (typeof(elem.tree) === "function") {
toSend = Strophe.serialize(elem.tree());
this.xmlOutput(elem.tree());
} else {
toSend = Strophe.serialize(elem);
this.xmlOutput(elem);
}
this.rawOutput(toSend);
this._ws.send(toSend);
},
/** Function: flush
* UNUSED
*/
flush: function ()
{
return
},
/** Function: sendIQ
* Helper function to send IQ stanzas.
*
* Parameters:
* (XMLElement) elem - The stanza to send.
* (Function) callback - The callback function for a successful request.
* (Function) errback - The callback function for a failed or timed
* out request. On timeout, the stanza will be null.
* (Integer) timeout - The time specified in milliseconds for a
* timeout to occur.
*
* Returns:
* The id used to send the IQ.
*/
sendIQ: function(elem, callback, errback, timeout) {
var timeoutHandler = null;
var that = this;
if (typeof(elem.tree) === "function") {
elem = elem.tree();
}
var id = elem.getAttribute('id');
// inject id if not found
if (!id) {
id = this.getUniqueId("sendIQ");
elem.setAttribute("id", id);
}
var handler = this.addHandler(function (stanza) {
// remove timeout handler if there is one
if (timeoutHandler) {
that.deleteTimedHandler(timeoutHandler);
}
var iqtype = stanza.getAttribute('type');
if (iqtype == 'result')
{
if (callback) {
callback(stanza);
}
} else if (iqtype == 'error') {
if (errback) {
errback(stanza);
}
} else {
throw {
name: "StropheError",
message: "Got bad IQ type of " + iqtype
};
}
}, null, 'iq', null, id);
// if timeout specified, setup timeout handler.
if (timeout)
{
timeoutHandler = this.addTimedHandler(timeout, function () {
// get rid of normal handler
that.deleteHandler(handler);
// call errback on timeout with null stanza
if (errback) {
errback(null);
}
return false;
});
}
this.send(elem);
return id;
},
/** Function: addTimedHandler
* Add a timed handler to the connection.
*
* This function adds a timed handler. The provided handler will
* be called every period milliseconds until it returns false,
* the connection is terminated, or the handler is removed. Handlers
* that wish to continue being invoked should return true.
*
* Because of method binding it is necessary to save the result of
* this function if you wish to remove a handler with
* deleteTimedHandler().
*
* Note that user handlers are not active until authentication is
* successful.
*
* Parameters:
* (Integer) period - The period of the handler.
* (Function) handler - The callback function.
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
addTimedHandler: function (period, handler)
{
var thand = new Strophe.TimedHandler(period, handler);
this.addTimeds.push(thand);
return thand;
},
/** Function: deleteTimedHandler
* Delete a timed handler for a connection.
*
* This function removes a timed handler from the connection. The
* handRef parameter is *not* the function passed to addTimedHandler(),
* but is the reference returned from addTimedHandler().
*
* Parameters:
* (Strophe.TimedHandler) handRef - The handler reference.
*/
deleteTimedHandler: function (handRef)
{
// this must be done in the Idle loop so that we don't change
// the handlers during iteration
this.removeTimeds.push(handRef);
},
/** Function: addHandler
* Add a stanza handler for the connection.
*
* This function adds a stanza handler to the connection. The
* handler callback will be called for any stanza that matches
* the parameters. Note that if multiple parameters are supplied,
* they must all match for the handler to be invoked.
*
* The handler will receive the stanza that triggered it as its argument.
* The handler should return true if it is to be invoked again;
* returning false will remove the handler after it returns.
*
* As a convenience, the ns parameters applies to the top level element
* and also any of its immediate children. This is primarily to make
* matching /iq/query elements easy.
*
* The options argument contains handler matching flags that affect how
* matches are determined. Currently the only flag is matchBare (a
* boolean). When matchBare is true, the from parameter and the from
* attribute on the stanza will be matched as bare JIDs instead of
* full JIDs. To use this, pass {matchBare: true} as the value of
* options. The default value for matchBare is false.
*
* The return value should be saved if you wish to remove the handler
* with deleteHandler().
*
* Parameters:
* (Function) handler - The user callback.
* (String) ns - The namespace to match.
* (String) name - The stanza name to match.
* (String) type - The stanza type attribute to match.
* (String) id - The stanza id attribute to match.
* (String) from - The stanza from attribute to match.
* (String) options - The handler options
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
addHandler: function (handler, ns, name, type, id, from, options)
{
var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
this.addHandlers.push(hand);
return hand;
},
/** Function: deleteHandler
* Delete a stanza handler for a connection.
*
* This function removes a stanza handler from the connection. The
* handRef parameter is *not* the function passed to addHandler(),
* but is the reference returned from addHandler().
*
* Parameters:
* (Strophe.Handler) handRef - The handler reference.
*/
deleteHandler: function (handRef)
{
// this must be done in the Idle loop so that we don't change
// the handlers during iteration
this.removeHandlers.push(handRef);
},
/** Function: disconnect
* Start the graceful disconnection process.
*
* This function starts the disconnection process. This process starts
* by sending unavailable presence and sending BOSH body of type
* terminate. A timeout handler makes sure that disconnection happens
* even if the BOSH server does not respond.
*
* The user supplied connection callback will be notified of the
* progress as this process happens.
*
* Parameters:
* (String) reason - The reason the disconnect is occuring.
*/
disconnect: function(reason) {
if(!this.connected || this._ws == null) {
return;
}
this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
Strophe.info("Disconnect was called because: " + reason);
this._ws.close();
},
/** PrivateFunction: _onDisconnectTimeout
* _Private_ timeout handler for handling non-graceful disconnection.
*
* If the graceful disconnect process does not complete within the
* time allotted, this handler finishes the disconnect anyway.
*
* Returns:
* false to remove the handler.
*/
_onDisconnectTimeout: function ()
{
Strophe.info("_onDisconnectTimeout was called");
this._doDisconnect();
return false;
},
/** PrivateFunction: _doDisconnect
* _Private_ function to disconnect.
*
* This is the last piece of the disconnection logic. This resets the
* connection and alerts the user's connection callback.
*/
_doDisconnect: function ()
{
Strophe.info("_doDisconnect was called");
this._onclose();
},
/** PrivateFunction: _changeConnectStatus
* _Private_ helper function that makes sure plugins and the user's
* callback are notified of connection status changes.
*
* Parameters:
* (Integer) status - the new connection status, one of the values
* in Strophe.Status
* (String) condition - the error condition or null
*/
_changeConnectStatus: function (status, condition)
{
// notify all plugins listening for status changes
for (var k in Strophe._connectionPlugins)
{
if (Strophe._connectionPlugins.hasOwnProperty(k))
{
var plugin = this[k];
if (plugin.statusChanged)
{
try {
plugin.statusChanged(status, condition);
} catch (err) {
Strophe.error("" + k + " plugin caused an exception changing status: " + err);
}
}
}
}
// notify the user's callback
if (typeof this.connect_callback == 'function')
{
try {
this.connect_callback(status, condition);
} catch (e) {
Strophe.error("User connection callback caused an exception: " + e);
}
}
},
/**
*
* Private Function: _onclose websocket event handler
*
*/
_onclose: function()
{
Strophe.info("websocket closed");
//console.log('_onclose - disconnected');
clearInterval(this.interval);
this.authenticated = false;
this.disconnecting = false;
this.streamId = null;
// tell the parent we disconnected
this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
this.connected = false;
// delete handlers
this.handlers = [];
this.timedHandlers = [];
this.removeTimeds = [];
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
if(this._ws.readyState != this._ws.CLOSED)
{
this._ws.close();
}
},
/**
*
* Private Function: _onmessage websocket event handler
*
*/
_onmessage: function(packet)
{
var elem;
try {
elem = this._textToXML(packet.data);
} catch (e) {
if (e != "parsererror") { throw e; }
this.disconnect("strophe-parsererror");
}
if (elem === null) { return; }
this.xmlInput(elem);
this.rawInput(packet.data);
// remove handlers scheduled for deletion
var i, hand;
while (this.removeHandlers.length > 0)
{
hand = this.removeHandlers.pop();
i = this.handlers.indexOf(hand);
if (i >= 0) {
this.handlers.splice(i, 1);
}
}
// add handlers scheduled for addition
while (this.addHandlers.length > 0)
{
this.handlers.push(this.addHandlers.pop());
}
// send each incoming stanza through the handler chain
var i, newList;
newList = this.handlers;
this.handlers = [];
for (i = 0; i < newList.length; i++)
{
var hand = newList[i];
if (hand.isMatch(elem) && (this.authenticated || !hand.user))
{
if (hand.run(elem))
{
this.handlers.push(hand);
}
} else {
this.handlers.push(hand);
}
}
},
/**
*
* Private Function: _textToXML convert text to DOM Document object
*
*/
_textToXML: function (text) {
var doc = null;
if (window['DOMParser']) {
var parser = new DOMParser();
doc = parser.parseFromString(text, 'text/xml');
} else if (window['ActiveXObject']) {
var doc = new ActiveXObject("MSXML2.DOMDocument");
doc.async = false;
doc.loadXML(text);
} else {
throw Error('No DOMParser object found.');
}
return doc.firstChild;
},
/** PrivateFunction: _onIdle
* _Private_ handler to process events during idle cycle.
*
* This handler is called every 100ms to fire timed handlers that
* are ready and keep poll requests going.
*/
_onIdle: function ()
{
var i, thand, since, newList;
// remove timed handlers that have been scheduled for deletion
while (this.removeTimeds.length > 0)
{
thand = this.removeTimeds.pop();
i = this.timedHandlers.indexOf(thand);
if (i >= 0) {
this.timedHandlers.splice(i, 1);
}
}
// add timed handlers scheduled for addition
while (this.addTimeds.length > 0)
{
this.timedHandlers.push(this.addTimeds.pop());
}
// call ready timed handlers
var now = new Date().getTime();
newList = [];
for (i = 0; i < this.timedHandlers.length; i++)
{
thand = this.timedHandlers[i];
if (this.authenticated || !thand.user) {
since = thand.lastCalled + thand.period;
if (since - now <= 0) {
if (thand.run()) {
newList.push(thand);
}
} else {
newList.push(thand);
}
}
}
this.timedHandlers = newList;
// reactivate the timer
clearTimeout(this._idleTimeout);
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
}
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -46,13 +46,17 @@ public class Handset extends BaseVerb {
@NotNull(message=Handset.MISSING_STEREO)
public String stereo;
public Handset(String cryptoSuite, String localCrypto, String remoteCrypto, String codec, String stereo)
@NotNull(message=Handset.MISSING_MIXER)
public String mixer;
public Handset(String cryptoSuite, String localCrypto, String remoteCrypto, String codec, String stereo, String mixer)
{
this.cryptoSuite = cryptoSuite;
this.localCrypto = localCrypto;
this.remoteCrypto = remoteCrypto;
this.codec = codec;
this.stereo = stereo;
this.mixer = mixer;
}
@Override
......@@ -66,6 +70,7 @@ public class Handset extends BaseVerb {
.append("remoteCrypto",remoteCrypto)
.append("codec",codec)
.append("stereo",stereo)
.append("stereo",mixer)
.toString();
}
}
......@@ -2,4 +2,13 @@ package com.rayo.core.verb;
public class OffHookCommand extends AbstractVerbCommand {
private Handset handset;
public void setHandset(Handset handset) {
this.handset = handset;
}
public Handset getHandset() {
return this.handset;
}
}
......@@ -43,10 +43,7 @@ public class HandsetProvider extends BaseProvider {
@Override
protected Object processElement(Element element) throws Exception
{
if (element.getName().equals("handset")) {
return buildHandset(element);
} else if (ONHOOK_QNAME.equals(element.getQName())) {
if (ONHOOK_QNAME.equals(element.getQName())) {
return buildOnHookCommand(element);
} else if (OFFHOOK_QNAME.equals(element.getQName())) {
......@@ -69,34 +66,33 @@ public class HandsetProvider extends BaseProvider {
return complete;
}
private Object buildHandset(Element element) throws URISyntaxException
{
Handset handset = new Handset( element.attributeValue("cryptoSuite"),
element.attributeValue("localCrypto"),
element.attributeValue("remoteCrypto"),
private Object buildOffHookCommand(Element element) throws URISyntaxException {
Handset handset = new Handset( element.attributeValue("cryptosuite"),
element.attributeValue("localcrypto"),
element.attributeValue("remotecrypto"),
element.attributeValue("codec"),
element.attributeValue("stereo"));
return handset;
element.attributeValue("stereo"),
element.attributeValue("mixer"));
OffHookCommand command = new OffHookCommand();
command.setHandset(handset);
return command;
}
private Object buildOnHookCommand(Element element) throws URISyntaxException {
return new OnHookCommand();
}
private Object buildOffHookCommand(Element element) throws URISyntaxException {
return new OffHookCommand();
}
// Object -> XML
// ================================================================================
@Override
protected void generateDocument(Object object, Document document) throws Exception {
if (object instanceof Handset) {
createHandset((Handset) object, document);
} else if (object instanceof OnHookCommand) {
if (object instanceof OnHookCommand) {
createOnHookCommand((OnHookCommand) object, document);
} else if (object instanceof OffHookCommand) {
......@@ -107,24 +103,23 @@ public class HandsetProvider extends BaseProvider {
}
}
private void createHandset(Handset handset, Document document) throws Exception {
Element root = document.addElement(new QName("handset", NAMESPACE));
private void createOffHookCommand(OffHookCommand command, Document document) throws Exception {
Handset handset = command.getHandset();
Element root = document.addElement(new QName("offhook", NAMESPACE));
root.addAttribute("cryptoSuite", handset.cryptoSuite);
root.addAttribute("localCrypto", handset.localCrypto);
root.addAttribute("remoteCrypto", handset.cryptoSuite);
root.addAttribute("codec", handset.codec);
root.addAttribute("stereo", handset.stereo);
root.addAttribute("mixer", handset.mixer);
}
private void createOnHookCommand(OnHookCommand command, Document document) throws Exception {
document.addElement(new QName("onhook", NAMESPACE));
}
private void createOffHookCommand(OffHookCommand command, Document document) throws Exception {
document.addElement(new QName("offhook", NAMESPACE));
}
private void createHandsetCompleteEvent(SayCompleteEvent event, Document document) throws Exception {
addCompleteElement(document, event, COMPLETE_NAMESPACE);
}
......
......@@ -338,11 +338,11 @@ public abstract class CallHandler extends Thread {
}
if (otherCall != null) {
Logger.println("Call " + cp + " forwarding dtmf key "
+ dtmfKeys + " to " + otherCall);
otherCall.getMemberSender().setDtmfKeyToSend(dtmfKeys);
}
Logger.println("Call " + cp + " forwarding dtmf key " + dtmfKeys + " to " + otherCall);
otherCall.getMemberSender().setDtmfKeyToSend(dtmfKeys);
} else {
getMemberSender().setDtmfKeyToSend(dtmfKeys);
}
} else {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println(cp + " Call not established, ignoring dtmf");
......@@ -642,6 +642,30 @@ public abstract class CallHandler extends Thread {
cancel(callsToCancel, reason, false);
}
public static void hangupOwner(String ownerId, String reason) {
Vector callsToCancel = new Vector();
synchronized(activeCalls) {
/*
* Make a list of all the calls we want to cancel, then cancel them.
* We have to cancel them while not synchronized or
* we could deadlock.
*/
for (int i = 0; i < activeCalls.size(); i++) {
CallHandler call = (CallHandler)activeCalls.elementAt(i);
CallParticipant cp = call.getCallParticipant();
if (cp.getCallOwner().equals(ownerId)) {
callsToCancel.add(call);
}
}
}
cancel(callsToCancel, reason, false);
}
public static void suspendBridge() {
cancel(activeCalls, "bridge suspended", true);
}
......
......@@ -684,7 +684,7 @@ public class ConferenceManager {
leave(member, true); // leave the temporary conference
member.reinitialize(newConferenceManager);
member.reinitialize(newConferenceManager, false);
newConferenceManager.joinConference(member); // join the new conference
}
......
......@@ -584,6 +584,11 @@ public class ConferenceMember implements TreatmentDoneListener,
}
public void reinitialize(ConferenceManager conferenceManager) {
reinitialize(conferenceManager, true);
}
public void reinitialize(ConferenceManager conferenceManager, boolean initialize) {
synchronized (conferenceManager) {
Logger.println("Call " + this + " Reinitializing");
......@@ -615,10 +620,11 @@ public class ConferenceMember implements TreatmentDoneListener,
* When the call is transferred to the actual conference,
* the conference parameters may be different.
*/
initialize(callHandler, memberSender.getSendAddress(),
memberSender.getMediaInfo().getPayload(),
memberReceiver.getMediaInfo().getPayload(),
(byte) memberReceiver.getTelephoneEventPayload(), rtcpAddress);
if (initialize)
{
initialize(callHandler, memberSender.getSendAddress(), memberSender.getMediaInfo().getPayload(), memberReceiver.getMediaInfo().getPayload(), (byte) memberReceiver.getTelephoneEventPayload(), rtcpAddress);
}
}
}
conferenceManager.joinDistributedConference(this);
......
......@@ -515,7 +515,7 @@ public class IncomingCallHandler extends CallHandler
return ((IncomingCallHandler)callHandler).transferCall(conferenceId);
}
private static ConferenceManager transferCall(CallHandler callHandler, String conferenceId) throws IOException
public static ConferenceManager transferCall(CallHandler callHandler, String conferenceId) throws IOException
{
/*
* Get current conference manager and member.
......
......@@ -99,127 +99,204 @@ public class RayoComponent extends AbstractComponent
final Element element = iq.getChildElement();
final String namespace = element.getNamespaceURI();
if ("urn:xmpp:rayo:handset:1".equals(namespace)) {
IQ reply = null;
try {
Object object = handsetProvider.fromXML(element);
if ("urn:xmpp:rayo:handset:1".equals(namespace)) {
IQ reply = null;
if (object instanceof Handset) {
reply = handleHandset((Handset) object, iq);
Object object = handsetProvider.fromXML(element);
} else if (object instanceof OnHookCommand) {
reply = handleOnOffHookCommand((Handset) object, true, iq);
if (object instanceof OnHookCommand) {
reply = handleOnOffHookCommand((OnHookCommand) object, iq);
} else if (object instanceof OffHookCommand) {
reply = handleOnOffHookCommand((Handset) object, false, iq);
} else if (object instanceof OffHookCommand) {
reply = handleOnOffHookCommand((OffHookCommand) object, iq);
}
return reply;
}
}
if ("urn:xmpp:tropo:say:1".equals(namespace)) {
IQ reply = null;
if ("urn:xmpp:tropo:say:1".equals(namespace)) {
IQ reply = null;
Object object = sayProvider.fromXML(element);
Object object = sayProvider.fromXML(element);
if (object instanceof Say) {
reply = handleSay((Say) object, iq);
if (object instanceof Say) {
reply = handleSay((Say) object, iq);
} else if (object instanceof PauseCommand) {
reply = handlePauseCommand(true, iq);
} else if (object instanceof PauseCommand) {
reply = handlePauseCommand(true, iq);
} else if (object instanceof ResumeCommand) {
reply = handlePauseCommand(false, iq);
} else if (object instanceof ResumeCommand) {
reply = handlePauseCommand(false, iq);
}
return reply;
}
}
if ("urn:xmpp:rayo:1".equals(namespace)) {
IQ reply = null;
if ("urn:xmpp:rayo:1".equals(namespace)) {
IQ reply = null;
Object object = rayoProvider.fromXML(element);
Object object = rayoProvider.fromXML(element);
if (object instanceof ConnectCommand) {
if (object instanceof ConnectCommand) {
} else if (object instanceof AcceptCommand) {
} else if (object instanceof AcceptCommand) {
} else if (object instanceof HoldCommand) {
} else if (object instanceof HoldCommand) {
} else if (object instanceof UnholdCommand) {
} else if (object instanceof UnholdCommand) {
} else if (object instanceof MuteCommand) {
} else if (object instanceof MuteCommand) {
} else if (object instanceof UnmuteCommand) {
} else if (object instanceof UnmuteCommand) {
} else if (object instanceof JoinCommand) {
reply = handleJoinCommand((JoinCommand) object, iq);
} else if (object instanceof JoinCommand) {
reply = handleJoinCommand((JoinCommand) object, iq);
} else if (object instanceof UnjoinCommand) {
reply = handleUnjoinCommand((UnjoinCommand) object, iq);
} else if (object instanceof UnjoinCommand) {
reply = handleUnjoinCommand((UnjoinCommand) object, iq);
} else if (object instanceof AnswerCommand) {
} else if (object instanceof AnswerCommand) {
} else if (object instanceof HangupCommand) {
reply = handleHangupCommand(iq);
} else if (object instanceof HangupCommand) {
reply = handleHangupCommand(iq);
} else if (object instanceof RejectCommand) {
} else if (object instanceof RejectCommand) {
} else if (object instanceof RedirectCommand) {
} else if (object instanceof RedirectCommand) {
} else if (object instanceof DialCommand) {
reply = handleDialCommand((DialCommand) object, iq);
} else if (object instanceof DialCommand) {
reply = handleDialCommand((DialCommand) object, iq);
} else if (object instanceof StopCommand) {
} else if (object instanceof StopCommand) {
} else if (object instanceof DtmfCommand) {
reply = handleDtmfCommand((DtmfCommand) object, iq);
} else if (object instanceof DtmfCommand) {
reply = handleDtmfCommand((DtmfCommand) object, iq);
} else if (object instanceof DestroyMixerCommand) {
} else if (object instanceof DestroyMixerCommand) {
}
return reply;
}
return null; // feature not implemented.
return reply;
} catch (Exception e) {
e.printStackTrace();
final IQ reply = IQ.createResultIQ(iq);
reply.setError(PacketError.Condition.internal_server_error);
return reply;
}
return null; // feature not implemented.
}
private IQ handlePauseCommand(boolean flag, IQ iq)
private IQ handleOnOffHookCommand(Object object, IQ iq)
{
Log.info("RayoComponent handlePauseCommand " + iq.getFrom());
Log.info("RayoComponent handleOnOffHookCommand");
final IQ reply = IQ.createResultIQ(iq);
final String entityId = iq.getTo().getNode();
final String treatmentId = iq.getTo().getResource();
IQ reply = IQ.createResultIQ(iq);
try {
CallHandler callHandler = CallHandler.findCall(entityId);
if (object instanceof OnHookCommand)
{
String key = iq.getTo().getNode();
try {
callHandler.getMember().pauseTreatment(treatmentId, flag);
if (key != null)
{
RelayChannel channel = plugin.getRelayChannel(iq.getTo().getNode());
} catch (Exception e1) {
reply.setError(PacketError.Condition.internal_server_error);
if (channel != null)
{
handleOnOffHook(object, channel);
} else {
reply.setError(PacketError.Condition.item_not_found);
}
} else {
reply.setError(PacketError.Condition.item_not_found);
}
} catch (NoSuchElementException e) { // not call, lets try mixer
} else {
try {
ConferenceManager conferenceManager = ConferenceManager.findConferenceManager(entityId);
conferenceManager.getWGManager().pauseConferenceTreatment(treatmentId, flag);
final Handset handset = ((OffHookCommand) object).getHandset();
final RelayChannel channel = plugin.createRelayChannel(iq.getFrom(), handset);
} catch (ParseException e1) {
if (channel != null) // rayo handset component can have only one call
{
final Element childElement = reply.setChildElement("ref", "urn:xmpp:rayo:1");
reply.setError(PacketError.Condition.item_not_found);
childElement.addAttribute(HOST, LocalIPResolver.getLocalIP());
childElement.addAttribute(LOCAL_PORT, Integer.toString(channel.getPortA()));
childElement.addAttribute(REMOTE_PORT, Integer.toString(channel.getPortB()));
childElement.addAttribute(ID, channel.getAttachment());
childElement.addAttribute(URI, "handset:" + channel.getAttachment() + "@rayo." + getDomain() + "/" + iq.getFrom().getNode());
Log.debug("Created handset channel {}:{}, {}:{}, {}:{}", new Object[]{HOST, LocalIPResolver.getLocalIP(), LOCAL_PORT, Integer.toString(channel.getPortA()), REMOTE_PORT, Integer.toString(channel.getPortB())});
handleOnOffHook(object, channel);
} else {
reply.setError(PacketError.Condition.internal_server_error);
}
}
return reply;
}
private void handleOnOffHook(Object object, RelayChannel channel)
{
final boolean flag = object instanceof OnHookCommand;
Log.info("RayoComponent handleOnOffHook " + flag);
try {
OutgoingCallHandler callHandler = channel.getCallHandler();
if (callHandler != null)
{
callHandler.cancelRequest("Reseting handset to " + (flag ? "on" : "off") + "hook");
callHandler = null;
}
if (!flag) // offhook
{
Handset handset = ((OffHookCommand) object).getHandset();
String mediaPreference = "PCMU/8000/1";
if (handset.codec == null || "OPUS".equals(handset.codec))
mediaPreference = "PCM/48000/2";
CallParticipant cp = new CallParticipant();
cp.setCallId(channel.getAttachment());
cp.setProtocol("WebRtc");
cp.setConferenceId(handset.mixer);
cp.setMediaPreference(mediaPreference);
cp.setRelayChannel(channel);
cp.setDisplayName(channel.getAttachment());
cp.setVoiceDetection(true);
cp.setCallOwner(channel.getFrom().toString());
callHandler = new OutgoingCallHandler(this, cp);
callHandler.start();
channel.setCallHandler(callHandler);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private IQ handleSay(Say command, IQ iq)
{
Log.info("RayoComponent handleSay " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
IQ reply = IQ.createResultIQ(iq);
final String entityId = iq.getTo().getNode();
final String treatmentId = command.getPrompt().getText();
......@@ -260,11 +337,45 @@ public class RayoComponent extends AbstractComponent
return reply;
}
private IQ handlePauseCommand(boolean flag, IQ iq)
{
Log.info("RayoComponent handlePauseCommand " + iq.getFrom());
IQ reply = IQ.createResultIQ(iq);
final String entityId = iq.getTo().getNode();
final String treatmentId = iq.getTo().getResource();
try {
CallHandler callHandler = CallHandler.findCall(entityId);
try {
callHandler.getMember().pauseTreatment(treatmentId, flag);
} catch (Exception e1) {
reply.setError(PacketError.Condition.internal_server_error);
}
} catch (NoSuchElementException e) { // not call, lets try mixer
try {
ConferenceManager conferenceManager = ConferenceManager.findConferenceManager(entityId);
conferenceManager.getWGManager().pauseConferenceTreatment(treatmentId, flag);
} catch (ParseException e1) {
reply.setError(PacketError.Condition.item_not_found);
}
}
return reply;
}
private IQ handleHangupCommand(IQ iq)
{
Log.info("RayoComponent handleHangupCommand " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
IQ reply = IQ.createResultIQ(iq);
try {
CallHandler callHandler = CallHandler.findCall(iq.getTo().getNode());
......@@ -281,7 +392,7 @@ public class RayoComponent extends AbstractComponent
{
Log.info("RayoComponent handleDtmfCommand " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
IQ reply = IQ.createResultIQ(iq);
try {
CallHandler callHandler = CallHandler.findCall(iq.getTo().getNode());
......@@ -298,7 +409,7 @@ public class RayoComponent extends AbstractComponent
{
Log.info("RayoComponent handleJoinCommand " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
IQ reply = IQ.createResultIQ(iq);
String mixer = null;
......@@ -328,7 +439,7 @@ public class RayoComponent extends AbstractComponent
{
Log.info("RayoComponent handleUnjoinCommand " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
IQ reply = IQ.createResultIQ(iq);
try {
IncomingCallHandler.transferCall(iq.getTo().getNode(), defaultIncomingConferenceId);
......@@ -358,7 +469,7 @@ public class RayoComponent extends AbstractComponent
{
Log.info("RayoComponent handleHandsetDialCommand " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
IQ reply = IQ.createResultIQ(iq);
Map<String, String> headers = command.getHeaders();
String from = command.getFrom().toString();
......@@ -368,10 +479,10 @@ public class RayoComponent extends AbstractComponent
boolean toXmpp = to.indexOf("xmpp:") == 0;
String codec = headers.get("caller-id");
String mixer = "rayo-mixer-" + System.currentTimeMillis();
String handsetId = headers.get("handset");
String mixer = null;
String callerId = headers.get("caller-id");
String calledId = headers.get("called-id");
String callId = headers.get("call-id");
String callerName = headers.get("caller-name");
String calledName = headers.get("called-name");
......@@ -386,27 +497,32 @@ public class RayoComponent extends AbstractComponent
}
}
if (callerId == null) callerId = "rayo-caller-" + System.currentTimeMillis();
if (calledId == null) calledId = "rayo-called-" + System.currentTimeMillis();
if (mixer != null)
{
if (callId == null) callId = "rayo-call-" + System.currentTimeMillis();
if (callerName == null) callerName = iq.getFrom().toString();
CallParticipant cp = new CallParticipant();
cp.setVoiceDetection(true);
cp.setCallOwner(iq.getFrom().toString());
CallParticipant cp = new CallParticipant();
cp.setVoiceDetection(true);
cp.setCallOwner(iq.getFrom().toString());
if (toPhone)
{
cp.setMediaPreference("PCMU/8000/1");
cp.setProtocol("SIP");
cp.setCallId(calledId);
cp.setPhoneNumber(to.substring(4));
cp.setName(calledName == null ? cp.getPhoneNumber() : calledName);
if (toPhone)
{
cp.setMediaPreference("PCMU/8000/1");
cp.setProtocol("SIP");
cp.setCallId(callId);
cp.setDisplayName(callerName);
cp.setPhoneNumber(to);
cp.setName(calledName == null ? cp.getPhoneNumber() : calledName);
cp.setConferenceId(mixer);
mixer = mixer == null ? cp.getPhoneNumber() : mixer;
cp.setConferenceId(mixer);
reply = doPhoneAndPcCall(handsetId, cp, reply);
doPhoneAndPcCall(iq.getFrom(), cp, reply, mixer);
} else if (toXmpp){
} else if (toXmpp){
} else {
reply.setError(PacketError.Condition.feature_not_implemented);
}
} else {
reply.setError(PacketError.Condition.feature_not_implemented);
......@@ -419,7 +535,7 @@ public class RayoComponent extends AbstractComponent
{
Log.info("RayoComponent handleBridgedDialCommand " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
IQ reply = IQ.createResultIQ(iq);
Map<String, String> headers = command.getHeaders();
String from = command.getFrom().toString();
......@@ -499,7 +615,7 @@ public class RayoComponent extends AbstractComponent
if (toHandset) // (no offer)
{
doPhoneAndPcCall(new JID(to.substring(8)), cp, reply, mixer);
//doPhoneAndPcCall(new JID(to.substring(8)), cp, reply, mixer);
} else { // (offer)
......@@ -519,7 +635,7 @@ public class RayoComponent extends AbstractComponent
if (fromHandset) // (no offer)
{
doPhoneAndPcCall(new JID(from.substring(8)), cp, reply, mixer);
//doPhoneAndPcCall(new JID(from.substring(8)), cp, reply, mixer);
} else { // (offer)
......@@ -544,28 +660,40 @@ public class RayoComponent extends AbstractComponent
return reply;
}
private void doPhoneAndPcCall(JID handsetJid, CallParticipant cp, IQ reply, String mixer)
private IQ doPhoneAndPcCall(String handsetId, CallParticipant cp, IQ reply)
{
Log.info("RayoComponent doPhoneAndPcCall " + handsetJid + " " + mixer);
String mixer = cp.getConferenceId();
Log.info("RayoComponent doPhoneAndPcCall " + handsetId + " " + mixer);
RelayChannel channel = plugin.getRelayChannel(handsetJid.getNode());
RelayChannel channel = plugin.getRelayChannel(handsetId);
if (channel != null)
{
try {
setMixer(mixer, channel, reply);
IncomingCallHandler.transferCall(channel.getAttachment(), mixer);
OutgoingCallHandler outgoingCallHandler = new OutgoingCallHandler(this, cp);
//OutgoingCallHandler handsetHandler = channel.getCallHandler();
//outgoingCallHandler.setOtherCall(handsetHandler);
//handsetHandler.setOtherCall(outgoingCallHandler);
outgoingCallHandler.start();
final Element childElement = reply.setChildElement("ref", "urn:xmpp:rayo:1");
childElement.addAttribute(URI, (String) "xmpp:" + cp.getCallId() + "@rayo." + getDomain());
childElement.addAttribute(ID, (String) cp.getCallId());
} catch (Exception e) {
e.printStackTrace();
reply.setError(PacketError.Condition.internal_server_error);
}
OutgoingCallHandler outgoingCallHandler = new OutgoingCallHandler(this, cp);
outgoingCallHandler.start();
} else {
reply.setError(PacketError.Condition.item_not_found);
}
return reply;
}
private void setMixer(String mixer, RelayChannel channel, IQ reply)
......@@ -584,91 +712,6 @@ public class RayoComponent extends AbstractComponent
}
private IQ handleHandset(Handset handset, IQ iq)
{
Log.info("RayoComponent handleHandset " + iq.getFrom());
final IQ reply = IQ.createResultIQ(iq);
final RelayChannel channel = plugin.createRelayChannel(iq.getFrom(), handset);
if (channel != null) // rayo handset component can have only one call
{
final Element childElement = reply.setChildElement("ref", "urn:xmpp:rayo:1");
childElement.addAttribute(HOST, LocalIPResolver.getLocalIP());
childElement.addAttribute(LOCAL_PORT, Integer.toString(channel.getPortA()));
childElement.addAttribute(REMOTE_PORT, Integer.toString(channel.getPortB()));
childElement.addAttribute(ID, channel.getAttachment());
childElement.addAttribute(URI, "handset:" + channel.getAttachment() + "@rayo." + getDomain() + "/" + iq.getFrom().getNode());
Log.debug("Created handset channel {}:{}, {}:{}, {}:{}", new Object[]{HOST, LocalIPResolver.getLocalIP(), LOCAL_PORT, Integer.toString(channel.getPortA()), REMOTE_PORT, Integer.toString(channel.getPortB())});
} else {
reply.setError(PacketError.Condition.internal_server_error);
}
return reply;
}
private IQ handleOnOffHookCommand(Handset handset, boolean flag, IQ iq)
{
Log.info("RayoComponent handleOnOffHookCommand " + flag);
final IQ reply = IQ.createResultIQ(iq);
RelayChannel channel = plugin.getRelayChannel(iq.getTo().getNode());
if (channel != null)
{
handleOnOffHook(flag, channel, handset);
} else {
reply.setError(PacketError.Condition.item_not_found);
}
return reply;
}
private void handleOnOffHook(boolean flag, RelayChannel channel, Handset handset)
{
Log.info("RayoComponent handleOnOffHook " + flag);
try {
OutgoingCallHandler callHandler = channel.getCallHandler();
if (callHandler != null)
{
callHandler.cancelRequest("Reseting handset to " + (flag ? "on" : "off") + "hook");
callHandler = null;
}
if (!flag) // offhook
{
String mediaPreference = "PCMU/8000/1";
if (handset.codec == null || "OPUS".equals(handset.codec))
mediaPreference = "PCM/48000/2";
CallParticipant cp = new CallParticipant();
cp.setCallId(channel.getAttachment());
cp.setProtocol("WebRtc");
cp.setConferenceId(defaultIncomingConferenceId);
cp.setMediaPreference(mediaPreference);
cp.setRelayChannel(channel);
cp.setDisplayName(channel.getAttachment());
cp.setVoiceDetection(true);
cp.setCallOwner(channel.getFrom().toString());
callHandler = new OutgoingCallHandler(this, cp);
callHandler.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String getDomain() {
return XMPPServer.getInstance().getServerInfo().getXMPPDomain();
......@@ -687,7 +730,7 @@ public class RayoComponent extends AbstractComponent
public void callEventNotification(com.sun.voip.CallEvent callEvent)
{
Log.info("RayoComponent callEventNotification " + callEvent);
JID from = new JID(callEvent.getInfo());
JID from = new JID(callEvent.getCallInfo());
if (from != null)
{
......@@ -695,13 +738,13 @@ public class RayoComponent extends AbstractComponent
String callState = callEvent.getCallState().toString();
Map<String, String> headers = new HashMap<String, String>();
headers.put("info", callEvent.getInfo() == null ? "" : callEvent.getInfo());
headers.put("callId", callEvent.getCallId() == null ? "" : callEvent.getCallId());
headers.put("callId", callEvent.getCallId());
headers.put("info", callEvent.getCallInfo() == null ? "" : callEvent.getCallInfo());
headers.put("mixer", callEvent.getConferenceId() == null ? "" : callEvent.getConferenceId());
headers.put("callInfo", callEvent.getCallInfo() == null ? "" : callEvent.getCallInfo());
Presence presence = new Presence();
presence.setFrom("rayo." + getDomain());
presence.setFrom(callEvent.getCallId() + "@rayo." + getDomain());
presence.setTo(from);
if ("001 STATE CHANGED".equals(myEvent)) {
......@@ -845,7 +888,7 @@ public class RayoComponent extends AbstractComponent
}
} catch (Exception e) {
e.printStackTrace();
//e.printStackTrace();
}
}
......
......@@ -53,6 +53,9 @@ import com.rayo.core.verb.*;
import org.voicebridge.*;
import com.sun.voip.server.*;
import com.sun.voip.*;
public class RayoPlugin implements Plugin, SessionEventListener {
......@@ -331,6 +334,8 @@ public class RayoPlugin implements Plugin, SessionEventListener {
public void anonymousSessionDestroyed(Session session)
{
Log.debug("RayoPlugin anonymousSessionDestroyed "+ session.getAddress().toString() + "\n" + ((ClientSession) session).getPresence().toXML());
CallHandler.hangupOwner(session.getAddress().toString(), "User has ended session");
}
public void resourceBound(Session session)
......@@ -347,5 +352,6 @@ public class RayoPlugin implements Plugin, SessionEventListener {
{
Log.debug("RayoPlugin sessionDestroyed "+ session.getAddress().toString() + "\n" + ((ClientSession) session).getPresence().toXML());
CallHandler.hangupOwner(session.getAddress().toString(), "User has ended session");
}
}
......@@ -297,6 +297,33 @@ public class Config implements MUCEventListener {
}
}
public static void recordCall(String username, String addressFrom, String addressTo, long datetime, int duration, String calltype) {
String sql = "INSERT INTO ofSipPhoneLog (username, addressFrom, addressTo, datetime, duration, calltype) values (?, ?, ?, ?, ?, ?)";
Connection con = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
psmt = con.prepareStatement(sql);
psmt.setString(1, username);
psmt.setString(2, addressFrom);
psmt.setString(3, addressTo);
psmt.setLong(4, datetime);
psmt.setInt(5, duration);
psmt.setString(6, calltype);
psmt.executeUpdate();
} catch (SQLException e) {
Log.debug(e.getMessage(), e);
} finally {
DbConnectionManager.closeConnection(rs, psmt, con);
}
}
public ProxyCredentials getProxyCredentialsByUser(String username)
{
......
......@@ -249,6 +249,10 @@ public class RelayChannel {
return callHandler;
}
public void setCallHandler(OutgoingCallHandler callHandler) {
this.callHandler = callHandler;
}
public Handset getHandset() {
return handset;
}
......
/**
* RAYO XMPP extensions
* RAYO : XMPP -0327 plugin for Strophe
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Strophe.addConnectionPlugin('rayo',
{
_connection: null,
init: function(conn)
{
this._connection = conn;
this.calls = {};
Strophe.addConnectionPlugin('rayo', {
_connection: null,
init: function(conn) {
this._connection = conn;
/* extend name space
* NS.RAYO - XMPP -0327
*
*/
Strophe.addNamespace('RAYO_CORE', "urn:xmpp:rayo:1");
Strophe.addNamespace('RAYO_CALL', "urn:xmpp:rayo:call:1");
Strophe.addNamespace('RAYO_MIXER', "urn:xmpp:rayo:mixer:1");
Strophe.addNamespace('RAYO_EXT', "urn:xmpp:rayo:ext:1");
Strophe.addNamespace('RAYO_EXT_COMPLETE', "urn:xmpp:rayo:ext:complete:1");
Strophe.addNamespace('RAYO_INPUT', "urn:xmpp:rayo:input:1");
Strophe.addNamespace('RAYO_INPUT_COMPLETE', "urn:xmpp:rayo:input:complete:1");
Strophe.addNamespace('RAYO_OUTPUT', "urn:xmpp:rayo:output:1");
Strophe.addNamespace('RAYO_OUTPUT_COMPLETE', "urn:xmpp:rayo:output:complete:1");
Strophe.addNamespace('RAYO_PROMPT', "urn:xmpp:rayo:promprt:1");
Strophe.addNamespace('RAYO_RECORD', "urn:xmpp:rayo:record:1");
Strophe.addNamespace('RAYO_RECORD_COMPLETE', "urn:xmpp:rayo:record:complete:1");
Strophe.addNamespace('RAYO_SAY', "urn:xmpp:tropo:say:1");
Strophe.addNamespace('RAYO_SAY_COMPLETE', "urn:xmpp:tropo:say:complete:1");
Strophe.addNamespace('RAYO_HANDSET', "urn:xmpp:rayo:handset:1");
Strophe.addNamespace('RAYO_HANDSET_COMPLETE', "urn:xmpp:rayo:handset:complete:1");
this._connection.addHandler(this.handlePresence.bind(this), null,"presence", null, null, null);
},
handset: function(config)
{
this.config = config;
var self = this;
var iq = $iq({to: server, from: this._connection.jid, type: "get"})
.c("handset", {xmlns: Strophe.NS.RAYO_HANDSET, cryptoSuite: config.cryptoSuite, localCrypto: config.localCrypto, remoteCrypto: config.remoteCrypto, mixer: config.mixer, codec: config.codec, stereo: config.stereo});
this._connection.sendIQ(iq, function(response)
{
$('ref', response).each(function()
Strophe.addNamespace('RAYO_CORE', "urn:xmpp:rayo:1");
Strophe.addNamespace('RAYO_CALL', "urn:xmpp:rayo:call:1");
Strophe.addNamespace('RAYO_MIXER', "urn:xmpp:rayo:mixer:1");
Strophe.addNamespace('RAYO_EXT', "urn:xmpp:rayo:ext:1");
Strophe.addNamespace('RAYO_EXT_COMPLETE', "urn:xmpp:rayo:ext:complete:1");
Strophe.addNamespace('RAYO_INPUT', "urn:xmpp:rayo:input:1");
Strophe.addNamespace('RAYO_INPUT_COMPLETE', "urn:xmpp:rayo:input:complete:1");
Strophe.addNamespace('RAYO_OUTPUT', "urn:xmpp:rayo:output:1");
Strophe.addNamespace('RAYO_OUTPUT_COMPLETE', "urn:xmpp:rayo:output:complete:1");
Strophe.addNamespace('RAYO_PROMPT', "urn:xmpp:rayo:prompt:1");
Strophe.addNamespace('RAYO_RECORD', "urn:xmpp:rayo:record:1");
Strophe.addNamespace('RAYO_RECORD_COMPLETE', "urn:xmpp:rayo:record:complete:1");
Strophe.addNamespace('RAYO_SAY', "urn:xmpp:tropo:say:1");
Strophe.addNamespace('RAYO_SAY_COMPLETE', "urn:xmpp:tropo:say:complete:1");
Strophe.addNamespace('RAYO_HANDSET', "urn:xmpp:rayo:handset:1");
Strophe.addNamespace('RAYO_HANDSET_COMPLETE', "urn:xmpp:rayo:handset:complete:1");
this._connection.addHandler(this.handlePresence.bind(this), null,"presence", null, null, null);
console.log('Rayo plugin initialised');
},
offhook: function(handset)
{
//console.log('Rayo plugin offhook');
if (this.handset && this.handset.mixer) // reuse mixer
{
var ref = {host: $(this).attr('host'), localport: $(this).attr('localport'), remoteport: $(this).attr('remoteport'), id: $(this).attr('id'), uri: $(this).attr('uri')}
self.config.onStart(ref);
});
});
},
handlePresence: function(presence)
{
handset.mixer = this.handset.mixer;
}
if (!handset.mixer) handset.mixer = "rayo-mixer-" + Math.random().toString(36).substr(2,9);
this.handset = handset;
var that = this;
}
navigator.webkitGetUserMedia({audio:true, video:false}, function(stream)
{
that.localStream = stream;
that._offhook1();
}, function(error) {
if (that.handset && that.handset.onError) that.handset.onError(error);
});
},
_offhook1: function()
{
//console.log('Rayo plugin _offhook1 ');
var that = this;
that.pc1 = new webkitRTCPeerConnection(null);
that.pc1.addStream(that.localStream);
that.pc1.createOffer(function(desc)
{
//console.log(desc.sdp);
that.pc1.setLocalDescription(desc);
var sdpObj1 = WebrtcSDP.parseSDP(desc.sdp);
sdpObj1.contents[0].codecs = [{clockrate: "48000", id: "111", name: "opus", channels: 2}];
var sdp = WebrtcSDP.buildSDP(sdpObj1);
//console.log(sdp);
that.cryptoSuite = sdpObj1.contents[0].crypto['crypto-suite'];
that.remoteCrypto = sdpObj1.contents[0].crypto['key-params'].substring(7);
that.pc2.setRemoteDescription(new RTCSessionDescription({type: "offer", sdp : sdp}));
that.pc2.createAnswer(function(desc)
{
that.pc2.setLocalDescription(desc);
var sdpObj2 = WebrtcSDP.parseSDP(desc.sdp);
that.localCrypto = sdpObj2.contents[0].crypto['key-params'].substring(7);
var sdp = WebrtcSDP.buildSDP(sdpObj2);
//console.log(sdp);
that.pc1.setRemoteDescription(new RTCSessionDescription({type: "answer", sdp : sdp}));
that._offhook2();
});
});
that.pc2 = new webkitRTCPeerConnection(null);
that.pc2.onaddstream = function(e)
{
that.audio = new Audio();
that.audio.autoplay = true;
that.audio.src = webkitURL.createObjectURL(e.stream)
};
},
_offhook2: function()
{
//console.log('Rayo plugin _offhook2 ' + this.cryptoSuite + " " + this.localCrypto + " " + this.remoteCrypto);
var that = this;
var iq = $iq({to: "rayo." + that._connection.domain, from: that._connection.jid, type: "get"}).c("offhook", {xmlns: Strophe.NS.RAYO_HANDSET, cryptoSuite: that.cryptoSuite, localCrypto: that.localCrypto, remoteCrypto: that.remoteCrypto, codec: that.handset.codec, stereo: that.handset.stereo, mixer: that.handset.mixer});
//console.log(iq.toString())
that._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') == "result")
{
$('ref', response).each(function()
{
that.handsetId = $(this).attr('id');
that.handsetUri = $(this).attr('uri');
that.relayHost = $(this).attr('host');
that.relayLocalPort = $(this).attr('localport');
that.relayRemotePort = $(this).attr('remoteport');
that.pc2.addIceCandidate(new RTCIceCandidate({sdpMLineIndex: "0", candidate: "a=candidate:3707591233 1 udp 2113937151 " + that.relayHost + " " + that.relayRemotePort + " typ host generation 0"}));
that.pc1.addIceCandidate(new RTCIceCandidate({sdpMLineIndex: "0", candidate: "a=candidate:3707591233 1 udp 2113937151 " + that.relayHost + " " + that.relayLocalPort + " typ host generation 0"}));
});
} else {
if (handset.onError) handset.onError("offhook failure");
}
});
},
onhook: function()
{
//console.log('Rayo plugin onhook ' + this.handsetId);
that = this;
var server = this.handsetId + "@rayo." + this._connection.domain;
this._connection.sendIQ($iq({to: server, from: this._connection.jid, type: "get"}).c('onhook', {xmlns: Strophe.NS.RAYO_HANDSET}), function(response)
{
that.localStream.stop();
that.localStream = null;
that.pc1.close();
that.pc2.close();
that.pc1 = null;
that.pc2 = null;
});
},
dial: function(config)
{
//console.log('Rayo plugin dial');
//console.log(config)
var callId = "rayo-call-" + Math.random().toString(36).substr(2,9);
var that = this;
var iq = $iq({to: "rayo." + this._connection.domain, from: this._connection.jid, type: "get"}).c("dial", {xmlns: Strophe.NS.RAYO_CORE, to: config.to, from: config.from});
iq.c("join", {xmlns: Strophe.NS.RAYO_CORE, 'mixer-name': this.handset.mixer}).up();
iq.c("header", {name: "call-id", value: callId}).up();
iq.c("header", {name: "handset", value: that.handsetId}).up();
if (config.headers)
{
for (var i=0; i<headers.length; i++)
{
iq.c("header", {name: config.headers[i].name, value: config.headers[i].value}).up();
}
}
//console.log(iq.toString());
this._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') != "result")
{
if (config.onError) config.onError("dial failure");
}
});
this.calls[callId] =
{
callId: callId,
onRing: config.onRing,
onAnswer: config.onAnswer,
onError: config.onError,
onEnd: config.onEnd,
from: config.from,
to: config.to,
hangup: function()
{
//console.log("hangup " + this.callId);
var iq = $iq({to: this.callId + "@rayo." + that._connection.domain, from: that._connection.jid, type: "get"}).c("hangup", {xmlns: Strophe.NS.RAYO_CORE});
that._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') != "result")
{
if (config.onError) config.onError("hangup failure");
}
});
},
answer: function()
{
},
digit: function(key)
{
//console.log("digit " + this.callId + " " + key);
var iq = $iq({to: this.callId + "@rayo." + that._connection.domain, from: that._connection.jid, type: "get"}).c("dtmf", {xmlns: Strophe.NS.RAYO_CORE, tones: key});
//console.log(iq.toString());
that._connection.sendIQ(iq, function(response)
{
if ($(response).attr('type') != "result")
{
if (config.onError) config.onError("dtmf failure");
}
});
}
};
return this.calls[callId];
},
handlePresence: function(presence)
{
//console.log('Rayo plugin handlePresence');
//console.log(presence);
var that = this;
var from = $(presence).attr('from');
$(presence).find('complete').each(function()
{
$(this).find('success').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_HANDSET_COMPLETE)
{
that.onhook();
if (that.handset && that.handset.onEnd) that.handset.onEnd();
}
});
});
$(presence).find('joined').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
if (that.handsetId == Strophe.getNodeFromJid(from))
{
if (that.handset && that.handset.onReady) that.handset.onReady();
}
}
});
$(presence).find('unjoined').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
if (that.handsetId == Strophe.getNodeFromJid(from))
{
if (that.handset && that.handset.onUnready) that.handset.onUnready();
}
}
});
$(presence).find('ringing').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
var callId = Strophe.getNodeFromJid(from);
var call = that.calls[callId];
if (call && call.onRing) call.onRing(call);
}
});
$(presence).find('answered').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
var callId = Strophe.getNodeFromJid(from);
var call = that.calls[callId];
if (call && call.onAnswer) call.onAnswer(call);
}
});
$(presence).find('end').each(function()
{
if ($(this).attr('xmlns') == Strophe.NS.RAYO_CORE)
{
var callId = Strophe.getNodeFromJid(from);
var call = that.calls[callId];
if (call && call.onEnd) call.onEnd(call);
that.calls[callId] = null;
}
});
return true;
}
});
;(function() {
// Helper library to translate to and from SDP and an intermediate javascript object
// representation of candidates, offers and answers
_parseLine = function(line) {
var s1 = line.split("=");
return {
type: s1[0],
contents: s1[1]
}
}
_parseA = function(attribute) {
var s1 = attribute.split(":");
return {
key: s1[0],
params: attribute.substring(attribute.indexOf(":")+1).split(" ")
}
}
_parseM = function(media) {
var s1 = media.split(" ");
return {
type:s1[0],
port:s1[1],
proto:s1[2],
pts:media.substring((s1[0]+s1[1]+s1[2]).length+3).split(" ")
}
}
_parseO = function(media) {
var s1 = media.split(" ");
return {
username:s1[0],
id:s1[1],
ver:s1[2],
nettype:s1[3],
addrtype:s1[4],
address:s1[5]
}
}
_parseC = function(media) {
var s1 = media.split(" ");
return {
nettype:s1[0],
addrtype:s1[1],
address:s1[2]
}
}
_parseCandidate = function (params) {
var candidate = {
foundation:params[0],
component:params[1],
protocol:params[2],
priority:params[3],
ip:params[4],
port:params[5]
};
var index = 6;
while (index + 1 <= params.length) {
if (params[index] == "typ") candidate["type"] = params[index+1];
if (params[index] == "generation") candidate["generation"] = params[index+1];
if (params[index] == "username") candidate["username"] = params[index+1];
if (params[index] == "password") candidate["password"] = params[index+1];
index += 2;
}
return candidate;
}
//a=rtcp:1 IN IP4 0.0.0.0
_parseRtcp = function (params) {
var rtcp = {
port:params[0]
};
if (params.length > 1) {
rtcp['nettype'] = params[1];
rtcp['addrtype'] = params[2];
rtcp['address'] = params[3];
}
return rtcp;
}
//a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:zvrxmXFpomTqz7CJYhN5G7JM3dVVxG/fZ0Il6DDo
_parseCrypto = function(params) {
var crypto = {
'tag':params[0],
'crypto-suite':params[1],
'key-params':params[2]
}
return crypto;
}
_parseFingerprint = function(params) {
var finger = {
'hash':params[0],
'print':params[1],
'required':'1'
}
return finger;
}
//a=rtpmap:101 telephone-event/8000"
_parseRtpmap = function(params) {
var bits = params[1].split("/");
var codec = {
id: params[0],
name: bits[0],
clockrate: bits[1]
}
if (bits.length >2){
codec.channels = bits[2];
}
return codec;
}
_parseSsrc = function(params, ssrc) {
var ssrcObj = {};
if (ssrc != undefined) ssrcObj = ssrc;
ssrcObj.ssrc = params[0];
var value = params[1];
ssrcObj[value.split(":")[0]] = value.split(":")[1];
return ssrcObj;
}
_parseGroup = function(params) {
var group = {
type: params[0]
}
group.contents = [];
var index = 1;
while (index + 1 <= params.length) {
group.contents.push(params[index]);
index = index + 1;
}
return group;
}
_parseMid = function(params) {
var mid = params[0];
return mid;
}
// Object -> SDP
_buildCandidate = function(candidateObj, iceObj) {
var c = candidateObj;
var sdp = "a=candidate:" + c.foundation + " " +
c.component + " " +
c.protocol.toUpperCase() + " " +
c.priority + " " +
c.ip + " " +
c.port;
if (c.type) sdp = sdp + " typ host"; //+ c.type;
if (c.component == 1) sdp = sdp + " name rtp";
if (c.component == 2) sdp = sdp + " name rtcp";
sdp = sdp + " network_name en0";
if (c.username && c.password ){
sdp = sdp + " username "+c.username;
sdp = sdp + " password "+c.password;
if (!iceObj.ufrag) iceObj.ufrag = c.username;
if (!iceObj.pwd) iceObj.pwd=c.username;;
} else if (iceObj) {
if (iceObj.ufrag) sdp = sdp + " username " + iceObj.ufrag;
if (iceObj.pwd) sdp = sdp + " password " + iceObj.pwd;
} else {
sdp = sdp+ " username root password mysecret";// I know a secret
}
if (c.generation) sdp = sdp + " generation " + c.generation;
sdp = sdp + "\r\n";
return sdp;
}
_buildCodec = function(codecObj) {
var sdp = "a=rtpmap:" + codecObj.id + " " + codecObj.name + "/" + codecObj.clockrate
if (codecObj.channels){
sdp+="/"+codecObj.channels;
}
sdp += "\r\n";
if (codecObj.ptime){
sdp+="a=ptime:"+codecObj.ptime;
sdp += "\r\n";
} else if (codecObj.name.toLowerCase().indexOf("opus")==0){
sdp+="a=ptime:20\r\n";
sdp+="a=fmtp:"+codecObj.id+" minptime=20 stereo=1\r\n";
}
if (codecObj.name.toLowerCase().indexOf("telephone-event")==0){
sdp+="a=fmtp:"+codecObj.id+" 0-15\r\n";
}
return sdp;
}
_buildCrypto = function(cryptoObj) {
var sdp = "a=crypto:" + cryptoObj.tag + " " + cryptoObj['crypto-suite'] + " " +
cryptoObj["key-params"] + "\r\n";
return sdp;
}
_buildFingerprint = function(fingerObj) {
var sdp = "a=fingerprint:" + fingerObj.hash + " " + fingerObj.print + "\r\n";
return sdp;
}
_buildIce= function(ice) {
var sdp="";
if (ice.ufrag) {
if (!ice.filterLines) {
sdp = sdp + "a=ice-ufrag:" + ice.ufrag + "\r\n";
sdp = sdp + "a=ice-pwd:" + ice.pwd + "\r\n";
}
if (ice.options) {
sdp = sdp + "a=ice-options:" + ice.options + "\r\n";
}
}
return sdp;
}
_buildSessProps = function(sdpObj) {
var sdp ="";
if (sdpObj.fingerprint) {
sdp = sdp + _buildFingerprint(sdpObj.fingerprint);
}
if (sdpObj.ice) {
sdp= sdp + _buildIce(sdpObj.ice);
}
return sdp;
}
_buildMedia =function(sdpObj) {
var sdp ="";
sdp += "m=" + sdpObj.media.type + " " + sdpObj.media.port + " " + sdpObj.media.proto;
var mi = 0;
while (mi + 1 <= sdpObj.media.pts.length) {
sdp = sdp + " " + sdpObj.media.pts[mi];
mi = mi + 1;
}
sdp = sdp + "\r\n";
if (sdpObj.connection) {
sdp = sdp + "c=" + sdpObj.connection.nettype + " " + sdpObj.connection.addrtype + " " +
sdpObj.connection.address + "\r\n";
}
if (sdpObj.mid) {
sdp = sdp + "a=mid:" + sdpObj.mid + "\r\n";
}
if (sdpObj.rtcp) {
sdp = sdp + "a=rtcp:" + sdpObj.rtcp.port + " " + sdpObj.rtcp.nettype + " " +
sdpObj.rtcp.addrtype + " " +
sdpObj.rtcp.address + "\r\n";
}
if (sdpObj.ice) {
sdp= sdp + _buildIce(sdpObj.ice);
}
var ci = 0;
while (ci + 1 <= sdpObj.candidates.length) {
sdp = sdp + _buildCandidate(sdpObj.candidates[ci], sdpObj.ice);
ci = ci + 1;
}
if (sdpObj.direction) {
if (sdpObj.direction == "recvonly") {
sdp = sdp + "a=recvonly\r\n";
} else if (sdpObj.direction == "sendonly") {
sdp = sdp + "a=sendonly\r\n";
} else if (sdpObj.direction == "none") {
sdp = sdp;
} else {
sdp = sdp + "a=sendrecv\r\n";
}
} else {
sdp = sdp + "a=sendrecv\r\n";
}
if (sdpObj['rtcp-mux']) {
sdp = sdp + "a=rtcp-mux" + "\r\n";
}
if (sdpObj.crypto) {
sdp = sdp + _buildCrypto(sdpObj.crypto);
}
if (sdpObj.fingerprint) {
sdp = sdp + _buildFingerprint(sdpObj.fingerprint);
}
var cdi = 0;
while (cdi + 1 <= sdpObj.codecs.length) {
sdp = sdp + _buildCodec(sdpObj.codecs[cdi]);
cdi = cdi + 1;
}
if (sdpObj.ssrc) {
var ssrc = sdpObj.ssrc;
if (ssrc.cname) sdp = sdp + "a=ssrc:" + ssrc.ssrc + " " + "cname:" + ssrc.cname + "\r\n";
if (ssrc.mslabel) sdp = sdp + "a=ssrc:" + ssrc.ssrc + " " + "mslabel:" + ssrc.mslabel + "\r\n";
if (ssrc.label) sdp = sdp + "a=ssrc:" + ssrc.ssrc + " " + "label:" + ssrc.label + "\r\n";
}
return sdp;
}
WebrtcSDP = {
getAttributes: function(element)
{
var res = {},
attr;
for(var i = 0, len = element.attributes.length; i < len; i++) {
if(element.attributes.hasOwnProperty(i)) {
attr = element.attributes[i];
res[attr.name] = attr.value;
}
}
return res;
},
each: function( object, callback, args )
{
var name, i = 0,
length = object.length,
isObj = length === undefined || $.isFunction(object);
if ( args ) {
if ( isObj ) {
for ( name in object ) {
if ( callback.apply( object[ name ], args ) === false ) {
break;
}
}
} else {
for ( ; i < length; ) {
if ( callback.apply( object[ i++ ], args ) === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isObj ) {
for ( name in object ) {
if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
break;
}
}
} else {
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
}
}
return object;
},
buildJingle: function(jingle, blob) {
var description = "urn:xmpp:jingle:apps:rtp:1";
var c = jingle;
if (blob.group) {
var bundle = "";
c.c('group', {type:blob.group.type,
contents:blob.group.contents.join(",")}).up();
}
WebrtcSDP.util.each(blob.contents, function () {
var sdpObj = this;
var desc = {xmlns:description,
media:sdpObj.media.type};
if (sdpObj.ssrc) {
desc.ssrc = sdpObj.ssrc.ssrc,
desc.cname = sdpObj.ssrc.cname,
desc.mslabel = sdpObj.ssrc.mslabel,
desc.label = sdpObj.ssrc.label
}
if (sdpObj.mid) {
desc.mid = sdpObj.mid
}
if (sdpObj['rtcp-mux']) {
desc['rtcp-mux'] = sdpObj['rtcp-mux'];
}
c = c.c('content', {creator:"initiator"})
.c('description', desc);
WebrtcSDP.util.each(sdpObj.codecs, function() {
c = c.c('payload-type', this).up();
});
if (sdpObj.crypto) {
c = c.c('encryption', {required: '1'}).c('crypto', sdpObj.crypto).up();
c = c.up();
}
// Raw candidates
c = c.up().c('transport',{xmlns:"urn:xmpp:jingle:transports:raw-udp:1"});
c = c.c('candidate', {component:'1',
ip: sdpObj.connection.address,
port: sdpObj.media.port}).up();
if(sdpObj.rtcp) {
c = c.c('candidate', {component:'2',
ip: sdpObj.rtcp.address,
port: sdpObj.rtcp.port}).up();
}
c = c.up();
// 3 places we might find ice creds - in order of priority:
// candidate username
// media level icefrag
// session level icefrag
var iceObj = {};
if (sdpObj.candidates[0].username ){
iceObj = {ufrag:sdpObj.candidates[0].username,pwd:sdpObj.candidates[0].password};
} else if ((sdpObj.ice) && (sdpObj.ice.ufrag)){
iceObj = sdpObj.ice;
} else if ((blob.session.ice) && (blob.session.ice.ufrag)){
iceObj = blob.session.ice;
}
// Ice candidates
var transp = {xmlns:"urn:xmpp:jingle:transports:ice-udp:1",
pwd: iceObj.pwd,
ufrag: iceObj.ufrag};
if (iceObj.options) {
transp.options = iceObj.options;
}
c = c.c('transport',transp);
WebrtcSDP.util.each(sdpObj.candidates, function() {
c = c.c('candidate', this).up();
});
// two places to find the fingerprint
// media
// session
var fp = null;
if (sdpObj.fingerprint) {
fp= sdpObj.fingerprint;
}else if(blob.session.fingerprint){
fp = blob.session.fingerprint;
}
if (fp){
c = c.c('fingerprint',{xmlns:"urn:xmpp:tmp:jingle:apps:dtls:0",
hash:fp.hash,
required:fp.required});
c.t(fp.print);
c.up();
}
c = c.up().up();
});
return c;
},
// jingle: Some Jingle to parse
// Returns a js object representing the SDP
parseJingle: function(jingle) {
var blobObj = {};
jingle.find('group').each(function () {
blobObj.group = {};
blobObj.group.type = $(this).attr('type');
blobObj.group.contents = $(this).attr('contents').split(",");
});
blobObj.contents = [];
jingle.find('content').each(function () {
var sdpObj = {};
var mediaObj = {};
mediaObj.pts = [];
blobObj.contents.push(sdpObj);
sdpObj.candidates = [];
sdpObj.codecs = [];
$(this).find('description').each(function () {
if($(this).attr('xmlns') == "urn:xmpp:jingle:apps:rtp:1"){
var mediaType = $(this).attr('media');
mediaObj.type = mediaType;
mediaObj.proto = "RTP/SAVPF"; // HACK
mediaObj.port = 1000;
var ssrcObj = {};
if ($(this).attr('ssrc')) {
ssrcObj.ssrc = $(this).attr('ssrc');
if ($(this).attr('cname')) ssrcObj.cname = $(this).attr('cname');
if ($(this).attr('mslabel')) ssrcObj.mslabel = $(this).attr('mslabel');
if ($(this).attr('label')) ssrcObj.label = $(this).attr('label');
sdpObj.ssrc = ssrcObj;
}
if ($(this).attr('rtcp-mux')) {
sdpObj['rtcp-mux'] = $(this).attr('rtcp-mux');
}
if ($(this).attr('mid')) {
sdpObj['mid'] = $(this).attr('mid');
}
sdpObj.media = mediaObj;
$(this).find('payload-type').each(function () {
var codec = WebrtcSDP.util.getAttributes(this);
//console.log("codec: "+JSON.stringify(codec,null," "));
sdpObj.codecs.push(codec);
mediaObj.pts.push(codec.id);
});
} else {
console.log("skip description with wrong xmlns: "+$(this).attr('xmlns'));
}
});
$(this).find('crypto').each(function () {
var crypto = WebrtcSDP.util.getAttributes(this);
//console.log("crypto: "+JSON.stringify(crypto,null," "));
sdpObj.crypto = crypto;
});
$(this).find('fingerprint').each(function () {
var fingerprint = WebrtcSDP.util.getAttributes(this);
fingerprint.print = Strophe.getText(this);
//console.log("fingerprint: "+JSON.stringify(fingerprint,null," "));
sdpObj.fingerprint = fingerprint;
});
sdpObj.ice = {};
$(this).find('transport').each(function () {
if ($(this).attr('xmlns') == "urn:xmpp:jingle:transports:raw-udp:1") {
$(this).find('candidate').each(function () {
var candidate = WebrtcSDP.util.getAttributes(this);
//console.log("candidate: "+JSON.stringify(candidate,null," "));
if (candidate.component == "1") {
sdpObj.media.port = candidate.port;
sdpObj.connection = {};
sdpObj.connection.address = candidate.ip;
sdpObj.connection.addrtype = "IP4";
sdpObj.connection.nettype = "IN";
}
if (candidate.component == "2") {
sdpObj.rtcp = {};
sdpObj.rtcp.port = candidate.port;
sdpObj.rtcp.address = candidate.ip;
sdpObj.rtcp.addrtype = "IP4";
sdpObj.rtcp.nettype = "IN";
}
});
}
if ($(this).attr('xmlns') == "urn:xmpp:jingle:transports:ice-udp:1") {
sdpObj.ice.pwd = $(this).attr('pwd');
sdpObj.ice.ufrag = $(this).attr('ufrag');
if ($(this).attr('options')) {
sdpObj.ice.options = $(this).attr('options');
}
$(this).find('candidate').each(function () {
var candidate = WebrtcSDP.util.getAttributes(this);
//console.log("candidate: "+JSON.stringify(candidate,null," "));
sdpObj.candidates.push(candidate);
});
}
});
});
return blobObj;
},
dumpSDP: function(sdpString) {
var sdpLines = sdpString.split("\r\n");
for (var sdpLine in sdpLines) {
//console.log(sdpLines[sdpLine]);
}
},
// sdp: an SDP text string representing an offer or answer, missing candidates
// Return an object representing the SDP in Jingle like constructs
parseSDP: function(sdpString) {
var contentsObj = {};
contentsObj.contents = [];
var sdpObj = null;
// Iterate the lines
var sdpLines = sdpString.split("\r\n");
for (var sdpLine in sdpLines) {
//console.log(sdpLines[sdpLine]);
var line = _parseLine(sdpLines[sdpLine]);
if (line.type == "o") {
contentsObj.session = _parseO(line.contents);
contentsObj.session.ice = {};
sdpObj = contentsObj.session;
}
if (line.type == "m") {
// New m-line,
// create a new content
var media = _parseM(line.contents);
sdpObj = {};
sdpObj.candidates = [];
sdpObj.codecs = [];
sdpObj.ice = {};
if (contentsObj.session.fingerprint != null){
sdpObj.fingerprint = contentsObj.session.fingerprint;
}
sdpObj.media = media;
contentsObj.contents.push(sdpObj);
}
if (line.type == "c") {
if (sdpObj != null) {
sdpObj.connection = _parseC(line.contents);
} else {
contentsObj.connection = _parseC(line.contents);
}
}
if (line.type == "a") {
var a = _parseA(line.contents);
switch (a.key) {
case "candidate":
var candidate = _parseCandidate(a.params);
sdpObj.candidates.push(candidate);
break;
case "group":
var group = _parseGroup(a.params);
contentsObj.group = group;
break;
case "mid":
var mid = _parseMid(a.params);
sdpObj.mid = mid;
break;
case "rtcp":
var rtcp = _parseRtcp(a.params);
sdpObj.rtcp = rtcp;
break;
case "rtcp-mux":
sdpObj['rtcp-mux'] = true;
break;
case "rtpmap":
var codec = _parseRtpmap(a.params);
if (codec) sdpObj.codecs.push(codec);
break;
case "sendrecv":
sdpObj.direction = "sendrecv";
break;
case "sendonly":
sdpObj.direction = "sendonly";
break;
case "recvonly":
sdpObj.recvonly = "recvonly";
break;
case "ssrc":
sdpObj.ssrc = _parseSsrc(a.params, sdpObj.ssrc);
break;
case "fingerprint":
var print = _parseFingerprint(a.params);
sdpObj.fingerprint = print;
break;
case "crypto":
var crypto = _parseCrypto(a.params);
sdpObj.crypto = crypto;
break;
case "ice-ufrag":
sdpObj.ice.ufrag = a.params[0];
break;
case "ice-pwd":
sdpObj.ice.pwd = a.params[0];
break;
case "ice-options":
sdpObj.ice.options = a.params[0];
break;
}
}
}
return contentsObj;
},
// sdp: an object representing the body
// Return a text string in SDP format
buildSDP: function(contentsObj) {
// Write some constant stuff
var session = contentsObj.session;
var sdp =
"v=0\r\n";
if (contentsObj.session) {
var session = contentsObj.session;
sdp = sdp + "o=" + session.username + " " + session.id + " " + session.ver + " " +
session.nettype + " " + session.addrtype + " " + session.address + "\r\n";
} else {
var id = new Date().getTime();
var ver = 2;
sdp = sdp + "o=-" + " 3" + id + " " + ver + " IN IP4 192.67.4.14" + "\r\n"; // does the IP here matter ?!?
}
sdp = sdp + "s=-\r\n" +
"t=0 0\r\n";
if (contentsObj.connection) {
var connection = contentsObj.connection;
sdp = sdp + "c=" + connection.nettype + " " + connection.addrtype +
" " + connection.address + "\r\n";
}
if (contentsObj.group) {
var group = contentsObj.group;
sdp = sdp + "a=group:" + group.type;
var ig = 0;
while (ig + 1 <= group.contents.length) {
sdp = sdp + " " + group.contents[ig];
ig = ig + 1;
}
sdp = sdp + "\r\n";
}
if (contentsObj.session){
sdp = sdp + _buildSessProps(contentsObj.session);
}
var contents = contentsObj.contents;
var ic = 0;
while (ic + 1 <= contents.length) {
var sdpObj = contents[ic];
sdp = sdp + _buildMedia(sdpObj);
ic = ic + 1;
}
return sdp;
},
// candidate: an SDP text string representing a cadidate
// Return: an object representing the candidate in Jingle like constructs
parseCandidate: function(candidateSDP) {
var line = _parseLine(candidateSDP);
return _parseCandidate(line.contents);
},
// candidate: an object representing the body
// Return a text string in SDP format
buildCandidate: function(candidateObj) {
return _buildCandidate(candidateObj);
}
};
}());
......@@ -26,7 +26,7 @@
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
......@@ -43,6 +43,12 @@
<h1>
User Service Plugin Changelog
</h1>
<p><b>1.4.1</b> -- September ??th, 2013</p>
<ul>
<li>Added ability to create new shared groups from list of groups when adding or updating users to roster</li>
</ul>
<p><b>1.4.0</b> -- Sep 13, 2013</p>
<ul>
<li>Requires Openfire 3.9.0.</li>
......
......@@ -5,7 +5,7 @@
<name>User Service</name>
<description>Allows administration of users via HTTP requests.</description>
<author>Justin Hunt</author>
<version>1.4.0</version>
<version>1.4.1</version>
<date>09/13/2013</date>
<minServerVersion>3.9.0</minServerVersion>
......
......@@ -58,7 +58,7 @@ User Service Plugin Readme
<p>
The User Service Plugin provides the ability to add,edit,delete users and manage their rosters by sending an http request to the server.
It is intended to be used by applications automating the user administration process.
It is intended to be used by applications automating the user administration process.
This plugin's functionality is useful for applications that need to administer users outside of the Openfire admin console.
An example of such an application might be a live sports reporting application that uses XMPP as its transport, and
creates/deletes users according to the receipt, or non receipt, of a subscription fee.
......@@ -73,16 +73,16 @@ userservice.jar file over the existing file.</p>
<h2>Configuration</h2>
Access to the service is restricted with a "secret" that can be viewed and
Access to the service is restricted with a "secret" that can be viewed and
set from the User Service page in the Openfire admin console. This page is
located on the admin console under "Server" and then "Server Settings".
This should really only be considered weak security. The plugin was initially written with the assumption that http access to the Openfire service was
only available to trusted machines. In the case of the plugin's author, a web application running on the same server as
only available to trusted machines. In the case of the plugin's author, a web application running on the same server as
Openfire makes the request.
<h2>Using the Plugin</h2>
To administer users, submit HTTP requests to the userservice service.
To administer users, submit HTTP requests to the userservice service.
The service address is [hostname]plugins/userService/userservice. For example,
if your server name is "example.com", the URL is http://example.com/plugins/userService/userservice<p>
......@@ -94,7 +94,7 @@ The following parameters can be passed into the request:<p>
<th colspan=2>Name</th><th>Description</th>
</tr>
<tr>
<td class="name">type</td><td>Required</td><td>The admin service required.
<td class="name">type</td><td>Required</td><td>The admin service required.
Possible values are 'add', 'delete', 'update', 'enable', 'disable', 'add_roster', 'update_roster', 'delete_roster'.</td>
</tr>
<tr>
......@@ -119,7 +119,10 @@ The following parameters can be passed into the request:<p>
</tr>
<tr>
<td class="name">groups</td><td>Optional</td>
<td>List of groups where the user is a member. Values are comma delimited.</td>
<td>
List of groups where the user is a member. Values are comma delimited. When used with types "add" or "update", it adds the user to shared groups and auto-creates new groups.<br/>
When used with 'add_roster' and 'update_roster', it adds the user to roster groups provided the group name does not clash with an existing shared group.
</td>
</tr>
<tr>
<td class="name">item_jid</td><td>Required for 'add_roster', 'update_roster', 'delete_roster' operations.</td>
......@@ -146,6 +149,16 @@ http://example.com:9090/plugins/userService/userservice?type=add&secret=bigsecre
</form>
</ul>
The following example adds a user, adds two shared groups (if not existing) and adds the user to both groups.
<ul>
<form>
<textarea cols=65 rows=3 wrap=virtual>
http://example.com:9090/plugins/userService/userservice?type=add&secret=bigsecret&username=kafka&password=drowssap&name=franz&email=franz@kafka.com&groups=support,finance
</textarea>
</form>
</ul>
The following example deletes a user
<ul>
......@@ -197,6 +210,16 @@ http://example.com:9090/plugins/userService/userservice?type=add_roster&secret=b
</form>
</ul>
The following example adds new roster item with subscription 'both' for user 'kafka' and adds kafka to roster groups 'family' and 'friends'
<ul>
<form>
<textarea cols=65 rows=3 wrap=virtual>
http://example.com:9090/plugins/userService/userservice?type=add_roster&secret=bigsecret&username=kafka&item_jid=franz@example.com&name=franz&subscription=3&groups=family,friends
</textarea>
</form>
</ul>
The following example updates existing roster item to subscription 'none' for user 'kafka'
<ul>
......@@ -218,15 +241,15 @@ http://example.com:9090/plugins/userService/userservice?type=delete_roster&secre
</ul>
<br><br>
* When sending double characters (Chinese/Japanese/Korean etc) you should URLEncode the string as utf8.<br>
In Java this is done like this<br>
* When sending double characters (Chinese/Japanese/Korean etc) you should URLEncode the string as utf8.<br>
In Java this is done like this<br>
URLEncoder.encode(username, "UTF-8"));
<br>If the strings are encoded incorrectly, double byte characters will look garbeled in the Admin Console.
<h2>Server Reply</h2>
The server will reply to all User Service requests with an XML result page.
The server will reply to all User Service requests with an XML result page.
If the request was processed successfully the return will be a "result" element with a text body of "OK".
If the request was unsuccessful, the return will be an "error" element with a text body of one of the following error strings.
<p>
......
......@@ -34,6 +34,7 @@ import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.openfire.group.GroupManager;
import org.jivesoftware.openfire.group.GroupNotFoundException;
import org.jivesoftware.openfire.group.GroupAlreadyExistsException;
import org.jivesoftware.openfire.lockout.LockOutManager;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.roster.RosterItem;
......@@ -66,14 +67,14 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
server = XMPPServer.getInstance();
userManager = server.getUserManager();
rosterManager = server.getRosterManager();
secret = JiveGlobals.getProperty("plugin.userservice.secret", "");
// If no secret key has been assigned to the user service yet, assign a random one.
if (secret.equals("")){
secret = StringUtils.randomString(8);
setSecret(secret);
}
// See if the service is enabled or not.
enabled = JiveGlobals.getBooleanProperty("plugin.userservice.enabled", false);
......@@ -91,26 +92,39 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
}
public void createUser(String username, String password, String name, String email, String groupNames)
throws UserAlreadyExistsException
throws UserAlreadyExistsException, GroupAlreadyExistsException, UserNotFoundException, GroupNotFoundException
{
userManager.createUser(username, password, name, email);
userManager.getUser(username);
if (groupNames != null) {
Collection<Group> groups = new ArrayList<Group>();
StringTokenizer tkn = new StringTokenizer(groupNames, ",");
while (tkn.hasMoreTokens()) {
while (tkn.hasMoreTokens())
{
String groupName = tkn.nextToken();
Group group = null;
try {
groups.add(GroupManager.getInstance().getGroup(tkn.nextToken()));
GroupManager.getInstance().getGroup(groupName);
} catch (GroupNotFoundException e) {
// Ignore this group
// Create this group ;
GroupManager.getInstance().createGroup(groupName);
}
group = GroupManager.getInstance().getGroup(groupName);
group.getProperties().put("sharedRoster.showInRoster", "onlyGroup");
group.getProperties().put("sharedRoster.displayName", groupName);
group.getProperties().put("sharedRoster.groupList", "");
groups.add(group);
}
for (Group group : groups) {
group.getMembers().add(server.createJID(username, null));
}
}
}
public void deleteUser(String username) throws UserNotFoundException{
User user = getUser(username);
userManager.deleteUser(user);
......@@ -141,9 +155,9 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
User user = getUser(username);
LockOutManager.getInstance().enableAccount(username);
}
public void updateUser(String username, String password, String name, String email, String groupNames)
throws UserNotFoundException
throws UserNotFoundException, GroupAlreadyExistsException
{
User user = getUser(username);
if (password != null) user.setPassword(password);
......@@ -153,12 +167,23 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
if (groupNames != null) {
Collection<Group> newGroups = new ArrayList<Group>();
StringTokenizer tkn = new StringTokenizer(groupNames, ",");
while (tkn.hasMoreTokens()) {
while (tkn.hasMoreTokens())
{
String groupName = tkn.nextToken();
Group group = null;
try {
newGroups.add(GroupManager.getInstance().getGroup(tkn.nextToken()));
group = GroupManager.getInstance().getGroup(groupName);
} catch (GroupNotFoundException e) {
// Ignore this group
// Create this group ;
group = GroupManager.getInstance().createGroup(groupName);
group.getProperties().put("sharedRoster.showInRoster", "onlyGroup");
group.getProperties().put("sharedRoster.displayName", groupName);
group.getProperties().put("sharedRoster.groupList", "");
}
newGroups.add(group);
}
Collection<Group> existingGroups = GroupManager.getInstance().getGroups(user);
......@@ -182,7 +207,7 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
/**
* Add new roster item for specified user
*
*
* @param username the username of the local user to add roster item to.
* @param itemJID the JID of the roster item to be added.
* @param itemName the nickname of the roster item.
......@@ -198,7 +223,7 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
getUser(username);
Roster r = rosterManager.getRoster(username);
JID j = new JID(itemJID);
try {
r.getRosterItem(j);
throw new UserAlreadyExistsException(j.toBareJID());
......@@ -206,7 +231,7 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
catch (UserNotFoundException e) {
//Roster item does not exist. Try to add it.
}
if (r != null) {
List<String> groups = new ArrayList<String>();
if (groupNames != null) {
......@@ -226,7 +251,7 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
/**
* Update roster item for specified user
*
*
* @param username the username of the local user to update roster item for.
* @param itemJID the JID of the roster item to be updated.
* @param itemName the nickname of the roster item.
......@@ -241,7 +266,7 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
getUser(username);
Roster r = rosterManager.getRoster(username);
JID j = new JID(itemJID);
RosterItem ri = r.getRosterItem(j);
List<String> groups = new ArrayList<String>();
......@@ -251,10 +276,10 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
groups.add(tkn.nextToken());
}
}
ri.setGroups(groups);
ri.setNickname(itemName);
if (subscription == null) {
subscription = "0";
}
......@@ -264,7 +289,7 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
/**
* Delete roster item for specified user. No error returns if nothing to delete.
*
*
* @param username the username of the local user to add roster item to.
* @param itemJID the JID of the roster item to be deleted.
* @throws UserNotFoundException if the user does not exist in the local server.
......@@ -276,10 +301,10 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
getUser(username);
Roster r = rosterManager.getRoster(username);
JID j = new JID(itemJID);
// No roster item is found. Uncomment the following line to throw UserNotFoundException.
//r.getRosterItem(j);
r.deleteRosterItem(j, true);
}
......@@ -301,7 +326,7 @@ public class UserServicePlugin implements Plugin, PropertyEventListener {
}
return userManager.getUser(targetJID.getNode());
}
/**
* Returns the secret key that only valid requests should know.
*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment