WIP WebSocket Server for Pathfinder.

- Enables Map data push to clients (Browser)
This commit is contained in:
Exodus4D
2016-12-28 16:08:16 +01:00
parent 31b7631450
commit c292087d9a
11 changed files with 600 additions and 475 deletions

View File

@@ -1,9 +1,27 @@
### Prototype ChatApp
### [WIP] WebSocket server for [Pathfinder](https://github.com/exodus4d/pathfinder)
**Demo:**
- https://www.test.pathfinder-w.space/
####Install:
1. Install [Composer](https://getcomposer.org/download/)
2. Install Composer dependencies from `composer.json` file:
- `php composer.phar install` (edit composer.phar path to your Composer installation)
3. Start WebSocket server `php cmd.php`
**Info:**
####Default Configuration
**Clients (WebBrowser) listen for connections**
- Host:`0.0.0.0.` (=> any client can connect)
- Port:`8020`
- URI:`127.0.0.1:8020` (Your WebServer (e.g. Nginx) should pass all WebSocket connections to this source)
**TCP Socket connection (Internal use fore WebServer <=> WebSocket communication)**
- Host:`127.0.0.1` (=> Assumed WebServer and WebSocket Server running on the same machine)
- Port:`5555`
- URI: `tcp://127.0.0.1:5555`
**[Optional]**
The default configuration should be fine for most installations.
You can change/overwrite the default **Host** and **Port** configuration by adding additional CLI parameters when starting the WebSocket server:
`php cmd.php --pf_listen_host [CLIENTS_HOST] --pf_listen_port [CLIENTS_PORT] --pf_host [TCP_HOST] --pf_port [TCP_PORT]`
####Info:
- [*Ratchet*](http://socketo.me/) - "WebSockets for PHP"
- [*Web Sockets API*](https://www.w3.org/TR/2009/WD-websockets-20091222/)
- [*Web Workers*](https://www.w3.org/TR/workers/) - (SharedWorker)

View File

@@ -1,45 +0,0 @@
<?php
namespace Exodus4D\Socket\Main;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->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();
}
}

494
app/Main/mapupdate.php Normal file
View File

@@ -0,0 +1,494 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 02.12.2016
* Time: 22:29
*/
namespace Exodus4D\Socket\Main;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class MapUpdate implements MessageComponentInterface {
/**
* expire time for map access tokens (seconds)
* @var int
*/
protected $mapAccessExpireSeconds = 30;
/**
* character access tokens for clients
* -> tokens are unique and expire onSubscribe!
* @var
*/
protected $characterAccessData;
/**
* access tokens for clients grouped by mapId
* -> tokens are unique and expire onSubscribe!
* @var
*/
protected $mapAccessData;
/**
* connected characters
* @var
*/
protected $characters;
/**
* valid client connections subscribed to maps
* @var array
*/
protected $subscriptions;
/**
* internal socket for response calls
* @var null | \React\ZMQ\SocketWrapper
*/
protected $internalSocket = null;
public function __construct($socket) {
$this->internalSocket = $socket;
$this->characterAccessData = [];
$this->mapAccessData = [];
$this->characters = [];
$this->subscriptions = [];
echo "__construct() \n";
}
public function onOpen(ConnectionInterface $conn) {
echo "NEW connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $conn, $msg) {
$msg = json_decode($msg, true);
if(
isset($msg['task']) &&
isset($msg['load'])
){
$task = $msg['task'];
$load = $msg['load'];
switch($task){
case 'subscribe':
$this->subscribe($conn, $load);
break;
default:
break;
}
}
}
public function onClose(ConnectionInterface $conn) {
$this->unSubscribeConnection($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
$this->unSubscribeConnection($conn);
$conn->close();
echo "An error has occurred: {$e->getMessage()}\n";
}
/**
* subscribes a connection to valid accessible maps
* @param ConnectionInterface $conn
* @param $subscribeData
*/
private function subscribe(ConnectionInterface $conn, $subscribeData){
$characterId = (int)$subscribeData['id'];
$characterToken = $subscribeData['token'];
if($characterId && $characterToken){
// check if character access token is valid (exists and not expired in $this->characterAccessData
if( $this->checkCharacterAccess($characterId, $characterToken) ){
$this->characters[$characterId][$conn->resourceId] = $conn;
// valid character -> check map access
foreach((array)$subscribeData['mapData'] as $data){
$mapId = (int)$data['id'];
$mapToken = $data['token'];
if($mapId && $mapToken){
// check if token is valid (exists and not expired) in $this->mapAccessData
if( $this->checkMapAccess($characterId, $mapId, $mapToken) ){
// valid map subscribe request
$this->subscriptions[$mapId][$characterId] = $characterId;
}
}
}
}
}
}
/**
* subscribes an active connection from maps
* @param ConnectionInterface $conn
*/
private function unSubscribeConnection(ConnectionInterface $conn){
$characterIds = $this->getCharacterIdsByConnection($conn);
foreach($characterIds as $characterId){
$this->unSubscribeCharacterId($characterId, $conn);
}
}
/**
* unSubscribe a $characterId from ALL maps
* -> if $conn is set -> just unSub the $characterId from this $conn
* @param int $characterId
* @param null $conn
* @return bool
*/
private function unSubscribeCharacterId($characterId, $conn = null){
if($characterId){
// unSub from $this->characters -------------------------------------------------------
if($conn){
// just unSub a specific connection (e.g. single browser window)
$resourceId = $conn->resourceId;
if( isset($this->characters[$characterId][$resourceId]) ){
unset($this->characters[$characterId][$resourceId]);
if( !count($this->characters[$characterId]) ){
// no connection left for this character
unset($this->characters[$characterId]);
}
}
}else{
// unSub ALL connections from a character (e.g. multiple browsers)
if( isset($this->characters[$characterId]) ){
unset($this->characters[$characterId]);
}
}
// unSub from $this->subscriptions ----------------------------------------------------
foreach($this->subscriptions as $mapId => $characterIds){
if(array_key_exists($characterId, $characterIds)){
unset($this->subscriptions[$mapId][$characterId]);
if( !count($this->subscriptions[$mapId]) ){
// no characters left on this map
unset($this->subscriptions[$mapId]);
}
}
}
}
return true;
}
/**
* delete mapId from subscriptions and broadcast "delete msg" to clients
* @param string $task
* @param int $mapId
* @return int
*/
private function deleteMapId($task, $mapId){
$connectionCount = $this->broadcastMapData($task, $mapId, $mapId);
// remove map from subscriptions
if( isset($this->subscriptions[$mapId]) ){
unset($this->subscriptions[$mapId]);
}
return $connectionCount;
}
/**
* @param ConnectionInterface $conn
* @return int[]
*/
private function getCharacterIdsByConnection(ConnectionInterface $conn){
$characterIds = [];
$resourceId = $conn->resourceId;
foreach($this->characters as $characterId => $resourceIDs){
if(
array_key_exists($resourceId, $resourceIDs) &&
!in_array($characterId, $characterIds)
){
$characterIds[] = $characterId;
}
}
return $characterIds;
}
/**
* @param $mapId
* @return array
*/
private function getCharacterIdsByMapId($mapId){
$characterIds = [];
if( !empty($this->subscriptions[$mapId]) ){
$characterIds = array_values( (array)$this->subscriptions[$mapId]);
}
return $characterIds;
}
/**
* get connection objects by characterIds
* @param int[] $characterIds
* @return \SplObjectStorage
*/
private function getConnectionsByCharacterIds($characterIds){
$connections = new \SplObjectStorage;
foreach($characterIds as $characterId){
if($charConnections = (array)$this->characters[$characterId] ){
foreach($charConnections as $conn){
if( !$connections->contains($conn) ){
$connections->attach($conn);
}
}
}
}
return $connections;
}
/**
* check character access against $this->characterAccessData whitelist
* @param $characterId
* @param $characterToken
* @return bool
*/
private function checkCharacterAccess($characterId, $characterToken){
$access = false;
if( !empty($characterAccessData = (array)$this->characterAccessData[$characterId]) ){
foreach($characterAccessData as $i => $data){
$deleteToken = false;
// check expire for $this->characterAccessData -> check ALL characters and remove expired
if( ((int)$data['expire'] - time()) > 0 ){
// still valid -> check token
if($characterToken === $data['token']){
$access = true;
$deleteToken = true;
}
}else{
// token expired
$deleteToken = true;
}
if($deleteToken){
unset($this->characterAccessData[$characterId][$i]);
// -> check if tokens for this charId is empty
if( empty($this->characterAccessData[$characterId]) ){
unset($this->characterAccessData[$characterId]);
}
}
}
}
return $access;
}
/**
* check map access against $this->mapAccessData whitelist
* @param $characterId
* @param $mapId
* @param $mapToken
* @return bool
*/
private function checkMapAccess($characterId, $mapId, $mapToken){
$access = false;
if( !empty($mapAccessData = (array)$this->mapAccessData[$mapId][$characterId]) ){
foreach($mapAccessData as $i => $data){
$deleteToken = false;
// check expire for $this->mapAccessData -> check ALL characters and remove expired
if( ((int)$data['expire'] - time()) > 0 ){
// still valid -> check token
if($mapToken === $data['token']){
$access = true;
$deleteToken = true;
}
}else{
// token expired
$deleteToken = true;
}
if($deleteToken){
unset($this->mapAccessData[$mapId][$characterId][$i]);
// -> check if tokens for this charId is empty
if( empty($this->mapAccessData[$mapId][$characterId]) ){
unset($this->mapAccessData[$mapId][$characterId]);
// -> check if map has no access tokens left for characters
if( empty($this->mapAccessData[$mapId]) ){
unset($this->mapAccessData[$mapId]);
}
}
}
}
}
return $access;
}
/**
* broadcast data ($load) to $connections
* @param ConnectionInterface[] | \SplObjectStorage $connections
* @param $task
* @param $load
* @param int[] $characterIds optional, recipients (e.g if multiple browser tabs are open)
*/
private function broadcastData($connections, $task, $load, $characterIds = []){
$response = [
'task' => $task,
'characterIds' => $characterIds,
'load' => $load
];
foreach($connections as $conn){
$conn->send( json_encode($response) );
}
}
// custom calls ===============================================================================
/**
* receive data from TCP socket (main App)
* -> send response back
* @param $data
*/
public function receiveData($data){
$data = (array)json_decode($data, true);
$load = $data['load'];
$task = $data['task'];
$response = false;
switch($data['task']){
case 'characterLogout':
$response = $this->unSubscribeCharacterId($load);
break;
case 'mapConnectionAccess':
$response = $this->setConnectionAccess($load);
break;
case 'mapAccess':
$response = $this->setAccess($task, $load);
break;
case 'mapUpdate':
$response = $this->broadcastMapUpdate($task, $load);
break;
case 'mapDeleted':
$response = $this->deleteMapId($task, $load);
break;
case 'healthCheck':
$response = 1;
break;
}
$this->internalSocket->send($response);
}
/**
* @param string $task
* @param array $mapData
* @return int
*/
private function broadcastMapUpdate($task, $mapData){
$mapId = (int)$mapData['config']['id'];
return $this->broadcastMapData($task, $mapId, $mapData);
}
/**
* send map data to ALL connected clients
* @param string $task
* @param int $mapId
* @param mixed $load
* @return int
*/
private function broadcastMapData($task, $mapId, $load){
$characterIds = $this->getCharacterIdsByMapId($mapId);
$connections = $this->getConnectionsByCharacterIds($characterIds);
$this->broadcastData($connections, $task, $load, $characterIds);
return count($connections);
}
/**
* set/update map access for allowed characterIds
* @param string $task
* @param array $accessData
* @return int count of connected characters
*/
private function setAccess($task, $accessData){
$NewMapCharacterIds = [];
if($mapId = (int)$accessData['id']){
$characterIds = (array)$accessData['characterIds'];
$currentMapCharacterIds = array_values((array)$this->subscriptions[$mapId]);
// check all charactersIds that have access... ----------------------------------------
foreach($characterIds as $characterId){
// ... for it least ONE active connection ...
if( !empty($this->characters[$characterId]) ){
// ... add characterId to new subscriptions for a map
$NewMapCharacterIds[$characterId] = $characterId;
}
}
// broadcast "map delete" to no longer valid characters -------------------------------
$removedMapCharacterIds = array_diff($currentMapCharacterIds, array_values($NewMapCharacterIds) );
$removedMapCharacterConnections = $this->getConnectionsByCharacterIds($removedMapCharacterIds);
$this->broadcastData($removedMapCharacterConnections, $task, $mapId, $removedMapCharacterIds);
// update map subscriptions -----------------------------------------------------------
if( !empty($NewMapCharacterIds) ){
// set new characters that have map access (overwrites existing subscriptions for that map)
$this->subscriptions[$mapId] = $NewMapCharacterIds;
}else{
// no characters (left) on this map
unset($this->subscriptions[$mapId]);
}
}
return count($NewMapCharacterIds);
}
/**
* set map access data (whitelist) tokens for map access
* @param $connectionAccessData
* @return bool
*/
private function setConnectionAccess($connectionAccessData) {
$response = false;
$characterId = (int)$connectionAccessData['id'];
$characterToken = $connectionAccessData['token'];
if(
$characterId &&
$characterToken
){
// expire time for character and map tokens
$expireTime = time() + $this->mapAccessExpireSeconds;
// tokens for character access
$this->characterAccessData[$characterId][] = [
'token' => $characterToken,
'expire' => $expireTime
];
foreach((array)$connectionAccessData['mapData'] as $mapData){
$mapId = (int)$mapData['id'];
$this->mapAccessData[$mapId][$characterId][] = [
'token' => $mapData['token'],
'expire' => $expireTime
];
}
$response = 'OK';
}
return $response;
}
}

View File

@@ -12,23 +12,51 @@ use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React;
class WebSockets {
function __construct(){
$this->startChat();
protected $dns;
protected $wsListenPort;
protected $wsListenHost;
function __construct($dns, $wsListenPort, $wsListenHost){
$this->dns = $dns;
$this->wsListenPort = (int)$wsListenPort;
$this->wsListenHost = $wsListenHost;
$this->startMapSocket();
}
private function startChat(){
$server = IoServer::factory(
private function startMapSocket(){
$loop = React\EventLoop\Factory::create();
// Listen for the web server to make a ZeroMQ push after an ajax request
$context = new React\ZMQ\Context($loop);
$pull = $context->getSocket(\ZMQ::SOCKET_REP);
// Binding to 127.0.0.1 means, the only client that can connect is itself
$pull->bind( $this->dns );
// main app -> inject socket for response
$mapUpdate = new Main\MapUpdate($pull);
// "onMessage" listener
$pull->on('message', [$mapUpdate, 'receiveData']);
// Set up our WebSocket server for clients wanting real-time updates
$webSock = new React\Socket\Server($loop);
// Binding to 0.0.0.0 means remotes can connect (Web Clients)
$webSock->listen($this->wsListenPort, $this->wsListenHost);
new IoServer(
new HttpServer(
new WsServer(
new Main\Chat()
$mapUpdate
)
),
8020
$webSock
);
$server->run();
$loop->run();
}
}

32
cmd.php
View File

@@ -3,4 +3,34 @@ require 'vendor/autoload.php';
use Exodus4D\Socket;
new Socket\WebSockets();
if(PHP_SAPI === 'cli'){
// optional CLI params
$options = getopt('', [
'pf_listen_host:',
'pf_listen_port:',
'pf_host:',
'pf_port:'
]);
/**
* WebSocket connection (for WebClients => Browser)
* default WebSocket URI: ws://127.0.0.1:8020
*
* pf_client_ip '0.0.0.0' <-- any client can connect
* pf_ws_port 8020 <-- any client can connect
*/
$wsListenHost = (!empty($options['pf_listen_host'])) ? $options['pf_listen_host'] : '0.0.0.0' ;
$wsListenPort = (!empty($options['pf_listen_port'])) ? (int)$options['pf_listen_port'] : 8020 ;
$host = (!empty($options['pf_host'])) ? $options['pf_host'] : '127.0.0.1' ;
$port = (!empty($options['pf_port'])) ? (int)$options['pf_port'] : 5555 ;
$dns = 'tcp://' . $host . ':' . $port;
new Socket\WebSockets($dns, $wsListenPort, $wsListenHost);
}else{
echo "Script need to be called by CLI!";
}

View File

@@ -1,10 +1,23 @@
{
"require": {
"cboden/ratchet": "dev-master"
},
"name": "exodus4d/pathfinder_websocket",
"description": "WebSocket extension for 'Pathfinder'",
"minimum-stability": "stable",
"license": "MIT",
"authors": [
{
"name": "Mark Friedrich",
"email": "pathfinder@exodus4d.de"
}
],
"autoload": {
"psr-4": {
"Exodus4D\\Socket\\": "app/"
}
},
"require": {
"php-64bit": ">=7.0",
"ext-zmq": "1.1.*",
"cboden/ratchet": "0.3.*",
"react/zmq": "0.3.*"
}
}

View File

@@ -1,72 +0,0 @@
.hidden {
display: none;
}
#wrapper {
width: 800px;
margin: 0 auto;
}
#leave-room {
margin-bottom: 10px;
float: right;
}
#user-container {
width: 500px;
margin: 0 auto;
text-align: center;
}
#main-container {
width: 500px;
margin: 0 auto;
}
#messages {
height: 300px;
width: 500px;
border: 1px solid #ccc;
padding: 20px;
text-align: left;
overflow-y: scroll;
}
#msg-container {
padding: 20px;
}
#msg {
width: 400px;
}
.user {
font-weight: bold;
}
.msg {
margin-bottom: 10px;
overflow: hidden;
}
.time {
float: right;
color: #939393;
font-size: 13px;
}
.details {
margin-top: 20px;
}
#socket-status,
#notification-status {
font-size: 40px;
}
.red{
color: red;
}
.green{
color: green;
}

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chat</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.3/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.2/moment.min.js"></script>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="wrapper">
Status: <span id="socket-status" class="red">&#x25cf;</span>
Notification: <span id="notification-status" class="red">&#x25cf;</span>
<div id="user-container">
<label for="user">What's your name?</label>
<input type="text" id="user" name="user">
<button type="button" id="join-chat">Join Chat</button>
</div>
<div id="main-container" class="hidden">
<button type="button" id="leave-room">Leave</button>
<button type="button" id="toggle-notification">Notification</button>
<div id="messages">
</div>
<div id="msg-container">
<input type="text" id="msg" name="msg">
<button type="button" id="send-msg">Send</button>
</div>
</div>
</div>
<script id="messages-template" type="text/x-handlebars-template">
{{#each messages}}
<div class="msg">
<div class="time">{{time}}</div>
<div class="details">
<span class="user">{{user}}</span>: <span class="text">{{text}}</span>
</div>
</div>
{{/each}}
</script>
<script src="js/worker/message.js"></script>
<script src="js/app.js"></script>
</body>
</html>

162
js/app.js
View File

@@ -1,162 +0,0 @@
var domain = location.host;
var workerProtocol = (window.location.protocol === 'https:') ? 'wss:' : 'ws:';
var workerUri = workerProtocol + '//' + domain + '/ws/chat';
window.onload = function (){
var msgWorker = this.msgWorker;
var worker = new SharedWorker('js/worker/chat.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;
}
// show webSocket info
console.info(load.socket);
}, false);
worker.onerror = function(e){
console.error('SharedWorker onerror:');
};
worker.port.start();
var msgWorkerInit = new msgWorker('ws:init');
msgWorkerInit.data({
uri: workerUri
});
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';
};
*/
};

