├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── conf └── chatbox.conf ├── index.js ├── package.json ├── public ├── AdminHelp.md ├── admin.css ├── admin.html ├── admin.js ├── chat-log.txt ├── client.css ├── client.js ├── index.html ├── jquery-1.9.0.min.js └── robots.txt ├── screenshots └── Screenshot.png └── wordpress ├── README.md ├── client.js ├── client.min.js └── index.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | *.publishproj 131 | 132 | # NuGet Packages Directory 133 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 134 | #packages/ 135 | 136 | # Windows Azure Build Output 137 | csx 138 | *.build.csdef 139 | 140 | # Windows Store app package directory 141 | AppPackages/ 142 | 143 | # Others 144 | sql/ 145 | *.Cache 146 | ClientBin/ 147 | [Ss]tyle[Cc]op.* 148 | ~$* 149 | *~ 150 | *.dbmdl 151 | *.[Pp]ublish.xml 152 | *.pfx 153 | *.publishsettings 154 | 155 | # RIA/Silverlight projects 156 | Generated_Code/ 157 | 158 | # Backup & report files from converting an old project file to a newer 159 | # Visual Studio version. Backup files are not needed, because we have git ;-) 160 | _UpgradeReport_Files/ 161 | Backup*/ 162 | UpgradeLog*.XML 163 | UpgradeLog*.htm 164 | 165 | # SQL Server files 166 | App_Data/*.mdf 167 | App_Data/*.ldf 168 | 169 | ############# 170 | ## Windows detritus 171 | ############# 172 | 173 | # Windows image file caches 174 | Thumbs.db 175 | ehthumbs.db 176 | 177 | # Folder config file 178 | Desktop.ini 179 | 180 | # Recycle Bin used on file shares 181 | $RECYCLE.BIN/ 182 | 183 | # Mac crap 184 | .DS_Store 185 | 186 | 187 | ############# 188 | ## Python 189 | ############# 190 | 191 | *.py[cod] 192 | 193 | # Packages 194 | *.egg 195 | *.egg-info 196 | dist/ 197 | build/ 198 | eggs/ 199 | parts/ 200 | var/ 201 | sdist/ 202 | develop-eggs/ 203 | .installed.cfg 204 | 205 | # Installer logs 206 | pip-log.txt 207 | 208 | # Unit test / coverage reports 209 | .coverage 210 | .tox 211 | 212 | #Translations 213 | *.mo 214 | 215 | #Mr Developer 216 | .mr.developer.cfg 217 | node_modules/ 218 | chat-log.txt 219 | 220 | 221 | public/socket.io.js 222 | 223 | #Chat Log 224 | public/chat-log.txt 225 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 ArchITech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # English description 2 | 3 | 4 | 5 | ## Chatbox 6 | 7 | A simple and fast chatbox app based on Node.js and Socket.io that features a powerful control panel for admin to use. 8 | 9 | 10 | ##### How to use 11 | 12 | ``` 13 | $ cd chatbox 14 | $ npm install 15 | $ node index.js 16 | ``` 17 | 18 | And point your browser to `http://localhost:4321` to go to chatbox page. 19 | 20 | If you want the application to run in the background, just do: 21 | ``` 22 | $ nohup node index.js > /dev/null & 23 | ``` 24 | Or use tools like `forever` or `pm2` to run it. 25 | 26 | Admin page is at `http://localhost:4321/admin.html`, default token is '12345'. 27 | 28 | Edit the token in `index.js` and put the same value in token field in Admin page then you are good to go. 29 | 30 | If you want hide the port like `localhost` (Not `localhost:4321`) in front page, just change the `index.js` port to 4321, and setting `public/client.js` port to 80(or 443). 31 | 32 | Quick way to change the port(e.g., change to 2231): 33 | ``` 34 | $ sed -i 's/var port =.*/var port = 2231;/g' ./public/client.js 35 | $ sed -i 's/var port =.*/var port = 2231;/g' ./index.js 36 | ``` 37 | 38 | Quick way to change the token(e.g., change to 54321): 39 | ``` 40 | $ sed -i 's/var token =.*/var token = "54321";/g' ./index.js 41 | ``` 42 | 43 | If you are using reverse proxy, setting the `using_reverse_proxy` to 1 in `index.js`. In your reverse proxy server, add the real ip address to `X-Real-IP` header. 44 | 45 | To embed this chatbox into a web page, just copy paste the content in public/index.html to the page you want to have chatbox, then change all included css file and JavaScript file path correctly. This app works great with light box library, I recommend using fancybox. 46 | 47 | When embedding this chatbox to Wordpress, you can see [this page](/wordpress/README.md) to know how to auto-sync the comment author name with chatbox visitor's nickname, so they don't need to enter nickname again. 48 | 49 | If you are get error in front page: 50 | ``` 51 | failed: Error during WebSocket handshake: Unexpected response code: 400 52 | ``` 53 | This is almost always due to not using https (SSL). Websocket over plain http is vulnerable to proxies in the middle (often transparent) operating at the http layer breaking the connection.The only way to avoid this is to use SSL all the time - this gives websocket the best chance of working. 54 | 55 | 56 | More info at [subsection 4.2.1 of the WebSockets RFC 6455](http://tools.ietf.org/html/rfc6455#section-4.2.1). 57 | 58 | 59 | ##### Demo 60 | 61 | The chatbox is usually minimized at left bottom by default. 62 | 63 | [http://lifeislikeaboat.com](http://lifeislikeaboat.com). 64 | 65 | [https://kn007.net/](https://kn007.net/) 66 | 67 | 68 | 69 | ----------------------------------------------------------- 70 | # 中文介绍 71 | 72 | 73 | 74 | ## 聊天盒 75 | 76 | 该聊天盒基于Node.js与Socket.io,充分利用了HTML5 Websocket双向通讯技术,在方便网站游客高速实时聊天的同时也提供网站管理员强大的控制面板,管理员可对全体游客或者特定游客进行各种操作。 77 | 78 | 79 | ##### 如何使用 80 | 81 | ``` 82 | $ cd chatbox 83 | $ npm install 84 | $ node index.js 85 | ``` 86 | 87 | 在本地安装则直接访问`http://localhost:4321`即可进入聊天盒。 88 | 89 | 如果你想让聊天盒在后台运行,可以用下面语句启动聊天盒: 90 | ``` 91 | $ nohup node index.js > /dev/null & 92 | ``` 93 | 也可以使用`forever`或`pm2`工具来运行。 94 | 95 | 控制台的访问地址为`http://localhost:4321/admin.html`, 默认的密码为“12345”。 96 | 97 | 您可以通过修改index.js文件里的Token值来改掉默认的管理员密码。 98 | 99 | 如果你想让访问的地址隐藏端口号(如localhost,而非源代码中默认的localhost:4321),请修改`index.js`文件中的端口号为服务器后端的监听端口(如4321),其次修改`public/client.js`的端口为80(或443)即可。 100 | 101 | 通过Shell快速修改端口(比如改成2231): 102 | ``` 103 | $ sed -i 's/var port =.*/var port = 2231;/g' ./public/client.js 104 | $ sed -i 's/var port =.*/var port = 2231;/g' ./index.js 105 | ``` 106 | 107 | 同理,可以快速修改管理员密码(比如改成54321): 108 | ``` 109 | $ sed -i 's/var token =.*/var token = "54321";/g' ./index.js 110 | ``` 111 | 112 | 如果你使用反向代理,请将`index.js`的`using_reverse_proxy`值修改为1,并在反向代理服务器添加X-Real-IP 头指向源IP。 113 | 114 | 如果想把聊天盒嵌入网站中,只要将`public/index.html`文件的内容复制粘贴到想要显示聊天盒的网页里,同时`public/index.html`中所有引入css和JavaScript文件地址需要修改正确。推荐配合fancybox插件使用来放大聊天盒里的图片。 115 | 116 | 嵌入Wordpress后,同步评论者用户名,可参照[此说明](/wordpress/README.md) 。 117 | 118 | 如果你在调试时出现: 119 | ``` 120 | failed: Error during WebSocket handshake: Unexpected response code: 400 121 | ``` 122 | 比较大的可能是在前端隐藏了端口并使用了http,具体可以看下面传送门的解释。简单来说Websocket通信在使用80端口转发时,80端口只负责连接,握手及通信在反代转发到后端端口通讯时可能会出错(在HTTP层被断开)。使用https可以避免这个问题,握手通讯皆用443端口。如果你不想使用https,那么建议你通过使用`http://localhost:4321`的方式来使用,而不隐藏端口;或是直接让nodejs监听80端口,而不通过反代。 123 | 124 | 更多资料可以参照WebSockets RFC 6455协议中的4.2.1章,[传送门](http://tools.ietf.org/html/rfc6455#section-4.2.1)。 125 | 126 | 127 | ##### 示例 128 | 129 | 聊天盒一般默认最小化于网页左下角 130 | 131 | [http://lifeislikeaboat.com](http://lifeislikeaboat.com) 132 | 133 | [https://kn007.net/](https://kn007.net/) 134 | 135 | 136 | ##### 截图 137 | 138 | ![screenshot](/screenshots/Screenshot.png?raw=true "Screenshot") 139 | -------------------------------------------------------------------------------- /conf/chatbox.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl http2; 3 | server_name chatbox.kn007.net; 4 | include kn007_net_security.conf; 5 | location ~ ^/(admin\.html|chat-log.txt) { 6 | proxy_set_header Upgrade $http_upgrade; 7 | proxy_set_header Connection "upgrade"; 8 | proxy_set_header Host $host; 9 | proxy_set_header X-Real-IP $remote_addr; 10 | proxy_http_version 1.1; 11 | proxy_pass http://127.0.0.1:2231; 12 | auth_basic "kn007's Auth System"; 13 | auth_basic_user_file /usr/local/nginx/passwd_chatbox.db; 14 | } 15 | location / { 16 | proxy_set_header Upgrade $http_upgrade; 17 | proxy_set_header Connection "upgrade"; 18 | proxy_set_header Host $host; 19 | proxy_set_header X-Real-IP $remote_addr; 20 | proxy_http_version 1.1; 21 | proxy_pass http://127.0.0.1:2231; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Setup basic express server 2 | var express = require('express'); 3 | var app = express(); 4 | var server = require('http').createServer(app); 5 | var io = require('socket.io')(server); 6 | 7 | //set chat history log file 8 | var fs = require('fs'); 9 | var filePath = __dirname+"/public/chat-log.txt"; 10 | 11 | //set timeout, default is 1 min 12 | //io.set("heartbeat timeout", 3*60*1000); 13 | 14 | //set which port this app runs on 15 | var port = 4321; 16 | //set admin password 17 | var token = "12345"; 18 | //set 1 if you using reverse proxy 19 | var using_reverse_proxy = 0; 20 | 21 | 22 | var socketList = []; 23 | // users are grouped by browser base on cookie's uuid implementation, 24 | // therefore 1 connection is the smallest unique unit and 1 user is not. 25 | // 1 user may contain multiple connections when he opens multiple tabs in same browser. 26 | var userDict = {}; 27 | var userCount = 0; 28 | 29 | var adminUser; 30 | 31 | var chatboxUpTime = (new Date()).toString(); 32 | var totalUsers = 0; 33 | var totalSockets = 0; 34 | var totalMsg = 0; 35 | server.listen(port, function () { 36 | console.log('Server listening at port %d', port); 37 | }); 38 | 39 | // Routing 40 | app.use(express.static(__dirname + '/public')); 41 | 42 | 43 | 44 | // Chatbox 45 | 46 | // log to console, if admin is online, send to admin as well 47 | function log(str) { 48 | console.log(str); 49 | if (adminUser && adminUser.id in userDict) { 50 | for(var i = 0; i < adminUser.socketList.length; i++) { 51 | var s = adminUser.socketList[i]; 52 | s.emit('server log', {log: str}); 53 | } 54 | } 55 | } 56 | 57 | // set username, avoid no name 58 | function setName(name) { 59 | 60 | if (typeof name != 'undefined' && name!=='') 61 | return name; 62 | return "no name"; 63 | } 64 | 65 | 66 | function getCookie(cookie, cname) { 67 | var name = cname + "="; 68 | var ca = cookie.split(';'); 69 | for(var i=0; i socket 184 | user.socketList.push(socket); 185 | socket.user = user; 186 | 187 | 188 | recordActionTime(socket); 189 | var action = {}; 190 | action.type = 'Join'; 191 | action.time = getTime(); 192 | action.url = socket.url; 193 | action.detail = socket.remoteAddress; 194 | user.actionList.push(action); 195 | 196 | }); 197 | 198 | // when the user disconnects.. 199 | socket.on('disconnect', function () { 200 | var user = socket.user; 201 | 202 | 203 | // remove from socket list 204 | var socketIndex = socketList.indexOf(socket); 205 | if (socketIndex != -1) { 206 | socketList.splice(socketIndex, 1); 207 | } 208 | 209 | 210 | // the user only exist after login 211 | if(user.notLoggedIn){ 212 | log('Socket disconnected before logging in.'); 213 | log('socket.id: '+socket.id); 214 | return; 215 | } 216 | 217 | log(user.username + ' closed a connection ('+(user.socketList.length-1)+').'); 218 | 219 | // also need to remove socket from user's socketlist 220 | // when a user has 0 socket connection, remove the user 221 | var socketIndexInUser = user.socketList.indexOf(socket); 222 | if (socketIndexInUser != -1) { 223 | user.socketList.splice(socketIndexInUser, 1); 224 | if(user.socketList.length === 0){ 225 | log("It's his last connection, he's gone."); 226 | delete userDict[user.id]; 227 | userCount--; 228 | // echo globally that this user has left 229 | socket.broadcast.emit('user left', { 230 | username: socket.user.username, 231 | numUsers: userCount 232 | }); 233 | 234 | }else{ 235 | var action = {}; 236 | action.type = 'Left'; 237 | action.time = getTime(); 238 | action.url = socket.url; 239 | action.detail = socket.remoteAddress; 240 | user.actionList.push(action); 241 | } 242 | } 243 | 244 | }); 245 | 246 | // this is when one user want to change his name 247 | // enforce that all his socket connections change name too 248 | socket.on('user edits name', function (data) { 249 | recordActionTime(socket); 250 | 251 | var oldName = socket.user.username; 252 | var newName = data.newName; 253 | socket.user.username = newName; 254 | 255 | if (newName === oldName) return; 256 | 257 | // sync name change 258 | var socketsToChangeName = socket.user.socketList; 259 | for (var i = 0; i< socketsToChangeName.length; i++) { 260 | 261 | socketsToChangeName[i].emit('change username', { username: newName }); 262 | 263 | } 264 | 265 | 266 | // echo globally that this client has changed name, including user himself 267 | io.sockets.emit('log change name', { 268 | username: socket.user.username, 269 | oldname: oldName 270 | }); 271 | 272 | 273 | var action = {}; 274 | action.type = 'change name'; 275 | action.time = getTime(); 276 | action.url = socket.url; 277 | action.detail = 'Changed name from' + oldName + ' to ' + newName; 278 | socket.user.actionList.push(action); 279 | 280 | }); 281 | 282 | socket.on('report', function (data) { 283 | log(data.username + ": " + data.msg); 284 | }); 285 | 286 | // when the client emits 'new message', this listens and executes 287 | socket.on('new message', function (data) { 288 | totalMsg++; 289 | recordActionTime(socket, data.msg); 290 | 291 | socket.msgCount++; 292 | socket.user.msgCount++; 293 | 294 | // socket.broadcast.emit('new message', {//send to everybody but sender 295 | io.sockets.emit('new message', {//send to everybody including sender 296 | username: socket.user.username, 297 | message: data.msg 298 | }); 299 | 300 | 301 | // log the message in chat history file 302 | var chatMsg = socket.user.username+": "+data.msg+'\n'; 303 | console.log(chatMsg); 304 | 305 | fs.appendFile(filePath, new Date() + "\t"+ chatMsg, function(err) { 306 | if(err) { 307 | return log(err); 308 | } 309 | console.log("The message is saved to log file!"); 310 | }); 311 | 312 | var action = {}; 313 | action.type = 'message'; 314 | action.time = getTime(); 315 | action.url = socket.url; 316 | action.detail = data.msg; 317 | socket.user.actionList.push(action); 318 | 319 | }); 320 | 321 | socket.on('base64 file', function (data) { 322 | recordActionTime(socket); 323 | 324 | log('received base64 file from' + data.username); 325 | 326 | // socket.broadcast.emit('base64 image', //exclude sender 327 | io.sockets.emit('base64 file', 328 | 329 | { 330 | username: socket.user.username, 331 | file: data.file, 332 | fileName: data.fileName 333 | } 334 | 335 | ); 336 | 337 | var action = {}; 338 | action.type = 'send file'; 339 | action.time = getTime(); 340 | action.url = socket.url; 341 | action.detail = data.fileName; 342 | socket.user.actionList.push(action); 343 | }); 344 | 345 | 346 | // when the client emits 'typing', we broadcast it to others 347 | socket.on('typing', function (data) { 348 | return; 349 | recordActionTime(socket); 350 | 351 | socket.broadcast.emit('typing', { 352 | username: socket.user.username 353 | }); 354 | }); 355 | 356 | // when the client emits 'stop typing', we broadcast it to others 357 | socket.on('stop typing', function (data) { 358 | return; 359 | recordActionTime(socket); 360 | 361 | socket.broadcast.emit('stop typing', { 362 | username: socket.user.username 363 | }); 364 | }); 365 | 366 | // for New Message Received Notification callback 367 | socket.on('reset2origintitle', function (data) { 368 | var socketsToResetTitle = socket.user.socketList; 369 | for (var i = 0; i< socketsToResetTitle.length; i++) { 370 | socketsToResetTitle[i].emit('reset2origintitle', {}); 371 | } 372 | }); 373 | 374 | 375 | 376 | //========================================================================== 377 | //========================================================================== 378 | // code below are for admin only, so we always want to verify token first 379 | //========================================================================== 380 | //========================================================================== 381 | 382 | 383 | // change username 384 | socket.on('admin change username', function (data) { 385 | 386 | if(data.token === token) { 387 | 388 | var user = userDict[data.userID]; 389 | var newName = data.newName; 390 | var oldName = user.username; 391 | user.username = newName; 392 | 393 | if (newName === oldName) return; 394 | 395 | // sync name change 396 | var socketsToChangeName = user.socketList; 397 | for (var i = 0; i< socketsToChangeName.length; i++) { 398 | 399 | socketsToChangeName[i].emit('change username', { username: newName }); 400 | 401 | } 402 | 403 | 404 | // echo globally that this client has changed name, including user himself 405 | io.sockets.emit('log change name', { 406 | username: user.username, 407 | oldname: oldName 408 | }); 409 | 410 | 411 | } 412 | 413 | }); 414 | 415 | 416 | // send script to target users 417 | socket.on('script', function (data) { 418 | 419 | if(data.token === token) { 420 | 421 | // handle individual sockets 422 | for (var i = 0; i < data.socketKeyList.length; i++) { 423 | var sid = data.socketKeyList[i]; 424 | io.to(sid).emit('script', {script: data.script}); 425 | } 426 | 427 | 428 | // handle users and all their sockets 429 | for (var i = 0; i < data.userKeyList.length; i++) { 430 | var userKey = data.userKeyList[i]; 431 | if(userKey in userDict) { // in case is already gone 432 | var user = userDict[userKey]; 433 | for (var j = 0; j< user.socketList.length; j++) { 434 | s = user.socketList[j]; 435 | s.emit('script', {script: data.script}); 436 | } 437 | } 438 | } 439 | } 440 | 441 | }); 442 | 443 | socket.on('getServerStat', function (data) { 444 | socket.emit('server stat', { 445 | chatboxUpTime: chatboxUpTime, 446 | totalUsers: totalUsers, 447 | totalSockets: totalSockets, 448 | totalMsg: totalMsg 449 | }); 450 | }); 451 | 452 | // send real time data statistic to admin 453 | // this callback is currently also used for authentication 454 | socket.on('getUserList', function (data) { 455 | 456 | if(data.token === token) { 457 | 458 | adminUser = socket.user; 459 | 460 | // Don't send the original user object or socket object to browser! 461 | // create simple models for socket and user to send to browser 462 | var simpleUserDict = {}; 463 | 464 | for (var key in userDict) { 465 | var user = userDict[key]; 466 | 467 | // create simpleUser model 468 | var simpleUser = {}; 469 | // is there a way to reduce code below? 470 | simpleUser.id = user.id; // key = user.id 471 | simpleUser.username = user.username; 472 | simpleUser.lastMsg = user.lastMsg; 473 | simpleUser.msgCount = user.msgCount; 474 | simpleUser.count = user.socketList.length; 475 | simpleUser.ip = user.ip; 476 | simpleUser.url = user.url; 477 | simpleUser.referrer = user.referrer; 478 | simpleUser.joinTime = user.joinTime; 479 | simpleUser.lastActive = user.lastActive; 480 | simpleUser.userAgent = user.userAgent; 481 | simpleUser.actionList = user.actionList; 482 | 483 | var simpleSocketList = []; 484 | for (var i = 0; i < user.socketList.length; i++) { 485 | var s = user.socketList[i]; 486 | 487 | // create simpleSocket model 488 | var simpleSocket = {}; 489 | simpleSocket.id = s.id; 490 | simpleSocket.ip = s.remoteAddress; 491 | simpleSocket.msgCount = s.msgCount; 492 | simpleSocket.lastMsg = s.lastMsg; 493 | simpleSocket.lastActive = s.lastActive; 494 | simpleSocket.url = s.url; 495 | simpleSocket.referrer = s.referrer; 496 | simpleSocket.joinTime = s.joinTime; 497 | 498 | simpleSocketList.push(simpleSocket); 499 | } 500 | 501 | simpleUser.socketList = simpleSocketList; 502 | 503 | simpleUserDict[simpleUser.id] = simpleUser; 504 | } 505 | 506 | 507 | 508 | socket.emit('listUsers', { 509 | userdict: simpleUserDict, 510 | success: true 511 | }); 512 | 513 | // getUserList might still be called when token is wrong 514 | }else { 515 | 516 | if (adminUser && adminUser.id === socket.user.id) { 517 | adminUser = undefined; 518 | } 519 | 520 | 521 | socket.emit('listUsers', { 522 | success: false 523 | }); 524 | } 525 | 526 | }); 527 | 528 | 529 | }); 530 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-chatbox", 3 | "version": "stable@160216", 4 | "description": "A simple chatbox using socket.io", 5 | "main": "index.js", 6 | "contributors": [ 7 | { 8 | "name": "Grant Timmerman" 9 | }, 10 | { 11 | "name": "ArchiTech", 12 | "website": "lifeislikeaboat.com" 13 | } 14 | ], 15 | "homepage": "https://arch1tect.github.io/Chatbox", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/Arch1tect/Chatbox.git" 19 | }, 20 | "license": "BSD", 21 | "dependencies": { 22 | "express": "3.4.8", 23 | "socket.io": "^1.3.7" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/AdminHelp.md: -------------------------------------------------------------------------------- 1 | Script Example 2 | 3 | //show chatbox 4 | show() 5 | //hide chatbox 6 | hide() 7 | //change page background color 8 | color('black') 9 | //make user say 'I admire you' publicly
10 | say('I admire you!') 11 | //make user type 'I love you' in his input bar(won't send)
12 | type('I love you') 13 | //make user send whatever is in his input bar publicly
14 | send() 15 | //play user join sound
16 | beep() 17 | //play new message sound
18 | newMsgBeep() 19 | //Redirect user to "www.example.com" 20 | window.location = "http://www.example.com" 21 | 22 | $.getScript("https://cdn.jsdelivr.net/jquery.jrumble/1.3/jquery.jrumble.min.js", function(data, textStatus, jqxhr) { 23 | 24 | $topbar.jrumble(); 25 | $topbar.trigger('startRumble'); 26 | 27 | }) 28 | 29 | //Make user load a 3rd party library then use it 30 | 31 | //Any JavaScript code can be ran on user's end, don't be evil. 32 | -------------------------------------------------------------------------------- /public/admin.css: -------------------------------------------------------------------------------- 1 | /* ======================================================= */ 2 | /* ================ Chatbox Admin Style =================*/ 3 | /* ======================================================= */ 4 | 5 | html { 6 | background: whitesmoke; 7 | font-family: Arial, Helvetica, sans-serif; 8 | /* font-family: "Times New Roman", Times, serif; */ 9 | } 10 | 11 | .socketchatbox-admin-title h1 { 12 | 13 | color: #3D3D3D; 14 | 15 | margin: 50px; 16 | 17 | font-size: 55px; 18 | } 19 | .socketchatbox-admin-instruction li{ 20 | margin:10px 21 | } 22 | #wrapper { 23 | /*min-height: 1000px; *//* we always want to show scroll bar */ 24 | } 25 | 26 | 27 | #sendScript { 28 | width: 100%; 29 | outline: none; 30 | padding: 10px; 31 | margin: 0px; 32 | } 33 | 34 | .socketchatbox-info-admin { 35 | right: 5%; 36 | width: 50%; 37 | position: absolute; 38 | /* max-width: 618px; */ 39 | word-wrap: break-word; 40 | /* background: rgba(255, 255, 255, 0.5); */ 41 | } 42 | 43 | 44 | .username-info-viewmore { 45 | margin-right: 10px; 46 | cursor: pointer; 47 | } 48 | .username-info-viewmore:hover{ 49 | background: lightgray; 50 | } 51 | #socketchatbox-checkAdmin { 52 | background: white; 53 | border: 1px solid lightgray; 54 | padding: 20px; 55 | } 56 | #socketchatbox-online { 57 | border: 1px solid lightgray; 58 | padding: 20px; 59 | background: white; 60 | } 61 | 62 | div#socketchatbox-online {} 63 | .socketchatbox-scriptHistory { 64 | border: 1px solid lightgray; 65 | padding: 20px; 66 | background: #EDEDED; 67 | } 68 | .socketchatbox-admin-leftside { 69 | width: 36%; 70 | margin-left: 5%; 71 | position: absolute; 72 | margin-bottom: 50px; 73 | } 74 | .socketchatbox-admin-server { 75 | border: 1px solid lightgray; 76 | padding-left: 5px; 77 | background: rgba(0, 0, 0, 0.75); 78 | word-wrap: break-word; 79 | font-size: small; 80 | overflow-y:scroll; 81 | height: 250px; 82 | } 83 | .log-time { 84 | width: 112px; 85 | margin-right: 21px; 86 | } 87 | .server-log-message { 88 | color: white;:; 89 | margin: 3px; 90 | margin-left: 7px; 91 | } 92 | .socketchatbox-admin-instruction { 93 | border: 1px solid lightgray; 94 | background: #FCFF8C; 95 | padding: 20px; 96 | } 97 | .socketchatbox-scriptHistoryScript { 98 | margin-top: 20px; 99 | max-height: 100px; 100 | overflow-y: scroll; 101 | } 102 | .username-info { 103 | margin-right: 5px; 104 | cursor: pointer; 105 | /* border: 1px solid white; */ 106 | } 107 | 108 | .selected { 109 | border: 1px solid lightgray; 110 | background: yellow; 111 | } 112 | 113 | .partially-selected { 114 | border: 1px solid lightgray; 115 | background: linear-gradient(90deg, yellow 50%, white 50%); 116 | } 117 | 118 | 119 | /* User detail popup */ 120 | 121 | .selectedSocket { 122 | background: yellow !important; 123 | } 124 | 125 | .socketchatbox-userdetail { 126 | border: 1px solid lightgray; 127 | background: #F9F9F9; 128 | margin: 10px; 129 | display:none; 130 | font-size: small; 131 | z-index: 9999; 132 | padding: 15px; 133 | } 134 | .socketchatbox-admin-userprofile { 135 | margin: 20px; 136 | } 137 | 138 | .socketchatbox-admin-userprofile p{ 139 | margin: 7px; 140 | } 141 | 142 | .socketchatbox-userdetail-actions { 143 | margin: 20px; 144 | margin-top:0px; 145 | } 146 | .socketchatbox-userdetail-actions-each { 147 | border: 1px solid lightgray; 148 | padding: 10px; 149 | 150 | } 151 | 152 | .socketchatbox-userdetail-actions-each:nth-child(odd) { 153 | background: #ededed; 154 | } 155 | 156 | .socketchatbox-userdetail-actions-each:nth-child(even) { 157 | background: #fafafa; 158 | } 159 | 160 | .socketchatbox-userdetail-name { 161 | font-size: large; 162 | font-weight: bold; 163 | } 164 | 165 | .socketchatbox-userdetail-sockets { 166 | /*max-height: 300px; 167 | overflow-y: scroll;*/ 168 | margin: 10px; 169 | } 170 | 171 | .socketchatbox-socketdetail { 172 | margin:5px; 173 | } 174 | 175 | .socketchatbox-socketdetail-each { 176 | padding: 15px; 177 | cursor: pointer; 178 | border: 1px solid lightgray; 179 | } 180 | 181 | .socketchatbox-socketdetail-each p{ 182 | margin:5px 183 | } 184 | 185 | .socketchatbox-socketdetail-each:nth-child(odd) { 186 | background: #ededed; 187 | } 188 | 189 | .socketchatbox-socketdetail-each:nth-child(even) { 190 | background: #fafafa; 191 | } 192 | 193 | .socketchatbox-actionhistory-url { 194 | float: right; 195 | } 196 | 197 | 198 | 199 | #socketchatbox-scriptSentStatus { 200 | margin: 10px; 201 | } 202 | 203 | .socketchatbox-admin-input { 204 | 205 | font-size: 100%; 206 | } 207 | 208 | .socketchatbox-admin-input textarea{ 209 | height: 150px; 210 | width: 100%; 211 | outline: none; 212 | padding-right: 5px; 213 | border: 1px solid lightgray; 214 | } 215 | 216 | button { 217 | border: 1px solid lightgray; 218 | margin-left: 10px; 219 | cursor: pointer; 220 | background: beige; 221 | } 222 | 223 | .socketchatbox-admin-changeUserName { 224 | /*margin:0px;*/ 225 | } 226 | 227 | .socketchatbox-refresh-interval { 228 | margin-left:30px; 229 | 230 | } 231 | 232 | .blue { 233 | background: #EDEDED; 234 | } 235 | 236 | .error { 237 | background: red !important; 238 | } 239 | .green { 240 | background: #C0FF3F; 241 | } 242 | .redFont { 243 | color: red; 244 | } 245 | 246 | 247 | -------------------------------------------------------------------------------- /public/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chatbox Admin 6 | 7 | 8 | 9 | 10 |

