diff --git a/src/main/resources/client/.bowerrc b/src/main/resources/client/.bowerrc
new file mode 100644
index 0000000000000000000000000000000000000000..36b07d51df4a82ead976a4a2beff356d39e8d469
--- /dev/null
+++ b/src/main/resources/client/.bowerrc
@@ -0,0 +1,3 @@
+{
+  "directory": "lib/"
+}
diff --git a/src/main/resources/client/bower.json b/src/main/resources/client/bower.json
new file mode 100644
index 0000000000000000000000000000000000000000..2ad31ad2f4db55d44536f74751fea8cd1e63e481
--- /dev/null
+++ b/src/main/resources/client/bower.json
@@ -0,0 +1,39 @@
+{
+  "name": "nars_web",
+  "version": "0.0.0",
+  "homepage": "https://github.com/automenta/opennars",
+  "authors": [
+    "SeH <automenta@gmail.com>"
+  ],
+  "description": "Client interface for nars_web",
+  "directory": "lib/",
+  "dependencies": {
+	"jquery":"",
+	"lodash":"",
+	"____jquery-ui":"",
+	"____jquery.terminal":""
+  },
+  "keywords": [
+    "nars",
+    "ai",
+    "logic",
+    "semantic",
+    "web",
+    "opennars",
+    "inference",
+    "machine",
+    "learning",
+    "ml",
+    "cognitive",
+    "cognition",
+    "bot"
+  ],
+  "license": "GPL",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ]
+}
diff --git a/src/main/resources/client/index.css b/src/main/resources/client/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..27522a3b55795f0d37668f95bb78a02045f2837c
--- /dev/null
+++ b/src/main/resources/client/index.css
@@ -0,0 +1,93 @@
+body {
+    padding-left: 2em;
+    padding-top: 0.25em;
+    padding-right: 2em;
+    font-size: 100%;
+    font-family: Arial, sans;
+}
+#io {  
+    padding-top: 1.8em;
+    width: 100%;
+    height: 95%;
+}
+#io .section {
+    width: 100%;
+    margin-top: 1em;
+    margin-bottom: 1em;
+}
+#io .section textarea {
+    width: 100%;
+    height: 4em;    
+}
+#io .section .output{
+    margin-left: 1em;
+    margin-top: 1em;
+    max-width: 95%;
+    overflow: auto;
+}
+#io .section button {
+    width: 1.5em;
+    position: absolute;
+    right: 2em;    
+    padding: 0;
+}
+
+pre { margin: 0; }
+
+.in {
+    color: purple;
+}
+.out {
+    color: blue;
+}
+.comment {
+    color: black;
+}
+.previousInput {
+    opacity: 0.85;    
+}
+#menu {
+    position: fixed;
+    left: 0;
+    top: 0;
+    background-color: rgba(225,225,225,0.95);
+    width: 100%;
+    font-size: 150%;
+    z-index: 5;
+    padding: 4px;
+}
+#menu button {
+    vertical-align:top;
+}
+#title {
+    opacity: 0.25;
+    letter-spacing: -5px;    
+    cursor: pointer;
+    float: right;
+    padding-right: 0.4em;
+}
+#title:hover {
+    opacity: 1.0;
+    letter-spacing: -5px;    
+    
+}
+#menubuttons {
+    float: left;    
+}
+#menubuttons button {
+    margin: 0;
+    font-size: 100%;
+    color: #777;
+}
+
+
+.active {
+    color: #000 !important;
+}
+#volume {
+    background-color: white;
+    float: left;
+    clear:none;
+    height: 2em;
+    margin-top: 0.5em;
+}
\ No newline at end of file
diff --git a/src/main/resources/client/index.html b/src/main/resources/client/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..82ee1a759ce9acd83b8d4d7a55cd8a8723e4be85
--- /dev/null
+++ b/src/main/resources/client/index.html
@@ -0,0 +1,234 @@
+<!-- Import JavaScript Libraries. -->
+<html>
+    <head>
+        <script type="text/javascript" src="web_socket.js"></script>
+        <script type="text/javascript" src="lib/jquery/dist/jquery.min.js"></script>
+        <!--<script type="text/javascript" src="lib/jquery-ui/ui/minified/jquery-ui.min.js"></script>
+        <script type="text/javascript" src="lib/jquery.terminal/js/jquery.terminal-min.js"></script>-->
+        <script type="text/javascript" src="lib/lodash/dist/lodash.min.js"></script>
+        <style type="text/css">
+            /*@import url("lib/jquery-ui/themes/dot-luv/jquery-ui.min.css");
+            @import url("lib/jquery-ui/themes/dot-luv/jquery.ui.theme.css");
+            @import url("lib/jquery.terminal/css/jquery.terminal.css");*/
+            @import url("index.css");
+        </style>
+        <style>
+        </style>
+
+    </head>
+    <body>
+        <div id="menu">   
+            <font size="2" color="gray">..Volume Level</font>
+            <input id="volume" type="number" name="Volume" min="0" max="100" value="100" title="Volume"/>
+            <a id="title">openNARS</a>
+        </div>
+
+        <div id="io">
+
+        </div>
+
+        <script type="text/javascript">
+
+            var nlp = 0;
+            var websocketPort = 10000;
+            var ws = new WebSocket('ws://' + window.location.hostname + ':' + websocketPort);
+
+            var prevOutput = null, currentOutput = null;
+
+            function getLineClass(m) {
+                if ((m[0] == '\"') && (m[m.length - 1] == '\"'))
+                    return 'comment';
+                if (m.trim().indexOf('IN:') == 0)
+                    return 'in';
+                if (m.trim().indexOf('OUT') == 0)
+                    return 'out';
+                return null;
+            }
+            var begin = '<tom --> [fast]>.\n<tim --> [fast]>.\n<tim <-> ?what>?';
+            function addIO(message) {
+                var d = $('<div/>').addClass('section');
+
+                prevOutput = currentOutput;
+
+                if (message && typeof message == "string") {
+                    d.append(message + '<br/>');
+                    currentOutput = d;
+                }
+                else {
+                    var input = $('<textarea/>').html(begin);
+
+
+                    begin = '';
+
+                    var outputDiv = $('<div/>').addClass('output');
+                    var sendButton = $('<button/>').html('&ldsh;').click(function() {
+                        arri = []
+                        try {
+                            var se = input.val()
+                            while (se.indexOf("why") != - 1 || se.indexOf("when") != - 1 || se.indexOf("who") != - 1 || se.indexOf("what") != - 1 || se.indexOf("where") != - 1)
+                                se = se.replace("why", "wqhy").replace("when", "wqhen").replace("who", "wqho").replace("what", "wqhat").replace("where", "wqhere")
+                            while (se.indexOf("Why") != - 1 || se.indexOf("When") != - 1 || se.indexOf("Who") != - 1 || se.indexOf("What") != - 1 || se.indexOf("Where") != - 1)
+                                se = se.replace("Why", "wqhy").replace("When", "wqhen").replace("Who", "wqho").replace("What", "wqhat").replace("Where", "wqhere")
+                            ws.send(se);
+                            input.attr('readonly', 'true');
+                            input.addClass('previousInput');
+                            sendButton.remove();
+                            addIO();
+                        } catch (e) {
+                            output('Error: ' + e);
+                        }
+
+                    });
+                    input.keydown(function(e) {
+                        if (e.ctrlKey && e.keyCode == 13) {
+                            sendButton.click();
+                        }
+                    });
+                    d.append(input);
+                    d.append(sendButton);
+                    d.append(outputDiv);
+                    currentOutput = outputDiv;
+                    setTimeout(function() {
+                        input.focus();
+                    }, 0);
+                }
+                $('#io').append(d);
+                scrollbottom();
+            }
+            
+            function scrollbottom() {
+                $('body').scrollTop($('body').prop('scrollHeight'));
+            }
+            
+            var cnt = 0;
+            var arri = [];
+            
+            function output(m, monospace) {
+                if (!prevOutput)
+                    prevOutput = currentOutput;
+                if (monospace) {
+                    if (m.indexOf("ERROR:") == -1) {
+                        var line = null;
+
+
+                        if (nlp == 0)
+                            line = m;
+                        else
+                        {
+                            if (m.indexOf("/") != -1 || m.indexOf("\\") != -1 || m.indexOf("|") != -1 || m.indexOf("&") != -1 || m.indexOf("~") != -1)
+                                "nope"
+                            else
+                            {
+                                res = m
+                                var i = 100
+                                while (i > 0 && (m.indexOf("*") != -1 || m.indexOf("-") != -1 || m.indexOf("<") != -1 || m.indexOf(">") != -1 || m.indexOf("(") != -1 || m.indexOf(")") != -1))
+                                {
+                                    res = res.replace("<->", "is similar to").replace("-->", "is").replace("<", "").replace(">", "").replace(",", " ").replace("(", "\"").replace(")", "\"").replace("* ", "")
+                                    i = i - 1
+                                }
+
+                                while (res.indexOf(",") != - 1)
+                                    res = res.replace(",", " ")
+                                //
+                                var potential = res.replace(" is ", " is ").replace("somewhat similar", "similar").replace("?1", "what").replace("?2", "what").replace("$1", "what").replace("$2", "what");
+                                var addit = 1
+                                if (potential.indexOf("IN:") == -1)//if(res.indexOf("%")==-1) //question
+                                {
+                                    for (var i = 0; i < cnt; i++)
+                                    {
+                                        if (arri[i] == potential.split("{")[0] && potential.indexOf("IN:") == -1)
+                                            addit = 0;
+                                    }
+                                    if (addit == 1)
+                                    {
+                                        cnt += 1;
+                                        arri.push(potential.split("{")[0]);
+                                    }
+                                }
+                                if (addit)
+                                    if (potential.indexOf("%") == -1 || parseFloat(potential.split(";")[1].split("%")[0]) >= 0.5)
+                                    {
+                                        if (potential.indexOf("OUT:") != -1)
+                                        { //you and i handling
+                                            while (potential.indexOf(" i?") != - 1 || potential.indexOf(" i!") != - 1 || potential.indexOf(" i.") != - 1 || potential.indexOf(" i ") != - 1)
+                                                potential = potential.replace(" i?", " Dyou?").replace(" i!", " Dyou!").replace(" i.", " Dyou.").replace(" i ", " Dyou ")
+                                            while (potential.indexOf(" you?") != - 1 || potential.indexOf(" you!") != - 1 || potential.indexOf(" you.") != - 1 || potential.indexOf(" you ") != - 1)
+                                                potential = potential.replace(" you?", " i?").replace(" you!", " i!").replace(" you.", " i.").replace(" you ", " i ")
+                                            while (potential.indexOf(" Dyou?") != - 1 || potential.indexOf(" Dyou!") != - 1 || potential.indexOf(" Dyou.") != - 1 || potential.indexOf(" Dyou ") != - 1)
+                                                potential = potential.replace(" Dyou?", " you?").replace(" Dyou!", " you!").replace(" Dyou.", " you.").replace(" Dyou ", " you ")
+                                        }
+                                        while (potential.indexOf("is has") != - 1 || potential.indexOf("is have") != - 1 || potential.indexOf("#1") != - 1 || potential.indexOf("#2") != - 1 || potential.indexOf(" i is ") != - 1 || potential.indexOf(" you is ") != - 1)
+                                            potential = potential.replace("is has", "has").replace("is have", "have").replace("#1", "something").replace("#2", "something").replace(" i is ", " i am ").replace(" you is ", " you are ")
+                                        while (potential.indexOf("--") != - 1)
+                                            potential = potential.replace("--", "not")
+                                        if (potential.indexOf("==") != -1)
+                                            potential = potential.replace("IN: ", "IN: if ")
+                                        line = (potential.replace("==", "then").replace("\" not", "\" is").replace("wqhat", "it").replace("wqho", "it").replace("wqhere", "it").replace("wqhat", "it").replace("wqhy", "it").replace("wqho", "it").replace("wqhen", "it"))
+                                    }
+                            }
+                        }
+                    }
+                    if (line && (line.length > 0)) {
+                        var l = $("<pre/>");
+                        l.text(line);
+                        var lc = getLineClass(m);
+                        if (lc)
+                            l.addClass(lc);
+                        prevOutput.append(l);
+                        scrollbottom();
+                    }
+                }
+                else {
+                    prevOutput.append("<span>" + m + '</span>\n');
+                    scrollbottom();
+                }
+            }
+
+
+            $(document).ready(function() {
+
+                ws.onopen = function() {
+                    addIO("Connected.");
+                    addIO();
+                };
+                ws.onmessage = function(e) {
+                    e.data.split("\n").forEach(function(l) {
+                        output(l, true);
+                    });
+                };
+                ws.onclose = function() {
+                    addIO("Disconnected.");
+                };
+
+
+            });
+
+            $('#volume').change(function() {
+                var volume = $('#volume').val();
+                ws.send('*volume=' + volume);
+            });
+            $('#pauseButton').click(function() {
+                ws.send('*stop');
+                $('#playButton').removeClass('active');
+                $('#pauseButton').addClass('active');
+            });
+            $('#playButton').click(function() {
+                ws.send('*start');
+                $('#playButton').addClass('active');
+                $('#pauseButton').removeClass('active');
+            });
+
+            $('#languageToggle').click(function() {
+                nlp = !nlp;
+                if (nlp) {
+                    $('#languageToggle').addClass('active');
+                }
+                else {
+                    $('#languageToggle').removeClass('active');
+                }
+            });
+
+
+        </script>
+    </body>
+</html>
diff --git a/src/main/resources/client/lib/README b/src/main/resources/client/lib/README
new file mode 100644
index 0000000000000000000000000000000000000000..5be8f5cd9a8b453a9313a4dedc084e204e73a28d
--- /dev/null
+++ b/src/main/resources/client/lib/README
@@ -0,0 +1,4 @@
+The javascript/CSS code in this directory is installed by the bower package manager.
+
+Run 'bower install' from the parent directory to install the libraries here.
+
diff --git a/src/main/resources/client/lib/jquery/.bower.json b/src/main/resources/client/lib/jquery/.bower.json
new file mode 100644
index 0000000000000000000000000000000000000000..671a83ae72219e2e97162d41c92fadad98689fec
--- /dev/null
+++ b/src/main/resources/client/lib/jquery/.bower.json
@@ -0,0 +1,37 @@
+{
+  "name": "jquery",
+  "version": "2.1.1",
+  "main": "dist/jquery.js",
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "build",
+    "speed",
+    "test",
+    "*.md",
+    "AUTHORS.txt",
+    "Gruntfile.js",
+    "package.json"
+  ],
+  "devDependencies": {
+    "sizzle": "1.10.19",
+    "requirejs": "2.1.10",
+    "qunit": "1.14.0",
+    "sinon": "1.8.1"
+  },
+  "keywords": [
+    "jquery",
+    "javascript",
+    "library"
+  ],
+  "homepage": "https://github.com/jquery/jquery",
+  "_release": "2.1.1",
+  "_resolution": {
+    "type": "version",
+    "tag": "2.1.1",
+    "commit": "4dec426aa2a6cbabb1b064319ba7c272d594a688"
+  },
+  "_source": "git://github.com/jquery/jquery.git",
+  "_target": ">=1.6",
+  "_originalSource": "jquery"
+}
\ No newline at end of file
diff --git a/src/main/resources/client/lib/jquery/MIT-LICENSE.txt b/src/main/resources/client/lib/jquery/MIT-LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cdd31b5c7103e4b0191fd1c860559b7b82806f01
--- /dev/null
+++ b/src/main/resources/client/lib/jquery/MIT-LICENSE.txt
@@ -0,0 +1,21 @@
+Copyright 2014 jQuery Foundation and other contributors
+http://jquery.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/main/resources/client/lib/jquery/bower.json b/src/main/resources/client/lib/jquery/bower.json
new file mode 100644
index 0000000000000000000000000000000000000000..c66a7506b46a75f7575ff6fbc3bbbd2f4c5ae67c
--- /dev/null
+++ b/src/main/resources/client/lib/jquery/bower.json
@@ -0,0 +1,27 @@
+{
+  "name": "jquery",
+  "version": "2.1.1",
+  "main": "dist/jquery.js",
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "build",
+    "speed",
+    "test",
+    "*.md",
+    "AUTHORS.txt",
+    "Gruntfile.js",
+    "package.json"
+  ],
+  "devDependencies": {
+    "sizzle": "1.10.19",
+    "requirejs": "2.1.10",
+    "qunit": "1.14.0",
+    "sinon": "1.8.1"
+  },
+  "keywords": [
+    "jquery",
+    "javascript",
+    "library"
+  ]
+}
diff --git a/src/main/resources/client/lib/lodash/.bower.json b/src/main/resources/client/lib/lodash/.bower.json
new file mode 100644
index 0000000000000000000000000000000000000000..e720686d19a70d89489523351704b1e5759a6fc3
--- /dev/null
+++ b/src/main/resources/client/lib/lodash/.bower.json
@@ -0,0 +1,33 @@
+{
+  "name": "lodash",
+  "version": "2.4.1",
+  "main": "dist/lodash.compat.js",
+  "ignore": [
+    ".*",
+    "*.custom.*",
+    "*.template.*",
+    "*.map",
+    "*.md",
+    "/*.min.*",
+    "/lodash.js",
+    "index.js",
+    "component.json",
+    "package.json",
+    "doc",
+    "modularize",
+    "node_modules",
+    "perf",
+    "test",
+    "vendor"
+  ],
+  "homepage": "https://github.com/lodash/lodash",
+  "_release": "2.4.1",
+  "_resolution": {
+    "type": "version",
+    "tag": "2.4.1",
+    "commit": "c7aa842eded639d6d90a5714d3195a8802c86687"
+  },
+  "_source": "git://github.com/lodash/lodash.git",
+  "_target": "*",
+  "_originalSource": "lodash"
+}
\ No newline at end of file
diff --git a/src/main/resources/client/lib/lodash/LICENSE.txt b/src/main/resources/client/lib/lodash/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..49869bbab37764f898974d758324cacaa8c7461f
--- /dev/null
+++ b/src/main/resources/client/lib/lodash/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/src/main/resources/client/lib/lodash/bower.json b/src/main/resources/client/lib/lodash/bower.json
new file mode 100644
index 0000000000000000000000000000000000000000..a6f139d7c3fea599bd27c405a685dee364bad2a0
--- /dev/null
+++ b/src/main/resources/client/lib/lodash/bower.json
@@ -0,0 +1,23 @@
+{
+  "name": "lodash",
+  "version": "2.4.1",
+  "main": "dist/lodash.compat.js",
+  "ignore": [
+    ".*",
+    "*.custom.*",
+    "*.template.*",
+    "*.map",
+    "*.md",
+    "/*.min.*",
+    "/lodash.js",
+    "index.js",
+    "component.json",
+    "package.json",
+    "doc",
+    "modularize",
+    "node_modules",
+    "perf",
+    "test",
+    "vendor"
+  ]
+}
diff --git a/src/main/resources/client/web_socket.js b/src/main/resources/client/web_socket.js
new file mode 100644
index 0000000000000000000000000000000000000000..0561d10c8ae517bd1ddf068c1d0294679561cad6
--- /dev/null
+++ b/src/main/resources/client/web_socket.js
@@ -0,0 +1,389 @@
+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+// Reference: http://dev.w3.org/html5/websockets/
+// Reference: http://tools.ietf.org/html/rfc6455
+
+(function() {
+  
+  if (window.WEB_SOCKET_FORCE_FLASH) {
+    // Keeps going.
+  } else if (window.WebSocket) {
+    return;
+  } else if (window.MozWebSocket) {
+    // Firefox.
+    window.WebSocket = MozWebSocket;
+    return;
+  }
+  
+  var logger;
+  if (window.WEB_SOCKET_LOGGER) {
+    logger = WEB_SOCKET_LOGGER;
+  } else if (window.console && window.console.log && window.console.error) {
+    // In some environment, console is defined but console.log or console.error is missing.
+    logger = window.console;
+  } else {
+    logger = {log: function(){ }, error: function(){ }};
+  }
+  
+  // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
+  if (swfobject.getFlashPlayerVersion().major < 10) {
+    logger.error("Flash Player >= 10.0.0 is required.");
+    return;
+  }
+  if (location.protocol == "file:") {
+    logger.error(
+      "WARNING: web-socket-js doesn't work in file:///... URL " +
+      "unless you set Flash Security Settings properly. " +
+      "Open the page via Web server i.e. http://...");
+  }
+
+  /**
+   * Our own implementation of WebSocket class using Flash.
+   * @param {string} url
+   * @param {array or string} protocols
+   * @param {string} proxyHost
+   * @param {int} proxyPort
+   * @param {string} headers
+   */
+  window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
+    var self = this;
+    self.__id = WebSocket.__nextId++;
+    WebSocket.__instances[self.__id] = self;
+    self.readyState = WebSocket.CONNECTING;
+    self.bufferedAmount = 0;
+    self.__events = {};
+    if (!protocols) {
+      protocols = [];
+    } else if (typeof protocols == "string") {
+      protocols = [protocols];
+    }
+    // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
+    // Otherwise, when onopen fires immediately, onopen is called before it is set.
+    self.__createTask = setTimeout(function() {
+      WebSocket.__addTask(function() {
+        self.__createTask = null;
+        WebSocket.__flash.create(
+            self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
+      });
+    }, 0);
+  };
+
+  /**
+   * Send data to the web socket.
+   * @param {string} data  The data to send to the socket.
+   * @return {boolean}  True for success, false for failure.
+   */
+  WebSocket.prototype.send = function(data) {
+    if (this.readyState == WebSocket.CONNECTING) {
+      throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+    }
+    // We use encodeURIComponent() here, because FABridge doesn't work if
+    // the argument includes some characters. We don't use escape() here
+    // because of this:
+    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+    // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+    // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+    // Note by wtritch: Hopefully this will not be necessary using ExternalInterface.  Will require
+    // additional testing.
+    var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
+    if (result < 0) { // success
+      return true;
+    } else {
+      this.bufferedAmount += result;
+      return false;
+    }
+  };
+
+  /**
+   * Close this web socket gracefully.
+   */
+  WebSocket.prototype.close = function() {
+    if (this.__createTask) {
+      clearTimeout(this.__createTask);
+      this.__createTask = null;
+      this.readyState = WebSocket.CLOSED;
+      return;
+    }
+    if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+      return;
+    }
+    this.readyState = WebSocket.CLOSING;
+    WebSocket.__flash.close(this.__id);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) {
+      this.__events[type] = [];
+    }
+    this.__events[type].push(listener);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) return;
+    var events = this.__events[type];
+    for (var i = events.length - 1; i >= 0; --i) {
+      if (events[i] === listener) {
+        events.splice(i, 1);
+        break;
+      }
+    }
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {Event} event
+   * @return void
+   */
+  WebSocket.prototype.dispatchEvent = function(event) {
+    var events = this.__events[event.type] || [];
+    for (var i = 0; i < events.length; ++i) {
+      events[i](event);
+    }
+    var handler = this["on" + event.type];
+    if (handler) handler.apply(this, [event]);
+  };
+
+  /**
+   * Handles an event from Flash.
+   * @param {Object} flashEvent
+   */
+  WebSocket.prototype.__handleEvent = function(flashEvent) {
+    
+    if ("readyState" in flashEvent) {
+      this.readyState = flashEvent.readyState;
+    }
+    if ("protocol" in flashEvent) {
+      this.protocol = flashEvent.protocol;
+    }
+    
+    var jsEvent;
+    if (flashEvent.type == "open" || flashEvent.type == "error") {
+      jsEvent = this.__createSimpleEvent(flashEvent.type);
+    } else if (flashEvent.type == "close") {
+      jsEvent = this.__createSimpleEvent("close");
+      jsEvent.wasClean = flashEvent.wasClean ? true : false;
+      jsEvent.code = flashEvent.code;
+      jsEvent.reason = flashEvent.reason;
+    } else if (flashEvent.type == "message") {
+      var data = decodeURIComponent(flashEvent.message);
+      jsEvent = this.__createMessageEvent("message", data);
+    } else {
+      throw "unknown event type: " + flashEvent.type;
+    }
+    
+    this.dispatchEvent(jsEvent);
+    
+  };
+  
+  WebSocket.prototype.__createSimpleEvent = function(type) {
+    if (document.createEvent && window.Event) {
+      var event = document.createEvent("Event");
+      event.initEvent(type, false, false);
+      return event;
+    } else {
+      return {type: type, bubbles: false, cancelable: false};
+    }
+  };
+  
+  WebSocket.prototype.__createMessageEvent = function(type, data) {
+    if (document.createEvent && window.MessageEvent && !window.opera) {
+      var event = document.createEvent("MessageEvent");
+      event.initMessageEvent("message", false, false, data, null, null, window, null);
+      return event;
+    } else {
+      // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
+      return {type: type, data: data, bubbles: false, cancelable: false};
+    }
+  };
+  
+  /**
+   * Define the WebSocket readyState enumeration.
+   */
+  WebSocket.CONNECTING = 0;
+  WebSocket.OPEN = 1;
+  WebSocket.CLOSING = 2;
+  WebSocket.CLOSED = 3;
+
+  WebSocket.__initialized = false;
+  WebSocket.__flash = null;
+  WebSocket.__instances = {};
+  WebSocket.__tasks = [];
+  WebSocket.__nextId = 0;
+  
+  /**
+   * Load a new flash security policy file.
+   * @param {string} url
+   */
+  WebSocket.loadFlashPolicyFile = function(url){
+    WebSocket.__addTask(function() {
+      WebSocket.__flash.loadManualPolicyFile(url);
+    });
+  };
+
+  /**
+   * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
+   */
+  WebSocket.__initialize = function() {
+    
+    if (WebSocket.__initialized) return;
+    WebSocket.__initialized = true;
+    
+    if (WebSocket.__swfLocation) {
+      // For backword compatibility.
+      window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+    }
+    if (!window.WEB_SOCKET_SWF_LOCATION) {
+      logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+      return;
+    }
+    if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
+        !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
+        WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
+      var swfHost = RegExp.$1;
+      if (location.host != swfHost) {
+        logger.error(
+            "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
+            "('" + location.host + "' != '" + swfHost + "'). " +
+            "See also 'How to host HTML file and SWF file in different domains' section " +
+            "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
+            "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
+      }
+    }
+    var container = document.createElement("div");
+    container.id = "webSocketContainer";
+    // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+    // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+    // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+    // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+    // the best we can do as far as we know now.
+    container.style.position = "absolute";
+    if (WebSocket.__isFlashLite()) {
+      container.style.left = "0px";
+      container.style.top = "0px";
+    } else {
+      container.style.left = "-100px";
+      container.style.top = "-100px";
+    }
+    var holder = document.createElement("div");
+    holder.id = "webSocketFlash";
+    container.appendChild(holder);
+    document.body.appendChild(container);
+    // See this article for hasPriority:
+    // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+    swfobject.embedSWF(
+      WEB_SOCKET_SWF_LOCATION,
+      "webSocketFlash",
+      "1" /* width */,
+      "1" /* height */,
+      "10.0.0" /* SWF version */,
+      null,
+      null,
+      {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+      null,
+      function(e) {
+        if (!e.success) {
+          logger.error("[WebSocket] swfobject.embedSWF failed");
+        }
+      }
+    );
+    
+  };
+  
+  /**
+   * Called by Flash to notify JS that it's fully loaded and ready
+   * for communication.
+   */
+  WebSocket.__onFlashInitialized = function() {
+    // We need to set a timeout here to avoid round-trip calls
+    // to flash during the initialization process.
+    setTimeout(function() {
+      WebSocket.__flash = document.getElementById("webSocketFlash");
+      WebSocket.__flash.setCallerUrl(location.href);
+      WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
+      for (var i = 0; i < WebSocket.__tasks.length; ++i) {
+        WebSocket.__tasks[i]();
+      }
+      WebSocket.__tasks = [];
+    }, 0);
+  };
+  
+  /**
+   * Called by Flash to notify WebSockets events are fired.
+   */
+  WebSocket.__onFlashEvent = function() {
+    setTimeout(function() {
+      try {
+        // Gets events using receiveEvents() instead of getting it from event object
+        // of Flash event. This is to make sure to keep message order.
+        // It seems sometimes Flash events don't arrive in the same order as they are sent.
+        var events = WebSocket.__flash.receiveEvents();
+        for (var i = 0; i < events.length; ++i) {
+          WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
+        }
+      } catch (e) {
+        logger.error(e);
+      }
+    }, 0);
+    return true;
+  };
+  
+  // Called by Flash.
+  WebSocket.__log = function(message) {
+    logger.log(decodeURIComponent(message));
+  };
+  
+  // Called by Flash.
+  WebSocket.__error = function(message) {
+    logger.error(decodeURIComponent(message));
+  };
+  
+  WebSocket.__addTask = function(task) {
+    if (WebSocket.__flash) {
+      task();
+    } else {
+      WebSocket.__tasks.push(task);
+    }
+  };
+  
+  /**
+   * Test if the browser is running flash lite.
+   * @return {boolean} True if flash lite is running, false otherwise.
+   */
+  WebSocket.__isFlashLite = function() {
+    if (!window.navigator || !window.navigator.mimeTypes) {
+      return false;
+    }
+    var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
+    if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+      return false;
+    }
+    return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+  };
+  
+  if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+    // NOTE:
+    //   This fires immediately if web_socket.js is dynamically loaded after
+    //   the document is loaded.
+    swfobject.addDomLoadEvent(function() {
+      WebSocket.__initialize();
+    });
+  }
+  
+})();