├── .gitignore ├── README.md ├── favicons ├── android-chrome-36x36.png ├── android-chrome-48x48.png ├── android-chrome-72x72.png ├── android-chrome-96x96.png ├── apple-touch-icon-114x114.png ├── apple-touch-icon-120x120.png ├── apple-touch-icon-57x57.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-72x72.png ├── apple-touch-icon-76x76.png ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── manifest.json ├── mstile-150x150.png ├── mstile-310x150.png └── mstile-70x70.png ├── images ├── blue_marker.png ├── grey_marker.png └── twitter_icon_small.png ├── index.html ├── javascript ├── html-sanitizer-minified.js ├── main.js ├── map.js └── modernizr.custom.js ├── map-chat.png ├── pom.xml ├── server ├── ChatVerticle.java └── conf.json └── stylesheets └── main.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 4 | 5 | *.iml 6 | 7 | ## Directory-based project format: 8 | .idea/ 9 | # if you remove the above rule, at least ignore the following: 10 | 11 | # User-specific stuff: 12 | # .idea/workspace.xml 13 | # .idea/tasks.xml 14 | # .idea/dictionaries 15 | 16 | # Sensitive or high-churn files: 17 | # .idea/dataSources.ids 18 | # .idea/dataSources.xml 19 | # .idea/sqlDataSources.xml 20 | # .idea/dynamic.xml 21 | # .idea/uiDesigner.xml 22 | 23 | # Gradle: 24 | # .idea/gradle.xml 25 | # .idea/libraries 26 | 27 | # Mongo Explorer plugin: 28 | # .idea/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.ipr 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | out/ 38 | 39 | # mpeltonen/sbt-idea plugin 40 | .idea_modules/ 41 | 42 | # JIRA plugin 43 | atlassian-ide-plugin.xml 44 | 45 | # Crashlytics plugin (for Android Studio and IntelliJ) 46 | com_crashlytics_export_strings.xml 47 | crashlytics.properties 48 | crashlytics-build.properties 49 | 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MapChat - [Demo](http://idoco.github.io/map-chat) 2 | A super simple location based chat 3 | 4 | ![](https://raw.githubusercontent.com/idoco/map-chat/master/map-chat.png) 5 | 6 | ## Features 7 | - Super simple location based chat. 8 | - No registration or message history. 9 | - Built-in Google Translate widget. 10 | - Create a private chat map by adding #name to the url. 11 | 12 | MapChat is using [ipinfo.io](http://ipinfo.io/) to identify the user location, since the geolcation API is no longer enabled in non-https websites. 13 | 14 | ## Embed MapChat in your website 15 | - Simply add this `iframe` to your website: 16 | ```html 17 | 20 | ``` 21 | - The minimum recommended size it 640x480. 22 | - It is recommended to embed private map chats by using a unique #topic. 23 | 24 | ## How to deploy your own instance 25 | - [Have Vert.x (2.1.5) on your path](http://vertx.io/vertx2/install.html). 26 | - Run the server with "vertx run ChatVerticle.java". 27 | - Open index.html. 28 | - Chat. 29 | 30 | ## Contributing to MapChat 31 | - Use GitHub Issues to report bugs and suggest new features. 32 | - Please search the existing issues for your bug and create a new one only if the issue is not yet tracked! 33 | - Feel free to fork this project and suggest new features as pull requests. 34 | 35 | ## [Demo](http://idoco.github.io/map-chat) 36 | This demo is hosted on GitHub pages and uses a single core Azure instance as the Vert.x SockJS server. 37 | -------------------------------------------------------------------------------- /favicons/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/android-chrome-36x36.png -------------------------------------------------------------------------------- /favicons/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/android-chrome-48x48.png -------------------------------------------------------------------------------- /favicons/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/android-chrome-72x72.png -------------------------------------------------------------------------------- /favicons/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/android-chrome-96x96.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #da532c 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/favicon.ico -------------------------------------------------------------------------------- /favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My app", 3 | "icons": [ 4 | { 5 | "src": "\/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-chrome-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-chrome-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /images/blue_marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/images/blue_marker.png -------------------------------------------------------------------------------- /images/grey_marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/images/grey_marker.png -------------------------------------------------------------------------------- /images/twitter_icon_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idoco/map-chat/fd78c08d803c5d51c564ef6e4c91e1e92cf5909d/images/twitter_icon_small.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | MapChat 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | Translation 29 |
30 |
31 | 36 | 37 | 38 | 66 | 67 | 68 | 69 | Fork me on GitHub 71 | 72 | 73 |
74 |
75 | 77 | 78 | 79 |
80 |
81 | 83 | 84 | 85 |
86 |
87 | 88 |
89 | 90 |
91 |
92 | 93 |
94 |
95 | 96 |
97 | 98 | 99 | 100 |
101 | 102 |
103 | 105 | 106 | 107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /javascript/html-sanitizer-minified.js: -------------------------------------------------------------------------------- 1 | (function(){var o=null,q={e:{NONE:0,URI:1,URI_FRAGMENT:11,SCRIPT:2,STYLE:3,ID:4,IDREF:5,IDREFS:6,GLOBAL_NAME:7,LOCAL_NAME:8,CLASSES:9,FRAME_TARGET:10},k:{"*::class":9,"*::dir":0,"*::id":4,"*::lang":0,"*::onclick":2,"*::ondblclick":2,"*::onkeydown":2,"*::onkeypress":2,"*::onkeyup":2,"*::onload":2,"*::onmousedown":2,"*::onmousemove":2,"*::onmouseout":2,"*::onmouseover":2,"*::onmouseup":2,"*::onscroll":2,"*::style":3,"*::title":0,"a::accesskey":0,"a::coords":0,"a::href":1,"a::hreflang":0,"a::name":7,"a::onblur":2, 2 | "a::onfocus":2,"a::rel":0,"a::rev":0,"a::shape":0,"a::tabindex":0,"a::target":10,"a::type":0,"area::accesskey":0,"area::alt":0,"area::coords":0,"area::href":1,"area::nohref":0,"area::onblur":2,"area::onfocus":2,"area::shape":0,"area::tabindex":0,"area::target":10,"bdo::dir":0,"blockquote::cite":1,"br::clear":0,"button::accesskey":0,"button::disabled":0,"button::name":8,"button::onblur":2,"button::onfocus":2,"button::tabindex":0,"button::type":0,"button::value":0,"canvas::height":0,"canvas::width":0, 3 | "caption::align":0,"col::align":0,"col::char":0,"col::charoff":0,"col::span":0,"col::valign":0,"col::width":0,"colgroup::align":0,"colgroup::char":0,"colgroup::charoff":0,"colgroup::span":0,"colgroup::valign":0,"colgroup::width":0,"del::cite":1,"del::datetime":0,"dir::compact":0,"div::align":0,"dl::compact":0,"font::color":0,"font::face":0,"font::size":0,"form::accept":0,"form::action":1,"form::autocomplete":0,"form::enctype":0,"form::method":0,"form::name":7,"form::onreset":2,"form::onsubmit":2, 4 | "form::target":10,"h1::align":0,"h2::align":0,"h3::align":0,"h4::align":0,"h5::align":0,"h6::align":0,"hr::align":0,"hr::noshade":0,"hr::size":0,"hr::width":0,"iframe::align":0,"iframe::frameborder":0,"iframe::height":0,"iframe::marginheight":0,"iframe::marginwidth":0,"iframe::width":0,"img::align":0,"img::alt":0,"img::border":0,"img::height":0,"img::hspace":0,"img::ismap":0,"img::name":7,"img::src":1,"img::usemap":11,"img::vspace":0,"img::width":0,"input::accept":0,"input::accesskey":0,"input::align":0, 5 | "input::alt":0,"input::autocomplete":0,"input::checked":0,"input::disabled":0,"input::ismap":0,"input::maxlength":0,"input::name":8,"input::onblur":2,"input::onchange":2,"input::onfocus":2,"input::onselect":2,"input::readonly":0,"input::size":0,"input::src":1,"input::tabindex":0,"input::type":0,"input::usemap":11,"input::value":0,"ins::cite":1,"ins::datetime":0,"label::accesskey":0,"label::for":5,"label::onblur":2,"label::onfocus":2,"legend::accesskey":0,"legend::align":0,"li::type":0,"li::value":0, 6 | "map::name":7,"menu::compact":0,"ol::compact":0,"ol::start":0,"ol::type":0,"optgroup::disabled":0,"optgroup::label":0,"option::disabled":0,"option::label":0,"option::selected":0,"option::value":0,"p::align":0,"pre::width":0,"q::cite":1,"select::disabled":0,"select::multiple":0,"select::name":8,"select::onblur":2,"select::onchange":2,"select::onfocus":2,"select::size":0,"select::tabindex":0,"table::align":0,"table::bgcolor":0,"table::border":0,"table::cellpadding":0,"table::cellspacing":0,"table::frame":0, 7 | "table::rules":0,"table::summary":0,"table::width":0,"tbody::align":0,"tbody::char":0,"tbody::charoff":0,"tbody::valign":0,"td::abbr":0,"td::align":0,"td::axis":0,"td::bgcolor":0,"td::char":0,"td::charoff":0,"td::colspan":0,"td::headers":6,"td::height":0,"td::nowrap":0,"td::rowspan":0,"td::scope":0,"td::valign":0,"td::width":0,"textarea::accesskey":0,"textarea::cols":0,"textarea::disabled":0,"textarea::name":8,"textarea::onblur":2,"textarea::onchange":2,"textarea::onfocus":2,"textarea::onselect":2, 8 | "textarea::readonly":0,"textarea::rows":0,"textarea::tabindex":0,"tfoot::align":0,"tfoot::char":0,"tfoot::charoff":0,"tfoot::valign":0,"th::abbr":0,"th::align":0,"th::axis":0,"th::bgcolor":0,"th::char":0,"th::charoff":0,"th::colspan":0,"th::headers":6,"th::height":0,"th::nowrap":0,"th::rowspan":0,"th::scope":0,"th::valign":0,"th::width":0,"thead::align":0,"thead::char":0,"thead::charoff":0,"thead::valign":0,"tr::align":0,"tr::bgcolor":0,"tr::char":0,"tr::charoff":0,"tr::valign":0,"ul::compact":0, 9 | "ul::type":0},d:{OPTIONAL_ENDTAG:1,EMPTY:2,CDATA:4,RCDATA:8,UNSAFE:16,FOLDABLE:32,SCRIPT:64,STYLE:128},f:{a:0,abbr:0,acronym:0,address:0,applet:16,area:2,article:16,aside:16,audio:16,b:0,base:18,basefont:18,bdi:16,bdo:0,big:0,blockquote:0,body:49,br:2,button:0,canvas:0,caption:0,center:0,cite:0,code:0,col:2,colgroup:1,data:16,datalist:16,dd:1,del:0,details:16,dfn:0,dir:0,div:0,dl:0,dt:1,em:0,fieldset:0,figcaption:16,figure:16,font:0,footer:16,form:0,frame:18,frameset:16,h1:0,h2:0,h3:0,h4:0,h5:0,h6:0, 10 | head:49,header:16,hgroup:16,hr:2,html:49,i:0,iframe:4,img:2,input:2,ins:0,isindex:18,kbd:0,label:0,legend:0,li:1,link:18,map:0,mark:16,menu:0,meta:18,meter:16,nav:0,nobr:0,noembed:4,noframes:20,noscript:20,object:16,ol:0,optgroup:0,option:1,output:16,p:1,param:18,pre:0,progress:16,q:0,s:0,samp:0,script:84,section:16,select:0,small:0,span:0,strike:0,strong:0,style:148,sub:0,summary:16,sup:0,table:0,tbody:1,td:1,textarea:8,tfoot:1,th:1,thead:1,time:16,title:24,tr:1,tt:0,u:0,ul:0,"var":0,video:16},A:{NOT_LOADED:0, 11 | SAME_DOCUMENT:1,NEW_DOCUMENT:2},w:{"a::href":2,"area::href":2,"blockquote::cite":0,"body::background":1,"del::cite":0,"form::action":2,"img::src":1,"input::src":1,"ins::cite":0,"q::cite":0},z:{UNSANDBOXED:2,SANDBOXED:1,DATA:0},v:{"a::href":2,"area::href":2,"blockquote::cite":2,"body::background":1,"del::cite":2,"form::action":2,"img::src":1,"input::src":1,"ins::cite":2,"q::cite":2}};q.ATTRIBS=q.k;q.ELEMENTS=q.f;q.URIEFFECTS=q.w;q.LOADERTYPES=q.v;q.atype=q.e;q.eflags=q.d;q.ltypes=q.z;q.ueffects=q.A; 12 | "undefined"!==typeof window&&(window.html4=q);var R=function(h){function S(a,d){var b;b=d.toLowerCase();if(w.hasOwnProperty(b))b=w[b];else{var e=b.match(T);b=e?String.fromCharCode(parseInt(e[1],10)):(e=b.match(U))?String.fromCharCode(parseInt(e[1],16)):""}return b}function x(a){return a.replace(V,S)}function y(a){return(""+a).replace(W,"&").replace(B,"<").replace(C,">").replace(X,""")}function D(a){return a.replace(Y,"&$1").replace(B,"<").replace(C,">")}function E(a){var d={l:a.l||a.cdata,m:a.m||a.comment,n:a.n||a.endDoc, 13 | h:a.h||a.endTag,c:a.c||a.pcdata,r:a.r||a.rcdata,t:a.t||a.startDoc,j:a.j||a.startTag};return function(a,e){var c;var k=/(<\/|<\!--|<[!?]|[&<>])/g;c=a+"";if(Z)c=c.split(k);else{for(var f=[],g=0,h;(h=k.exec(c))!==o;)f.push(c.substring(g,h.index)),f.push(h[0]),g=h.index+h[0].length;f.push(c.substring(g));c=f}F(d,c,0,{g:!1,o:!1},e)}}function m(a,d,b,e,c){return function(){F(a,d,b,e,c)}}function F(a,d,b,e,c){try{a.t&&0==b&&a.t(c);for(var k,f,g,l=d.length;b"===d[b+1])b+=2,g=k[1].toLowerCase(),a.h&&a.h(g,c,n,m(a,d,b,e,c));else{var r=d,s=b,t=a,p=c,A=n,z=e,v=G(r,s);v?(t.h&&t.h(v.name,p,A,m(t,r,s,z,p)),b=v.next):b=r.length}else a.c&&a.c("</",c,n,m(a,d,b,e,c));break;case "<":if(k=/^([\w:]+)\s*\/?/.exec(i))if(k[0].length===i.length&&">"===d[b+1]){b+=2;g=k[1].toLowerCase();a.j&&a.j(g,[],c,n,m(a,d,b,e,c)); 15 | var w=h.f[g];w&H&&(b=I(d,{name:g,next:b,d:w},a,c,n,e))}else{var r=d,s=a,t=c,p=n,A=e,u=G(r,b);u?(s.j&&s.j(u.name,u.B,t,p,m(s,r,u.next,A,t)),b=u.d&H?I(r,u,s,t,p,A):u.next):b=r.length}else a.c&&a.c("<",c,n,m(a,d,b,e,c));break;case "<\!--":if(!e.o){for(f=b+1;f"===d[f]&&/--$/.test(d[f-1]));f++);if(f"!==d[f];f++);f"!==d[f];f++);f":a.c&&a.c(">",c,n,m(a,d,b,e,c));break;case "":break;default:a.c&&a.c(j,c,n,m(a,d,b,e,c))}}a.n&&a.n(c)}catch(y){if(y!==n)throw y;}}function I(a,d,b,e,c,k){var f=a.length;z.hasOwnProperty(d.name)||(z[d.name]=RegExp("^"+d.name+"(?:[\\s\\/]|$)","i"));for(var g= 17 | z[d.name],l=d.next,j=d.next+1;j"!==a[k];k++)c+=a[k];if(!(f<=k)){for(var g=[];""!==c;)if(b=aa.exec(c))if(b[4]&&!b[5]||b[6]&&!b[7]){for(var b= 18 | b[4]||b[6],l=!1,c=[c,a[k++]];k"===a[k])break}else 0<=a[k].indexOf(b)&&(l=!0);c.push(a[k])}if(f<=k)break;c=c.join("")}else{var l=b[1].toLowerCase(),j;if(b[2]){j=b[3];var i=j.charCodeAt(0);if(34===i||39===i)j=j.substr(1,j.length-2);j=x(j.replace(ba,""))}else j=l;g.push(l,j);c=c.substr(b[0].length)}else c=c.replace(/^[\s\S][^a-z\s]*/,"");e.B=g;e.next=k+1;return e}}function J(a){function d(a,b){e||b.push(a)}var b,e;return E({startDoc:function(){b=[];e=!1},startTag:function(c,d,f){if(!e&& 19 | h.f.hasOwnProperty(c)){var g=h.f[c];if(!(g&h.d.FOLDABLE))if(d=a(c,d)){g&h.d.EMPTY||b.push(c);f.push("<",c);c=0;for(g=d.length;c")}else e=!(g&h.d.EMPTY)}},endTag:function(a,d){if(e)e=!1;else if(h.f.hasOwnProperty(a)){var f=h.f[a];if(!(f&(h.d.EMPTY|h.d.FOLDABLE))){if(f&h.d.OPTIONAL_ENDTAG)for(f=b.length;0<=--f;){var g=b[f];if(g===a)break;if(!(h.f[g]&h.d.OPTIONAL_ENDTAG))return}else for(f=b.length;0<=--f&&b[f]!==a;); 20 | if(!(0>f)){for(var i=b.length;--i>f;)g=b[i],h.f[g]&h.d.OPTIONAL_ENDTAG||d.push("");b.length=f;d.push("")}}}},pcdata:d,rcdata:d,cdata:d,endDoc:function(a){for(;b.length;b.length--)a.push("")}})}function K(a,d,b,e,c){if(!c)return o;var h=(""+a).match(ca);return h&&(!h[1]||da.test(h[1]))?c(a,d,b,e):o}function p(a,d,b,e,c){b||a(d+" removed",{C:"removed",tagName:d});if(e!==c){var h="changed";e&&!c?h="removed":!e&&c&&(h="added");a(d+"."+b+" "+h,{C:h,tagName:d,G:b,oldValue:e, 21 | newValue:c})}}function L(a,d,b){d=d+"::"+b;if(a.hasOwnProperty(d))return a[d];d="*::"+b;if(a.hasOwnProperty(d))return a[d]}function M(a,d,b,e,c){for(var i=0;i",amp:"&",nbsp:"\u00a0",quot:'"',apos:"'"},T=/^#(\d+)$/,U=/^#x([0-9A-Fa-f]+)$/,ba=/\0/g,V=/&(#[0-9]+|#[xX][0-9A-Fa-f]+|\w+);/g, 24 | $=/^(#[0-9]+|#[xX][0-9A-Fa-f]+|\w+);/,W=/&/g,Y=/&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi,B=/[<]/g,C=/>/g,X=/\"/g,aa=/^\s*([-.:\w]+)(?:\s*(=)\s*((")[^"]*("|$)|(')[^']*('|$)|(?=[a-z][-\w]*\s*=)|[^"'\s]*))?/i,Z=3==="a,b".split(/(,)/).length,H=h.d.CDATA|h.d.RCDATA,n={},z={},ca=/^(?:([^:/?# ]+):)?/,da=/^(?:https?|mailto)$/i,i={};i.I=i.escapeAttrib=y;i.J=i.makeHtmlSanitizer=J;i.K=i.makeSaxParser=E;i.L=i.makeTagPolicy=P;i.M=i.normalizeRCData=D;i.N=i.sanitize=function(a,d,b,e){return Q(a,P(d,b,e))}; 25 | i.O=i.sanitizeAttribs=M;i.P=i.sanitizeWithPolicy=Q;i.Q=i.unescapeEntities=x;return i}(q),ea=R.sanitize;"undefined"!==typeof window&&(window.html=R,window.html_sanitize=ea);})(); 26 | -------------------------------------------------------------------------------- /javascript/main.js: -------------------------------------------------------------------------------- 1 | 2 | var eb; 3 | var retryCount = 10; 4 | 5 | // Support dynamic topic registration by #word 6 | var urlHashTopic = location.hash ? location.hash.substring(1).toLowerCase() : null; 7 | var topic = urlHashTopic ? urlHashTopic : "main"; 8 | 9 | function initialiseEventBus(){ 10 | eb = new vertx.EventBus("http://localhost:8080/chat"); 11 | 12 | eb.onopen = function () { 13 | subscribe(topic); 14 | }; 15 | 16 | eb.onclose = function(){ 17 | if (retryCount) { 18 | retryCount--; 19 | console.log('Connection lost, scheduling reconnect'); 20 | setTimeout(initialiseEventBus, 1000); 21 | } else{ 22 | Materialize.toast('Connection lost, please refresh :( ', 10000); 23 | } 24 | }; 25 | } 26 | 27 | function sendMessage(topic, input) { 28 | if (input.val()) { 29 | publish(topic, input.val()); 30 | input.val(''); 31 | } 32 | } 33 | 34 | function publish(address, message) { 35 | if (eb) { 36 | var json = createMessage(message); 37 | eb.publish(address, json); 38 | } 39 | } 40 | 41 | function subscribe(address) { 42 | if (eb) { 43 | eb.registerHandler(address, function (msg) { 44 | if (msg.newSessionId) { 45 | retryCount = 5; 46 | mySessionId = msg.newSessionId; 47 | publish(topic,""); // Sending a first empty message 48 | } else { 49 | displayMessageOnMap(msg); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | $( document ).ready(function() { 56 | if(!Modernizr.websockets || !Modernizr.geolocation){ 57 | Materialize.toast('Browser not supported :(', 10000); 58 | } 59 | 60 | $("#side-nav-button").sideNav(); 61 | 62 | var input = $("#input"); 63 | input.keyup(function (e) { 64 | if (e.keyCode == 13) { 65 | sendMessage(topic, input); 66 | } 67 | }); 68 | input.focus(); 69 | 70 | $("#send-button").click(function(){ 71 | sendMessage(topic, input); 72 | }); 73 | 74 | $("#notification_lever").change(function() { 75 | advanced = !advanced; 76 | Materialize.toast(advanced ? 'Notifications On' : 'Notifications Off', 3000); 77 | }); 78 | 79 | $("#accurate_location_lever").change(function() { 80 | shareAccurateLocation = !shareAccurateLocation; 81 | Materialize.toast(shareAccurateLocation ? 'Sharing Your Accurate Location' : 'Sharing Your Fuzzy Location', 3000); 82 | }); 83 | 84 | if (topic != "main"){ 85 | Materialize.toast("Private chat map - "+topic, 5000); 86 | } 87 | 88 | Materialize.toast("New: Click a user dot to mute it!", 7000); 89 | }); 90 | -------------------------------------------------------------------------------- /javascript/map.js: -------------------------------------------------------------------------------- 1 | 2 | var mySessionId; 3 | var map; 4 | var userLocation; 5 | var fuzzyUserLocation; 6 | var markersMap = {}; 7 | var markerImage; 8 | var advanced = false; 9 | var infoWindowZIndex = 100; 10 | var shareAccurateLocation = false; 11 | 12 | var isLowResolution = window.screen.width < 768; 13 | var defaultZoom = isLowResolution ? 2 : 3; 14 | var minZoom = isLowResolution ? 1 : 3; 15 | 16 | var locationOptions = { 17 | enableHighAccuracy: true, 18 | timeout: 10000, 19 | maximumAge: 10000 20 | }; 21 | 22 | var entityMap = { 23 | "&": "&", 24 | "<": "<", 25 | ">": ">", 26 | '"': '"', 27 | "#": "#", 28 | "'": ''', 29 | "/": '/', 30 | "卐": 'I am a dick ', 31 | "卍": 'I am a dick ' 32 | }; 33 | 34 | function initialize() { 35 | 36 | var defaultLatLng = new google.maps.LatLng(32.078043, 34.774177); // Add the coordinates 37 | 38 | markerImage = { 39 | url: 'images/blue_marker.png', 40 | scaledSize: new google.maps.Size(30, 30) 41 | }; 42 | 43 | disabledMarkerImage = { 44 | url: 'images/grey_marker.png', 45 | scaledSize: new google.maps.Size(30, 30) 46 | }; 47 | 48 | 49 | var mapOptions = { 50 | center: defaultLatLng, 51 | zoom: defaultZoom, // The initial zoom level when your map loads (0-20) 52 | minZoom: minZoom, // Minimum zoom level allowed (0-20) 53 | maxZoom: 18, // Maximum soom level allowed (0-20) 54 | zoomControl:false, // Set to true if using zoomControlOptions below, or false to remove all zoom controls. 55 | mapTypeId: google.maps.MapTypeId.ROADMAP, // Set the type of Map 56 | scrollwheel: true, // Enable Mouse Scroll zooming 57 | 58 | // All of the below are set to true by default, so simply remove if set to true: 59 | panControl:false, // Set to false to disable 60 | mapTypeControl:false, // Disable Map/Satellite switch 61 | scaleControl:false, // Set to false to hide scale 62 | streetViewControl:false, // Set to disable to hide street view 63 | overviewMapControl:false, // Set to false to remove overview control 64 | rotateControl:false // Set to false to disable rotate control 65 | }; 66 | var mapDiv = document.getElementById('map-canvas'); 67 | map = new google.maps.Map(mapDiv, mapOptions); 68 | 69 | navigator.geolocation.getCurrentPosition(onFirstPosition, onPositionError, locationOptions); 70 | } 71 | 72 | function onFirstPosition(position){ 73 | setUserLocation(position.coords.latitude, position.coords.longitude); 74 | initialiseEventBus(); 75 | map.panTo(userLocation); 76 | } 77 | 78 | function onPositionUpdate(position) { 79 | if (markersMap[mySessionId]) { //update user marker position 80 | setUserLocation(position.coords.latitude, position.coords.longitude); 81 | var userMarker = markersMap[mySessionId].marker; 82 | userMarker.setPosition(shareAccurateLocation ? userLocation : fuzzyUserLocation); 83 | } 84 | } 85 | 86 | function onPositionError(err) { 87 | // try fallback location provider ipinfo.io or generate random location 88 | $.getJSON("http://ipinfo.io", onFallbackLocationProviderResponse, useRandomLocation); 89 | } 90 | 91 | function onFallbackLocationProviderResponse(ipinfo){ 92 | console.log("Found location ["+ipinfo.loc+"] by ipinfo.io"); 93 | var latLong = ipinfo.loc.split(","); 94 | onFirstPosition({ 95 | "coords" : { 96 | latitude : parseFloat(latLong[0]), 97 | longitude : parseFloat(latLong[1]) 98 | } 99 | }); 100 | } 101 | 102 | function useRandomLocation(err) { 103 | Materialize.toast('User location problem, using random location :P', 7000); 104 | // These ranges cover only the center of the map 105 | var lat = (90 * Math.random() - 22.5).toFixed(3); 106 | var lng = (180 * Math.random() - 90).toFixed(3); 107 | onFirstPosition({ 108 | "coords" : { 109 | latitude : parseFloat(lat), 110 | longitude : parseFloat(lng) 111 | } 112 | }); 113 | } 114 | 115 | function setUserLocation(lat, lng){ 116 | userLocation = new google.maps.LatLng(lat, lng); 117 | fuzzyUserLocation = new google.maps.LatLng(Math.round(lat * 100) / 100, Math.round(lng * 100) / 100); 118 | } 119 | 120 | function createMessage(text){ 121 | return { 122 | lat: shareAccurateLocation ? userLocation.lat() : fuzzyUserLocation.lat(), 123 | lng: shareAccurateLocation ? userLocation.lng() : fuzzyUserLocation.lng(), 124 | text: text 125 | }; 126 | } 127 | 128 | function displayMessageOnMap(msg){ 129 | var newPosition = new google.maps.LatLng(msg.lat,msg.lng); 130 | var msgSessionId = msg.sessionId; 131 | 132 | // xss prevention hack 133 | msg.text = html_sanitize(msg.text); 134 | 135 | msg.text = String(msg.text).replace(/[&<>"#'\/卐卍]/g, function (s) { 136 | return entityMap[s]; 137 | }); 138 | 139 | // msg.text = msg.text ? embedTweet(msg.text) : ""; 140 | msg.text = msg.text.replace(/#(\S*)/g,'#$1'); 141 | 142 | // linkify 143 | msg.text = msg.text.replace(/(\b(https?|ftp|file)://[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, 144 | "$1"); 145 | 146 | if(markersMap[msgSessionId]){ // update existing marker 147 | var existingMarker = markersMap[msgSessionId].marker; 148 | var existingInfoWindow = markersMap[msgSessionId].infoWindow; 149 | var existingTimeoutId = markersMap[msgSessionId].timeoutId; 150 | 151 | existingMarker.setPosition(newPosition); 152 | existingInfoWindow.setContent(msg.text); 153 | existingInfoWindow.setZIndex(infoWindowZIndex); 154 | infoWindowZIndex++; 155 | if (msg.text && !markersMap[msgSessionId].disabled) { 156 | if (existingTimeoutId){ 157 | clearTimeout(existingTimeoutId); 158 | } 159 | markersMap[msgSessionId].timeoutId = 160 | setTimeout(function() { existingInfoWindow.close() }, 10000); 161 | existingInfoWindow.open(map, existingMarker); 162 | } 163 | } else { // new marker 164 | var infoWindow = new google.maps.InfoWindow({ 165 | content: msg.text, 166 | maxWidth: 400, 167 | disableAutoPan: true, 168 | zIndex: infoWindowZIndex 169 | }); 170 | infoWindowZIndex++; 171 | 172 | var marker = new google.maps.Marker({ 173 | position: newPosition, 174 | map: map, 175 | draggable: false, 176 | icon: markerImage, 177 | title: "Click to mute/un-mute User "+msgSessionId 178 | }); 179 | 180 | marker.addListener('click',function() { 181 | if (markersMap[msgSessionId].disabled) { 182 | markersMap[msgSessionId].disabled = false; 183 | marker.setIcon(markerImage); 184 | } else{ 185 | markersMap[msgSessionId].disabled = true; 186 | marker.setIcon(disabledMarkerImage); 187 | infoWindow.close(); 188 | } 189 | }); 190 | 191 | if (msg.text) { 192 | infoWindow.open(map, marker); 193 | } 194 | 195 | var timeoutId = setTimeout(function() { infoWindow.close() }, 10000); 196 | markersMap[msgSessionId] = { 197 | marker: marker, 198 | infoWindow: infoWindow, 199 | timeoutId: timeoutId, 200 | disabled: false 201 | } 202 | } 203 | 204 | if (advanced){ 205 | runAdvancedOptions(msg); 206 | } 207 | } 208 | 209 | function embedTweet(text) { 210 | var tweetText = "Someone wrote " + text + " on "; 211 | var tweetUrl = "https:\/\/twitter.com\/share?url=http://idoco.github.io/map-chat&text=" + tweetText; 212 | var width = 500, height = 300; 213 | var left = (screen.width / 2) - (width / 2); 214 | var top = (screen.height / 2) - (height / 2); 215 | return " " + 218 | " <\/a> " + text; 219 | } 220 | 221 | function clearMessageFromMap(){ 222 | for (var markerSessionId in markersMap) { 223 | if (markersMap.hasOwnProperty(markerSessionId)) { 224 | markersMap[markerSessionId].infoWindow.close(); 225 | } 226 | } 227 | } 228 | 229 | function changeZoom(factor){ 230 | map.setZoom(map.getZoom() + factor); 231 | } 232 | 233 | function runAdvancedOptions(msg){ 234 | if (msg.sessionId == mySessionId){ 235 | return; 236 | } 237 | 238 | if (Notification.permission !== "granted"){ 239 | Notification.requestPermission(); 240 | } 241 | 242 | new Notification('Incoming MapChat', { 243 | icon: 'favicons/apple-touch-icon-120x120.png', 244 | body: msg.text ? "Incoming message: "+msg.text : "New user" 245 | }); 246 | } 247 | 248 | // This should be displayed when the app is opened from a mobile facebook app WebView (Until a better solution is found) 249 | if (window.navigator.userAgent.indexOf("FBAV") > 0) { 250 | document.write( 251 | "
" + 252 | "
" + 253 | "
" + 254 | "This page will not work inside the facebook app, " + 255 | "please open it in the native browser." + 256 | "
" + 257 | "
" + 258 | "
" 259 | ); 260 | } else { 261 | google.maps.event.addDomListener(window, 'load', initialize); 262 | } 263 | -------------------------------------------------------------------------------- /javascript/modernizr.custom.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.8.3 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-websockets-geolocation-shiv-cssclasses-domprefixes-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function x(a){j.cssText=a}function y(a,b){return x(prefixes.join(a+";")+(b||""))}function z(a,b){return typeof a===b}function A(a,b){return!!~(""+a).indexOf(b)}function B(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:z(f,"function")?f.bind(d||b):f}return!1}var d="2.8.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={},q={},r={},s=[],t=s.slice,u,v={}.hasOwnProperty,w;!z(v,"undefined")&&!z(v.call,"undefined")?w=function(a,b){return v.call(a,b)}:w=function(a,b){return b in a&&z(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.geolocation=function(){return"geolocation"in navigator},p.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a};for(var C in p)w(p,C)&&(u=C.toLowerCase(),e[u]=p[C](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)w(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},x(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._domPrefixes=o,e._cssomPrefixes=n,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+s.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f 2 | 4 | 4.0.0 5 | 6 | idoco 7 | mapchat 8 | jar 9 | 1.0-SNAPSHOT 10 | Project - mapchat 11 | http://maven.apache.org 12 | 13 | 14 | org.sonatype.oss 15 | oss-parent 16 | 7 17 | 18 | 19 | 20 | UTF-8 21 | 22 | 25 | false 26 | 27 | 29 | false 30 | 31 | 32 | ${project.groupId}~${project.artifactId}~${project.version} 33 | 34 | 36 | target/mods 37 | 38 | 39 | 2.1.5 40 | 2.0.3-final 41 | 4.11 42 | 43 | 44 | 3.0 45 | 2.6 46 | 2.5 47 | 2.0.11-final 48 | 2.14 49 | 2.14 50 | 2.14 51 | 2.9 52 | 2.7 53 | 54 | 55 | 56 | 57 | sonatype-nexus-snapshots 58 | https://oss.sonatype.org/content/repositories/snapshots 59 | 60 | 61 | 62 | 63 | 64 | 65 | io.vertx 66 | vertx-core 67 | ${vertx.version} 68 | compile 69 | 70 | 71 | io.vertx 72 | vertx-platform 73 | ${vertx.version} 74 | compile 75 | 76 | 77 | io.vertx 78 | vertx-hazelcast 79 | ${vertx.version} 80 | compile 81 | 82 | 83 | 84 | junit 85 | junit 86 | 4.11 87 | test 88 | 89 | 90 | io.vertx 91 | testtools 92 | ${vertx.testtools.version} 93 | test 94 | 95 | 96 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | io.vertx 114 | vertx-maven-plugin 115 | ${maven.vertx.plugin.version} 116 | 124 | 125 | 126 | 127 | io.vertx 128 | vertx-platform 129 | ${vertx.version} 130 | 131 | 132 | io.vertx 133 | vertx-core 134 | ${vertx.version} 135 | 136 | 137 | io.vertx 138 | vertx-hazelcast 139 | ${vertx.version} 140 | 141 | 142 | 143 | 144 | PullInDeps 145 | prepare-package 146 | 147 | pullInDeps 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-compiler-plugin 157 | ${maven.compiler.plugin.version} 158 | 159 | 1.7 160 | 1.7 161 | 162 | 163 | 164 | maven-resources-plugin 165 | ${maven.resources.plugin.version} 166 | 167 | 168 | copy-mod-to-target 169 | process-classes 170 | 171 | copy-resources 172 | 173 | 174 | true 175 | ${mods.directory}/${module.name} 176 | 177 | 178 | target/classes 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | org.apache.maven.plugins 187 | maven-dependency-plugin 188 | ${maven.dependency.plugin.version} 189 | 190 | 191 | copy-mod-dependencies-to-target 192 | process-classes 193 | 194 | copy-dependencies 195 | 196 | 197 | ${mods.directory}/${module.name}/lib 198 | runtime 199 | 200 | 201 | 202 | copy-mod-dependencies-to-target-dependencies 203 | process-classes 204 | 205 | copy-dependencies 206 | 207 | 208 | target/dependencies 209 | runtime 210 | 211 | 212 | 213 | 214 | 215 | org.apache.maven.plugins 216 | maven-surefire-plugin 217 | ${maven.surefire.plugin.version} 218 | 219 | 220 | **/unit/*Test*.java 221 | 222 | 223 | 224 | 225 | org.apache.maven.plugins 226 | maven-failsafe-plugin 227 | ${maven.failsafe.plugin.version} 228 | 229 | 230 | 231 | vertx.mods 232 | ${mods.directory} 233 | 234 | 235 | 236 | **/integration/**/*Test* 237 | 238 | 239 | 240 | 241 | 242 | integration-test 243 | verify 244 | 245 | 246 | 247 | 248 | 249 | org.apache.maven.plugins 250 | maven-surefire-report-plugin 251 | ${maven.surefire.report.plugin.version} 252 | 253 | 254 | generate-test-report 255 | test 256 | 257 | report-only 258 | 259 | 260 | 261 | generate-integration-test-report 262 | integration-test 263 | 264 | failsafe-report-only 265 | 266 | 267 | 268 | 269 | 270 | maven-assembly-plugin 271 | 272 | 273 | src/main/assembly/mod.xml 274 | 275 | 276 | 277 | 278 | assemble 279 | package 280 | 281 | single 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | org.apache.maven.plugins 292 | maven-surefire-report-plugin 293 | ${maven.surefire.report.plugin.version} 294 | 295 | 296 | org.apache.maven.plugins 297 | maven-javadoc-plugin 298 | ${maven.javadoc.plugin.version} 299 | 300 | true 301 | 302 | 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /server/ChatVerticle.java: -------------------------------------------------------------------------------- 1 | package server; 2 | 3 | import org.vertx.java.core.AsyncResult; 4 | import org.vertx.java.core.Handler; 5 | import org.vertx.java.core.buffer.Buffer; 6 | import org.vertx.java.core.http.HttpServer; 7 | import org.vertx.java.core.json.JsonArray; 8 | import org.vertx.java.core.json.JsonObject; 9 | import org.vertx.java.core.logging.Logger; 10 | import org.vertx.java.core.sockjs.EventBusBridgeHook; 11 | import org.vertx.java.core.sockjs.SockJSServer; 12 | import org.vertx.java.core.sockjs.SockJSSocket; 13 | import org.vertx.java.platform.Container; 14 | import org.vertx.java.platform.Verticle; 15 | 16 | import java.net.InetAddress; 17 | import java.net.UnknownHostException; 18 | import java.util.HashMap; 19 | import java.util.HashSet; 20 | import java.util.Set; 21 | 22 | public class ChatVerticle extends Verticle { 23 | 24 | public void start() { 25 | HttpServer server = vertx.createHttpServer(); 26 | 27 | JsonArray permitted = new JsonArray(); 28 | permitted.add(new JsonObject()); // Let everything through 29 | 30 | SockJSServer sockJSServer = vertx.createSockJSServer(server); 31 | sockJSServer.setHook(new ServerHook(container)); 32 | sockJSServer.bridge(new JsonObject().putString("prefix", "/chat"), permitted, permitted); 33 | 34 | server.listen(8080); 35 | } 36 | 37 | private static class ServerHook implements EventBusBridgeHook { 38 | private final Logger logger; 39 | 40 | //todo: this should be shared between vertices 41 | private final Set blackList = new HashSet<>(); 42 | private final HashMap sessionIdToLastMessageTime = new HashMap<>(); 43 | 44 | public ServerHook(Container container) { 45 | this.logger = container.logger(); 46 | JsonArray storedBlacklist = container.config().getArray("blacklist",new JsonArray()); 47 | for (Object ip : storedBlacklist) { 48 | try { 49 | blackList.add(InetAddress.getByName(ip.toString())); 50 | } catch (UnknownHostException e) { 51 | logger.error("Could not parse blacklisted host/ip "+ip); 52 | } 53 | } 54 | } 55 | 56 | @Override 57 | public boolean handleSocketCreated(SockJSSocket sock) { 58 | String origin = sock.headers().get("origin"); 59 | return origin != null && origin.startsWith("http://localhost") && 60 | !isBlackListed(sock); 61 | } 62 | 63 | public boolean handlePreRegister(SockJSSocket sock, String address) { 64 | InetAddress remoteAddress = sock.remoteAddress().getAddress(); 65 | String sessionId = sock.writeHandlerID(); 66 | logger.info("IP " + remoteAddress + " registered as " + sessionId + " to " + address); 67 | 68 | JsonObject registrationWrapper = new JsonObject(); 69 | registrationWrapper.putString("address",address); 70 | registrationWrapper.putString("type","publish"); 71 | 72 | JsonObject registrationBody = new JsonObject(); 73 | String handlerID = sock.writeHandlerID(); 74 | registrationBody.putString("newSessionId",handlerID); 75 | 76 | registrationWrapper.putObject("body",registrationBody); 77 | 78 | sock.write(new Buffer(registrationWrapper.encode())); 79 | return true; 80 | } 81 | 82 | @Override 83 | public boolean handleSendOrPub(SockJSSocket sock, boolean send, JsonObject msg, String address) { 84 | String sessionId = sock.writeHandlerID(); 85 | 86 | if (msg.toString().length() > 256) { 87 | blackList(sock, "msg too long"); 88 | return false; 89 | } 90 | 91 | long currentTimeMillis = System.currentTimeMillis(); 92 | Long lastMessageTime = sessionIdToLastMessageTime.get(sessionId); 93 | if (lastMessageTime != null && currentTimeMillis - lastMessageTime < 500){ 94 | blackList(sock, "rate too high"); 95 | return false; 96 | } 97 | 98 | JsonObject body = msg.getObject("body"); 99 | body.putString("sessionId", sessionId); 100 | sessionIdToLastMessageTime.put(sessionId,currentTimeMillis); 101 | return true; 102 | } 103 | 104 | private boolean isBlackListed(SockJSSocket sock) { 105 | InetAddress remoteAddress = sock.remoteAddress().getAddress(); 106 | if(blackList.contains(remoteAddress)){ 107 | logger.warn("BlackListed communication detected from " + remoteAddress); 108 | return true; 109 | } else { 110 | return false; 111 | } 112 | } 113 | 114 | private void blackList(SockJSSocket sock, String reason) { 115 | InetAddress address = sock.remoteAddress().getAddress(); 116 | logger.warn("Address " + address + " blacklisted - " + reason); 117 | blackList.add(address); 118 | sock.close(); 119 | } 120 | 121 | public void handleSocketClosed(SockJSSocket sock) { } 122 | public void handlePostRegister(SockJSSocket sock, String address) { } 123 | public boolean handleUnregister(SockJSSocket sock, String address) { return true; } 124 | public boolean handleAuthorise( 125 | JsonObject message, String sessionID, Handler> handler) {return false;} 126 | } 127 | } -------------------------------------------------------------------------------- /server/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "blacklist" : [] 3 | } -------------------------------------------------------------------------------- /stylesheets/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow-y: hidden; 3 | } 4 | 5 | .navbar-fixed { 6 | height: 0; 7 | } 8 | 9 | .translation-wrapper { 10 | color: white; 11 | position: absolute; 12 | right: 25px; 13 | padding-top: 20px; 14 | z-index: 500 15 | } 16 | 17 | .translation-dropdown { 18 | float: right; 19 | padding-left: 10px 20 | } 21 | 22 | .fork-me-link { 23 | position: absolute; 24 | right: 0; 25 | border: 0; 26 | z-index: 1000; 27 | } 28 | 29 | .switch label { 30 | color: white; 31 | } 32 | 33 | .switch label input[type=checkbox]:checked+.lever{ 34 | background-color: #B6B6B6; 35 | } 36 | 37 | .switch label input[type=checkbox]:checked+.lever:after{ 38 | background-color: #FF9800; 39 | } 40 | 41 | html, body, #map-canvas { 42 | height: 100%; 43 | margin: 0; 44 | padding: 0 45 | } 46 | 47 | .input-wrapper { 48 | position: fixed; 49 | bottom: 30px; 50 | width: 100%; 51 | } 52 | 53 | .input-field label { 54 | color: black; 55 | } 56 | 57 | .input-field input { 58 | max-width: 58%; 59 | 60 | border: none !important; 61 | border-radius: 5px; 62 | background-color: white; 63 | padding-left: 10px; 64 | 65 | -webkit-box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19) !important; 66 | -moz-box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19) !important; 67 | box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19) !important; 68 | } 69 | 70 | .fixed-action-btn { 71 | z-index: 995; 72 | } 73 | 74 | .zoom-buttons { 75 | top: 90px; 76 | left: 30px; 77 | width: 30px !important; 78 | height: 90px !important; 79 | } 80 | 81 | .btn { 82 | padding: 0 5px; 83 | } 84 | 85 | .btn-send { 86 | bottom: 40px; 87 | right: 20px; 88 | } 89 | 90 | .btn-clear{ 91 | bottom: 40px; 92 | left: 20px; 93 | width: 56px !important; 94 | } --------------------------------------------------------------------------------