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('↲').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(); + }); + } + +})();