Chatbox Control Panel

11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 |
x
19 |
20 |
21 |
22 |
23 |
    24 |
    25 | 26 |
    27 |
    28 | 32 |
    33 |
    34 | 35 | 36 |
    37 | 38 |
    39 | 40 | 41 | 42 |
    43 | 44 |
    45 |
    46 | 47 |

    48 | 49 |
    50 | Instructions

    51 |
  • 52 | The first thing you need to do is to enter your token and click update, the 53 | default token is "12345", you can change the token in index.js.
  • 54 |
  • Once the admin validation is successful, you can see all users online at the moment.
  • 55 |
  • Click on the '[ ↓ ]' after user name to see more details, including all his sockets information.
  • 56 |
  • To run JavaScript on user's browser, you need to select the user as target before sending your script.
  • 57 | 58 | You can see some example scripts in AdminHelp.md. 59 | 60 | And here's the chat log file. 61 |
    62 | 63 |
    64 | 65 |
    66 | 67 |
    68 | Token Verification: Waiting... 69 |

    70 | Token: 71 | 72 | 73 | 74 | 75 |
    76 |

    77 | 78 |
    79 | Users Online 80 | 81 | 82 |

    83 | Refresh Interval: 5 sec 84 |

    85 |
    86 | Waiting... 87 |
    88 |
    89 |
    90 |
    91 |

    Name: 92 | 93 |

    94 |

    Last Message:

    95 |

    Total Messages:

    96 |

    Landing Page:

    97 |

    Referrer:

    98 |

    Idle Time:

    99 |

    Join Time: 100 |

    101 |

    IP: 102 | 103 | 104 |

    105 |

    User-Agent:

    106 |
    107 |
    108 |
    Live Connection
    109 | 110 | 111 |
    112 |
    113 | 114 |
    Action History
    115 |
    116 |
    117 | 118 | 119 |
    120 | 121 |
    122 | 123 |

    124 | 125 |
    126 |
    127 | Client-Side Scripting 128 | 129 | 130 | 131 | 132 | 133 | 134 |
    135 | 136 |
    137 | 138 | 139 |
    140 | 141 | 142 |
    143 | 144 |
    145 | 146 | 147 |
    148 |
    149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /public/admin.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | var socket = chatboxClient.socket; 4 | var verified = false; 5 | 6 | 7 | var scriptHist = []; 8 | var scriptPointer = -1; 9 | var refreshInterval = 5; // unit is second not milisecond 10 | var refreshIntervalID; 11 | var token = ""; 12 | var selectedUsers = {}; // user with all sockets selected 13 | var partiallyselectedUsers = {}; // user with some of sockets selected 14 | var selectedSockets = {}; // a simple array of socket's ID 15 | var userDict = {}; // similar to the userDict on server, but store simpleUser/simpleSocket objects 16 | var socketDict = {}; // similar ... 17 | 18 | var openedUserID; 19 | 20 | var $inputScriptMessage = $('.socketchatbox-admin-input textarea'); // admin script message input box 21 | 22 | adminInit(); 23 | 24 | 25 | function countKeys(myObj) { 26 | var count = 0; 27 | for (var k in myObj) { 28 | if (myObj.hasOwnProperty(k)) { 29 | ++count; 30 | } 31 | } 32 | return count; 33 | } 34 | 35 | // Send a script (Admin only) 36 | function sendScript() { 37 | var script = $inputScriptMessage.val(); 38 | var userCount = countKeys(selectedUsers); 39 | var socketCount = countKeys(selectedSockets); 40 | 41 | if (userCount + socketCount > 0) { 42 | // empty the input field 43 | $inputScriptMessage.val(''); 44 | 45 | var userKeyList = []; 46 | var socketKeyList = []; 47 | for(var userKey in selectedUsers){ 48 | userKeyList.push(userKey); 49 | } 50 | for(var socketKey in selectedSockets){ 51 | socketKeyList.push(socketKey); 52 | } 53 | 54 | var data = {}; 55 | data.token = token; 56 | data.script = script; 57 | data.userKeyList = userKeyList; 58 | data.socketKeyList = socketKeyList; 59 | socket.emit('script', data); 60 | 61 | // save script to local array 62 | scriptHist.push(script); 63 | scriptPointer = scriptHist.length-1; 64 | setHistoryScript(); 65 | 66 | var msg = 'Script is sent to '; 67 | if (userCount > 0) 68 | msg += userCount+' users '; 69 | if (socketCount > 0) 70 | msg += socketCount+' sockets.'; 71 | 72 | $('#socketchatbox-scriptSentStatus').text(msg); 73 | $('#socketchatbox-scriptSentStatus').removeClass('redFont'); 74 | 75 | } 76 | else{ 77 | $('#socketchatbox-scriptSentStatus').text('Must select at least one user to send script to.'); 78 | $('#socketchatbox-scriptSentStatus').addClass('redFont'); 79 | 80 | } 81 | 82 | // need to scroll down to really see this message 83 | window.scrollTo(0,document.body.scrollHeight); 84 | 85 | } 86 | 87 | 88 | $('.socketchatbox-admin-blockIP').click(function() { 89 | console.log("not available in this version, please wait and update."); 90 | }); 91 | 92 | 93 | $('.socketchatbox-admin-lookupIP').click(function() { 94 | window.open("https://geoiptool.com/en/?ip="); 95 | }); 96 | 97 | 98 | $('#sendScript').click(function() { 99 | sendScript(); 100 | }); 101 | 102 | $('#selectAll').click(function() { 103 | selectNoSocketNorUser(); 104 | 105 | for(var userKey in userDict) { 106 | var user = userDict[userKey]; 107 | user.selectedSocketCount = user.count; 108 | selectedUsers[userKey] = user; 109 | } 110 | 111 | syncHightlightGUI(); 112 | 113 | }); 114 | 115 | $('#selectNone').click(function() { 116 | selectNoSocketNorUser(); 117 | 118 | syncHightlightGUI(); 119 | 120 | }); 121 | 122 | // doesn't update GUI 123 | function selectNoSocketNorUser() { 124 | selectedUsers = []; 125 | selectedSockets = []; 126 | partiallyselectedUsers = {}; 127 | for(var userKey in userDict) { 128 | var user = userDict[userKey]; 129 | user.selectedSocketCount = 0; 130 | } 131 | } 132 | 133 | function removeUserSocketsFromSelectedSockets(user) { 134 | for(var i=0; i 0) { 207 | //console.log('add to partiallyselectedUsers'); 208 | 209 | partiallyselectedUsers[user.id] = user; 210 | for(var i = 0; i < user.socketList.length; i++) { 211 | var ss = user.socketList[i]; 212 | if(ss.id!=s.id) { 213 | selectedSockets[ss.id] = ss; 214 | //console.log('add socket to selectedSockets, sid: '+ ss.id); 215 | 216 | } 217 | } 218 | } 219 | }else{ 220 | //console.log('the user was not selected.'); 221 | 222 | 223 | if (socketID in selectedSockets) { // user must be in the partiallySelectedUserList 224 | // socket previously selected, now deselect 225 | delete selectedSockets[socketID]; 226 | user.selectedSocketCount--; 227 | if(user.selectedSocketCount<0) { 228 | console.log(user.selectedSocketCount<0); 229 | } 230 | if(user.selectedSocketCount===0) 231 | delete partiallyselectedUsers[user.id]; 232 | 233 | 234 | }else{ // user not in partially selected user list nor in the selected user list 235 | // socket previously not selected, now select it unless this user is getting into selected user list 236 | user.selectedSocketCount++; // should equal to 1 237 | if(user.selectedSocketCount!=1) 238 | console.log("user.selectedSocketCount should be one, but it's "+user.selectedSocketCount); 239 | if(user.selectedSocketCount == user.count){ 240 | selectedUsers[user.id] = user; 241 | for(var i = 0; i < user.socketList.length; i++) { 242 | var ss = user.socketList[i]; 243 | delete selectedSockets[ss.id]; 244 | 245 | } 246 | 247 | 248 | }else{ 249 | 250 | // ensure in partiallyselecteduserlist, maybe already in 251 | partiallyselectedUsers[user.id] = user; 252 | selectedSockets[socketID] = s; 253 | } 254 | } 255 | } 256 | 257 | 258 | //console.log(user.selectedSocketCount); 259 | syncHightlightGUI(); 260 | 261 | 262 | }); 263 | 264 | // update GUI to sync with data, call this every time you change value of user.selectedSocketCount 265 | function syncHightlightGUI() { 266 | // sync user highlight 267 | for(var key in userDict) { 268 | var user = userDict[key]; 269 | // check to see what status username selection should be in 270 | if (user.selectedSocketCount === 0) { 271 | // deselect 272 | user.jqueryObj.removeClass('selected'); 273 | user.jqueryObj.removeClass('partially-selected'); 274 | 275 | 276 | }else if (user.selectedSocketCount < user.count) { 277 | // partial select 278 | if(user.jqueryObj) { 279 | user.jqueryObj.removeClass('selected'); 280 | user.jqueryObj.addClass('partially-selected'); 281 | } 282 | 283 | }else { 284 | // full select 285 | if(user.jqueryObj) { 286 | user.jqueryObj.removeClass('partially-selected'); 287 | user.jqueryObj.addClass('selected'); 288 | } 289 | } 290 | 291 | if (user.id == openedUserID) { 292 | for(var i = 0; i < user.socketList.length; i++) { 293 | var s = user.socketList[i]; 294 | if(user.id in selectedUsers || s.id in selectedSockets){ 295 | 296 | s.jqueryObj.addClass('selectedSocket'); 297 | 298 | }else{ 299 | s.jqueryObj.removeClass('selectedSocket'); 300 | } 301 | } 302 | }else { 303 | 304 | for(var i = 0; i < user.socketList.length; i++) { 305 | var s = user.socketList[i]; 306 | if(s.jqueryObj) 307 | s.jqueryObj.removeClass('selectedSocket'); 308 | 309 | } 310 | 311 | } 312 | 313 | } 314 | } 315 | 316 | 317 | function loadUserDetail (user) { 318 | 319 | // user info 320 | 321 | $('.socketchatbox-userdetail-name').text(user.username); 322 | 323 | // don't refresh the element if value is the same, we don't want to interrupt editing name 324 | if ($('.socketchatbox-userdetail-name-edit').data('name') !==user.username){ 325 | 326 | $('.socketchatbox-userdetail-name-edit').val(user.username); 327 | $('.socketchatbox-userdetail-name-edit').data('name',user.username); 328 | } 329 | $('.socketchatbox-admin-changeUserName').data('id',user.id); 330 | $('.socketchatbox-userdetail-landingpage').text(user.url); 331 | $('.socketchatbox-userdetail-referrer').text(user.referrer); 332 | $('.socketchatbox-userdetail-ip').text(user.ip); 333 | $('.socketchatbox-userdetail-jointime').text(getTimeElapsed(user.joinTime)); 334 | $('.socketchatbox-userdetail-totalmsg').text(user.msgCount); 335 | if(!user.lastMsg) 336 | user.lastMsg = ""; 337 | $('.socketchatbox-userdetail-lastmsg').text("\""+user.lastMsg+"\""); 338 | 339 | 340 | $('.socketchatbox-userdetail-lastactive').text(getTimeElapsed(user.lastActive)); 341 | $('.socketchatbox-userdetail-useragent').text(user.userAgent); 342 | 343 | 344 | // socket info 345 | 346 | $('.socketchatbox-userdetail-sockets').html(''); 347 | 348 | for (var i = 0; i< user.socketList.length; i++) { 349 | var s = user.socketList[i]; 350 | var $socketInfo = $("

    "; 352 | socketInfoHTML += "

    ID: " + s.id + "

    "; 353 | socketInfoHTML += "

    URL: " + s.url + "

    "; 354 | if (s.referrer) 355 | socketInfoHTML += "

    Referrer: " + s.referrer + "

    "; 356 | socketInfoHTML += "

    IP: " + s.ip + "

    "; 357 | socketInfoHTML += "

    Total Messages: " + s.msgCount + "

    "; 358 | 359 | if (s.lastMsg) 360 | socketInfoHTML += "

    Last Message: \"" + s.lastMsg + "\"

    "; 361 | 362 | socketInfoHTML += "

    Idle Time: " + getTimeElapsed(s.lastActive) + "

    "; 363 | socketInfoHTML += "

    Connection Time: " + getTimeElapsed(s.joinTime) + "

    "; 364 | 365 | $socketInfo.html(socketInfoHTML); 366 | $socketInfo.addClass('socketchatbox-socketdetail-each'); 367 | 368 | $socketInfo.data('id', s.id); 369 | // link jquery object with socket object 370 | s.jqueryObj = $socketInfo; 371 | $('.socketchatbox-userdetail-sockets').append($socketInfo); 372 | } 373 | 374 | // action history 375 | var $actionHistoryDiv = $('.socketchatbox-userdetail-actions'); 376 | $actionHistoryDiv.html(''); 377 | 378 | for (var i = 0; i < user.actionList.length; i++) { 379 | var action = user.actionList[i]; 380 | var $actionDiv = $('
    '); 381 | //new Date(Number(action.time)) // full time format 382 | var d = new Date(Number(action.time)); 383 | var str = ('0' + d.getHours()).slice(-2) + ":" + ('0' + d.getMinutes()).slice(-2) + ":" + ('0' + d.getSeconds()).slice(-2); 384 | str += "" + action.url + ""; 385 | str += "
    Action: " + action.type ; 386 | if (action.detail) { 387 | str += "
    Detail: " + action.detail; 388 | } 389 | 390 | $actionDiv.html(str); 391 | $actionDiv.addClass('socketchatbox-userdetail-actions-each'); 392 | 393 | $actionHistoryDiv.append($actionDiv); 394 | } 395 | 396 | 397 | 398 | 399 | syncHightlightGUI(); 400 | 401 | } 402 | 403 | $(document).on('click', '.username-info-viewmore', function() { 404 | var $this = $(this); 405 | var userID = $this.data('id'); 406 | var user = userDict[userID]; 407 | 408 | // already opened, close now 409 | if (openedUserID === userID) { 410 | 411 | $('.socketchatbox-admin-userdetail-pop').hide(); 412 | $this.text('[ ↓ ]'); 413 | $this.removeClass('blue'); 414 | openedUserID = ''; 415 | 416 | }else{ 417 | 418 | if (openedUserID in userDict) { 419 | var preOpenedUser = userDict[openedUserID]; 420 | preOpenedUser.arrowSpan.text('[ ↓ ]'); 421 | preOpenedUser.arrowSpan.removeClass('blue'); 422 | 423 | } 424 | 425 | $this.text('[ ↑ ]'); 426 | $this.addClass('blue'); 427 | 428 | openedUserID = userID; 429 | user.arrowSpan = $this; 430 | // Populate data into popup 431 | loadUserDetail(user); 432 | 433 | // TODO: show full browse history 434 | // url ----------- how long stay on page ------ etc. learn from GA dashboard 435 | 436 | // show 437 | if (!$('.socketchatbox-admin-userdetail-pop').is(":visible")) 438 | $('.socketchatbox-admin-userdetail-pop').slideFadeToggle(); 439 | } 440 | 441 | }); 442 | 443 | 444 | function getTimeElapsed(startTime, fromTime) { 445 | 446 | // time difference in ms 447 | var timeDiff = startTime - fromTime; 448 | if (fromTime!==0) 449 | timeDiff = (new Date()).getTime() - startTime; 450 | // strip the ms 451 | timeDiff /= 1000; 452 | var seconds = Math.round(timeDiff % 60); 453 | timeDiff = Math.floor(timeDiff / 60); 454 | var minutes = Math.round(timeDiff % 60); 455 | timeDiff = Math.floor(timeDiff / 60); 456 | var hours = Math.round(timeDiff % 24); 457 | timeDiff = Math.floor(timeDiff / 24); 458 | var days = timeDiff ; 459 | var timeStr = ""; 460 | if(days) 461 | timeStr += days + " d "; 462 | if(hours) 463 | timeStr += hours + " hr "; 464 | if(minutes) 465 | timeStr += minutes + " min "; 466 | 467 | timeStr += seconds + " sec"; 468 | return timeStr; 469 | } 470 | 471 | 472 | 473 | 474 | $.fn.slideFadeToggle = function(easing, callback) { 475 | return this.animate({ opacity: 'toggle', height: 'toggle' }, 'fast', easing, callback); 476 | }; 477 | 478 | 479 | var $tokenStatus = $('#socketchatbox-tokenStatus'); 480 | // Only admin should receive this message 481 | socket.on('listUsers', function (data) { 482 | 483 | 484 | // clear online user display 485 | $('#socketchatbox-online-users').html(''); 486 | 487 | 488 | if (!data.success) { 489 | console.log('bad token: '+ token); 490 | $('#socketchatbox-online-users').html('Invalid Token!'); 491 | $tokenStatus.html('Invalid Token!'); 492 | $tokenStatus.addClass('error'); 493 | $tokenStatus.removeClass('green'); 494 | //$('.socketchatbox-admin-server').hide(); 495 | 496 | } else { 497 | 498 | //$('.socketchatbox-admin-server').show(); 499 | $tokenStatus.html('Valid Token'); 500 | $tokenStatus.removeClass('error'); 501 | $tokenStatus.addClass('green'); 502 | 503 | if (!verified){ 504 | verified = true; 505 | getServerStat(); 506 | } 507 | 508 | // load new data about users and their sockets 509 | userDict = data.userdict; 510 | var newSelectedUsers = []; 511 | var newSelectedSockets = []; 512 | var newOpenedUserID; 513 | socketDict = {}; 514 | partiallyselectedUsers = {}; 515 | 516 | // add selectedSocketCount to user 517 | // link socket to user, put socket in socketDict 518 | // link user with its jqueryObj 519 | // link socket with its jqueryObj 520 | // display online user 521 | // update user detail window if opened 522 | for (var key in userDict) { 523 | 524 | var user = userDict[key]; 525 | 526 | var isSelectedUser = false; 527 | var isPartiallySelectedUser = false; 528 | 529 | 530 | user.selectedSocketCount = 0; // for socket/user selection purpose 531 | 532 | if(user.id in selectedUsers) { 533 | isSelectedUser = true; 534 | newSelectedUsers[user.id] = user; 535 | user.selectedSocketCount = user.count; // all sockets selected 536 | } 537 | 538 | 539 | for (var i = 0; i < user.socketList.length; i++) { 540 | 541 | var s = user.socketList[i]; 542 | s.user = user; 543 | socketDict[s.id] = s; 544 | 545 | if(!isSelectedUser && s.id in selectedSockets) { 546 | 547 | user.selectedSocketCount++; 548 | if(user.selectedSocketCount === user.count) { 549 | 550 | isSelectedUser = true; 551 | newSelectedUsers[user.id] = user; 552 | 553 | 554 | }else{ 555 | 556 | isPartiallySelectedUser = true; 557 | partiallyselectedUsers[s.id] = s; 558 | 559 | } 560 | } 561 | 562 | } 563 | 564 | // display online user 565 | 566 | var nameWithCount = user.username; 567 | 568 | // show number of connections of this user if more than one 569 | if(user.count > 1){ 570 | nameWithCount += "("+user.count+")"; 571 | } 572 | 573 | var $usernameSpan = $(""); 574 | $usernameSpan.text(nameWithCount); 575 | $usernameSpan.prop('title', 'Join Time: '+ getTimeElapsed(user.joinTime)); // better info to show? 576 | $usernameSpan.addClass("username-info"); 577 | $usernameSpan.data('id', user.id); 578 | 579 | // add [ ↓ ] after the user's name 580 | var $downArrowSpan = $(""); 581 | if (user.id === openedUserID){ 582 | $downArrowSpan.text('[ ↑ ]'); 583 | $downArrowSpan.prop('title', 'Close User Detail'); 584 | 585 | $downArrowSpan.addClass('blue'); 586 | user.arrowSpan = $downArrowSpan; 587 | 588 | } else { 589 | $downArrowSpan.text('[ ↓ ]'); 590 | $downArrowSpan.prop('title', 'Open User Detail'); 591 | 592 | } 593 | 594 | $downArrowSpan.addClass("username-info-viewmore"); 595 | $downArrowSpan.data('id', user.id); 596 | 597 | 598 | // also link user with his jquery object 599 | user.jqueryObj = $usernameSpan; 600 | 601 | $('#socketchatbox-online-users').append($usernameSpan); 602 | $('#socketchatbox-online-users').append($downArrowSpan); 603 | 604 | // reload user detail if this is the user selected 605 | if(user.id === openedUserID) { 606 | loadUserDetail(user); 607 | newOpenedUserID = user.id; 608 | } 609 | } 610 | 611 | // data transfer done, update local stored data 612 | openedUserID = newOpenedUserID; 613 | selectedUsers = newSelectedUsers; 614 | selectedSocket = newSelectedSockets; 615 | 616 | // update view 617 | syncHightlightGUI(); 618 | } 619 | 620 | }); 621 | socket.on('server stat', function (data) { 622 | var $serverStatMsg = $('

    '); 623 | $serverStatMsg.html("

    Welcome, Admin!

    The Chatbox was started on "+data.chatboxUpTime + 624 | ".

    There have been "+data.totalUsers + 625 | " users, " + data.totalSockets+" sockets and " + data.totalMsg + " messages.

    "); 626 | $serverStatMsg.addClass('server-log-message'); 627 | 628 | $('.socketchatbox-admin-server').append($serverStatMsg); 629 | }); 630 | 631 | socket.on('server log', function (data) { 632 | var $serverLogMsg = $('

    '); 633 | var d = new Date(); 634 | var $timeStr = $('0){ 699 | scriptPointer--; 700 | setHistoryScript(); 701 | } 702 | 703 | }); 704 | 705 | $('.nextScript').click(function() { 706 | if(scriptPointer=0) 714 | $inputScriptMessage.val(scriptHist[scriptPointer]); 715 | 716 | }); 717 | 718 | $('#socketchatbox-updateToken').click(function() { 719 | updateToken($('#socketchatbox-token').val()); 720 | }); 721 | }); 722 | -------------------------------------------------------------------------------- /public/chat-log.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/client.css: -------------------------------------------------------------------------------- 1 | /* ======================================================= */ 2 | /* ================ General Chatbox Style =================*/ 3 | /* ======================================================= */ 4 | 5 | 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | ul { 12 | list-style: none; 13 | word-wrap: break-word; 14 | } 15 | .socketchatbox-resize { 16 | 17 | top:0px; 18 | height:20px; 19 | width:20px; 20 | /* background-color:#000; 21 | */ position:absolute; 22 | z-index: 99999; 23 | 24 | } 25 | 26 | #socketchatbox-nw { 27 | left:0px; 28 | cursor:nw-resize; 29 | } 30 | 31 | #socketchatbox-ne{ 32 | right:0px; 33 | cursor:ne-resize; 34 | } 35 | 36 | #socketchatbox-imagefile { 37 | width:100%; 38 | } 39 | #socketchatbox-body { 40 | display: none 41 | } 42 | .socketchatbox-filebutton input[type="file"] { 43 | position: fixed; 44 | top: -1000px; 45 | } 46 | 47 | #socketchatbox-sendMedia { 48 | background: lightgrey; 49 | border: 1px solid lightgray; 50 | color:gray; 51 | } 52 | 53 | #socketchatbox-sendMedia :hover { 54 | cursor:pointer; 55 | color:black; 56 | } 57 | 58 | #socketchatbox-showHideChatbox { 59 | float:right; 60 | color: white; 61 | margin-right: 15px; 62 | } 63 | #socketchatbox-closeChatbox { 64 | float:right; 65 | color: white; 66 | margin-right: 25px; 67 | } 68 | #socketchatbox-username { 69 | float:left; 70 | margin-left: 20px; 71 | min-width: 20%; 72 | color: white; 73 | max-width:60%; 74 | } 75 | 76 | #socketchatbox-top { 77 | min-width: 200px; 78 | cursor: pointer; 79 | height: 30px; 80 | background: black; 81 | color: black; 82 | font-size: 90%; 83 | border: 1px solid black; 84 | opacity: 0.7; 85 | line-height: 30px; 86 | } 87 | 88 | 89 | .socketchatbox-page { 90 | font-size: 80%; 91 | margin: 0; 92 | padding: 0; 93 | position: fixed; 94 | bottom: 0px; 95 | overflow: auto; 96 | max-width: 100%; 97 | line-height: 1em; 98 | /* resize: horizontal; 99 | */ background: white; 100 | z-index: 9999; 101 | font-family: Arial, Helvetica, sans-serif; 102 | 103 | } 104 | .socketchatbox-messages img{ 105 | max-width: 100%; 106 | } 107 | 108 | .socketchatbox-messages video{ 109 | max-width: 100%; 110 | } 111 | 112 | .socketchatbox-log { 113 | color: gray; 114 | font-size: smaller; 115 | margin: 5px; 116 | text-align: center; 117 | } 118 | 119 | 120 | .socketchatbox-chatArea { 121 | height: 350px; 122 | width: 350px; 123 | max-width: 100%; 124 | background: #EDEDED; 125 | border: 1px solid lightgray; 126 | } 127 | 128 | .socketchatbox-message-wrapper { 129 | width: 100%; 130 | overflow: auto; 131 | } 132 | 133 | .socketchatbox-messagetime { 134 | color: gray; 135 | } 136 | 137 | .socketchatbox-messages { 138 | height: 100%; 139 | margin: 0; 140 | overflow-y: scroll; 141 | padding: 10px; 142 | } 143 | #socketchatbox-txt_fullname { 144 | color: black; 145 | } 146 | .socketchatbox-message-me >.socketchatbox-username{ 147 | /*display: none;*/ 148 | text-align: left; 149 | } 150 | 151 | .socketchatbox-username { 152 | line-height: 1em; 153 | } 154 | 155 | 156 | .socketchatbox-message { 157 | margin-bottom: 15px; 158 | } 159 | 160 | 161 | .socketchatbox-message-others { 162 | float: right; 163 | } 164 | 165 | 166 | 167 | .socketchatbox-messageBody { 168 | margin-top: 7px; 169 | margin-left: 3px; 170 | margin-right: 3px; 171 | background-color: #FFFFFF; 172 | border-radius: 5px; 173 | box-shadow: 0 0 6px #B2B2B2; 174 | display: inline-block; 175 | padding: 10px 18px; 176 | position: relative; 177 | vertical-align: top; 178 | line-height: 1.5em; 179 | word-break: break-all; 180 | } 181 | 182 | .socketchatbox-messageBody::before { 183 | /* background-color: #F2F2F2; 184 | content: "\00a0"; 185 | display: block; 186 | height: 16px; 187 | position: absolute; 188 | top: 11px; 189 | transform: rotate( 29deg ) skew( -25deg ); 190 | -moz-transform: rotate( 29deg ) skew( -25deg ); 191 | -ms-transform: rotate( 29deg ) skew( -25deg ); 192 | -o-transform: rotate( 29deg ) skew( -25deg ); 193 | -webkit-transform: rotate( 29deg ) skew( -25deg ); 194 | width: 20px;*/ 195 | } 196 | 197 | 198 | .socketchatbox-messageBody-me { 199 | background: #BBFF00; 200 | } 201 | .socketchatbox-messageBody-me::before { 202 | /* box-shadow: -2px 2px 2px 0 rgba( 178, 178, 178, .4 ); 203 | left: -9px; 204 | background: #C8FF56;*/ 205 | } 206 | .socketchatbox-messageBody-others { 207 | float: right; 208 | } 209 | .socketchatbox-messageBody-others::before { 210 | /* box-shadow: 2px -2px 2px 0 rgba( 178, 178, 178, .4 ); 211 | right: -9px; */ 212 | } 213 | 214 | .socketchatbox-typing { 215 | color: gray; 216 | } 217 | 218 | .socketchatbox-username { 219 | font-weight: 700; 220 | text-align: right; 221 | font-size: smaller; 222 | } 223 | 224 | 225 | .socketchatbox-inputMessage { 226 | height: 40px; 227 | outline: none; 228 | padding-left: 5px; 229 | width: 100%; 230 | font-size: 100%; 231 | } 232 | 233 | -------------------------------------------------------------------------------- /public/client.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | var chatboxname = 'Chatbox'; 4 | // change this to your port 5 | var port = 4321; 6 | var hostname = location.hostname; 7 | //hostname="lifeislikeaboat.com"; 8 | var domain = location.protocol + "//" + hostname + ":" + port; 9 | var socket; 10 | var FADE_TIME = 150; // ms 11 | var TYPING_TIMER_LENGTH = 400; // ms 12 | var COLORS = [ 13 | 'black' 14 | ]; 15 | 16 | // Initialize variables 17 | var d = new Date(); 18 | var $window = $(window); 19 | var $username = $('#socketchatbox-username'); 20 | var $usernameInput = $('.socketchatbox-usernameInput'); // Input for username 21 | var $messages = $('.socketchatbox-messages'); // Messages area 22 | var $inputMessage = $('.socketchatbox-inputMessage'); // Input message input box 23 | var $chatBox = $('.socketchatbox-page'); 24 | var $topbar = $('#socketchatbox-top'); 25 | var $chatBody = $('#socketchatbox-body'); 26 | var sendingFile = false; 27 | var grayChatBoxTimer; 28 | var newMsgSound; 29 | var newUserSound; 30 | 31 | var initialize = 0; 32 | var typing = false; 33 | var lastTypingTime; 34 | var username = 'visitor#'+ d.getMinutes()+ d.getSeconds(); 35 | var comment_author = ''; 36 | var totalUser = 0; 37 | // This uuid is unique for each browser but not unique for each connection 38 | // because one browser can have multiple tabs each with connections to the chatbox server. 39 | // And this uuid should always be passed on login, it's used to identify/combine user, 40 | // multiple connections from same browser are regarded as same user. 41 | var uuid = "uuid not set!"; 42 | 43 | // New Message Received Notification 44 | // 1 -- Change Page Title Once (when the webpage state is not visible) 45 | // 2 -- Flash Page Title (when the webpage state is not visible) 46 | // 3 -- Change Page Title Once (always, just notify in 3 seconds) 47 | // Other -- Do Not Change Page Title 48 | var changeTitleMode = 2; 49 | var changeTitle = { 50 | time: 0, 51 | originTitle: document.title, 52 | timer: null, 53 | done: 0, 54 | change: function() { 55 | document.title = "~New Message Received~ " + changeTitle.originTitle; 56 | changeTitle.done = 1; 57 | }, 58 | notify: function() { 59 | if(document.title.indexOf("~New Message Received~")) clearTimeout(changeTitle.timer); 60 | document.title = "~New Message Received~ " + changeTitle.originTitle; 61 | changeTitle.timer = setTimeout(function(){changeTitle.reset();},3000); 62 | changeTitle.done = 0; //Always be 0 63 | }, 64 | flash: function() { 65 | changeTitle.timer = setTimeout(function () { 66 | changeTitle.time++; 67 | changeTitle.flash(); 68 | if (changeTitle.time % 2 === 0) { 69 | document.title = "~ ~ " + changeTitle.originTitle; 70 | }else{ 71 | document.title = "~New Message Received~ " + changeTitle.originTitle; 72 | } 73 | }, 500); 74 | changeTitle.done = 1; 75 | }, 76 | reset: function() { 77 | clearTimeout(changeTitle.timer); 78 | document.title = changeTitle.originTitle; 79 | changeTitle.done = 0; 80 | } 81 | }; 82 | 83 | function getCookie(cname) { 84 | var name = cname + "="; 85 | var ca = document.cookie.split(';'); 86 | for(var i=0; i').addClass('socketchatbox-log').text(message); 297 | addMessageElement($el, options); 298 | } 299 | 300 | // Process message before displaying 301 | function processChatMessage (data, options) { 302 | 303 | //avoid empty name 304 | if (typeof data.username === 'undefined' || data.username==='') 305 | data.username = "empty name"; 306 | 307 | // Don't fade the message in if there is an 'X was typing' 308 | var $typingMessages = getTypingMessages(data); 309 | options = options || {}; 310 | if ($typingMessages.length !== 0) { 311 | options.fade = false; 312 | $typingMessages.remove(); 313 | } 314 | 315 | var d = new Date(); 316 | var posttime = ''; 317 | if (!options.loadFromCookie) { 318 | posttime += ""; 319 | posttime += ' ('+('0' + d.getHours()).slice(-2) + ":" + ('0' + d.getMinutes()).slice(-2) + ":" + ('0' + d.getSeconds()).slice(-2)+')'; 320 | posttime += ""; 321 | } 322 | 323 | var $usernameDiv = $('
    ') 324 | .html($("
    ").text(data.username).html()+posttime) 325 | .css('color', getUsernameColor(data.username)); 326 | $usernameDiv.addClass('socketchatbox-username'); 327 | var $messageBodyDiv = $(''); 328 | if (data.username === username) { 329 | $messageBodyDiv.addClass('socketchatbox-messageBody-me'); 330 | } else { 331 | $messageBodyDiv.addClass('socketchatbox-messageBody-others'); 332 | } 333 | var messageToSaveIntoCookie = ""; 334 | 335 | // receiving image file in base64 336 | if (options.file) { 337 | var mediaType = "img"; 338 | if (data.file.substring(0,10)==='data:video') 339 | mediaType = "video controls"; 340 | 341 | if (data.file.substring(0,10)==='data:image' || data.file.substring(0,10)==='data:video') { 342 | $messageBodyDiv.html("<"+mediaType+" class='chatbox-image' src='"+data.file+"'>"); 343 | }else{ 344 | $messageBodyDiv.html(""+data.fileName+""); 345 | } 346 | 347 | messageToSaveIntoCookie = data.fileName+"(File)"; 348 | 349 | }else{ 350 | 351 | messageToSaveIntoCookie = data.message; 352 | 353 | if (checkImageUrl(data.message)) { 354 | //receiving image url 355 | $messageBodyDiv.html(""); 356 | }else { 357 | //receiving plain text 358 | $messageBodyDiv.text(data.message); 359 | } 360 | } 361 | 362 | // receiving new message 363 | if (!options.loadFromCookie && !options.typing) { 364 | 365 | // play new msg sound and change chatbox color to notify users 366 | if (data.username !== username) { 367 | newMsgBeep(); 368 | if(document.hidden && changeTitleMode === 1 && changeTitle.done === 0) changeTitle.change(); 369 | if(document.hidden && changeTitleMode === 2 && changeTitle.done === 0) changeTitle.flash(); 370 | if(document.hidden && changeTitleMode === 3 && changeTitle.done === 0) changeTitle.notify(); 371 | if(!document.hidden) socket.emit('reset2origintitle', {}); 372 | $('#chat-top').css('background','yellowgreen'); 373 | clearTimeout(grayChatBoxTimer); 374 | grayChatBoxTimer = setTimeout(function(){ 375 | $('#chat-top').css('background','lightgray'); 376 | },60*1000); 377 | } 378 | 379 | writeChatHistoryIntoCookie(data.username, messageToSaveIntoCookie); 380 | } 381 | 382 | 383 | var typingClass = options.typing ? 'socketchatbox-typing' : ''; 384 | var $messageWrapper = $("
    "); 385 | var $messageDiv = $("
    ") 386 | .data('username', data.username) 387 | .addClass(typingClass) 388 | .append($usernameDiv, $messageBodyDiv); 389 | $messageWrapper.append($messageDiv); 390 | if (data.username === username) { 391 | $messageDiv.addClass('socketchatbox-message-me'); 392 | } else { 393 | $messageDiv.addClass('socketchatbox-message-others'); 394 | } 395 | 396 | addMessageElement($messageWrapper, options); 397 | } 398 | 399 | 400 | 401 | 402 | // Adds a message element to the messages and scrolls to the bottom 403 | // el - The element to add as a message 404 | // options.fade - If the element should fade-in (default = true) 405 | // options.prepend - If the element should prepend 406 | // all other messages (default = false) 407 | function addMessageElement (el, options) { 408 | 409 | var $el = $(el); 410 | 411 | // Setup default options 412 | options = options || {}; 413 | 414 | if (typeof options.fade === 'undefined') { 415 | options.fade = true; 416 | } 417 | if (typeof options.prepend === 'undefined') { 418 | options.prepend = false; 419 | } 420 | 421 | // Apply options 422 | if (options.fade) { 423 | $el.hide().fadeIn(FADE_TIME); 424 | } 425 | if (options.prepend) { 426 | $messages.prepend($el); 427 | } else { 428 | $messages.append($el); 429 | } 430 | 431 | //loading media takes time so we delay the scroll down 432 | setTimeout(function(){ 433 | $messages[0].scrollTop = $messages[0].scrollHeight; 434 | },50); 435 | } 436 | 437 | // Prevents input from having injected markup 438 | function cleanInput (input) { 439 | return $('
    ').text(input).text(); 440 | } 441 | 442 | 443 | function addParticipantsMessage (numUsers) { 444 | var message = ''; 445 | if (numUsers === 1) { 446 | message += "You are the only user online"; 447 | }else if (totalUser === 0) { 448 | message += "There are " + numUsers + " users online"; 449 | } 450 | log(message); 451 | 452 | totalUser = numUsers; 453 | } 454 | 455 | // Adds the visual chat typing message 456 | function addChatTyping (data) { 457 | data.message = 'is typing'; 458 | options={}; 459 | options.typing = true; 460 | processChatMessage(data, options); 461 | } 462 | 463 | // Removes the visual chat typing message 464 | function removeChatTyping (data) { 465 | getTypingMessages(data).fadeOut(function() { 466 | $(this).remove(); 467 | }); 468 | } 469 | 470 | 471 | // Updates the typing event 472 | function updateTyping() { 473 | 474 | if (!typing) { 475 | typing = true; 476 | socket.emit('typing', {name:username}); 477 | } 478 | lastTypingTime = (new Date()).getTime(); 479 | 480 | setTimeout(function() { 481 | var typingTimer = (new Date()).getTime(); 482 | var timeDiff = typingTimer - lastTypingTime; 483 | if (timeDiff >= TYPING_TIMER_LENGTH && typing) { 484 | socket.emit('stop typing', {name:username}); 485 | typing = false; 486 | } 487 | }, TYPING_TIMER_LENGTH); 488 | 489 | } 490 | 491 | // Gets the 'X is typing' messages of a user 492 | function getTypingMessages (data) { 493 | return $('.socketchatbox-typing.socketchatbox-message').filter(function (i) { 494 | return $(this).data('username') === data.username; 495 | }); 496 | } 497 | 498 | // Gets the color of a username through our hash function 499 | function getUsernameColor (username) { 500 | // Compute hash code 501 | var hash = 7; 502 | for (var i = 0; i < username.length; i++) { 503 | hash = username.charCodeAt(i) + (hash << 5) - hash; 504 | } 505 | // Calculate color 506 | var index = Math.abs(hash % COLORS.length); 507 | return COLORS[index]; 508 | } 509 | 510 | function clearNewMessageNotification() { 511 | changeTitle.reset(); 512 | socket.emit('reset2origintitle', {}); 513 | } 514 | 515 | // When user change his username by editing though GUI, go through server to get permission 516 | // since we may have rules about what names are forbidden in the future 517 | function changeNameByEdit() { 518 | var name = $('#socketchatbox-txt_fullname').val(); 519 | name = $.trim(name); 520 | if (name === username || name === "") { 521 | $username.text(username); 522 | } else if (!sendingFile) { 523 | askServerToChangeName(name); 524 | } 525 | } 526 | // Tell server that user want to change username 527 | function askServerToChangeName (newName) { 528 | socket.emit('user edits name', {newName: newName}); 529 | if(getCookie('chatboxOpen')==='1') 530 | $username.text('Changing your name...'); 531 | } 532 | 533 | 534 | // Change local username value and update local cookie 535 | function changeLocalUsername(name) { 536 | if(name) { 537 | username = name; 538 | addCookie('chatname', name); 539 | if(getCookie('chatboxOpen')==='1') 540 | $username.text(username); 541 | } 542 | } 543 | 544 | 545 | function newMsgBeep() { 546 | if (typeof newMsgSound === 'undefined') 547 | newMsgSound = new Audio("data:audio/wav;base64,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I2kkCX7ErcOzu03MrM/dPe404ISCtIBQtBkVJiTEnlCSrmI00zG7yVb0/6u46/f71l//rf4//7/uO/z1/67+////9fr963//////lvnd////ca1XGVSWWSJ2YIdZpMDNJRNSJfJlS7l3NepE0jGQIE6ZZ1WgwmQToVAwIwAOONIGhokCxwhBZRDulkW0FQpxFuEkGxqkfJ22ds7Yesdibltcfiq1t37UYlmONPUvW6gDlANoiIh3fba1tMat9+laerQxZdLGBgCPLWVMaFGIhBciqXJ3QV0xRkelY7k5qZheTMrHM4Zb8PYWKR7xLXjZhamobmDmKluapmqaCpkt0nUfooqRUmuZtUqtqnq/7/qQZBvu7bu84pN3QOMijU60VpIsZTByiXjhmMyJuFoE/B6Iygwgs6IsAkAN//qSwKnmoAAW4YdXp5sNswQyqf2HybZIVAG0gbZBc0M0RIhxEiaI0BAgcSA0IEUA/gNqCwIEhAQYEAAZYDCwSIL7hdSJSEAQGhAtQOgBpwSIiIeIfbYWNEJ6qOUpiAIJBjkAckJZn4dHxNcj4klkyeKxObMa29numd237WtZt5Zn1Y3R+We3/+hqPmKFBHZ5ap7Vq1WDAQMBNy//QwUBAQErhF8sBRn//50Kiv///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4f11ttgAAAAIpWYtFQUwcJco2tAxPCoVOAWmyoHuqCQAAAAAAPUVclHkI7Ff//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6ksDpbqmAGGUZTeewS7Bvg2f1h5hW//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////x/gAAAKAAAAPKnQ6laFSj//rgPDaTaCQHJZEzIjsoGC7O0U///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+pLAuHP/gC74ETvHpEBoTAGltYeYBP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8JJBAAOJY1jav////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////qSwLRB/4AwSAseh6QgICMA49AggAT////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6ksBYY/+AMWABLgAAACAAACXAAAAE////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+pLAWGP/gDFgAS4AAAAgAAAlwAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); 548 | newMsgSound.play(); 549 | } 550 | 551 | function beep() { 552 | if (typeof newUserSound === 'undefined') 553 | newUserSound = new Audio("data:audio/wav;base64,//uQRAAAAWMSLwUIYAAsYkXgoQwAEaYLWfkWgAI0wWs/ItAAAGDgYtAgAyN+QWaAAihwMWm4G8QQRDiMcCBcH3Cc+CDv/7xA4Tvh9Rz/y8QADBwMWgQAZG/ILNAARQ4GLTcDeIIIhxGOBAuD7hOfBB3/94gcJ3w+o5/5eIAIAAAVwWgQAVQ2ORaIQwEMAJiDg95G4nQL7mQVWI6GwRcfsZAcsKkJvxgxEjzFUgfHoSQ9Qq7KNwqHwuB13MA4a1q/DmBrHgPcmjiGoh//EwC5nGPEmS4RcfkVKOhJf+WOgoxJclFz3kgn//dBA+ya1GhurNn8zb//9NNutNuhz31f////9vt///z+IdAEAAAK4LQIAKobHItEIYCGAExBwe8jcToF9zIKrEdDYIuP2MgOWFSE34wYiR5iqQPj0JIeoVdlG4VD4XA67mAcNa1fhzA1jwHuTRxDUQ//iYBczjHiTJcIuPyKlHQkv/LHQUYkuSi57yQT//uggfZNajQ3Vmz+Zt//+mm3Wm3Q576v////+32///5/EOgAAADVghQAAAAA//uQZAUAB1WI0PZugAAAAAoQwAAAEk3nRd2qAAAAACiDgAAAAAAABCqEEQRLCgwpBGMlJkIz8jKhGvj4k6jzRnqasNKIeoh5gI7BJaC1A1AoNBjJgbyApVS4IDlZgDU5WUAxEKDNmmALHzZp0Fkz1FMTmGFl1FMEyodIavcCAUHDWrKAIA4aa2oCgILEBupZgHvAhEBcZ6joQBxS76AgccrFlczBvKLC0QI2cBoCFvfTDAo7eoOQInqDPBtvrDEZBNYN5xwNwxQRfw8ZQ5wQVLvO8OYU+mHvFLlDh05Mdg7BT6YrRPpCBznMB2r//xKJjyyOh+cImr2/4doscwD6neZjuZR4AgAABYAAAABy1xcdQtxYBYYZdifkUDgzzXaXn98Z0oi9ILU5mBjFANmRwlVJ3/6jYDAmxaiDG3/6xjQQCCKkRb/6kg/wW+kSJ5//rLobkLSiKmqP/0ikJuDaSaSf/6JiLYLEYnW/+kXg1WRVJL/9EmQ1YZIsv/6Qzwy5qk7/+tEU0nkls3/zIUMPKNX/6yZLf+kFgAfgGyLFAUwY//uQZAUABcd5UiNPVXAAAApAAAAAE0VZQKw9ISAAACgAAAAAVQIygIElVrFkBS+Jhi+EAuu+lKAkYUEIsmEAEoMeDmCETMvfSHTGkF5RWH7kz/ESHWPAq/kcCRhqBtMdokPdM7vil7RG98A2sc7zO6ZvTdM7pmOUAZTnJW+NXxqmd41dqJ6mLTXxrPpnV8avaIf5SvL7pndPvPpndJR9Kuu8fePvuiuhorgWjp7Mf/PRjxcFCPDkW31srioCExivv9lcwKEaHsf/7ow2Fl1T/9RkXgEhYElAoCLFtMArxwivDJJ+bR1HTKJdlEoTELCIqgEwVGSQ+hIm0NbK8WXcTEI0UPoa2NbG4y2K00JEWbZavJXkYaqo9CRHS55FcZTjKEk3NKoCYUnSQ0rWxrZbFKbKIhOKPZe1cJKzZSaQrIyULHDZmV5K4xySsDRKWOruanGtjLJXFEmwaIbDLX0hIPBUQPVFVkQkDoUNfSoDgQGKPekoxeGzA4DUvnn4bxzcZrtJyipKfPNy5w+9lnXwgqsiyHNeSVpemw4bWb9psYeq//uQZBoABQt4yMVxYAIAAAkQoAAAHvYpL5m6AAgAACXDAAAAD59jblTirQe9upFsmZbpMudy7Lz1X1DYsxOOSWpfPqNX2WqktK0DMvuGwlbNj44TleLPQ+Gsfb+GOWOKJoIrWb3cIMeeON6lz2umTqMXV8Mj30yWPpjoSa9ujK8SyeJP5y5mOW1D6hvLepeveEAEDo0mgCRClOEgANv3B9a6fikgUSu/DmAMATrGx7nng5p5iimPNZsfQLYB2sDLIkzRKZOHGAaUyDcpFBSLG9MCQALgAIgQs2YunOszLSAyQYPVC2YdGGeHD2dTdJk1pAHGAWDjnkcLKFymS3RQZTInzySoBwMG0QueC3gMsCEYxUqlrcxK6k1LQQcsmyYeQPdC2YfuGPASCBkcVMQQqpVJshui1tkXQJQV0OXGAZMXSOEEBRirXbVRQW7ugq7IM7rPWSZyDlM3IuNEkxzCOJ0ny2ThNkyRai1b6ev//3dzNGzNb//4uAvHT5sURcZCFcuKLhOFs8mLAAEAt4UWAAIABAAAAAB4qbHo0tIjVkUU//uQZAwABfSFz3ZqQAAAAAngwAAAE1HjMp2qAAAAACZDgAAAD5UkTE1UgZEUExqYynN1qZvqIOREEFmBcJQkwdxiFtw0qEOkGYfRDifBui9MQg4QAHAqWtAWHoCxu1Yf4VfWLPIM2mHDFsbQEVGwyqQoQcwnfHeIkNt9YnkiaS1oizycqJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU="); 554 | newUserSound.play(); 555 | } 556 | 557 | function writeChatHistoryIntoCookie(username, msg) { 558 | var chatHistory = []; 559 | try{ 560 | chatHistory = JSON.parse(getCookie('chathistory')); 561 | }catch(e){} 562 | 563 | if (chatHistory.length===0|| 564 | // avoid same message being saved when user open multiple tabs 565 | chatHistory[chatHistory.length-1].username!==username|| 566 | chatHistory[chatHistory.length-1].message!==msg){ 567 | 568 | var dataToSaveIntoCookie = {}; 569 | dataToSaveIntoCookie.username = username; 570 | dataToSaveIntoCookie.message = msg; 571 | chatHistory.push(dataToSaveIntoCookie); 572 | // keep most recent 20 messages only 573 | chatHistory = chatHistory.slice(Math.max(chatHistory.length - 20, 0)); 574 | addCookie('chathistory',JSON.stringify(chatHistory)); 575 | } 576 | } 577 | 578 | function loadHistoryChatFromCookie() { 579 | var chatHistory = []; 580 | try{ 581 | chatHistory = JSON.parse(getCookie('chathistory')); 582 | }catch(e){} 583 | if(chatHistory.length){ 584 | log("----Chat History----"); 585 | options = {}; 586 | options.loadFromCookie = true; 587 | for(var i=0; i 1) { 618 | host = strAry[strAry.length - 2] + "." + strAry[strAry.length - 1]; 619 | } 620 | } 621 | return '.' + host; 622 | } 623 | 624 | 625 | 626 | 627 | 628 | function doNothing(e){ 629 | e.preventDefault(); 630 | e.stopPropagation(); 631 | } 632 | 633 | function fileTooBig(data){ 634 | 635 | var fileSize = data.size/1024/1024; //MB 636 | var File_Size_Limit = 5; 637 | if (fileSize > File_Size_Limit){ 638 | 639 | alert("Don't upload file larger than "+File_Size_Limit+" MB!"); 640 | return true; 641 | } 642 | 643 | return false; 644 | 645 | } 646 | 647 | function readThenSendFile(data){ 648 | 649 | if(sendingFile){ 650 | alert('Still sending last file!'); 651 | return; 652 | } 653 | 654 | if(fileTooBig(data)) 655 | return; 656 | 657 | 658 | var reader = new FileReader(); 659 | reader.onload = function(evt){ 660 | var msg ={}; 661 | msg.username = username; 662 | msg.file = evt.target.result; 663 | msg.fileName = data.name; 664 | socket.emit('base64 file', msg); 665 | $inputMessage.val('Sending file...'); 666 | sendingFile = true; 667 | $inputMessage.prop('disabled', true); 668 | }; 669 | reader.readAsDataURL(data); 670 | } 671 | 672 | 673 | $window.keydown(function (event) { 674 | 675 | // When the client hits ENTER on their keyboard 676 | if (event.which === 13) { 677 | 678 | if ($('#socketchatbox-txt_fullname').is(":focus")) { 679 | changeNameByEdit(); 680 | $inputMessage.focus(); 681 | return; 682 | } 683 | 684 | if (username && $inputMessage.is(":focus")) { 685 | sendMessage(); 686 | socket.emit('stop typing', {name:username}); 687 | typing = false; 688 | } 689 | } 690 | 691 | // When the client hits ESC on their keyboard 692 | if (event.which === 27) { 693 | if ($('#socketchatbox-txt_fullname').is(":focus")) { 694 | $username.text(username); 695 | $inputMessage.focus(); 696 | return; 697 | } 698 | } 699 | 700 | }); 701 | 702 | $inputMessage.on('input', function() { 703 | updateTyping(); 704 | }); 705 | 706 | 707 | // Focus input when clicking on the message input's border 708 | $inputMessage.click(function() { 709 | $inputMessage.focus(); 710 | }); 711 | 712 | $('#socketchatbox-closeChatbox').click(function() { 713 | $chatBox.hide(); 714 | }); 715 | 716 | 717 | 718 | // Prepare file drop box. 719 | $chatBox.on('dragenter', doNothing); 720 | $chatBox.on('dragover', doNothing); 721 | $chatBox.on('drop', function(e){ 722 | e.originalEvent.preventDefault(); 723 | var data = e.originalEvent.dataTransfer.files[0]; 724 | readThenSendFile(data); 725 | }); 726 | 727 | $('#socketchatbox-imagefile').bind('change', function(e) { 728 | var data = e.originalEvent.target.files[0]; 729 | readThenSendFile(data); 730 | }); 731 | 732 | $topbar.click(function() { 733 | 734 | if($chatBody.is(":visible")){ 735 | 736 | hide(); 737 | addCookie('chatboxOpen',0); 738 | }else { 739 | show(); 740 | addCookie('chatboxOpen',1); 741 | } 742 | }); 743 | 744 | // user edit username 745 | $username.click(function(e) { 746 | if(getCookie('chatboxOpen')!=='1') return; 747 | if(sendingFile) return; 748 | e.stopPropagation(); 749 | if($('#socketchatbox-txt_fullname').length > 0) return; 750 | //if($('#socketchatbox-txt_fullname').is(":focus")) return; 751 | 752 | var name = $(this).text(); 753 | $(this).html(''); 754 | $('') 755 | .attr({ 756 | 'type': 'text', 757 | 'name': 'fname', 758 | 'id': 'socketchatbox-txt_fullname', 759 | 'size': '10', 760 | 'value': name 761 | }) 762 | .appendTo('#socketchatbox-username'); 763 | $('#socketchatbox-txt_fullname').focus(); 764 | }); 765 | 766 | document.addEventListener('visibilitychange', function() { 767 | if(!document.hidden) clearNewMessageNotification(); 768 | if(getCookie('chatboxOpen')==='1') { 769 | show(); 770 | }else{ 771 | hide(); 772 | } 773 | }); 774 | 775 | 776 | 777 | //resize 778 | 779 | var prev_x = -1; 780 | var prev_y = -1; 781 | var dir = null; 782 | $(".socketchatbox-resize").mousedown(function(e){ 783 | prev_x = e.clientX; 784 | prev_y = e.clientY; 785 | dir = $(this).attr('id'); 786 | e.preventDefault(); 787 | e.stopPropagation(); 788 | }); 789 | 790 | $(document).mousemove(function(e){ 791 | 792 | if (prev_x == -1) 793 | return; 794 | 795 | var boxW = $(".socketchatbox-chatArea").outerWidth(); 796 | var boxH = $(".socketchatbox-chatArea").outerHeight(); 797 | var dx = e.clientX - prev_x; 798 | var dy = e.clientY - prev_y; 799 | 800 | //Check directions 801 | if (dir.indexOf('n') > -1) //north 802 | { 803 | boxH -= dy; 804 | } 805 | 806 | if (dir.indexOf('w') > -1) //west 807 | { 808 | boxW -= dx; 809 | } 810 | if (dir.indexOf('e') > -1) //east 811 | { 812 | boxW += dx; 813 | } 814 | 815 | //console.log('boxW '+boxW); 816 | //console.log('boxH '+boxH); 817 | if(boxW<210) boxW = 210; 818 | if(boxH<30) boxH = 30; 819 | 820 | $(".socketchatbox-chatArea").css({ 821 | "width":(boxW)+"px", 822 | "height":(boxH)+"px", 823 | }); 824 | 825 | prev_x = e.clientX; 826 | prev_y = e.clientY; 827 | }); 828 | 829 | $(document).mouseup(function(){ 830 | prev_x = -1; 831 | prev_y = -1; 832 | }); 833 | 834 | 835 | // The functions below are for admin to use, user himself can't really call them 836 | 837 | 838 | function say(str) { 839 | sendMessageToServer(str); 840 | } 841 | 842 | function report(str) { 843 | if(str) 844 | reportToServer(str); 845 | 846 | else if($inputMessage.val()){ 847 | // if no input, report whatever in user's input field 848 | report($inputMessage.val()); 849 | $inputMessage.val(''); 850 | 851 | } 852 | } 853 | 854 | function type(str) { 855 | show(); 856 | var oldVal = $inputMessage.val(); 857 | $inputMessage.focus().val(oldVal+str.charAt(0)); 858 | if(str.length>1){ 859 | var time = 150; 860 | if(str.charAt(1)===' ') 861 | time = 500; 862 | setTimeout(function(){type(str.substring(1));},time); 863 | } 864 | } 865 | 866 | function send() { 867 | report($inputMessage.val()); 868 | $inputMessage.val(''); 869 | } 870 | 871 | function show(){ 872 | $('#socketchatbox-showHideChatbox').text("↓"); 873 | $username.text(username); 874 | $chatBody.show(); 875 | if (initialize === -1) { 876 | initialize = 1; 877 | log(); 878 | } 879 | 880 | //show resize cursor 881 | $('.socketchatbox-resize').css('z-index', 99999); 882 | 883 | } 884 | function hide(){ 885 | $('#socketchatbox-showHideChatbox').text("↑"); 886 | $username.text(chatboxname); 887 | $chatBody.hide(); 888 | 889 | //hide resize cursor 890 | $('.socketchatbox-resize').css('z-index', -999); 891 | } 892 | function color(c){ 893 | $('html').css('background-color', c); 894 | } 895 | function black(){ 896 | $('html').css('background-color', 'black'); 897 | } 898 | function white(){ 899 | $('html').css('background-color', 'white'); 900 | } 901 | 902 | }); 903 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chatbox 6 | 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    x
    15 |
    16 | 17 |
    18 |
    19 |
    20 |
      21 |
      22 | 23 |
      24 |
      25 | 29 |
      30 |
      31 | 32 | 33 |
      34 | 35 |
      36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /screenshots/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kn007/Chatbox/754b30df21a11b13751bc510b12bd3a389f3d0f9/screenshots/Screenshot.png -------------------------------------------------------------------------------- /wordpress/README.md: -------------------------------------------------------------------------------- 1 | # English description 2 | 3 | 4 | 5 | ##### Automatic Sync Wordpress Comment Author Name to Chatbox's Nickname 6 | 7 | First, in `public/client.js`, add a line like this: 8 | ``` 9 | var wordpress_cookie = 'comment_author_fb594a9f9824f4e2bfe1ef5fb8f628ad'; 10 | var comment_author = ''; 11 | ``` 12 | You can add it after `var port`, the value can get by `COOKIEHASH` or `md5(home_url());`. 13 | 14 | Quick way to change the `wordpress_cookie`'s value: 15 | ``` 16 | $ sed -i "s/var wordpress_cookie =.*/var wordpress_cookie = 'comment_author_$(echo -n https://kn007.net | md5sum | cut -d ' ' -f1)';/g" ./public/client.js 17 | ``` 18 | Replaced the `https://kn007.net` to your blog url, no trailing slash. 19 | 20 | Also in `public/client.js`, add a new action: 21 | ``` 22 | socket.on('wordpress check', function (data) { 23 | setTimeout(function(){syncCommentAuthorName();},1000); 24 | }); 25 | function syncCommentAuthorName() { 26 | setTimeout(function(){syncCommentAuthorName();},2000); 27 | if(chatboxClient.getCookie(wordpress_cookie)==='') return; 28 | comment_author = decodeURI(chatboxClient.getCookie(wordpress_cookie)); 29 | if(username===comment_author) return; 30 | askServerToChangeName(comment_author); 31 | } 32 | ``` 33 | 34 | Then, find the `function init()`, prior to add the following code into the function: 35 | ``` 36 | if(chatboxClient.getCookie(wordpress_cookie)!=='') { 37 | comment_author = decodeURI(chatboxClient.getCookie(wordpress_cookie)); 38 | chatboxClient.addCookie('chatname', comment_author); 39 | } 40 | ``` 41 | 42 | Last step, find `user.socketList.push(socket)` in `index.js`, add following code before it. 43 | ``` 44 | socket.emit('wordpress check', {}); 45 | ``` 46 | 47 | Done. 48 | 49 | If you need disallow change the chatbox nickname in Wordpress, find the `$('#socketchatbox-username').click(function(e)` in `public/client.js`, include this code: 50 | ``` 51 | if(comment_author!=='') return; 52 | ``` 53 | 54 | Another blog web software also can modify like this. 55 | 56 | 57 | ##### Demo 58 | 59 | [https://kn007.net/](https://kn007.net/) (Chatbox will show after post comment) 60 | 61 | 62 | 63 | 64 | ----------------------------------------------------------- 65 | # 中文介绍 66 | 67 | 68 | 69 | ##### 在Wordpress使用聊天盒时,同步评论者昵称 70 | 71 | 1.在`public/client.js`中增加一行(比如在`var port`后面): 72 | ``` 73 | var wordpress_cookie = 'comment_author_fb594a9f9824f4e2bfe1ef5fb8f628ad'; 74 | var comment_author = ''; 75 | ``` 76 | 后面的hash字符可以通过wordpress输出`COOKIEHASH`或者通过`md5(home_url());`得出。 77 | 78 | 在shell下快速修改的方法: 79 | ``` 80 | $ sed -i "s/var wordpress_cookie =.*/var wordpress_cookie = 'comment_author_$(echo -n https://kn007.net | md5sum | cut -d ' ' -f1)';/g" ./public/client.js 81 | ``` 82 | 其中`https://kn007.net`就是你的WP博客网址,替换成你的,谨记最后面不带斜杠。 83 | 84 | 2.在`public/client.js`中,注册动作: 85 | ``` 86 | socket.on('wordpress check', function (data) { 87 | setTimeout(function(){syncCommentAuthorName();},1000); 88 | }); 89 | function syncCommentAuthorName() { 90 | setTimeout(function(){syncCommentAuthorName();},2000); 91 | if(chatboxClient.getCookie(wordpress_cookie)==='') return; 92 | comment_author = decodeURI(chatboxClient.getCookie(wordpress_cookie)); 93 | if(username===comment_author) return; 94 | askServerToChangeName(comment_author); 95 | } 96 | ``` 97 | 98 | 3.在`index.js`中,在`user.socketList.push(socket)`前面加入: 99 | ``` 100 | socket.emit('wordpress check', {}); 101 | ``` 102 | 103 | 4.在`public/client.js`中,找到`function init()`,在函数中最前面加入: 104 | ``` 105 | if(chatboxClient.getCookie(wordpress_cookie)!=='') { 106 | comment_author = decodeURI(chatboxClient.getCookie(wordpress_cookie)); 107 | addCookie('chatname', comment_author); 108 | } 109 | ``` 110 | 如此便好。 111 | 112 | 如果你需要强制聊天盒昵称与Wordpress评论名称同步,禁止被修改,请在`public/client.js`找到`$('#socketchatbox-username').click(function(e)`,在其函数里面加入: 113 | ``` 114 | if(comment_author!=='') return; 115 | ``` 116 | 117 | 最后要说的是,其他博客程序,修改方法类似。 118 | 119 | 以上修改可以参考本目录附带的`index.js`和`client.js`。 120 | 121 | 122 | ##### 示例 123 | 124 | [https://kn007.net/](https://kn007.net/) (聊天盒需要评论后方才显示) 125 | 126 | -------------------------------------------------------------------------------- /wordpress/client.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | var chatboxname = 'Chatbox'; 4 | // change this to your port 5 | var port = 4321; 6 | var hostname = location.hostname; 7 | //hostname="lifeislikeaboat.com"; 8 | var domain = location.protocol + "//" + hostname + ":" + port; 9 | var socket; 10 | 11 | var wordpress_cookie = 'comment_author_fb594a9f9824f4e2bfe1ef5fb8f628ad'; 12 | 13 | var FADE_TIME = 150; // ms 14 | var TYPING_TIMER_LENGTH = 400; // ms 15 | var COLORS = [ 16 | 'black' 17 | ]; 18 | 19 | // Initialize variables 20 | var d = new Date(); 21 | var $window = $(window); 22 | var $username = $('#socketchatbox-username'); 23 | var $messages = $('.socketchatbox-messages'); // Messages area 24 | var $inputMessage = $('.socketchatbox-inputMessage'); // Input message input box 25 | var $chatBox = $('.socketchatbox-page'); 26 | var $topbar = $('#socketchatbox-top'); 27 | var $chatBody = $('#socketchatbox-body'); 28 | var sendingFile = false; 29 | var newMsgSound; 30 | var newUserSound; 31 | 32 | var initialize = 0; 33 | var typing = false; 34 | var lastTypingTime; 35 | var username = 'visitor#'+ d.getMinutes()+ d.getSeconds(); 36 | var comment_author = ''; 37 | var totalUser = 0; 38 | // This uuid is unique for each browser but not unique for each connection 39 | // because one browser can have multiple tabs each with connections to the chatbox server. 40 | // And this uuid should always be passed on login, it's used to identify/combine user, 41 | // multiple connections from same browser are regarded as same user. 42 | var uuid = "uuid not set!"; 43 | 44 | // New Message Received Notification 45 | // 1 -- Change Page Title Once (when the webpage state is not visible) 46 | // 2 -- Flash Page Title (when the webpage state is not visible) 47 | // 3 -- Change Page Title Once (always, just notify in 3 seconds) 48 | // Other -- Do Not Change Page Title 49 | var changeTitleMode = 2; 50 | var changeTitle = { 51 | time: 0, 52 | originTitle: document.title, 53 | timer: null, 54 | done: 0, 55 | change: function() { 56 | document.title = "~New Message Received~ " + changeTitle.originTitle; 57 | changeTitle.done = 1; 58 | }, 59 | notify: function() { 60 | if(document.title.indexOf("~New Message Received~")) clearTimeout(changeTitle.timer); 61 | document.title = "~New Message Received~ " + changeTitle.originTitle; 62 | changeTitle.timer = setTimeout(function(){changeTitle.reset();},3000); 63 | changeTitle.done = 0; //Always be 0 64 | }, 65 | flash: function() { 66 | changeTitle.timer = setTimeout(function () { 67 | changeTitle.time++; 68 | changeTitle.flash(); 69 | if (changeTitle.time % 2 === 0) { 70 | document.title = "~ ~ " + changeTitle.originTitle; 71 | }else{ 72 | document.title = "~New Message Received~ " + changeTitle.originTitle; 73 | } 74 | }, 500); 75 | changeTitle.done = 1; 76 | }, 77 | reset: function() { 78 | clearTimeout(changeTitle.timer); 79 | document.title = changeTitle.originTitle; 80 | changeTitle.done = 0; 81 | } 82 | }; 83 | 84 | function getCookie(cname) { 85 | var name = cname + "="; 86 | var ca = document.cookie.split(';'); 87 | for(var i=0; i').addClass('socketchatbox-log').text(message); 318 | addMessageElement($el, options); 319 | } 320 | 321 | // Process message before displaying 322 | function processChatMessage (data, options) { 323 | 324 | //avoid empty name 325 | if (typeof data.username === 'undefined' || data.username==='') 326 | data.username = "empty name"; 327 | 328 | // Don't fade the message in if there is an 'X was typing' 329 | var $typingMessages = getTypingMessages(data); 330 | options = options || {}; 331 | if ($typingMessages.length !== 0) { 332 | options.fade = false; 333 | $typingMessages.remove(); 334 | } 335 | 336 | var d = new Date(); 337 | var posttime = ''; 338 | if (!options.loadFromCookie) { 339 | posttime += ""; 340 | posttime += ' ('+('0' + d.getHours()).slice(-2) + ":" + ('0' + d.getMinutes()).slice(-2) + ":" + ('0' + d.getSeconds()).slice(-2)+')'; 341 | posttime += ""; 342 | } 343 | 344 | var $usernameDiv = $('
      ') 345 | .html($("
      ").text(data.username).html()+posttime) 346 | .css('color', getUsernameColor(data.username)); 347 | $usernameDiv.addClass('socketchatbox-username'); 348 | var $messageBodyDiv = $(''); 349 | if (data.username === username) { 350 | $messageBodyDiv.addClass('socketchatbox-messageBody-me'); 351 | } else { 352 | $messageBodyDiv.addClass('socketchatbox-messageBody-others'); 353 | } 354 | var messageToSaveIntoCookie = ""; 355 | 356 | // receiving image file in base64 357 | if (options.file) { 358 | var mediaType = "img"; 359 | if (data.file.substring(0,10)==='data:video') 360 | mediaType = "video controls"; 361 | 362 | if (data.file.substring(0,10)==='data:image' || data.file.substring(0,10)==='data:video') { 363 | $messageBodyDiv.html("<"+mediaType+" class='chatbox-image' src='"+data.file+"'>"); 364 | }else{ 365 | $messageBodyDiv.html(""+data.fileName+""); 366 | } 367 | 368 | messageToSaveIntoCookie = data.fileName+"(File)"; 369 | 370 | }else{ 371 | 372 | messageToSaveIntoCookie = data.message; 373 | 374 | if (checkImageUrl(data.message)) { 375 | //receiving image url 376 | $messageBodyDiv.html(""); 377 | }else { 378 | //receiving plain text 379 | $messageBodyDiv.text(data.message); 380 | } 381 | } 382 | 383 | // receiving new message 384 | if (!options.loadFromCookie && !options.typing) { 385 | 386 | // play new msg sound and change chatbox color to notify users 387 | if (data.username !== username) { 388 | newMsgBeep(); 389 | if(document.hidden && changeTitleMode === 1 && changeTitle.done === 0) changeTitle.change(); 390 | if(document.hidden && changeTitleMode === 2 && changeTitle.done === 0) changeTitle.flash(); 391 | if(document.hidden && changeTitleMode === 3 && changeTitle.done === 0) changeTitle.notify(); 392 | if(!document.hidden) socket.emit('reset2origintitle', {}); 393 | } 394 | 395 | writeChatHistoryIntoCookie(data.username, messageToSaveIntoCookie); 396 | } 397 | 398 | 399 | var typingClass = options.typing ? 'socketchatbox-typing' : ''; 400 | var $messageWrapper = $("
      "); 401 | var $messageDiv = $("
      ") 402 | .data('username', data.username) 403 | .addClass(typingClass) 404 | .append($usernameDiv, $messageBodyDiv); 405 | $messageWrapper.append($messageDiv); 406 | if (data.username === username) { 407 | $messageDiv.addClass('socketchatbox-message-me'); 408 | } else { 409 | $messageDiv.addClass('socketchatbox-message-others'); 410 | } 411 | 412 | addMessageElement($messageWrapper, options); 413 | } 414 | 415 | 416 | 417 | 418 | // Adds a message element to the messages and scrolls to the bottom 419 | // el - The element to add as a message 420 | // options.fade - If the element should fade-in (default = true) 421 | // options.prepend - If the element should prepend 422 | // all other messages (default = false) 423 | function addMessageElement (el, options) { 424 | 425 | var $el = $(el); 426 | 427 | // Setup default options 428 | options = options || {}; 429 | 430 | if (typeof options.fade === 'undefined') { 431 | options.fade = true; 432 | } 433 | if (typeof options.prepend === 'undefined') { 434 | options.prepend = false; 435 | } 436 | 437 | // Apply options 438 | if (options.fade) { 439 | $el.hide().fadeIn(FADE_TIME); 440 | } 441 | if (options.prepend) { 442 | $messages.prepend($el); 443 | } else { 444 | $messages.append($el); 445 | } 446 | 447 | //loading media takes time so we delay the scroll down 448 | setTimeout(function(){ 449 | $messages[0].scrollTop = $messages[0].scrollHeight; 450 | },50); 451 | } 452 | 453 | // Prevents input from having injected markup 454 | function cleanInput (input) { 455 | return $('
      ').text(input).text(); 456 | } 457 | 458 | 459 | function addParticipantsMessage (numUsers) { 460 | var message = ''; 461 | if (numUsers === 1) { 462 | message += "You are the only user online"; 463 | }else if (totalUser === 0) { 464 | message += "There are " + numUsers + " users online"; 465 | } 466 | log(message); 467 | 468 | totalUser = numUsers; 469 | } 470 | 471 | // Adds the visual chat typing message 472 | function addChatTyping (data) { 473 | data.message = 'is typing'; 474 | options={}; 475 | options.typing = true; 476 | processChatMessage(data, options); 477 | } 478 | 479 | // Removes the visual chat typing message 480 | function removeChatTyping (data) { 481 | getTypingMessages(data).fadeOut(function() { 482 | $(this).remove(); 483 | }); 484 | } 485 | 486 | 487 | // Updates the typing event 488 | function updateTyping() { 489 | 490 | if (!typing) { 491 | typing = true; 492 | socket.emit('typing', {name:username}); 493 | } 494 | lastTypingTime = (new Date()).getTime(); 495 | 496 | setTimeout(function() { 497 | var typingTimer = (new Date()).getTime(); 498 | var timeDiff = typingTimer - lastTypingTime; 499 | if (timeDiff >= TYPING_TIMER_LENGTH && typing) { 500 | socket.emit('stop typing', {name:username}); 501 | typing = false; 502 | } 503 | }, TYPING_TIMER_LENGTH); 504 | 505 | } 506 | 507 | // Gets the 'X is typing' messages of a user 508 | function getTypingMessages (data) { 509 | return $('.socketchatbox-typing.socketchatbox-message').filter(function (i) { 510 | return $(this).data('username') === data.username; 511 | }); 512 | } 513 | 514 | // Gets the color of a username through our hash function 515 | function getUsernameColor (username) { 516 | // Compute hash code 517 | var hash = 7; 518 | for (var i = 0; i < username.length; i++) { 519 | hash = username.charCodeAt(i) + (hash << 5) - hash; 520 | } 521 | // Calculate color 522 | var index = Math.abs(hash % COLORS.length); 523 | return COLORS[index]; 524 | } 525 | 526 | function clearNewMessageNotification() { 527 | changeTitle.reset(); 528 | socket.emit('reset2origintitle', {}); 529 | } 530 | 531 | // Tell server that user want to change username 532 | function askServerToChangeName (newName) { 533 | socket.emit('user edits name', {newName: newName}); 534 | if(getCookie('chatboxOpen')==='1') 535 | $username.text('Changing your name...'); 536 | } 537 | 538 | 539 | // Change local username value and update local cookie 540 | function changeLocalUsername(name) { 541 | if(name) { 542 | username = name; 543 | addCookie('chatname', name); 544 | if(getCookie('chatboxOpen')==='1') 545 | $username.text(username); 546 | } 547 | } 548 | 549 | 550 | function newMsgBeep() { 551 | if (typeof newMsgSound === 'undefined') 552 | newMsgSound = new Audio("data:audio/wav;base64,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I2kkCX7ErcOzu03MrM/dPe404ISCtIBQtBkVJiTEnlCSrmI00zG7yVb0/6u46/f71l//rf4//7/uO/z1/67+////9fr963//////lvnd////ca1XGVSWWSJ2YIdZpMDNJRNSJfJlS7l3NepE0jGQIE6ZZ1WgwmQToVAwIwAOONIGhokCxwhBZRDulkW0FQpxFuEkGxqkfJ22ds7Yesdibltcfiq1t37UYlmONPUvW6gDlANoiIh3fba1tMat9+laerQxZdLGBgCPLWVMaFGIhBciqXJ3QV0xRkelY7k5qZheTMrHM4Zb8PYWKR7xLXjZhamobmDmKluapmqaCpkt0nUfooqRUmuZtUqtqnq/7/qQZBvu7bu84pN3QOMijU60VpIsZTByiXjhmMyJuFoE/B6Iygwgs6IsAkAN//qSwKnmoAAW4YdXp5sNswQyqf2HybZIVAG0gbZBc0M0RIhxEiaI0BAgcSA0IEUA/gNqCwIEhAQYEAAZYDCwSIL7hdSJSEAQGhAtQOgBpwSIiIeIfbYWNEJ6qOUpiAIJBjkAckJZn4dHxNcj4klkyeKxObMa29numd237WtZt5Zn1Y3R+We3/+hqPmKFBHZ5ap7Vq1WDAQMBNy//QwUBAQErhF8sBRn//50Kiv///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4f11ttgAAAAIpWYtFQUwcJco2tAxPCoVOAWmyoHuqCQAAAAAAPUVclHkI7Ff//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6ksDpbqmAGGUZTeewS7Bvg2f1h5hW//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////x/gAAAKAAAAPKnQ6laFSj//rgPDaTaCQHJZEzIjsoGC7O0U///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+pLAuHP/gC74ETvHpEBoTAGltYeYBP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8JJBAAOJY1jav////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////qSwLRB/4AwSAseh6QgICMA49AggAT////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6ksBYY/+AMWABLgAAACAAACXAAAAE////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+pLAWGP/gDFgAS4AAAAgAAAlwAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); 553 | newMsgSound.play(); 554 | } 555 | 556 | function writeChatHistoryIntoCookie(username, msg) { 557 | var chatHistory = []; 558 | try{ 559 | chatHistory = JSON.parse(getCookie('chathistory')); 560 | }catch(e){} 561 | 562 | if (chatHistory.length===0|| 563 | // avoid same message being saved when user open multiple tabs 564 | chatHistory[chatHistory.length-1].username!==username|| 565 | chatHistory[chatHistory.length-1].message!==msg){ 566 | 567 | var dataToSaveIntoCookie = {}; 568 | dataToSaveIntoCookie.username = username; 569 | dataToSaveIntoCookie.message = msg; 570 | chatHistory.push(dataToSaveIntoCookie); 571 | // keep most recent 20 messages only 572 | chatHistory = chatHistory.slice(Math.max(chatHistory.length - 20, 0)); 573 | addCookie('chathistory',JSON.stringify(chatHistory)); 574 | } 575 | } 576 | 577 | function loadHistoryChatFromCookie() { 578 | var chatHistory = []; 579 | try{ 580 | chatHistory = JSON.parse(getCookie('chathistory')); 581 | }catch(e){} 582 | if(chatHistory.length){ 583 | log("----Chat History----"); 584 | options = {}; 585 | options.loadFromCookie = true; 586 | for(var i=0; i 1) { 617 | host = strAry[strAry.length - 2] + "." + strAry[strAry.length - 1]; 618 | } 619 | } 620 | return '.' + host; 621 | } 622 | 623 | 624 | 625 | 626 | 627 | function doNothing(e){ 628 | e.preventDefault(); 629 | e.stopPropagation(); 630 | } 631 | 632 | function fileTooBig(data){ 633 | 634 | var fileSize = data.size/1024/1024; //MB 635 | var File_Size_Limit = 5; 636 | if (fileSize > File_Size_Limit){ 637 | 638 | alert("Don't upload file larger than "+File_Size_Limit+" MB!"); 639 | return true; 640 | } 641 | 642 | return false; 643 | 644 | } 645 | 646 | function readThenSendFile(data){ 647 | 648 | if(sendingFile){ 649 | alert('Still sending last file!'); 650 | return; 651 | } 652 | 653 | if(fileTooBig(data)) 654 | return; 655 | 656 | 657 | var reader = new FileReader(); 658 | reader.onload = function(evt){ 659 | var msg ={}; 660 | msg.username = username; 661 | msg.file = evt.target.result; 662 | msg.fileName = data.name; 663 | socket.emit('base64 file', msg); 664 | $inputMessage.val('Sending file...'); 665 | sendingFile = true; 666 | $inputMessage.prop('disabled', true); 667 | }; 668 | reader.readAsDataURL(data); 669 | } 670 | 671 | 672 | $window.keydown(function (event) { 673 | 674 | // When the client hits ENTER on their keyboard 675 | if (event.which === 13) { 676 | if (username && $inputMessage.is(":focus")) { 677 | sendMessage(); 678 | socket.emit('stop typing', {name:username}); 679 | typing = false; 680 | } 681 | } 682 | 683 | }); 684 | 685 | $inputMessage.on('input', function() { 686 | updateTyping(); 687 | }); 688 | 689 | 690 | // Focus input when clicking on the message input's border 691 | $inputMessage.click(function() { 692 | $inputMessage.focus(); 693 | }); 694 | 695 | $('#socketchatbox-closeChatbox').click(function() { 696 | $chatBox.hide(); 697 | }); 698 | 699 | 700 | 701 | // Prepare file drop box. 702 | $chatBox.on('dragenter', doNothing); 703 | $chatBox.on('dragover', doNothing); 704 | $chatBox.on('drop', function(e){ 705 | e.originalEvent.preventDefault(); 706 | var data = e.originalEvent.dataTransfer.files[0]; 707 | readThenSendFile(data); 708 | }); 709 | 710 | $('#socketchatbox-imagefile').bind('change', function(e) { 711 | var data = e.originalEvent.target.files[0]; 712 | readThenSendFile(data); 713 | }); 714 | 715 | $topbar.click(function() { 716 | 717 | if($chatBody.is(":visible")){ 718 | 719 | hide(); 720 | addCookie('chatboxOpen',0); 721 | }else { 722 | show(); 723 | addCookie('chatboxOpen',1); 724 | } 725 | }); 726 | 727 | document.addEventListener('visibilitychange', function() { 728 | if(!document.hidden) clearNewMessageNotification(); 729 | if(getCookie('chatboxOpen')==='1') { 730 | show(); 731 | }else{ 732 | hide(); 733 | } 734 | }); 735 | 736 | 737 | 738 | //resize 739 | 740 | var prev_x = -1; 741 | var prev_y = -1; 742 | var dir = null; 743 | $(".socketchatbox-resize").mousedown(function(e){ 744 | prev_x = e.clientX; 745 | prev_y = e.clientY; 746 | dir = $(this).attr('id'); 747 | e.preventDefault(); 748 | e.stopPropagation(); 749 | }); 750 | 751 | $(document).mousemove(function(e){ 752 | 753 | if (prev_x == -1) 754 | return; 755 | 756 | var boxW = $(".socketchatbox-chatArea").outerWidth(); 757 | var boxH = $(".socketchatbox-chatArea").outerHeight(); 758 | var dx = e.clientX - prev_x; 759 | var dy = e.clientY - prev_y; 760 | 761 | //Check directions 762 | if (dir.indexOf('n') > -1) //north 763 | { 764 | boxH -= dy; 765 | } 766 | 767 | if (dir.indexOf('w') > -1) //west 768 | { 769 | boxW -= dx; 770 | } 771 | if (dir.indexOf('e') > -1) //east 772 | { 773 | boxW += dx; 774 | } 775 | 776 | //console.log('boxW '+boxW); 777 | //console.log('boxH '+boxH); 778 | if(boxW<210) boxW = 210; 779 | if(boxH<30) boxH = 30; 780 | 781 | $(".socketchatbox-chatArea").css({ 782 | "width":(boxW)+"px", 783 | "height":(boxH)+"px", 784 | }); 785 | 786 | prev_x = e.clientX; 787 | prev_y = e.clientY; 788 | }); 789 | 790 | $(document).mouseup(function(){ 791 | prev_x = -1; 792 | prev_y = -1; 793 | }); 794 | 795 | 796 | // The functions below are for admin to use, user himself can't really call them 797 | 798 | 799 | function say(str) { 800 | sendMessageToServer(str); 801 | } 802 | 803 | function report(str) { 804 | if(str) 805 | reportToServer(str); 806 | 807 | else if($inputMessage.val()){ 808 | // if no input, report whatever in user's input field 809 | report($inputMessage.val()); 810 | $inputMessage.val(''); 811 | 812 | } 813 | } 814 | 815 | function type(str) { 816 | show(); 817 | var oldVal = $inputMessage.val(); 818 | $inputMessage.focus().val(oldVal+str.charAt(0)); 819 | if(str.length>1){ 820 | var time = 150; 821 | if(str.charAt(1)===' ') 822 | time = 500; 823 | setTimeout(function(){type(str.substring(1));},time); 824 | } 825 | } 826 | 827 | function send() { 828 | report($inputMessage.val()); 829 | $inputMessage.val(''); 830 | } 831 | 832 | function show(){ 833 | $('#socketchatbox-showHideChatbox').text("↓"); 834 | $username.text(username); 835 | $chatBody.show(); 836 | if (initialize === -1) { 837 | initialize = 1; 838 | log(); 839 | } 840 | 841 | //show resize cursor 842 | $('.socketchatbox-resize').css('z-index', 99999); 843 | 844 | } 845 | function hide(){ 846 | $('#socketchatbox-showHideChatbox').text("↑"); 847 | $username.text(chatboxname); 848 | $chatBody.hide(); 849 | 850 | //hide resize cursor 851 | $('.socketchatbox-resize').css('z-index', -999); 852 | } 853 | function color(c){ 854 | $('html').css('background-color', c); 855 | } 856 | function black(){ 857 | $('html').css('background-color', 'black'); 858 | } 859 | function white(){ 860 | $('html').css('background-color', 'white'); 861 | } 862 | 863 | }); 864 | -------------------------------------------------------------------------------- /wordpress/client.min.js: -------------------------------------------------------------------------------- 1 | eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('$(6(){6 A(a){1B(9 b=a+"=",c=u.3x.3w(";"),d=0;d").U("p-F").G(a);1V(c,b)}6 1s(a,b){"14"!=15 a.8&&""!==a.8||(a.8="5U O");9 c=21(a);b=b||{},0!==c.B&&(b.1K=!1,c.2z());9 d=I 1r,e="";b.27||(e+="<28 12=\'p-6l\'>",e+=" ("+("0"+d.6t()).1N(-2)+":"+("0"+d.32()).1N(-2)+":"+("0"+d.31()).1N(-2)+")",e+="");9 f=$("").R($("").G(a.8).R()+e).S("1h",2W(a.8));f.U("p-8");9 g=$(\'<28 12="p-2q">\');a.8===8?g.U("p-2q-2U"):g.U("p-2q-2P");9 h="";M(b.D){9 i="2O";"1d:1T"===a.D.V(0,10)&&(i="1T 6y"),"1d:1U"===a.D.V(0,10)||"1d:1T"===a.D.V(0,10)?g.R("<"+i+" 12=\'2N-1U\' 2M=\'"+a.D+"\'>"):g.R(""+a.1x+""),h=a.1x+"(5u)"}5N h=a.C,38(a.C)?g.R("<2O 12=\'2N-1U\' 2M=\'"+a.C+"\'>"):g.G(a.C);b.27||b.y||(a.8!==8&&(2J(),u.1n&&1===1A&&0===r.Q&&r.1q(),u.1n&&2===1A&&0===r.Q&&r.2a(),u.1n&&3===1A&&0===r.Q&&r.2H(),u.1n||q.K("1E",{})),2F(a.8,h));9 j=b.y?"p-y":"",k=$(""),l=$("").1d("8",a.8).U(j).2g(f,g);k.2g(l),a.8===8?l.U("p-C-2U"):l.U("p-C-2P"),1V(k,b)}6 1V(a,b){9 c=$(a);b=b||{},"14"==15 b.1K&&(b.1K=!0),"14"==15 b.1G&&(b.1G=!1),b.1K&&c.X().3P(2E),b.1G?$1f.1G(c):$1f.2g(c),Z(6(){$1f[0].58=$1f[0].59},50)}6 3d(a){E $("").G(a).G()}6 1I(a){9 b="";1===a?b+="5l 2D 5p 5q 1t 2C":0===2n&&(b+="5v 2D "+a+" 5B 2C"),F(b),2n=a}6 2B(a){a.C="2p y",18={},18.y=!0,1s(a,18)}6 2r(a){21(a).5S(6(){$(2s).2z()})}6 2A(){y||(y=!0,q.K("y",{O:8})),2u=(I 1r).1X(),Z(6(){9 a=(I 1r).1X(),b=a-2u;b>=2v&&y&&(q.K("2w y",{O:8}),y=!1)},2v)}6 21(a){E $(".p-y.p-C").5Z(6(b){E $(2s).1d("8")===a.8})}6 2W(a){1B(9 b=7,c=0;c1&&(a=e[e.B-2]+"."+e[e.B-1])}E"."+a}6 1R(a){a.2y(),a.2Q()}6 2R(a){9 b=a.5t/2S/2S,c=5;E b>c&&(2T("5w\'t 5x D 5y 5z "+c+" 5A!"),!0)}6 2x(a){M(1C)E 5C 2T("5D 5E 5F D!");M(!2R(a)){9 b=I 5G;b.5H=6(b){9 c={};c.8=8,c.D=b.1l.5I,c.1x=a.O,q.K("2h D",c),$s.J("5J D..."),1C=!0,$s.5K("3a",!0)},b.5L(a)}}6 5M(a){1W(a)}6 1O(a){a?3b(a):$s.J()&&(1O($s.J()),$s.J(""))}6 2V(a){17();9 b=$s.J();M($s.2l().J(b+a.1Q(0)),a.B>1){9 c=2X;" "===a.1Q(1)&&(c=2Y),Z(6(){2V(a.V(1))},c)}}6 5R(){1O($s.J()),$s.J("")}6 17(){$("#p-2Z").G("↓"),$8.G(8),$1z.17(),1e===-1&&(1e=1,F()),$(".p-2e").S("z-33",5X)}6 X(){$("#p-2Z").G("↑"),$8.G(m),$1z.X(),$(".p-2e").S("z-33",-5Y)}6 1h(a){$("R").S("26-1h",a)}6 2k(){$("R").S("26-1h","2k")}6 36(){$("R").S("26-1h","36")}9 m="62",37=64,1w=1j.1w,2d=1j.65+"//"+1w+":"+37,q,1i="66",2E=2X,2v=68,2t=["2k"],d=I 1r,$1D=$(1D),$8=$("#p-8"),$1f=$(".p-1f"),$s=$(".p-s"),$1a=$(".p-6a"),$39=$("#p-6c"),$1z=$("#p-6d"),1C=!1,1J,6e,1e=0,y=!1,2u,8="6g#"+d.32()+d.31(),1b="",2n=0,11="11 6h 6i!",1A=2,r={2f:0,1c:u.Y,1k:1v,Q:0,1q:6(){u.Y="~1F 1y 1H~ "+r.1c,r.Q=1},2H:6(){u.Y.1m("~1F 1y 1H~")&&3h(r.1k),u.Y="~1F 1y 1H~ "+r.1c,r.1k=Z(6(){r.1L()},6r),r.Q=0},2a:6(){r.1k=Z(6(){r.2f++,r.2a(),r.2f%2===0?u.Y="~ ~ "+r.1c:u.Y="~1F 1y 1H~ "+r.1c},2Y),r.Q=1},1L:6(){3h(r.1k),u.Y=r.1c,r.Q=0}};3r();9 n={q:q,A:A,H:H};1D.6s=n,q.v("3j",6(a){q.K("3j",{8:8,11:11,3g:1j.1u,3k:u.3k}),2c()}),q.v("3l I 1t",6(a){9 b="6x, "+8;F(b,{}),1I(a.1Z)}),q.v("3l I 6z",6(a){2m(a.8);9 b="6A, "+8;F(b,{}),q.K("1E",{})}),q.v("6B 6C",6(a){Z(6(){29()},3u)}),q.v("I C",6(a){1s(a)}),q.v("2h D",6(a){9 b={};b.D=!0,1s(a,b),a.8===8&&2c()}),q.v("3n",6(a){6E(a.3n)}),q.v("1q 8",6(a){2m(a.8)}),q.v("1t 3o",6(a){F(a.8+" 3o"),1I(a.1Z)}),q.v("1t 3p",6(a){F(a.8+" 3p"),1I(a.1Z),2r(a)}),q.v("F 1q O",6(a){F(a.6H+" 6I O 6J "+a.8)}),q.v("y",6(a){2B(a)}),q.v("2w y",6(a){2r(a)}),q.v("1E",6(a){r.1L()}),$1D.6K(6(a){13===a.6L&&8&&$s.2p(":2l")&&(3e(),q.K("2w y",{O:8}),y=!1)}),$s.v("6M",6(){2A()}),$s.1S(6(){$s.2l()}),$("#p-6O").1S(6(){$1a.X()}),$1a.v("6P",1R),$1a.v("6Q",1R),$1a.v("6R",6(a){a.2j.2y();9 b=a.2j.6T.3s[0];2x(b)}),$("#p-6V").6W("1q",6(a){9 b=a.2j.1l.3s[0];2x(b)}),$39.1S(6(){$1z.2p(":6X")?(X(),H("19",0)):(17(),H("19",1))}),u.6Y("6Z",6(){u.1n||34(),"1"===A("19")?17():X()});9 o=-1,1o=-1,1p=1v;$(".p-2e").73(6(a){o=a.2i,1o=a.23,1p=$(2s).76("77"),a.2y(),a.2Q()}),$(u).78(6(a){M(o!=-1){9 b=$(".p-1Y").7a(),c=$(".p-1Y").7b(),d=a.2i-o,e=a.23-1o;1p.1m("n")>-1&&(c-=e),1p.1m("w")>-1&&(b-=d),1p.1m("e")>-1&&(b+=d),b<3y&&(b=3y),c<30&&(c=30),$(".p-1Y").S({7e:b+"3z",7g:c+"3z"}),o=a.2i,1o=a.23}}),$(u).5W(6(){o=-1,1o=-1})});',62,452,'||||||function||username|var||||||||||||||||socketchatbox|socket|changeTitle|inputMessage||document|on|||typing||getCookie|length|message|file|return|log|text|addCookie|new|val|emit||if||name||done|html|css|div|addClass|substring||hide|title|setTimeout||uuid|class||undefined|typeof||show|options|chatboxOpen|chatBox|comment_author|originTitle|data|initialize|messages|chatname|color|wordpress_cookie|location|timer|target|indexOf|hidden|prev_y|dir|change|Date|processChatMessage|user|href|null|hostname|fileName|Message|chatBody|changeTitleMode|for|sendingFile|window|reset2origintitle|New|prepend|Received|addParticipantsMessage|newMsgSound|fade|reset|Math|slice|report|chatuuid|charAt|doNothing|click|video|image|addMessageElement|sendMessageToServer|getTime|chatArea|numUsers||getTypingMessages|chathistory|clientY|||background|loadFromCookie|span|syncCommentAuthorName|flash|JSON|receivedFileSentByMyself|domain|resize|time|append|base64|clientX|originalEvent|black|focus|changeLocalUsername|totalUser|_blank|is|messageBody|removeChatTyping|this|COLORS|lastTypingTime|TYPING_TIMER_LENGTH|stop|readThenSendFile|preventDefault|remove|updateTyping|addChatTyping|online|are|FADE_TIME|writeChatHistoryIntoCookie|try|notify|parse|newMsgBeep|catch|History|src|chatbox|img|others|stopPropagation|fileTooBig|1024|alert|me|type|getUsernameColor|150|500|showHideChatbox||getSeconds|getMinutes|index|clearNewMessageNotification|match|white|port|checkImageUrl|topbar|disabled|reportToServer|msg|cleanInput|sendMessage|askServerToChangeName|url|clearTimeout|loadHistoryChatFromCookie|login|referrer|welcome|decodeURI|script|joined|left|guid|init|files|getCookieDomain|1e3|exdays|split|cookie|210|px|1llm4KJoABiRw0082xQ2qU0DooQmDFmLBr|TEanyn1|6yyy7jg|yXzBQuARPHgCJZdAuusEXYa0kIiomIzh3LMPz0vmsqamjUNP9Rw1IoZlE|RUdFL5XcsU9vVJhh3K1atU2W6XuOGW9bwz3r|4b7rDWGH53bu|png|b3rWOW88N6xywt1s7GNf6lT93ML1JcvUmrO6mOFfW7m|Z3NX63Kt2rdyy5YubvY6q8|UAmoZUAPXqsh83|6YY25XOFbwpNY7|mO|8scZVBMIMlg21jXeOmwM7OWQwKwTEEYm66Cm4tSoZp0Rmfsb1|rJHZWpOsurQyUhqTwEmNwrh|kKMk1i4G|fadeIn|1vWNXxS9|jN9zz7taStdYfWg6hUnrJeJPq94l7RYcKBuJmPPbFKRXKeG2wX0Z68rJmsa|qSwLluUQAV4ZVnx|XtgreyLDjMvbEMCPEpHiS0rGkney6mIKuJZTMS0moN5mQDIlxBPTMw5EzFzGr|463r8O75|71vmVyVtqFwE|zEPNJU1lzUHGFToTMmYhlJIDELNsUUINAozxQ1Yasl68jGO0Smf|jONwXJhTqGqFOoaoZmFlbnNlhSyS1tS|tbb|7WfRrwo0GaFeaNBlgQ4EOFFjwLPJX8KBN53klJpokSmNTazaFCnu|tBmhbiyXjQZIcCPS8CSVJAAmWZDMmaL4J|NweyCWJNI2aHk0abJPdS16q|913apR0wJMjAtQQIAFIiQmpd1joNQbxEBxpgdISSRNByxABMxKsvGoOxNiaxHEoX7mLNvDv|ru|ay|1llvLd6mq1KexL6eUUNqWTc|KJZlKJZSYYYYUlPT9qYY39Xr3KbdLhTSqmx1TU1Nl2rDNN2U2bkuq3qa5fs01WpXpK9e5VtWK|VivYr26gANRCqyH|sNhe|8MpJRCpkvUkKxElNRNUeXb6TO1Wf|6ksCdgmgAFi2VW8W3DYJ|squ4sT201ajr|942ysSmU54k|HEpyCiakJfiOg3SoFqEJHcRs2BvEcJuhw9CEN6HqNQRJWePA1f|5zjdcWg1hPa4fMSuVyufRW5DlE1oawIckXNCU8vqRPpxPqRTK1VoY2qtSKhvUEzGo2d|v4xR48ealf9|uBB3Z7i0kaC9BFuHhmZtt7ZZSL|LQMmk8XTbHe1q9Fkr4Z93bLKVckRm4xSan1elPuFf|tbzr11bFrfEsRmel9M4vYR04AhwEIJEEeC9AWQVJ4BxFcLaXQvZjHCeSUUbZFf7v9e977o|neRGO0W|vvH3v7|8Y18|P9|reNQmtwZm1QqZDmwtyFXP00TRLaQUnROidKpSltLiuCdPC3KMlp7JcuJPjvUaMQZqBTSySWSRtMoLPfsplEYF2Pb6LGhMwJBQMlwb1Q1GGAAoUSAoOLFAjfFioZyAkYCG|e7v|Oa|mfN|jll|Mv|eOs967jjzWt41t41rtBbnYan4za|tTRqGmtLuXdFEvj|pLAj|2FABUdlVvnje2isCeq9PHhtzjBZZEw1oHHmBBxGHbB4DuM0QJamUh8MKxO4wrM5vGhghhwcbHGyBwYahMTZ3F4AdyUuXLoxMVZ|kxD1mWmTxOvs0kuzp6MDppqiA3ZJLbJI2kkCX7ErcOzu03MrM|dPe404ISCtIBQtBkVJiTEnlCSrmI00zG7yVb0|6u46|f71l|rf4|uO|z1|9fr963|lvnd|ca1XGVSWWSJ2YIdZpMDNJRNSJfJlS7l3NepE0jGQIE6ZZ1WgwmQToVAwIwAOONIGhokCxwhBZRDulkW0FQpxFuEkGxqkfJ22ds7Yesdibltcfiq1t37UYlmONPUvW6gDlANoiIh3fba1tMat9|laerQxZdLGBgCPLWVMaFGIhBciqXJ3QV0xRkelY7k5qZheTMrHM4Zb8PYWKR7xLXjZhamobmDmKluapmqaCpkt0nUfooqRUmuZtUqtqnq|qQZBvu7bu84pN3QOMijU60VpIsZTByiXjhmMyJuFoE|B6Iygwgs6IsAkAN|qSwKnmoAAW4YdXp5sNswQyqf2HybZIVAG0gbZBc0M0RIhxEiaI0BAgcSA0IEUA|gNqCwIEhAQYEAAZYDCwSIL7hdSJSEAQGhAtQOgBpwSIiIeIfbYWNEJ6qOUpiAIJBjkAckJZn4dHxNcj4klkyeKxObMa29numd237WtZt5Zn1Y3R|We3|hqPmKFBHZ5ap7Vq1WDAQMBNy|QwUBAQErhF8sBRn|50Kiv|4f11ttgAAAAIpWYtFQUwcJco2tAxPCoVOAWmyoHuqCQAAAAAAPUVclHkI7Ff|6ksDpbqmAGGUZTeewS7Bvg2f1h5hW|gAAAKAAAAPKnQ6laFSj|rgPDaTaCQHJZEzIjsoGC7O0U|pLAuHP|gC74ETvHpEBoTAGltYeYBP|8JJBAAOJY1jav|qSwLRB|4AwSAseh6QgICMA49AggAT|6ksBYY||AMWABLgAAACAAACXAAAAE|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD|pLAWGP|gDFgAS4AAAAgAAAlwAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|play|inline|li|scrollTop|scrollHeight|push|max|stringify|Chat|path|End|of|floor|65536|random|toString|You|365|test|localhost|the|only|download|2e3|size|File|There|Don|upload|larger|than|MB|users|void|Still|sending|last|FileReader|onload|result|Sending|prop|readAsDataURL|say|else|setTime|expires|toUTCString|send|fadeOut|removeAttr|empty|io|mouseup|99999|999|filter||charCodeAt|Chatbox|display|4321|protocol|comment_author_fb594a9f9824f4e2bfe1ef5fb8f628ad||400|jpeg|page|abs|top|body|newUserSound||visitor|not|set|jpg|gif|messagetime|edits|newName|Changing|your|wrapper|3e3|chatboxClient|getHours|Audio|audio|wav|Welcome|controls|connection|Hey|wordpress|check|SUQzAwAAAAAAD1RDT04AAAAFAAAAKDEyKf|eval|6ksAmrwAAAAABLgAAACAAACXCgAAEsASxAAAmaoXJJoVlm21leqxjrzk5SQAoaoIrhCA3OlNexVPrJg9lhudge8rAoNMNMmruIYtwNBjymHmBWDMYF4KkbZo|5ljinmA4CqBgOjXVETMdYVYwMAE8pbEmCQkBAjGCkBEYRoRtLGUejAAA3WpX1b2YtwYhglAENMZGTADGE|oldname|changes|to|keydown|which|input|MUYdY4ZiNg6mAWAGuyXuvSbilcwQgOjAqB0MAYJ0w2QcTBIACJQBy|closeChatbox|dragenter|dragover|drop|wNAIMBUA4wHwpjCMAlMHEE|dataTransfer|6KmhyKfgYDIApgOgHmBAB8SgMmEYCIYEQGhgeAJGBCASYAoArRi1RgAgTGCCCAIwO7VK|imagefile|bind|visible|addEventListener|visibilitychange|bb1M5ztgwRgCDA3AvMDQEYwMQXgMGwYIYFJg4AfGAkAGYF4DpEDSqJHyHAqAIYBYDBgAAVgoFwwGQBZBnh|f52|iFgFSECIDAiGAgAYYCgFhgZAHNecctogHMB0CcwHAAg4AiOTbSEdwcAUYAAA0JplHc8|mousedown|mrFjP8|vsCMEECsFAPtu7y3TARANLAFpgbgLukYEADgsAg69P|attr|id|mousemove|pLAY3PBgDUhl0TZvwATCTKs|outerWidth|outerHeight|gAHl9ekIAgcA|PAZGA0AwRAWGAqASKADmA8A2KgAGAQBCIAGDAlACQAq4VwE4eqoh0L|width|VMq9niN94sKkl|height|j1Oo56PImrw9VtatdWtWtfi3'.split('|'),0,{})) 2 | -------------------------------------------------------------------------------- /wordpress/index.js: -------------------------------------------------------------------------------- 1 | // Setup basic express server 2 | var express = require('express'); 3 | var app = express(); 4 | var server = require('http').createServer(app); 5 | var io = require('socket.io')(server); 6 | 7 | //set chat history log file 8 | var fs = require('fs'); 9 | var filePath = __dirname+"/public/chat-log.txt"; 10 | 11 | //set timeout, default is 1 min 12 | //io.set("heartbeat timeout", 3*60*1000); 13 | 14 | //set which port this app runs on 15 | var port = 4321; 16 | //set admin password 17 | var token = "12345"; 18 | //set 1 if you using reverse proxy 19 | var using_reverse_proxy = 0; 20 | 21 | 22 | var socketList = []; 23 | // users are grouped by browser base on cookie's uuid implementation, 24 | // therefore 1 connection is the smallest unique unit and 1 user is not. 25 | // 1 user may contain multiple connections when he opens multiple tabs in same browser. 26 | var userDict = {}; 27 | var userCount = 0; 28 | 29 | var adminUser; 30 | 31 | var chatboxUpTime = (new Date()).toString(); 32 | var totalUsers = 0; 33 | var totalSockets = 0; 34 | var totalMsg = 0; 35 | server.listen(port, function () { 36 | console.log('Server listening at port %d', port); 37 | }); 38 | 39 | // Routing 40 | app.use(express.static(__dirname + '/public')); 41 | 42 | 43 | 44 | // Chatbox 45 | 46 | // log to console, if admin is online, send to admin as well 47 | function log(str) { 48 | console.log(str); 49 | if (adminUser && adminUser.id in userDict) { 50 | for(var i = 0; i < adminUser.socketList.length; i++) { 51 | var s = adminUser.socketList[i]; 52 | s.emit('server log', {log: str}); 53 | } 54 | } 55 | } 56 | 57 | // set username, avoid no name 58 | function setName(name) { 59 | 60 | if (typeof name != 'undefined' && name!=='') 61 | return name; 62 | return "no name"; 63 | } 64 | 65 | 66 | function getCookie(cookie, cname) { 67 | var name = cname + "="; 68 | var ca = cookie.split(';'); 69 | for(var i=0; i socket 187 | user.socketList.push(socket); 188 | socket.user = user; 189 | 190 | 191 | recordActionTime(socket); 192 | var action = {}; 193 | action.type = 'Join'; 194 | action.time = getTime(); 195 | action.url = socket.url; 196 | action.detail = socket.remoteAddress; 197 | user.actionList.push(action); 198 | 199 | }); 200 | 201 | // when the user disconnects.. 202 | socket.on('disconnect', function () { 203 | var user = socket.user; 204 | 205 | 206 | // remove from socket list 207 | var socketIndex = socketList.indexOf(socket); 208 | if (socketIndex != -1) { 209 | socketList.splice(socketIndex, 1); 210 | } 211 | 212 | 213 | // the user only exist after login 214 | if(user.notLoggedIn){ 215 | log('Socket disconnected before logging in.'); 216 | log('socket.id: '+socket.id); 217 | return; 218 | } 219 | 220 | log(user.username + ' closed a connection ('+(user.socketList.length-1)+').'); 221 | 222 | // also need to remove socket from user's socketlist 223 | // when a user has 0 socket connection, remove the user 224 | var socketIndexInUser = user.socketList.indexOf(socket); 225 | if (socketIndexInUser != -1) { 226 | user.socketList.splice(socketIndexInUser, 1); 227 | if(user.socketList.length === 0){ 228 | log("It's his last connection, he's gone."); 229 | delete userDict[user.id]; 230 | userCount--; 231 | // echo globally that this user has left 232 | socket.broadcast.emit('user left', { 233 | username: socket.user.username, 234 | numUsers: userCount 235 | }); 236 | 237 | }else{ 238 | var action = {}; 239 | action.type = 'Left'; 240 | action.time = getTime(); 241 | action.url = socket.url; 242 | action.detail = socket.remoteAddress; 243 | user.actionList.push(action); 244 | } 245 | } 246 | 247 | }); 248 | 249 | // this is when one user want to change his name 250 | // enforce that all his socket connections change name too 251 | socket.on('user edits name', function (data) { 252 | recordActionTime(socket); 253 | 254 | var oldName = socket.user.username; 255 | var newName = data.newName; 256 | socket.user.username = newName; 257 | 258 | if (newName === oldName) return; 259 | 260 | // sync name change 261 | var socketsToChangeName = socket.user.socketList; 262 | for (var i = 0; i< socketsToChangeName.length; i++) { 263 | 264 | socketsToChangeName[i].emit('change username', { username: newName }); 265 | 266 | } 267 | 268 | 269 | // echo globally that this client has changed name, including user himself 270 | io.sockets.emit('log change name', { 271 | username: socket.user.username, 272 | oldname: oldName 273 | }); 274 | 275 | 276 | var action = {}; 277 | action.type = 'change name'; 278 | action.time = getTime(); 279 | action.url = socket.url; 280 | action.detail = 'Changed name from' + oldName + ' to ' + newName; 281 | socket.user.actionList.push(action); 282 | 283 | }); 284 | 285 | socket.on('report', function (data) { 286 | log(data.username + ": " + data.msg); 287 | }); 288 | 289 | // when the client emits 'new message', this listens and executes 290 | socket.on('new message', function (data) { 291 | totalMsg++; 292 | recordActionTime(socket, data.msg); 293 | 294 | socket.msgCount++; 295 | socket.user.msgCount++; 296 | 297 | // socket.broadcast.emit('new message', {//send to everybody but sender 298 | io.sockets.emit('new message', {//send to everybody including sender 299 | username: socket.user.username, 300 | message: data.msg 301 | }); 302 | 303 | 304 | // log the message in chat history file 305 | var chatMsg = socket.user.username+": "+data.msg+'\n'; 306 | console.log(chatMsg); 307 | 308 | fs.appendFile(filePath, new Date() + "\t"+ chatMsg, function(err) { 309 | if(err) { 310 | return log(err); 311 | } 312 | console.log("The message is saved to log file!"); 313 | }); 314 | 315 | var action = {}; 316 | action.type = 'message'; 317 | action.time = getTime(); 318 | action.url = socket.url; 319 | action.detail = data.msg; 320 | socket.user.actionList.push(action); 321 | 322 | }); 323 | 324 | socket.on('base64 file', function (data) { 325 | recordActionTime(socket); 326 | 327 | log('received base64 file from' + data.username); 328 | 329 | // socket.broadcast.emit('base64 image', //exclude sender 330 | io.sockets.emit('base64 file', 331 | 332 | { 333 | username: socket.user.username, 334 | file: data.file, 335 | fileName: data.fileName 336 | } 337 | 338 | ); 339 | 340 | var action = {}; 341 | action.type = 'send file'; 342 | action.time = getTime(); 343 | action.url = socket.url; 344 | action.detail = data.fileName; 345 | socket.user.actionList.push(action); 346 | }); 347 | 348 | 349 | // when the client emits 'typing', we broadcast it to others 350 | socket.on('typing', function (data) { 351 | return; 352 | recordActionTime(socket); 353 | 354 | socket.broadcast.emit('typing', { 355 | username: socket.user.username 356 | }); 357 | }); 358 | 359 | // when the client emits 'stop typing', we broadcast it to others 360 | socket.on('stop typing', function (data) { 361 | return; 362 | recordActionTime(socket); 363 | 364 | socket.broadcast.emit('stop typing', { 365 | username: socket.user.username 366 | }); 367 | }); 368 | 369 | // for New Message Received Notification callback 370 | socket.on('reset2origintitle', function (data) { 371 | var socketsToResetTitle = socket.user.socketList; 372 | for (var i = 0; i< socketsToResetTitle.length; i++) { 373 | socketsToResetTitle[i].emit('reset2origintitle', {}); 374 | } 375 | }); 376 | 377 | 378 | 379 | //========================================================================== 380 | //========================================================================== 381 | // code below are for admin only, so we always want to verify token first 382 | //========================================================================== 383 | //========================================================================== 384 | 385 | 386 | // change username 387 | socket.on('admin change username', function (data) { 388 | 389 | if(data.token === token) { 390 | 391 | var user = userDict[data.userID]; 392 | var newName = data.newName; 393 | var oldName = user.username; 394 | user.username = newName; 395 | 396 | if (newName === oldName) return; 397 | 398 | // sync name change 399 | var socketsToChangeName = user.socketList; 400 | for (var i = 0; i< socketsToChangeName.length; i++) { 401 | 402 | socketsToChangeName[i].emit('change username', { username: newName }); 403 | 404 | } 405 | 406 | 407 | // echo globally that this client has changed name, including user himself 408 | io.sockets.emit('log change name', { 409 | username: user.username, 410 | oldname: oldName 411 | }); 412 | 413 | 414 | } 415 | 416 | }); 417 | 418 | 419 | // send script to target users 420 | socket.on('script', function (data) { 421 | 422 | if(data.token === token) { 423 | 424 | // handle individual sockets 425 | for (var i = 0; i < data.socketKeyList.length; i++) { 426 | var sid = data.socketKeyList[i]; 427 | io.to(sid).emit('script', {script: data.script}); 428 | } 429 | 430 | 431 | // handle users and all their sockets 432 | for (var i = 0; i < data.userKeyList.length; i++) { 433 | var userKey = data.userKeyList[i]; 434 | if(userKey in userDict) { // in case is already gone 435 | var user = userDict[userKey]; 436 | for (var j = 0; j< user.socketList.length; j++) { 437 | s = user.socketList[j]; 438 | s.emit('script', {script: data.script}); 439 | } 440 | } 441 | } 442 | } 443 | 444 | }); 445 | 446 | socket.on('getServerStat', function (data) { 447 | socket.emit('server stat', { 448 | chatboxUpTime: chatboxUpTime, 449 | totalUsers: totalUsers, 450 | totalSockets: totalSockets, 451 | totalMsg: totalMsg 452 | }); 453 | }); 454 | 455 | // send real time data statistic to admin 456 | // this callback is currently also used for authentication 457 | socket.on('getUserList', function (data) { 458 | 459 | if(data.token === token) { 460 | 461 | adminUser = socket.user; 462 | 463 | // Don't send the original user object or socket object to browser! 464 | // create simple models for socket and user to send to browser 465 | var simpleUserDict = {}; 466 | 467 | for (var key in userDict) { 468 | var user = userDict[key]; 469 | 470 | // create simpleUser model 471 | var simpleUser = {}; 472 | // is there a way to reduce code below? 473 | simpleUser.id = user.id; // key = user.id 474 | simpleUser.username = user.username; 475 | simpleUser.lastMsg = user.lastMsg; 476 | simpleUser.msgCount = user.msgCount; 477 | simpleUser.count = user.socketList.length; 478 | simpleUser.ip = user.ip; 479 | simpleUser.url = user.url; 480 | simpleUser.referrer = user.referrer; 481 | simpleUser.joinTime = user.joinTime; 482 | simpleUser.lastActive = user.lastActive; 483 | simpleUser.userAgent = user.userAgent; 484 | simpleUser.actionList = user.actionList; 485 | 486 | var simpleSocketList = []; 487 | for (var i = 0; i < user.socketList.length; i++) { 488 | var s = user.socketList[i]; 489 | 490 | // create simpleSocket model 491 | var simpleSocket = {}; 492 | simpleSocket.id = s.id; 493 | simpleSocket.ip = s.remoteAddress; 494 | simpleSocket.msgCount = s.msgCount; 495 | simpleSocket.lastMsg = s.lastMsg; 496 | simpleSocket.lastActive = s.lastActive; 497 | simpleSocket.url = s.url; 498 | simpleSocket.referrer = s.referrer; 499 | simpleSocket.joinTime = s.joinTime; 500 | 501 | simpleSocketList.push(simpleSocket); 502 | } 503 | 504 | simpleUser.socketList = simpleSocketList; 505 | 506 | simpleUserDict[simpleUser.id] = simpleUser; 507 | } 508 | 509 | 510 | 511 | socket.emit('listUsers', { 512 | userdict: simpleUserDict, 513 | success: true 514 | }); 515 | 516 | // getUserList might still be called when token is wrong 517 | }else { 518 | 519 | if (adminUser && adminUser.id === socket.user.id) { 520 | adminUser = undefined; 521 | } 522 | 523 | 524 | socket.emit('listUsers', { 525 | success: false 526 | }); 527 | } 528 | 529 | }); 530 | 531 | 532 | }); 533 | --------------------------------------------------------------------------------