diff --git a/src/messenger/js/JSO.java b/src/messenger/js/JSO.java new file mode 100644 index 00000000..580c8510 --- /dev/null +++ b/src/messenger/js/JSO.java @@ -0,0 +1,735 @@ +/* + * JavaScript Obfucator is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * JavaScript Obfucator is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import java.util.*; +import java.io.*; + +/** + * Obfuscate the JavaScript code. + * updated by Evgeny Gryaznov, March 2006 + * @author Shane Ng + */ +public class JSO { + + private Random randomizer = new Random(321); + + public static final String[] reserved = { + "abstract", "else", "instanceof", "switch", "boolean", "enum", "int", + "synchronized", "break", "export", "interface", "this", "byte", "extends", + "long", "throw", "case", "false", "native", "throws", "catch", "final", + "new", "transient", "char", "finally", "null", "true", "class", "float", + "package", "try", "const", "for", "private", "typeof", "continue", "function", + "protected", "var", "debugger", "goto", "public", "void", "default", "if", + "return", "volatile", "delete", "implements", "short", "while", "do", "import", + "static", "with", "double", "in", "super", "undefined", "arguments" + }; + + public static final String[] builtIn = { + "history", "packages", "pageXOffset", "pageYOffset", "isNaN", "array", "java", + "plugin", "clientInformation", "prototype", "layer", "layers", "crypto", "date", "secure", + "embed", "navigator", "product", "netscape", "escape", "eval", "sun", + "taint", "fileUpload", "toString", "unescape", "untaint", "frameRate", + "valueOf", "getClass", "encodeURIComponent", "overrideMimeType", + + // Types + "Math", "Date", "Array", "RegExp", "Image", "Function", "ActiveXObject", + "Number", "String", "Object", "JavaClass", "JavaObject", "JavaPackage", "JavaArray", + + // DOM + "anchor", "image", "area", "checkbox", "password", "radio", "textarea", + "contentDocument", "contentWindow", "document", + "window", "element", "location", "option", "style", "body", + + // Properties + "accessKey", "action", "activeElement", "align", "aLink", "aLinkColor", "alt", + "altHTML", "altKey", "appCodeName", "appMinorVersion", "appName", "appVersion", + "autocomplete", "availHeight", "availWidth", "background", "backgroundAttachment", + "backgroundColor", "backgroundImage", "backgroundPosition", "backgroundPositionX", + "backgroundPositionY", "backgroundRepeat", "balance", "behavior", "bgColor", + "bgProperties", "border", "borderBottom", "borderBottomColor", "borderBottomStyle", + "borderBottomWidth", "borderCollapse", "borderColor", "borderColorDark", + "borderColorLight", "borderLeft", "borderLeftColor", "borderLeftStyle", + "borderLeftWidth", "borderRight", "borderRightColor", "borderRightStyle", + "borderRightWidth", "borderStyle", "borderTop", "borderTopColor", "borderTopStyle", + "borderTopWidth", "borderWidth", "bottom", "bottomMargin", "boundingHeight", "boundingLeft", + "boundingTop", "boundingWidth", "browserLanguage", "bufferDepth", "button", + "cancelBubble", "canHaveChildren", "caption", "cellIndex", "cellPadding", "cellSpacing", + "checked", "classid", "className", "clear", "clientHeight", "clientLeft", "clientTop", + "clientWidth", "clientX", "clientY", "clip", "clipBottom", "clipLeft", "clipRight", + "clipTop", "closed", "code", "codeBase", "codeType", "color", "colorDepth", "cols", + "colSpan", "compact", "complete", "content", "cookie", "cookieEnabled", "coords", + "cpuClass", "cssText", "ctrlKey", "cursor", "data", "dataFld", "dataFormatAs", + "dataPageSize", "dataSrc", "defaultCharset", "defaultChecked", "defaultSelected", + "defaultStatus", "defaultValue", "defer", "designMode", "dialogArguments", "dialogHeight", + "dialogLeft", "dialogTop", "dialogWidth", "dir", "direction", "disabled", "display", + "documentElement", "domain", "dropEffect", "dynsrc", "effectAllowed", "encoding", "event", + "expando", "face", "fgColor", "fileCreatedDate", "fileModifiedDate", "fileSize", + "fileUpdatedDate", "filter", "firstChild", "font", "fontFamily", "fontSize", + "fontSmoothingEnabled", "fontStyle", "fontVariant", "fontWeight", "form", "frame", + "frameBorder", "frameSpacing", "fromElement", "hash", "height", "hidden", "host", + "hostname", "href", "hspace", "htmlFor", "htmlText", "httpEquiv", "id", "imeMode", + "indeterminate", "index", "innerHTML", "innerText", "isMap", "isTextEdit", "keyCode", + "lang", "language", "lastChild", "lastModified", "layoutGrid", "layoutGridChar", + "layoutGridCharSpacing", "layoutGridLine", "layoutGridMode", "layoutGridType", "left", + "leftMargin", "length", "letterSpacing", "lineBreak", "lineHeight", "link", "linkColor", + "listStyle", "listStyleImage", "listStylePosition", "listStyleType", "loop", "lowsrc", + "margin", "marginBottom", "marginHeight", "marginLeft", "marginRight", "marginTop", + "marginWidth", "maxLength", "media", "menuArguments", "method", "Methods", "multiple", + "name", "nameProp", "nextSibling", "nodeName", "nodeType", "nodeValue", "noHref", + "noResize", "noShade", "noWrap", "object", "offscreenBuffering", "offsetHeight", + "offsetLeft", "offsetParent", "offsetTop", "offsetWidth", "offsetX", "offsetY", + "onLine", "opener", "outerHTML", "outerText", "overflow", "overflowX", "overflowY", + "owningElement", "padding", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", + "pageBreakAfter", "pageBreakBefore", "palette", "parent", "parentElement", "parentNode", + "parentStyleSheet", "parentTextEdit", "parentWindow", "pathname", "pixelBottom", + "pixelHeight", "pixelLeft", "pixelRight", "pixelTop", "pixelWidth", "platform", + "pluginspage", "port", "posBottom", "posHeight", "position", "posLeft", "posRight", + "posTop", "posWidth", "previousSibling", "propertyName", "protocol", "qualifier", + "readOnly", "reason", "recordNumber", "recordset", "referrer", "rel", + "repeat", "returnValue", "rev", "right", "rightMargin", "rowIndex", "rowSpan", + "rubyAlign", "rubyOverhang", "rubyPosition", "scopeName", "screenLeft", + "screenTop", "screenX", "screenY", "scrollAmount", "scrollDelay", + "scrollHeight", "scrolling", "scrollLeft", "scrollTop", "scrollWidth", "search", + "sectionRowIndex", "selected", "selectedIndex", "selectorText", "self", "shape", + "shiftKey", "size", "sourceIndex", "span", "specified", "src", "srcElement", "srcFilter", + "srcUrn", "start", "status", "styleFloat", "systemLanguage", "tabIndex", "tableLayout", + "tagName", "tagUrn", "target", "text", "textAlign", "textAutospace", "textDecoration", + "textDecorationLineThrough", "textDecorationNone", "textDecorationOverline", + "textDecorationUnderline", "textIndent", "textJustify", "textTransform", "tFoot", "tHead", + "title", "toElement", "top", "topMargin", "trueSpeed", "type", "unicodeBidi", "uniqueID", + "units", "updateInterval", "URL", "urn", "useMap", "userAgent", "userLanguage", "vAlign", + "value", "vcard_name", "verticalAlign", "visibility", "vLink", "vlinkColor", "volume", + "vspace", "whiteSpace", "width", "wordBreak", "wordSpacing", "wrap", "x", + "XMLDocument", "y", "zIndex", + // non-IE + "outerHeight", "innerHeight", "outerWidth", "innerWidth", + "which", + + // collections + "all", "anchors", "applets", "areas", "attributes", "behaviorUrns", + "bookmarks", "boundElements", "cells", "childNodes", "children", + "controlRange", "elements", "embeds", "filters", "forms", "frames", + "images", "imports", "links", "mimeTypes", "options", "plugins", "rows", + "rules", "scripts", "styleSheets", "tBodies", "TextRectangle", + + // Methods + "add", "addBehavior", "AddFavorite", "addImport", "addRule", "alert", + "appendChild", "applyElement", "assign", "attachEvent", "back", "blur", + "clearAttributes", "clearData", "clearInterval", "clearRequest", + "clearTimeout", "click", "cloneNode", "close", "collapse", "compareEndPoints", + "componentFromPoint", "confirm", "contains", "createCaption", "createControlRange", + "createElement", "createRange", "createStyleSheet", "createTextNode", "createTextRange", + "createTFoot", "createTHead", "deleteCaption", "deleteCell", "deleteRow", "deleteTFoot", + "deleteTHead", "detachEvent", "doScroll", "duplicate", "elementFromPoint", + "empty", "execCommand", "execScript", "expand", "findText", "firstPage", "focus", + "forward", "getAdjacentText", "getAttribute", "getBookmark", "getBoundingClientRect", + "getClientRects", "getData", "getElementById", "getElementsByName", "getElementsByTagName", + "getExpression", "go", "hasChildNodes", "inRange", + "insertAdjacentElement", "insertAdjacentHTML", "insertAdjacentText", "insertBefore", + "insertCell", "insertRow", "isEqual", "IsSubscribed", "item", "javaEnabled", "lastPage", + "mergeAttributes", "move", "moveBy", "moveEnd", "moveRow", "moveStart", "moveTo", + "moveToBookmark", "moveToElementText", "moveToPoint", "namedRecordset", "navigate", + "NavigateAndFind", "nextPage", "open", "pasteHTML", + "previousPage", "print", "prompt", "queryCommandEnabled", "queryCommandIndeterm", + "queryCommandState", "queryCommandSupported", "queryCommandValue", "recalc", "refresh", + "releaseCapture", "reload", "remove", "removeAttribute", "removeBehavior", + "removeChild", "removeExpression", "removeNode", "removeRule", "replace", + "replaceAdjacentText", "replaceChild", "replaceNode", "reset", "resizeBy", + "resizeTo", "scroll", "scrollBy", "scrollIntoView", "scrollTo", "select", + "setAttribute", "setCapture", "setData", "setEndPoint", "setExpression", "setInterval", + "setTimeout", "ShowBrowserUI", "showHelp", "showModalDialog", "showModelessDialog", + "splitText", "stop", "submit", "swapNode", "tags", "taintEnabled", + "urns", "write", "writeln", + // builtIn + "toUpperCase", "toLowerCase", "match", "substring", "split", "indexOf", + "parseFloat", "parseInt", + "getYear", "getTime", "getMonth", "getFullYear", "getDay", "getDate", + "exec", "join", "call", "floor", "toUTCString", + + // events + "onabort", "onafterprint", "onafterupdate", "onbeforecopy", "onbeforecut", + "onbeforeeditfocus", "onbeforepaste", "onbeforeprint", "onbeforeunload", + "onbeforeupdate", "onblur", "onbounce", "oncellchange", "onchange", "onclick", + "oncontextmenu", "oncopy", "oncut", "ondataavailable", "ondatasetchanged", "ondatasetcomplete", + "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragleave", "ondragover", + "ondragstart", "ondrop", "onerror", "onerrorupdate", "onfilterchange", "onfinish", + "onfocus", "onhelp", "onkeydown", "onkeypress", "onkeyup", "onload", + "onlosecapture", "onmousedown", "onmousemove", "onmouseout", "onmouseover", "onmouseup", + "onpaste", "onpropertychange", "onreset", "onresize", "onrowenter", + "onrowexit", "onrowsdelete", "onrowsinserted", "onscroll", "onselect", "onselectstart", + "onstart", "onstop", "onsubmit", "onunload", + + // Ajax + "XMLHttpRequest", "readyState", "onreadystatechange", "responseXML", + "responseText", "responseBody", "statusText", + "send", "abort", "setRequestHeader", "getResponseHeader", "getAllResponseHeaders", + "timeout" + }; + + public static final char[] DELIMITER = {'?', ':', '!', '=', '(', ')', '[', ']', + '{', '}', '\r', '\n', '\t', ' ', '\"', '\'', '<', '>', ',', '.', '/', + '\\', '+', '-', '*', '&', '|', '^', '%', ';' + }; + + + public static final String[] alpha = { + "m", "n", "q", "r", "s", "t", "u", + "h", "i", "j", "k", "l", "o", "p", + "d", "e", "f", "g", "a", "b", "c", + "v", "w", "x", "y", "z", "$", "_" + }; + + public static final HashSet exclusionTokenSet = new HashSet(); + public static final HashSet forceReplace = new HashSet(); + public static final HashSet forceReplaceInStrings = new HashSet(); + + public static final HashMap forceTextualReplace = new HashMap(); + + public static int ref = alpha.length; + public static HashMap map = new HashMap(); + + public static final String ARG_EXCLUDE_TOKENS = "e="; + public static final String ARG_DESTINATION_DIR = "d="; + public static final String ARG_OBFUSCATE_STRING = "o="; + public static final String ARG_DEBUG = "debug"; + public static final String ARG_DEBUGEXCLUDE = "debugnames"; + public static final String ARG_REPLACE = "textrepl="; + + private double[] stringObfuscationParameter = {0, 0, 0.5}; + private String[] file = null; + public static boolean isDebug = false; + public static boolean isExcludeAll = false; + private String destinationDir = null; + private JSOState state = new JSOState(); + private String delimiter = new String(DELIMITER); + private HashSet encounteredInStrings = new HashSet(); + private HashMap encounteredInStringsFiles = new HashMap(); + + public static void main(String[] args) throws Exception { + ArrayList fileList = new ArrayList(args.length); + String[] file = null; + String destinationDir = null; + double[] stringObfuscationParameter = {1, .59, 0.5}; + + if (args.length == 0) { + printUsage(); + return; + } else if (args.length > 1) { + for (int i = 0; i < args.length; i++) { + if( args[i].equals(ARG_DEBUG)) { + isDebug = true; + } else if( args[i].equals(ARG_DEBUGEXCLUDE) ) { + isExcludeAll = true; + } else if (args[i].startsWith(ARG_EXCLUDE_TOKENS)) { + readexclusionTokenSet(args[i].substring(ARG_EXCLUDE_TOKENS.length())); + } else if (args[i].startsWith(ARG_DESTINATION_DIR) && destinationDir == null) { + File dir = new File(args[i].substring(ARG_DESTINATION_DIR.length())); + if (!dir.exists() && !dir.mkdirs()) { + System.err.println("Cannot create the output directory \"" + dir.getName() + "\""); + return; + } else if (dir.exists() && dir.isFile()) { + System.err.println("The output parameter \"" + args[i] + "\" is not a directory"); + return; + } + destinationDir = dir.getAbsolutePath(); + } else if (args[i].startsWith(ARG_OBFUSCATE_STRING)) { + String[] param = args[i].substring(ARG_OBFUSCATE_STRING.length()).split(",", 3); + if (param.length >= 2) { + try { + stringObfuscationParameter[0] = Double.parseDouble(param[0]); + stringObfuscationParameter[1] = Double.parseDouble(param[1]); + if (param.length == 3) { + stringObfuscationParameter[2] = Double.parseDouble(param[2]); + } + } catch (NumberFormatException e) { + System.err.println("The obfuscation parameters are not numbers."); + return; + } + } else { + System.err.println("At least 2 obfuscation parameters are required, e.g. o=0.4,0.7."); + return; + } + } else if( args[i].startsWith(ARG_REPLACE) ) { + String[] param = args[i].substring(ARG_REPLACE.length()).split(",", 3); + if (param.length == 2) { + forceTextualReplace.put(param[0], param[1]); + } else { + System.err.println("2 parameters are required, e.g. textrepl=a,b"); + return; + } + } else { + fileList.add(args[i]); + } + } + file = new String[fileList.size()]; + fileList.toArray(file); + } else { + file = new String[]{args[0]}; + } + addexclusionTokenSet(reserved); + addexclusionTokenSet(builtIn); + + JSO obfuscator = new JSO(file, destinationDir, stringObfuscationParameter); + obfuscator.run(); + } + + private static void printUsage() { + System.err.println("Usage: java JSO [options]"); + System.err.println(""); + System.err.println("where the options are:"); + System.err.println("\te="); + System.err.println("\t\t- filename of the exception list"); + System.err.println("\t\t- exception tokens are delimited by tab, space, dot, comma, "); + System.err.println("\t\t single quote and double quote"); + System.err.println("\td="); + System.err.println("\t\t- the output directory"); + System.err.println("\t\t- print to the STDOUT if not specified"); + System.err.println("\to="); + System.err.println("\t\t- If it is specified, the characters in string literals will be "); + System.err.println("\t\t encoded to either \\uXXXX (hexidemcial) or \\XXX (octal) format"); + System.err.println("\t\t- The parameters are a 2 or 3 floating point values delimited "); + System.err.println(" by commas. e.g. 0.5,0.3 or 0.5,0.3,0.9"); + System.err.println("\t\t- The values are "); + System.err.println("\t\t * probability to encode a string"); + System.err.println("\t\t * probability to encode a character in a candidate string"); + System.err.println("\t\t * probability to encode a character into \\uXXXX format"); + System.err.println("\t\t- The last parameter is set to 0.5 if not specified"); + System.err.println(""); + System.err.println("Press Enter to read the examples..."); + try{ + System.in.read(); + } catch (Exception e){} + System.err.println("Examples:"); + System.err.println(""); + System.err.println(" Obfuscate all scripts in the current directory and output to ./out directory:"); + System.err.println("\tjava JSO *.js d=out"); + System.err.println(""); + System.err.println(" Pipe the STDOUT output to x.o.js:"); + System.err.println("\tjava JSO x.js > x.o.js "); + System.err.println(""); + System.err.println(" Merge a.js and b.js and pipe the merged output to script.js. Tokens in "); + System.err.println(" exception list, noReplace.txt will not be replaced:"); + System.err.println("\tjava JSO a.js b.js e=noReplace.txt > script.js"); + System.err.println(""); + System.err.println(" Obfuscate the 100% of string literals, 68% of the characters will be encoded. "); + System.err.println(" 50% of the characters will be encoded as \\uXXXX format (default):"); + System.err.println("\tjava JSO x.js o=1,0.68"); + } + + public JSO(String[] file, String destinationDir, double[] stringObfuscationParameter){ + this.file = file; + this.destinationDir = destinationDir; + if (stringObfuscationParameter != null && stringObfuscationParameter.length >= 2) { + this.stringObfuscationParameter = stringObfuscationParameter; + } + } + + public void run() throws IOException { + for (int i = 0; i < file.length; i++) { + BufferedReader in = new BufferedReader(new FileReader(file[i])); + PrintWriter out = null; + File f = new File(file[i]); + + if (destinationDir == null) { + out = new PrintWriter(System.out, true); + } else { + out = new PrintWriter(new FileWriter(new File(destinationDir + File.separator + f.getName()))); + } + + this.obfuscate(in, out, f.getName()); + System.out.println("obfuscated " + f.getName()); + + in.close(); + out.flush(); + out.close(); + } + + + System.err.println( "Obfuscated" ); + System.err.println(map.toString().replace(',', '\n')); + + encounteredInStrings.retainAll(map.keySet()); + System.err.println( "Not Obfuscated in strings:" ); + for( Iterator it = encounteredInStrings.iterator(); it.hasNext(); ) { + String s = (String)it.next(); + System.err.println( "\t"+s+": " + (String)encounteredInStringsFiles.get(s) ); + } + } + + private void obfuscate(BufferedReader in, PrintWriter out, String fname) throws IOException { + state.reset(); + + int line_counter = 0; + for (String line = in.readLine(); line != null; line = in.readLine()) { + line_counter++; + if( !isDebug ) + line = line.trim(); + if (line.length() == 0) { + if( isDebug ) + out.println(); + continue; + } + if( line.startsWith( "//-" ) ) { + String[] toAdd = line.substring(3).split(","); + for( int i = 0; i < toAdd.length; i++ ) + exclusionTokenSet.add(toAdd[i].trim()); + continue; + } else if( line.startsWith("//+")) { + String[] toAdd = line.substring(3).split(","); + for( int i = 0; i < toAdd.length; i++ ) + forceReplace.add(toAdd[i].trim()); + continue; + } else if( line.startsWith("//'")) { + String[] toAdd = line.substring(3).split(","); + for( int i = 0; i < toAdd.length; i++ ) { + forceReplace.add(toAdd[i].trim()); + forceReplaceInStrings.add(toAdd[i].trim()); + } + } + + for( Iterator it = forceTextualReplace.keySet().iterator(); it.hasNext(); ) { + String key = (String)it.next(); + line = line.replaceAll(key, (String)forceTextualReplace.get(key)); + } + + StringTokenizer st = new StringTokenizer(line, delimiter, true); + + if (st.hasMoreTokens()) { + state.setToken(st.nextToken()); + } + + for (; state.token != null; state.skipToken()) { + if (st.hasMoreTokens()) { + state.setNextToken(st.nextToken()); + } else { + state.noToken(); + } + + boolean doubleSlashed = state.flipFlags(); + if (doubleSlashed) { + break; + } + + handleToken(out, fname, line_counter); + } + + if( isDebug ) + out.println(); + else if (!state.delimiter && !state.commented ) + out.print(" "); + } + } + + private void handleToken(PrintWriter out, String fname, int line ) { + if (state.token.length() > 0) { + if (state.delimiter) { + if (state.inString() && !state.backslashed && state.c != '\\' && + state.c != '\"' && state.c != '\'') { + state.token = obfuscateQuotedString(state.token); + } + } else { + if (state.inString()) { + if( forceReplaceInStrings.contains(state.token) && canReplace(state.token) ) { + state.token = generateToken(state.token); + } else if( Character.isJavaIdentifierStart(state.token.charAt(0))) { + encounteredInStrings.add(state.token); + String s = (String)encounteredInStringsFiles.get(state.token); + s = ( s == null ) ? "" : s + ", "; + s += fname +"[" + line + "]"; + encounteredInStringsFiles.put(state.token, s); + } + if (!state.backslashed) { + state.token = obfuscateQuotedString(state.token); + } + } else if( !state.commented ) { + if(canReplace(state.token)){ + state.token = generateToken(state.token); + } else if( state.token.length() > 0 && Character.isDigit( state.token.charAt(0) ) ) { + try { + int i = Integer.parseInt( state.token ); + double res = randomizer.nextDouble(); + int e = (int)(i * res); + + if( i > 3 ) { + if( res < 0.2 && i >= 16 ) + state.token = "0x"+Integer.toHexString(i); + else if( res < 0.6 && i >= 8 ) + state.token = "0" + Integer.toOctalString(i); + else + state.token = "(" + e + "+" + (i-e) + ")"; + } + + } catch( NumberFormatException ex ) { + } + } + } + } + } + + if (!state.commented && (state.printToken || state.inString())) { + out.print(state.token); + } + + if (state.c == '}' && !state.commented && !isDebug) { + out.print(" "); + } + } + + private static void readexclusionTokenSet(String file) throws IOException { + BufferedReader in = null; + try { + in = new BufferedReader(new FileReader(file)); + for (String line = in.readLine(); line != null; line = in.readLine()) { + StringTokenizer st = new StringTokenizer(line, "\t ,.\"\'"); + for (; st.hasMoreTokens();) { + exclusionTokenSet.add(st.nextToken()); + } + } + } finally { + if (in != null) { + in.close(); + } + } + } + + private String obfuscateQuotedString(String token) { + if (randomizer.nextDouble() < stringObfuscationParameter[0]) { + StringBuffer buffer = new StringBuffer(token.length()); + int n = token.length(); + int pos = 0; + for (int i = 0; i < n; i++) { + if (randomizer.nextDouble() < stringObfuscationParameter[1]) { + buffer.append(token.substring(pos, i)); + encode(token.charAt(i), buffer); + pos = i + 1; + } + } + if (pos < n) { + buffer.append(token.substring(pos)); + } + return buffer.toString(); + } else { + return token; + } + } + + private void encode(char c, StringBuffer buffer) { + if (randomizer.nextDouble() < stringObfuscationParameter[2] || c > 0777) { + buffer.append("\\u"); + encode(c, 16, 4, buffer); + } else { + buffer.append("\\"); + encode(c, 8, 3, buffer); + } + } + + private void encode(char c, int radix, int length, StringBuffer buffer) { + String value = Integer.toString(c, radix); + int n = length - value.length(); + + if (n > 0) { + for (int i = 0; i < n; i++) { + buffer.append('0'); + } + buffer.append(value); + } else { + buffer.append(value.substring(-n)); + } + } + + private static String generateToken(String token) { + if (map.containsKey(token)) { + return (String) map.get(token); + } else { + String result = null; + do { + StringBuffer buffer = new StringBuffer(token.length()); + for (int i = ref; i > 0; i = i / alpha.length) { + buffer.append(alpha[i % alpha.length]); + } + + ref++; + result = buffer.toString(); + } while (exclusionTokenSet.contains(result) || map.containsValue(result)); + + map.put(token, result); + return result; + } + } + + private static boolean canReplace(String token) { + if (token.length() <= 1 || token.charAt(0) == '$' ) + return false; + if (map.containsKey(token)) + return true; + if( isExcludeAll ) + return false; + if( forceReplace.contains(token) ) + return true; + if (exclusionTokenSet.contains(token) ) + return false; + if (Character.isDigit(token.charAt(0))) + return false; + if( token.charAt(0) == '_' ) + return true; + return true; + } + + private static void addexclusionTokenSet(String[] array) { + if (array != null) { + for (int i = 0; i < array.length; i++) { + if( exclusionTokenSet.contains(array[i]) ) + System.err.println( "warn, already excluded: " + array[i] ); + exclusionTokenSet.add(array[i]); + } + } + } + + public static boolean isDelimiter(String token) { + if (token != null && token.length() > 0) { + for (int i = 0; i < DELIMITER.length; i++) { + if (token.charAt(0) == DELIMITER[i]) { + return true; + } + } + } + return false; + } +} + +class JSOState { + boolean dotted = false; + boolean doubleQuoted = false; + boolean singleQuoted = false; + boolean backslashed = false; + boolean commented = false; + boolean printToken = true; + boolean delimiter = false; + + String token; + String lastToken; + String nextToken; + + char c0 = 0; + char c = 0; + char c2 = 0; + + void reset() { + dotted = false; + doubleQuoted = false; + singleQuoted = false; + backslashed = false; + commented = false; + printToken = true; + delimiter = false; + + token = null; + lastToken = null; + nextToken = null; + + c0 = 0; + c = 0; + c2 = 0; + } + + boolean printable() { + return !commented && (printToken || inString()); + } + + boolean inString() { + return doubleQuoted || singleQuoted; + } + + boolean delimiterSurrounded() { + return !JSO.isDelimiter(nextToken) && !JSO.isDelimiter(lastToken); + } + + boolean isWhitespace(){ + return Character.isWhitespace(c); + } + + String setToken(String value) { + String oldToken = lastToken; + lastToken = token; + token = value; + nextToken = null; + + if (value != null) { + c0 = c; + c = token == null ? 0 : token.charAt(0); + c2 = 0; + + backslashed = c0 == '\\'; + dotted = c0 == '.'; + delimiter = JSO.isDelimiter(token); + printToken = true; + } + + return oldToken; + } + + String tokenBackslashed() { + String result = null; + int index = 0; + if (c == 'u') { + index = 4; + } else if (Character.isDigit(c)) { + index = 3; + } else { + throw new IllegalStateException("Token not backslashed or invalid JavaScript syntax."); + } + result = token.substring(0, index); + token = token.substring(index); + + return result; + } + + void setNextToken(String value) { + nextToken = value; + c2 = value.charAt(0); + } + + void skipToken() { + this.setToken(nextToken); + } + + void noToken() { + nextToken = null; + c2 = 0; + } + + boolean flipFlags() { + if (isWhitespace()) { + printToken = delimiterSurrounded() || JSO.isDebug; + } else if (c == '/') { + if (!commented && c2 == '/') { + return true; + } else if (!commented && c2 == '*' && + !inString()) { + commented = true; + } else if (commented && c0 == '*') { + commented = false; + printToken = false; + } + } else if (c == '\"' && !singleQuoted && !backslashed && !commented) { + doubleQuoted = !doubleQuoted; + } else if (c == '\'' && !doubleQuoted && !backslashed && !commented) { + singleQuoted = !singleQuoted; + } + return false; + } +} diff --git a/src/messenger/js/build.xml b/src/messenger/js/build.xml new file mode 100644 index 00000000..d8556318 --- /dev/null +++ b/src/messenger/js/build.xml @@ -0,0 +1,34 @@ + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/messenger/js/chat.js b/src/messenger/js/chat.js index ddcfb53a..05741df1 100644 --- a/src/messenger/js/chat.js +++ b/src/messenger/js/chat.js @@ -1,19 +1,16 @@ var FrameUtils = { getDocument: function(frm) { if (frm.contentDocument) { - return frm.contentDocument; + return frm.contentDocument; } else if (frm.contentWindow) { return frm.contentWindow.document; } else if (frm.document) { return frm.document; } else { - alert( myRealAgent + ": cannot find document in frame " + frm); - //for( var a in frm ) - // alert( a ); return null; } }, - + initFrame: function(frm) { var doc = this.getDocument(frm); doc.open(); @@ -57,8 +54,11 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { this.setOptions(_options); this._options.onComplete = this.requestComplete.bind(this); this._options.onException = this.handleException.bind(this); + this._options.onTimeout = this.handleTimeout.bind(this); + this._options.timeout = 5000; this.updater = {}; this.frequency = (this._options.frequency || 2); + this.lastupdate = 0; this.cansend = true; FrameUtils.initFrame(this._options.container); if( this._options.message ) { @@ -70,9 +70,15 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { }, handleException: function(_request, ex) { - this.setStatus(ex.name + " occured: " + ex.message); + this.setStatus("offline, reconnecting"); this.stopUpdate(); - this.timer = setTimeout(this.update.bind(this), this.frequency * 1000); + this.timer = setTimeout(this.update.bind(this), 1000); + }, + + handleTimeout: function(_request) { + this.setStatus("timeout, reconnecting"); + this.stopUpdate(); + this.timer = setTimeout(this.update.bind(this), 1000); }, updateOptions: function(act) { @@ -82,9 +88,9 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { if( this._options.user ) this._options.parameters += "&user=true"; if( act == 'refresh' && this._options.message && this._options.message.value != '' ) - this._options.parameters += "&typed=1"; + this._options.parameters += "&typed=1"; }, - + enableInput: function(val) { if( this._options.message ) this._options.message.disabled = !val; @@ -103,24 +109,26 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { }, requestComplete: function(_response) { - this.enableInput(true); - this.cansend = true; - var xmlRoot = Ajax.getXml(_response); - if( xmlRoot && xmlRoot.tagName == 'thread' ) { - this.updateContent( xmlRoot ); - } else { - this.handleError(_response, xmlRoot, 'refresh messages failed'); - } - + try { + this.enableInput(true); + this.cansend = true; + var xmlRoot = Ajax.getXml(_response); + if( xmlRoot && xmlRoot.tagName == 'thread' ) { + this.updateContent( xmlRoot ); + } else { + this.handleError(_response, xmlRoot, 'refresh messages failed'); + } + } catch (e) { + } this.timer = setTimeout(this.update.bind(this), this.frequency * 1000); }, postMessage: function(msg) { - if( msg == "" || !this.cansend) { - return; - } - this.cansend = false; - this.stopUpdate(); + if( msg == "" || !this.cansend) { + return; + } + this.cansend = false; + this.stopUpdate(); this.updateOptions("post"); var postOptions = {}.extend(this._options); postOptions.parameters += "&message=" + encodeURIComponent(msg); @@ -137,10 +145,10 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { }, changeName: function(newname) { - new Ajax.Request(this._options.servl, {parameters:'act=rename&thread=' + (this._options.threadid || -1) + + new Ajax.Request(this._options.servl, {parameters:'act=rename&thread=' + (this._options.threadid || -1) + '&token=' + (this._options.token || 0) + '&name=' + encodeURIComponent(newname)}); }, - + onThreadClosed: function(_response) { var xmlRoot = Ajax.getXml(_response); if( xmlRoot && xmlRoot.tagName == 'closed' ) { @@ -165,14 +173,14 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { showTyping: function(istyping) { if( $("typingdiv") ) { $("typingdiv").style.display=istyping ? 'inline' : 'none'; - } + } }, setupAvatar: function(avatar) { var imageLink = NodeUtils.getNodeText(avatar); if( this._options.avatar && this._options.user ) { - this._options.avatar.innerHTML = imageLink != "" - ? "\"\"\"\"\"\"/" : ""; } @@ -186,7 +194,7 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { if( _lastid ) { this._options.lastid = _lastid; } - + var typing = NodeUtils.getAttrValue(xmlRoot, "typing"); if( typing ) { this.showTyping(typing == '1'); @@ -195,20 +203,34 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { for( var i = 0; i < xmlRoot.childNodes.length; i++ ) { var node = xmlRoot.childNodes[i]; if( node.tagName == 'message' ) { - haveMessage = true; + haveMessage = true; this.processMessage(result_div, node); } else if( node.tagName == 'avatar' ) { this.setupAvatar(node); - } - // TODO thread events + } } - if( haveMessage ) { + if(window.location.search.indexOf('trace=on')>=0) { + var val = "updated"; + if(this.lastupdate > 0) { + var seconds = ((new Date()).getTime() - this.lastupdate)/1000; + val = val + ", " + seconds + " secs"; + if(seconds > 10) { + alert(val); + } + } + this.lastupdate = (new Date()).getTime(); + this.setStatus(val); + } else { + this.clearStatus(); + } + if( haveMessage ) { FrameUtils.scrollDown(this._options.container); - if( !this.focused ) + if( !this.focused ) { window.focus(); + } } }, - + handleKeyDown: function(k) { if( k ){ ctrl=k.ctrlKey;k=k.which; } else { k=event.keyCode;ctrl=event.ctrlKey; } if( this._options.message && ((k==13 && (ctrl || myRealAgent == 'opera')) || (k==10)) ) { @@ -226,19 +248,26 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { if( xmlRoot && xmlRoot.tagName == 'error' ) { this.setStatus(NodeUtils.getNodeValue(xmlRoot,"descr")); } else { - this.setStatus(_action+', ' + Ajax.getError(_response)); - } + this.setStatus("reconnecting"); + } + }, + + showStatusDiv: function(k) { + if( $("engineinfo") ) { + $("engineinfo").style.display='inline'; + $("engineinfo").innerHTML = k; + } }, setStatus: function(k) { if( this.statusTimeout ) clearTimeout(this.statusTimeout); - window.status = k; + this.showStatusDiv(k); this.statusTimeout = setTimeout(this.clearStatus.bind(this), 4000); }, clearStatus: function() { - window.status = ""; + $("engineinfo").style.display='none'; } }); @@ -246,7 +275,7 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, { HSplitter = Class.create(); HSplitter.prototype = { initialize: function(_options) { - this._options = _options; + this._options = _options; this.captured = 0; if( this._options.first && this._options.second && this._options.control ) { this._options.control.onmousedown = this.onmousedownEvent.bind(this); @@ -255,7 +284,7 @@ HSplitter.prototype = { } }, - onmousedownEvent: function(e) { + onmousedownEvent: function(e) { var ev = e || event; if( this._options.control.setCapture ) @@ -294,13 +323,14 @@ HSplitter.prototype = { var Chat = { threadUpdater : {}, hSplitter : {}, - + applyName: function() { Chat.threadUpdater.changeName($('uname').value); $('changename1').style.display='none'; $('changename2').style.display='inline'; + $('unamelink').innerHTML=$('uname').value; }, - + showNameField: function() { $('changename1').style.display='inline'; $('changename2').style.display='none'; @@ -323,7 +353,7 @@ Behaviour.register({ 'select#predefined' : function(el) { el.onchange = function() { var message = $('msgwnd'); - message.value = this.options[this.selectedIndex].innerText || this.options[this.selectedIndex].innerHTML; + message.value = this.options[this.selectedIndex].innerText || this.options[this.selectedIndex].innerHTML; this.selectedIndex = 0; message.focus(); }; @@ -350,7 +380,7 @@ Behaviour.register({ }, 'a#refresh' : function(el) { el.onclick = function() { - Chat.threadUpdater.stopUpdate(); + Chat.threadUpdater.stopUpdate(); Chat.threadUpdater.update(); }; }, @@ -365,4 +395,4 @@ EventHelper.register(window, 'onload', function(){ Chat.webimRoot = threadParams.wroot; Chat.hSplitter = new HSplitter({control:$("spl1"), first:$("msgwndtd"), second:$("chatwndtd"), minfirst:30, minsec:30}); Chat.threadUpdater = new Ajax.ChatThreadUpdater(({container:myRealAgent=='safari'?self.frames[0]:$("chatwnd"),avatar:$("avatarwnd"),message:$("msgwnd")}).extend( threadParams || {} )); -}); +}); \ No newline at end of file diff --git a/src/messenger/js/common.js b/src/messenger/js/common.js index bc9ff3d3..8538941b 100644 --- a/src/messenger/js/common.js +++ b/src/messenger/js/common.js @@ -17,7 +17,7 @@ var Class = { create: function() { - return function() { + return function() { this./**/initialize./**/apply(this, arguments); }; }, @@ -92,10 +92,10 @@ PeriodicalExecuter.prototype = { onTimerEvent: function() { if (!this.currentlyExecuting) { - try { + try { this.currentlyExecuting = true; - this.callback(); - } finally { + this.callback(); + } finally { this.currentlyExecuting = false; } } @@ -115,7 +115,7 @@ function findObj( id ) if( x.length == 0 ) return null; if( x.length == 1 ) return x[ 0 ]; } - + return x; } @@ -136,7 +136,7 @@ function $() { if (typeof elem == 'string') elem = findObj(elem); - if (arguments.length == 1) + if (arguments.length == 1) return elem; elems.push(elem); @@ -150,15 +150,15 @@ if (!Function.prototype.apply) { var parameterStrings = new Array(); if (!obj) obj = window; if (!params) params = new Array(); - + for (var i = 0; i < params.length; i++) parameterStrings[i] = 'params[' + i + ']'; - + obj.$apply$ = this; - var result = eval('obj.$apply$(' + + var result = eval('obj.$apply$(' + parameterStrings.join(', ') + ')'); obj.$apply$ = null; - + return result; }; } @@ -174,7 +174,7 @@ var Ajax = { getXml: function(_response) { if( _response && - _response.status >= 200 && + _response.status >= 200 && _response.status < 300 ) { var xmlDoc = _response.responseXML; if( xmlDoc && xmlDoc.documentElement ) @@ -182,7 +182,7 @@ var Ajax = { } return null; }, - + getError: function(_response) { return _response.statusText || "connection error N" + _response.status; }, @@ -200,10 +200,15 @@ Ajax.Base.prototype = { }.extend(_options || {}); }, + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + responseIsSuccess: function() { - return this./**/transport.status == undefined - || this.transport.status == 0 - || (this.transport.status >= 200 && this.transport.status < 300); + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); }, responseIsFailure: function() { @@ -212,13 +217,15 @@ Ajax.Base.prototype = { }; Ajax./**/Request = Class.create(); -Ajax.Request./**/Events = +Ajax.Request./**/Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Class.inherit( Ajax.Request, Ajax.Base, { initialize: function(url, _options) { this.transport = Ajax.getTransport(); this.setOptions(_options); + this.transportTimer = {}; + this.finished = false; this.request(url); }, @@ -230,11 +237,13 @@ Class.inherit( Ajax.Request, Ajax.Base, { if (this._options._method == 'get' && parameters.length > 0) url += '?' + parameters; - this.transport.open(this._options._method, url, this._options.asynchronous); + this.transport.open(this._options._method.toUpperCase(), url, this._options.asynchronous); if (this._options.asynchronous) { this.transport.onreadystatechange = this.onStateChange.bind(this); - setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + if(this._options.timeout) { + this.transportTimer = setTimeout(this.handleTimeout.bind(this), this._options.timeout); + } } this.setRequestHeaders(); @@ -248,18 +257,19 @@ Class.inherit( Ajax.Request, Ajax.Base, { }, setRequestHeaders: function() { - var requestHeaders = + var requestHeaders = ['X-Requested-With', 'XMLHttpRequest']; if (this._options._method == 'post') { - requestHeaders.push('Content-type', + requestHeaders.push('Content-type', 'application/x-www-form-urlencoded'); - /* Force "Connection: close" for Mozilla browsers to work around - * a bug where XMLHttpReqeuest sends an incorrect Content-length - * header. See Mozilla Bugzilla #246651. + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. */ - if (this.transport.overrideMimeType) + if (this.transport.overrideMimeType && + (navigator.userAgent.match("/Gecko\/(\d{4})/") || [0,2005])[1] < 2005) requestHeaders.push('Connection', 'close'); } @@ -276,18 +286,10 @@ Class.inherit( Ajax.Request, Ajax.Base, { this.respondToReadyState(this.transport.readyState); }, - header: function(name) { - try { - return this.transport.getResponseHeader(name); - } catch (e) {} - }, - - evalResponse: function() { - try { - return eval(this.transport.responseText); - } catch (e) { - this.dispatchException(e); - } + handleTimeout: function() { + if(this.finished) { return; } + this.finished = true; + (this._options.onTimeout || Ajax.emptyFunction)(this); }, respondToReadyState: function(readystate) { @@ -295,26 +297,18 @@ Class.inherit( Ajax.Request, Ajax.Base, { if (event == 'Complete') { try { - (this._options['on' + this.transport.status] - || this._options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] - || Ajax.emptyFunction)(this.transport); + if(!this.finished) { + this.finished = true; + if(this._options.timeout) { clearTimeout(this.transportTimer); } + (this._options.onComplete || Ajax.emptyFunction)(this.transport); + } } catch (e) { this.dispatchException(e); } - if ((this.header('Content-type') || '').match("text\\/javascript")) - this.evalResponse(); - } - - try { - (this._options['on' + event] || Ajax.emptyFunction)(this.transport); - } catch (e) { - this.dispatchException(e); - } - - /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ - if (event == 'Complete') + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ this.transport.onreadystatechange = Ajax.emptyFunction; + } }, dispatchException: function(exception) { @@ -325,7 +319,7 @@ Class.inherit( Ajax.Request, Ajax.Base, { var EventHelper = { register : function(obj, ev,func){ var oldev = obj[ev]; - + if (typeof oldev != 'function') { obj[ev] = func; } else { @@ -341,7 +335,7 @@ var EventHelper = { Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work of Simon Willison (see comments by Simon below). http://ripcord.co.nz/behaviour/ -*/ +*/ var Behaviour = { list : new Array, @@ -436,11 +430,11 @@ document.getElementsBySelector = function(selector) { } // [evgeny] code for attribute selection is removed... - + if (!currentContext[0]){ return; } - + // If we get here, token is JUST an element (not a class or ID selector) tag_name = token; var found = new Array; @@ -492,14 +486,14 @@ var CommonUtils = { return _row; if( _table.rows['head'] != null ) return null; - + for( k=0; k < _table.rows.length; k++ ) { if( _table.rows[k].id == _id ) return _table.rows[k]; } - return null; + return null; }, - + getCell: function(_id,_row,_table) { var _cell = _row.cells[_id]; if( _cell != null ) @@ -510,9 +504,9 @@ var CommonUtils = { if( _row.cells[k].id == _id ) return _row.cells[k]; } - return null; + return null; }, - + insertCell: function(_row,_id,_className,_align,_height, _inner) { var cell = _row.insertCell(-1); cell.id = _id; @@ -524,3 +518,4 @@ var CommonUtils = { cell.innerHTML = _inner; } }; + diff --git a/src/messenger/js/users.js b/src/messenger/js/users.js index af115a46..1f9893f3 100644 --- a/src/messenger/js/users.js +++ b/src/messenger/js/users.js @@ -5,16 +5,25 @@ Class.inherit( Ajax.PeriodicalUpdater, Ajax.Base, { this.setOptions(_options); this._options.onComplete = this.requestComplete.bind(this); this._options.onException = this.handleException.bind(this); + this._options.onTimeout = this.handleTimeout.bind(this); + this._options.timeout = 5000; this.frequency = (this._options.frequency || 2); this.updater = {}; this.update(); }, - + handleException: function(_request, ex) { if( this._options.handleError ) - this._options.handleError( ex.name + " occured: " + ex.message ); + this._options.handleError("offline, reconnecting"); this.stopUpdate(); - this.timer = setTimeout(this.update.bind(this), this.frequency * 1000); + this.timer = setTimeout(this.update.bind(this), 1000); + }, + + handleTimeout: function(_request) { + if( this._options.handleError ) + this._options.handleError("timeout, reconnecting"); + this.stopUpdate(); + this.timer = setTimeout(this.update.bind(this), 1000); }, stopUpdate: function() { @@ -30,14 +39,16 @@ Class.inherit( Ajax.PeriodicalUpdater, Ajax.Base, { }, requestComplete: function(presponse) { - if (presponse != null && presponse.status == 200 ) { - var xmlDoc = presponse.responseXML; - (this._options.updateContent || Ajax.emptyFunction)( xmlDoc ); - } else { - if( this._options.handleError ) - this._options.handleError(Ajax.getError(_response)); + try { + var xmlRoot = Ajax.getXml(presponse); + if( xmlRoot ) { + (this._options.updateContent || Ajax.emptyFunction)( xmlRoot ); + } else { + if( this._options.handleError ) + this._options.handleError("reconnecting"); + } + } catch(e) { } - this.timer = setTimeout(this.update.bind(this), this.frequency * 1000); } }); @@ -49,13 +60,13 @@ var HtmlGenerationUtils = { cell.style.backgroundImage = 'url('+webimRoot+'/images/tablediv3.gif)'; cell.innerHTML = ''; }, - + removeHr: function(_table, _index ) { _table.deleteRow(_index+2); _table.deleteRow(_index+1); _table.deleteRow(_index); }, - + insertHr: function(_table, _index) { var row = _table.insertRow(_index); var cell = row.insertCell(-1); @@ -74,12 +85,12 @@ var HtmlGenerationUtils = { cell.height = 2; }, - popupLink: function(link, title, wndid, inner, width, height) { - return ''+ inner+''; }, - + generateOneRowTable: function(content) { return '' + content + '
'; }, @@ -87,21 +98,18 @@ var HtmlGenerationUtils = { viewOpenCell: function(username,servlet,id,canview,canopen,ban,message) { var cellsCount = 2; var link = servlet+"?thread="+id; - var innerContent = ( ban == "full" ) ? ""+username+"" : ( ( ban == "other" ) ? ""+username+"" : username ); var gen = ''; - gen += HtmlGenerationUtils.popupLink( canopen ? link : link+"&viewonly=true", localized[canopen ? 0 : 1], "ImCenter"+id, innerContent, 600, 420); + gen += HtmlGenerationUtils.popupLink( link, localized[canopen ? 0 : 1], "ImCenter"+id, username, 600, 420, ban); gen += ''; if( canopen ) { gen += ''; - gen += HtmlGenerationUtils.popupLink( link, localized[0], "ImCenter"+id, ''+localized[0]+'', 600, 420); + gen += HtmlGenerationUtils.popupLink( link, localized[0], "ImCenter"+id, ''+localized[0]+'', 600, 420, null); gen += ''; cellsCount++; } - + return HtmlGenerationUtils.generateOneRowTable(gen); } - - }; Ajax.ThreadListUpdater = Class.create(); @@ -118,22 +126,22 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { this.t = this._options.table; this.periodicalUpdater = new Ajax.PeriodicalUpdater(this._options); }, - + updateParams: function() { return "company=" + this._options.company + "&since=" + this._options.lastrevision; }, - + setStatus: function(msg) { this._options.status.innerHTML = msg; }, - + handleError: function(s) { this.setStatus( s ); }, - + updateThread: function(node) { var id, stateid, vstate, canview = false, canopen = false, ban = null; - + for( var i = 0; i < node.attributes.length; i++ ) { var attr = node.attributes[i]; if( attr.nodeName == "id" ) @@ -144,15 +152,15 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { vstate = attr.nodeValue; else if( attr.nodeName == "canopen" ) canopen = true; - + } - + function setcell(_table, row,id,pcontent) { var cell = CommonUtils.getCell( id, row, _table ); if( cell ) cell.innerHTML = pcontent; } - + var row = CommonUtils.getRow("thr"+id, this.t); if( stateid == "closed" ) { if( row ) { @@ -169,13 +177,12 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { var agent = NodeUtils.getNodeValue(node,"agent"); var modified = NodeUtils.getNodeValue(node,"modified"); var message = NodeUtils.getNodeValue(node,"message"); - var etc = ""; - - + var etc = ''+NodeUtils.getNodeValue(node,"useragent")+''; + etc = HtmlGenerationUtils.generateOneRowTable(etc); var startRow = CommonUtils.getRow(stateid, this.t); var endRow = CommonUtils.getRow(stateid+"end", this.t); - + if( row != null && (row.rowIndex <= startRow.rowIndex || row.rowIndex >= endRow.rowIndex ) ) { HtmlGenerationUtils.removeHr(this.t, row.rowIndex+1); this.t.deleteRow(row.rowIndex); @@ -187,7 +194,7 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { HtmlGenerationUtils.insertHr(this.t, startRow.rowIndex+2); row.id = "thr"+id; this.threadTimers[id] = new Array(vtime,modified,stateid); - CommonUtils.insertCell(row, "name", "table", null, 30, HtmlGenerationUtils.viewOpenCell(vname,this._options.agentservl,id,canview,canopen,ban,message) ); + CommonUtils.insertCell(row, "name", "table", null, 30, HtmlGenerationUtils.viewOpenCell(vname,this._options.agentservl,id,canview,canopen,ban,message)); HtmlGenerationUtils.insertSplitter(row); CommonUtils.insertCell(row, "contid", "table", "center", null, vaddr ); HtmlGenerationUtils.insertSplitter(row); @@ -200,7 +207,7 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { CommonUtils.insertCell(row, "wait", "table", "center", null, (stateid!='chat' ? this.getTimeSince(modified) : '-') ); HtmlGenerationUtils.insertSplitter(row); CommonUtils.insertCell(row, "etc", "table", "center", null, etc ); - + if( stateid == 'wait' || stateid == 'prio' ) return true; } else { @@ -215,7 +222,7 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { } return false; }, - + updateQueueMessages: function() { function updateQueue(t,id,nclients) { var startRow = t.rows[id]; @@ -228,12 +235,12 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { _status.innerHTML = (startRow.rowIndex + 1 == endRow.rowIndex) ? nclients : ""; _status.height = (startRow.rowIndex + 1 == endRow.rowIndex) ? 30 : 10; } - + updateQueue(this.t, "wait", this._options.noclients); updateQueue(this.t, "prio", this._options.noclients); updateQueue(this.t, "chat", this._options.noclients); }, - + getTimeSince: function(srvtime) { var secs = Math.floor(((new Date()).getTime()-srvtime-this.delta)/1000); var minutes = Math.floor(secs/60); @@ -251,7 +258,7 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { return prefix + minutes+":"+secs; }, - + updateTimers: function() { for (var i in this.threadTimers) { if (this.threadTimers[i] != null) { @@ -269,19 +276,18 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { } } }, - - updateContent: function(xmldoc) { - var root = xmldoc.documentElement; + + updateContent: function(root) { var newAdded = false; if( root.tagName == 'threads' ) { var _time = NodeUtils.getAttrValue(root, "time"); var _revision = NodeUtils.getAttrValue(root, "revision" ); - + if( _time ) this.delta = (new Date()).getTime() - _time; if( _revision ) this._options.lastrevision = _revision; - + for( var i = 0; i < root.childNodes.length; i++ ) { var node = root.childNodes[i]; if( node.tagName == 'thread' ) @@ -291,12 +297,13 @@ Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, { this.updateQueueMessages(); this.updateTimers(); this.setStatus( "Up to date" ); - if( newAdded ) + if( newAdded ) { window.focus(); + } } else if( root.tagName == 'error' ) { - this.setStatus( "error: " + NodeUtils.getNodeValue(root,"descr") ); + this.setStatus(NodeUtils.getNodeValue(root,"descr") ); } else { - this.setStatus( "wrong response" ); + this.setStatus( "reconnecting" ); } } }); @@ -305,6 +312,5 @@ var webimRoot = ""; EventHelper.register(window, 'onload', function(){ webimRoot = updaterOptions.wroot; - new Ajax.ThreadListUpdater(({table:$("threadlist"),status:$("connstatus")}).extend(updaterOptions || {})); + new Ajax.ThreadListUpdater(({table:$("threadlist"),status:$("connstatus")}).extend(updaterOptions || {})); }); -