diff --git a/app/main/chat.php b/app/main/chat.php new file mode 100644 index 0000000..ba07d4d --- /dev/null +++ b/app/main/chat.php @@ -0,0 +1,45 @@ +clients = new \SplObjectStorage; + echo "__construct() \n"; + } + + public function onOpen(ConnectionInterface $conn) { + //store the new connection + $this->clients->attach($conn); + echo "NEW connection! ({$conn->resourceId})\n"; + } + + public function onMessage(ConnectionInterface $from, $msg) { + $numRecv = count($this->clients) - 1; + echo sprintf('Connection %d sending to %d other connection%s' . "\n" + , $from->resourceId, $numRecv, $numRecv == 1 ? '' : 's'); + + foreach ($this->clients as $client) { + // if ($from !== $client) { + $client->send($msg); + // } + } + } + + public function onClose(ConnectionInterface $conn) { + $this->clients->detach($conn); + + echo "Connection {$conn->resourceId} has disconnected\n"; + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + echo "An error has occurred: {$e->getMessage()}\n"; + + $conn->close(); + } +} \ No newline at end of file diff --git a/app/websockets.php b/app/websockets.php new file mode 100644 index 0000000..9c27f55 --- /dev/null +++ b/app/websockets.php @@ -0,0 +1,34 @@ +startChat(); + } + + private function startChat(){ + $server = IoServer::factory( + new HttpServer( + new WsServer( + new Main\Chat() + ) + ), + 8020 + ); + + $server->run(); + } + +} \ No newline at end of file diff --git a/cmd.php b/cmd.php new file mode 100644 index 0000000..f01c96a --- /dev/null +++ b/cmd.php @@ -0,0 +1,6 @@ + + + + + Chat + + + + + + + +
+ Status: + Notification: + +
+ + + +
+ + + +
+ + + + + + + + \ No newline at end of file diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..ab9d269 --- /dev/null +++ b/js/app.js @@ -0,0 +1,155 @@ +window.onload = function (){ + + var msgWorker = this.msgWorker; + + var worker = new SharedWorker('js/worker/chat_new.js', 'worker_name'); + + worker.port.addEventListener('message', function(e){ + let load = e.data; + load.__proto__ = msgWorker.prototype; + + switch(load.command){ + case 'ws:open': + // WebSocket in SharedWorker is open + setSocketStatus(true); + initChat(); + break; + case 'ws:send': + updateMessages(load.data()); + break; + case 'ws:closed': + setSocketStatus(false); + break; + } + }, false); + + worker.onerror = function(e){ + console.error('SharedWorker onerror:'); + }; + + worker.port.start(); + + var msgWorkerInit = new msgWorker('ws:init'); + msgWorkerInit.data({ + uri: 'ws://wstest.local:8080' + }); + worker.port.postMessage(msgWorkerInit); + + + // Chat init ================================================================================== + var user; + var messages = []; + + var messages_template = Handlebars.compile($('#messages-template').html()); + + var initChat = function(){ + + $('#join-chat').click(function(){ + user = $('#user').val(); + $('#user-container').addClass('hidden'); + $('#main-container').removeClass('hidden'); + + var msgWorkerSend = new msgWorker('ws:send'); + msgWorkerSend.data({ + 'user': user, + 'text': user + ' entered the room', + 'time': moment().format('hh:mm a') + }); + + worker.port.postMessage(msgWorkerSend); + + $('#user').val(''); + }); + + $('#send-msg').click(function(){ + var text = $('#msg').val(); + + var msgWorkerSend = new msgWorker('ws:send'); + msgWorkerSend.data({ + 'user': user, + 'text': text, + 'time': moment().format('hh:mm a') + }); + + worker.port.postMessage(msgWorkerSend); + + $('#msg').val(''); + }); + + $('#leave-room').click(function(){ + var msgWorkerSend = new msgWorker('ws:send'); + msgWorkerSend.data({ + 'user': user, + 'text': user + ' has left the room', + 'time': moment().format('hh:mm a') + }); + + worker.port.postMessage(msgWorkerSend); + + $('#messages').html(''); + messages = []; + + $('#main-container').addClass('hidden'); + $('#user-container').removeClass('hidden'); + + + }); + }; + + var setSocketStatus = function(status){ + $('#socket-status').toggleClass('red', !status).toggleClass('green', status); + }; + + var updateMessages = function(msg){ + messages.push(msg); + var messages_html = messages_template({'messages': messages}); + $('#messages').html(messages_html); + $("#messages").animate({ scrollTop: $('#messages')[0].scrollHeight}, 1000); + }; + + // Notification init ========================================================================== + + var updateNotification = function(status){ + $('#notification-status').toggleClass('red', !status).toggleClass('green', status); + }; + + var notifyMe = function(){ + var msgWorkerNotify = new msgWorker('ws:notify'); + + if (Notification.permission === 'granted'){ + msgWorkerNotify.data({ + status: true + }); + worker.port.postMessage(msgWorkerNotify); + + updateNotification(true); + }else{ + Notification.requestPermission(function (permission) { + msgWorkerNotify.data({ + status: permission === 'granted' + }); + worker.port.postMessage(msgWorkerNotify); + + updateNotification(permission === 'granted'); + }); + } + }; + + $('#toggle-notification').on('click', notifyMe); + + // ============================================================================================ + /* + window.onbeforeunload = function() { + var msgWorkerClose = new msgWorker('ws:close'); + worker.port.postMessage(msgWorkerClose); + + //console.log('test close'); + //worker.port.close(); + + return 'sdf'; + }; + */ +}; + + + diff --git a/js/worker/chat.js b/js/worker/chat.js new file mode 100644 index 0000000..767c0db --- /dev/null +++ b/js/worker/chat.js @@ -0,0 +1,87 @@ +'use strict'; +self.importScripts('message.js'); + +var socket = new WebSocket(self.name); +var ports = []; +var notifications = false; +var tmp = self; +console.log(socket._socket); + +self.addEventListener('connect', function (event) { + var port = event.ports[0]; + ports.push(port); +console.log('B: ' + socket.readyState); + port.onmessage = function (event) { + let load = event.data; + load.__proto__ = msgWorker.prototype; + +console.log('C: ' + socket.readyState); + + switch(load.command){ + case 'send': + socket.send(JSON.stringify(load.data())); + break; + case 'WS_close': + closeSocket(socket); + break; + case 'notify': + notifications = load.data().status; + } + }; + + + if(socket.readyState === socket.OPEN){ + var msgWorkerOpen = new msgWorker('ready'); + port.postMessage(msgWorkerOpen); + } + + port.start(); +}, false); + + +socket.onopen = function(e){ + + var msgWorkerOpen = new msgWorker('open'); + for (var i = 0; i < ports.length; i++) { + ports[i].postMessage(msgWorkerOpen); + } + + socket.onmessage = function(e){ + let load = JSON.parse(e.data); + var msgWorkerSend = new msgWorker('send'); + msgWorkerSend.data(load); + + for (var i = 0; i < ports.length; i++) { + ports[i].postMessage(msgWorkerSend); + } + + if(notifications){ + new Notification('Message: ' + load.text); + } + }; + + socket.onclose = function(){ + + console.log(this.remoteAddress); + console.info('ws: onclose()'); + }; + + socket.onerror = function(){ + console.error('ws: onerror()'); + }; + +}; + +// Util ================================================================ +var closeSocket = function(socket){ + // only close if active + if(socket.readyState === socket.OPEN){ + // send "close" event before close call + var msgWorkerWsClosed = new msgWorker('WS_closed'); + for (var i = 0; i < ports.length; i++) { + ports[i].postMessage(msgWorkerWsClosed); + } + + socket.close(); + } +}; diff --git a/js/worker/chat_new.js b/js/worker/chat_new.js new file mode 100644 index 0000000..d0e167b --- /dev/null +++ b/js/worker/chat_new.js @@ -0,0 +1,90 @@ +'use strict'; +self.importScripts('message.js'); + +var socket = null; +var ports = []; +var notifications = false; + +var initSocket = function(uri){ + var msgWorkerOpen = new msgWorker('ws:open'); + + if(socket === null){ + socket = new WebSocket(uri); + + socket.onopen = function(e){ + + // + ports[ports.length - 1].postMessage(msgWorkerOpen); + + socket.onmessage = function(e){ + + let load = JSON.parse(e.data); + var msgWorkerSend = new msgWorker('ws:send'); + msgWorkerSend.data(load); + + for (var i = 0; i < ports.length; i++) { + ports[i].postMessage(msgWorkerSend); + } + + if(notifications){ + new Notification('Message: ' + load.text); + } + }; + + socket.onclose = function(){ + console.info('ws: onclose()'); + }; + + socket.onerror = function(){ + console.error('ws: onerror()'); + }; + } + }else{ + // socket still open + ports[ports.length - 1].postMessage(msgWorkerOpen); + } +}; + +self.addEventListener('connect', function (event){ + var port = event.ports[0]; + ports.push(port); + + port.addEventListener('message', function (e){ + let load = e.data; + load.__proto__ = msgWorker.prototype; + + switch(load.command){ + case 'ws:init': + initSocket(load.data().uri); + break; + case 'ws:send': + socket.send(JSON.stringify(load.data())); + break; + case 'ws:close': + closeSocket(socket); + break; + case 'ws:notify': + notifications = load.data().status; + break; + } + }, false); + + port.start(); +}, false); + + + +// Util ================================================================ +var closeSocket = function(socket){ + // only close if active + console.log(socket.readyState + ' - ' + socket.OPEN); + if(socket.readyState === socket.OPEN){ + // send "close" event before close call + var msgWorkerWsClosed = new msgWorker('ws:closed'); + for (var i = 0; i < ports.length; i++) { + ports[i].postMessage(msgWorkerWsClosed); + } + + socket.close(); + } +}; diff --git a/js/worker/message.js b/js/worker/message.js new file mode 100644 index 0000000..5527c44 --- /dev/null +++ b/js/worker/message.js @@ -0,0 +1,18 @@ +var msgWorker = class MsgWorkerTest { + constructor(cmd){ + this.cmd = cmd; + this.msgBody = null; + } + + get command(){ + return this.cmd; + } + + + data(data) { + if(data){ + this.msgBody = data; + } + return this.msgBody; + } +};