├── .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 | 
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 |
71 |
72 |
73 |
87 |
88 |
89 |
90 |
95 |
96 |
101 |
102 |
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("",g,">");b.length=f;d.push("",a,">")}}}},pcdata:d,rcdata:d,cdata:d,endDoc:function(a){for(;b.length;b.length--)a.push("",b[b.length-1],">")}})}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 | }
--------------------------------------------------------------------------------