View File

@@ -1,90 +0,0 @@
'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){
msgWorkerOpen.socket = this;
ports[ports.length - 1].postMessage(msgWorkerOpen);
socket.onmessage = function(e){
let load = JSON.parse(e.data);
let msgWorkerSend = new msgWorker('ws:send');
msgWorkerSend.socket = this;
msgWorkerSend.data(load);
for (let i = 0; i < ports.length; i++) {
ports[i].postMessage(msgWorkerSend);
}
if(notifications){
new Notification('Message: ' + load.text);
}
};
socket.onclose = function(){
let msgWorkerWsClosed = new msgWorker('ws:closed');
msgWorkerWsClosed.socket = this;
console.log(socket.readyState);
for (let i = 0; i < ports.length; i++) {
ports[i].postMessage(msgWorkerWsClosed);
}
};
socket.onerror = function(){
console.error('ws: onerror()');
};
}
}else{
// socket still open
ports[ports.length - 1].postMessage(msgWorkerOpen);
}
};
self.addEventListener('connect', function (event){
let 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();
break;
case 'ws:notify':
notifications = load.data().status;
break;
}
}, false);
port.start();
}, false);
// Util ================================================================
var closeSocket = function(){
// only close if active
if(socket.readyState === socket.OPEN){
socket.close();
}
};

View File

@@ -1,36 +0,0 @@
var msgWorker = class MessageWorker {
constructor(cmd){
// message properties
this.cmd = cmd;
this.msgBody = null;
// webSocket props
this.ws = {
url: undefined,
readyState: undefined,
};
}
set socket(socket){
this.ws.url = socket.url;
this.ws.readyState = socket.readyState;
}
get socket(){
return this.ws;
}
get command(){
return this.cmd;
}
data(data) {
if(data){
this.msgBody = data;
}
return this.msgBody;
}
};